build static method
Implementation
static UDocTextPage build(int pageIndex, Size size, List<UPdfPositionedGlyph> glyphs) {
if (glyphs.isEmpty) return UDocTextPage(pageIndex: pageIndex, runs: const <UDocTextRun>[], text: "", size: size);
final List<UPdfPositionedGlyph> usable = <UPdfPositionedGlyph>[];
final List<UPdfPositionedGlyph> marks = <UPdfPositionedGlyph>[];
for (final UPdfPositionedGlyph glyph in glyphs) {
if (!glyph.rect.isFinite || glyph.rect.height <= 0.2) continue;
(_isAttachable(glyph.text) || _isSeparator(glyph.text) || glyph.text.trim().isEmpty ? marks : usable).add(glyph);
}
usable.sort((UPdfPositionedGlyph a, UPdfPositionedGlyph b) => a.rect.center.dy.compareTo(b.rect.center.dy));
final List<List<UPdfPositionedGlyph>> lines = <List<UPdfPositionedGlyph>>[];
List<UPdfPositionedGlyph> current = <UPdfPositionedGlyph>[];
double center = 0;
double height = 0;
for (final UPdfPositionedGlyph glyph in usable) {
if (current.isNotEmpty) {
final double tolerance = max(height, glyph.rect.height) * 0.45;
if ((glyph.rect.center.dy - center).abs() <= tolerance) {
current.add(glyph);
center = (center * (current.length - 1) + glyph.rect.center.dy) / current.length;
height = max(height, glyph.rect.height);
continue;
}
lines.add(current);
}
current = <UPdfPositionedGlyph>[glyph];
center = glyph.rect.center.dy;
height = glyph.rect.height;
}
if (current.isNotEmpty) lines.add(current);
// Diacritics and zero-width joiners may come from a fallback font with a
// different box: attach each to the line it overlaps instead of its own line.
for (final UPdfPositionedGlyph mark in marks) {
List<UPdfPositionedGlyph>? best;
double bestScore = double.infinity;
for (final List<UPdfPositionedGlyph> line in lines) {
double top = double.infinity;
double bottom = double.negativeInfinity;
double left = double.infinity;
double right = double.negativeInfinity;
for (final UPdfPositionedGlyph glyph in line) {
top = min(top, glyph.rect.top);
bottom = max(bottom, glyph.rect.bottom);
left = min(left, glyph.rect.left);
right = max(right, glyph.rect.right);
}
if (mark.rect.center.dx < left - 2 || mark.rect.center.dx > right + 2) continue;
final double score = (mark.rect.center.dy - (top + bottom) / 2).abs() / max(1, max(bottom - top, mark.rect.height));
if (score < bestScore) {
bestScore = score;
best = line;
}
}
if (best != null && bestScore < 1.2) best.add(mark);
}
final List<UDocTextRun> runs = <UDocTextRun>[];
final StringBuffer pageText = StringBuffer();
int lineIndex = 0;
for (final List<UPdfPositionedGlyph> line in lines) {
line.sort((UPdfPositionedGlyph a, UPdfPositionedGlyph b) => a.rect.left.compareTo(b.rect.left));
bool emitted = false;
for (final List<UPdfPositionedGlyph> segment in _segments(line)) {
final UDocTextRun? run = _run(segment, lineIndex);
if (run == null) continue;
runs.add(run);
pageText.writeln(run.text);
emitted = true;
}
if (emitted) lineIndex++;
}
return UDocTextPage(pageIndex: pageIndex, runs: runs, text: pageText.toString().trimRight(), size: size);
}