js.documents 14.6.4 → 14.6.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) hide show
  1. package/README.md +3 -3
  2. package/dist/{composition-plan-DMTkmk8n.js → composition-plan-BLhc55_x.js} +1 -1
  3. package/dist/{composition-plan-DJQgPvBj.cjs → composition-plan-CgjS-XZU.cjs} +1 -1
  4. package/dist/convert/composition-plan.cjs +1 -1
  5. package/dist/convert/composition-plan.js +1 -1
  6. package/dist/convert/composition-to-pdf.cjs +1 -1
  7. package/dist/convert/composition-to-pdf.js +1 -1
  8. package/dist/convert/composition.cjs +1 -1
  9. package/dist/convert/composition.js +1 -1
  10. package/dist/convert/from-pdf.cjs +2 -2
  11. package/dist/convert/from-pdf.js +2 -2
  12. package/dist/convert/local.cjs +1 -1
  13. package/dist/convert/local.js +1 -1
  14. package/dist/index.cjs +2 -2
  15. package/dist/index.js +2 -2
  16. package/dist/layout/lattice.cjs +28 -5
  17. package/dist/layout/lattice.d.cts +16 -1
  18. package/dist/layout/lattice.d.ts +16 -1
  19. package/dist/layout/lattice.js +25 -6
  20. package/dist/layout/reading-order.cjs +2 -0
  21. package/dist/layout/reading-order.d.cts +3 -1
  22. package/dist/layout/reading-order.d.ts +3 -1
  23. package/dist/layout/reading-order.js +1 -1
  24. package/dist/layout/reconstruct-lines.cjs +41 -11
  25. package/dist/layout/reconstruct-lines.js +42 -12
  26. package/dist/layout/reconstruct-presentations-prod.cjs +1 -1
  27. package/dist/layout/reconstruct-presentations-prod.js +1 -1
  28. package/dist/layout/reconstruct-tables.cjs +1 -1
  29. package/dist/layout/reconstruct-tables.js +1 -1
  30. package/dist/layout/reconstruct.cjs +1 -1
  31. package/dist/layout/reconstruct.js +1 -1
  32. package/dist/layout/ruled-rows.cjs +214 -0
  33. package/dist/layout/ruled-rows.d.cts +11 -0
  34. package/dist/layout/ruled-rows.d.ts +11 -0
  35. package/dist/layout/ruled-rows.js +213 -0
  36. package/dist/{reconstruct-presentations-prod-D1KbGPxa.js → reconstruct-presentations-prod-DV7UQqMT.js} +19 -5
  37. package/dist/{reconstruct-presentations-prod-CsvGlvP2.cjs → reconstruct-presentations-prod-DtpKKgRY.cjs} +19 -5
  38. package/package.json +3 -3
package/README.md CHANGED
@@ -859,7 +859,7 @@ The package is layered from generic primitives outward to the two conversion dir
859
859
  - **`src/markdown/`** — third adapter family, via `markdown-codec`. `readMarkdownContent` passes `readMarkdownContent`'s (markdown-codec's flat reader, so named since that package's 4.0.0; the bare `readMarkdown` name is now its tree-form `DocumentTree` reader) result through the math-lowering pass (`math.ts` — markdown-codec's preserved `$$` display blocks and `\( \)` inline spans become two-layer formula blocks, with the document's symbol table seeded from its own prose). `buildMarkdownText` wraps `writeMarkdownContent`, reconstructing markdown math syntax from formula blocks carrying a presentation layer. `text.ts` is the byte↔text boundary. `MarkdownEditor` holds a mutable in-memory `ContentDocument`.
860
860
  - **`src/csv/`** — fourth adapter family, sharing the spreadsheet variant with xlsx/ods. `records.ts` is the RFC 4180 record parser/writer (one shared `quoteCsvField`, also used by the `.odb` CSV exporter); `text.ts` is the byte↔text boundary, working the character encoding out from the bytes through [byte-codec](../byte-codec/README.md#text-decoding) rather than assuming UTF-8, so Excel's own windows-1252 export reads, and refusing bytes that are not text at all; `read.ts` turns records into a spreadsheet `ContentDocument` (first record as verbatim string header, data cells through the same cell-typing heuristic `pdfToOds` uses); `write.ts` turns one sheet of a spreadsheet `ContentDocument` back into records via each cell's `displayText`. TSV is the same format with `{ delimiter: '\t' }` on either side.
861
861
  - **`src/svg/`** — fifth adapter family, sharing the drawing variant with odg. `text.ts` is the byte↔text boundary: unlike csv and markdown, it does not detect the encoding from the bytes, because SVG is XML and XML's own `<?xml ... encoding="..."?>` declaration is authoritative; `xml-encoding.ts` reads that declaration (or a byte order mark) from the leading bytes per XML 1.0's own external-encoding-detection algorithm, ahead of any real decode, with UTF-8 the strict default when neither is present; `read.ts` maps the six SVG shape primitives (rect/circle/ellipse/line/polyline/polygon/path) onto a one-page drawing `ContentDocument`, with transform lists composed as 2×3 affines and CSS lengths and the viewBox map resolved into page points; `write.ts` writes the six primitives back out, one shape element each; `path.ts` is the full SVG path-data grammar (M/L/H/V/C/S/Z plus Q/T/A and the relative forms — S/Q/T convert exactly, A is the one bounded approximation at ≤90° per cubic); `transform.ts` parses and composes the transform attribute and classifies the result by frame representability; `units.ts` resolves CSS length units and the viewBox; `paint.ts` resolves fill/stroke presentation attributes and dash styles; `diagnostics.ts` is the shared scope-limit vocabulary.
862
- - **`src/layout/`** — the pure conversion algorithms: `engine.ts` (wordprocessing → layout: flow, line-breaking, pagination), `slides.ts` (presentation → layout: direct placement), `sheets.ts` (spreadsheet → layout: grid, print settings, the first algorithm accepting `AbortSignal`), `drawing.ts` (drawing → layout: vector primitives + shape reuse), `reconstruct.ts` (layout → content: baseline clustering for wordprocessing/presentation, near-1:1 mapping for drawing, gridline-lattice-or-text-clustering for spreadsheet; plus the PDF construct surfacing — link reconciliation to `ContentRun.hyperlink` or link constructs, hidden-layer content dropped, anchor/comment and AcroForm contentControl constructs, and tagged-structure semantics where the file states them: heading levels from owning `H1`..`H6` elements over the font-size census, lattice-free table recovery from `/Table`/`/TR`/`/TH`/`/TD` ownership, and `division` constructs around `/Part`/`/Sect`/`/Div` extents).
862
+ - **`src/layout/`** — the pure conversion algorithms: `engine.ts` (wordprocessing → layout: flow, line-breaking, pagination), `slides.ts` (presentation → layout: direct placement), `sheets.ts` (spreadsheet → layout: grid, print settings, the first algorithm accepting `AbortSignal`), `drawing.ts` (drawing → layout: vector primitives + shape reuse), `reconstruct.ts` (layout → content: baseline clustering for wordprocessing/presentation, with a superscript or subscript returned to the line of the run it touches and marked with `ContentRun.verticalAlign` by pdf-codec's `scriptPositionOf`, so `pdfToMarkdown` writes `m<sup>2</sup>` rather than `m` and a stray `2`; near-1:1 mapping for drawing, gridline-lattice-or-text-clustering for spreadsheet; plus the PDF construct surfacing — link reconciliation to `ContentRun.hyperlink` or link constructs, hidden-layer content dropped, anchor/comment and AcroForm contentControl constructs, and tagged-structure semantics where the file states them: heading levels from owning `H1`..`H6` elements over the font-size census, lattice-free table recovery from `/Table`/`/TR`/`/TH`/`/TD` ownership, and `division` constructs around `/Part`/`/Sect`/`/Div` extents).
863
863
  - **`src/hsqldb/`** — `.odb` decoders, four tiers: `script.ts` (TEXT-script DDL/DML parser), `rowformat.ts`/`cache.ts` (CACHED binary row-store), `binary-script.ts` (BINARY/COMPRESSED whole-script). All import only `document-schema.js` — no odf.js knowledge.
864
864
  - **`src/firebird/`** — Tier 3: gbak logical-backup reader. `reader.ts` (attribute framing + RLE decompression + XDR decoding), `schema.ts`/`data.ts` (table/row walking). No ratified spec — built against Firebird's own engine source.
865
865
  - **`src/odb/`** — decoder-selection and pivot-mapping: `read.ts` routes to the right tier, `spreadsheet.ts`/`csv.ts` map to output formats. `odb/sql/` is the bounded SQL engine, `odb/formula/` is the rpt formula engine, `odb/report/` is the renderer, `odb/values.ts` is shared comparison/aggregation semantics.
@@ -934,7 +934,7 @@ To run a single test file: `pnpm vitest run src/path/to/file.test.ts`.
934
934
  - **Font resolution uses a real registry, standard 14 as last resort.** A family with no embedded/caller/vendored face (Aptos, third-party typefaces) renders through the nearest standard-14 face with a width-correction factor — expect a visual approximation, not line-identical output. MathML formula rendering is separate: it embeds STIX Two Math, not registry-resolvable.
935
935
  - **Justified paragraphs stretch inter-word gaps** in all three layout engines (`engine.ts`, `slides.ts`, `sheets.ts`). `justifyLineGapsPt` divides slack evenly across detected word gaps; final lines stay left-aligned.
936
936
  - **Encrypted PDFs and CCITT/JBIG2/JPX images are real capabilities** in pdf-codec — not scope boundaries. The permanent boundary is adversarial/malformed-input robustness.
937
- - **PDF → docx/pptx/odt/odp table recovery requires a real drawn gridline lattice** — never text alignment (which would invent structure). A lattice with no text inside is rejected.
937
+ - **PDF → docx/pptx/odt/odp table recovery requires drawn rules** — never text alignment alone (which would invent structure). Either a real gridline lattice, or a table ruled between its rows (`src/layout/ruled-rows.ts`): at least three horizontal rules of one span, with the columns taken from the gutters every row holding text in more than one place leaves, a heading row above the first rule read when its lines keep to the columns, a row crossing a gutter read as a spanning cell, and a band whose every line fills its columns split into a row per line. Such a grid is refused when every column reads as running prose or when anything other than horizontal strokes and full-width shading is drawn inside it (a diagram). A lattice with no text inside is rejected.
938
938
  - **Merged table cells round-trip as merged.** docx: horizontal merge collapses to one `w:tc` with `w:gridSpan`; vertical merge needs one `w:tc` per covered row with `w:vMerge`. ODF: one entry per grid position, covered cells get `table:covered-table-cell`.
939
939
  - **docx headers/footers/comments/footnotes/numbering are readable via `readDocxExtras`** — `readDocxContent` still drops them (`ContentDocument` has nowhere to put them). `PAGE`/`NUMPAGES` field substitution is never read (it's a render-time value).
940
940
  - **A docx inline image reads as a real `ContentImageBlock`** — `buildDocxPackage` recognises the flat docx reader's two-block pattern (empty-text paragraph + image) and writes it back as one paragraph, avoiding spurious blank paragraphs on round trip.
@@ -1012,7 +1012,7 @@ Read as **row → column**. `✓` lossless, `~` bounded, `✗` lossy, `✗✗` s
1012
1012
 
1013
1013
  **odf → PDF and embedded formulas** render faithful mathematical typesetting through STIX Two Math: real box-model layout, per-glyph metrics, font-wide constants from the `MATH` table, stretchy fences and braces via `MathVariants`. `pdfToOdf` is not attempted — recovering a semantic operator tree from glyphs is OCR-adjacent.
1014
1014
 
1015
- **PDF → docx/pptx/odt/odp** is best-effort reconstruction from geometry. Reading order, font properties, page count survive; paragraph boundaries are inferred from baseline spacing. Text set in columns is read one column at a time: a vertical strip that no text touches over a run of lines, with running prose on both sides (a measure of at least twenty characters, lines that wrap, word gaps narrower than the strip), splits the page there before lines are clustered, so two columns whose baselines line up no longer interleave line by line. Lines crossing the strip (a full-width heading, a boxed passage) are read in page order between the column bands above and below them, and images and recovered tables are placed by the column they sit in. A table's rows are still read across, since its columns are too narrow or too ragged to pass as prose; a page with no such strip, or with text whose horizontal extent the reader did not report, reads exactly as clustering the whole page always did (`src/layout/reading-order.ts`). Tables recover only from a real gridline lattice. Vector primitives recover into a nested drawing document.
1015
+ **PDF → docx/pptx/odt/odp** is best-effort reconstruction from geometry. Reading order, font properties, page count survive; paragraph boundaries are inferred from baseline spacing. Text set in columns is read one column at a time: a vertical strip that no text touches over a run of lines, with running prose on both sides (a measure of at least twenty characters, lines that wrap, word gaps narrower than the strip), splits the page there before lines are clustered, so two columns whose baselines line up no longer interleave line by line. Lines crossing the strip (a full-width heading, a boxed passage) are read in page order between the column bands above and below them, and images and recovered tables are placed by the column they sit in. A table's rows are still read across, since its columns are too narrow or too ragged to pass as prose; a page with no such strip, or with text whose horizontal extent the reader did not report, reads exactly as clustering the whole page always did (`src/layout/reading-order.ts`). Tables recover only from drawn rules: a real gridline lattice, or a table ruled between its rows with its columns set apart by whitespace, which is read cell by cell so a cell's wrapped lines stay together however the producer ordered its drawing (see the table-recovery gotcha below). Vector primitives recover into a nested drawing document.
1016
1016
 
1017
1017
  **PDF → odg** is near-1:1 mapping (no clustering needed). Kind narrows upstream: rotated rects, freeform curves, multi-subpath figures become `path`.
1018
1018
 
@@ -23,7 +23,7 @@ import { buildCsvText } from "./csv/write.js";
23
23
  import { i as readSvgContent } from "./read-BSH--XB4.js";
24
24
  import { buildSvgText } from "./svg/write.js";
25
25
  import { throwIfAborted } from "./ports/abort.js";
26
- import { _ as reconstructWordprocessing, g as reconstructSpreadsheet, i as reconstructPresentation, r as reconstructDrawing } from "./reconstruct-presentations-prod-D1KbGPxa.js";
26
+ import { _ as reconstructWordprocessing, g as reconstructSpreadsheet, i as reconstructPresentation, r as reconstructDrawing } from "./reconstruct-presentations-prod-DV7UQqMT.js";
27
27
  import { stampPdfPackageTables } from "./convert/pdf-package-tables.js";
28
28
  import { drawingToPresentation, presentationToDrawing, presentationToWordprocessing, spreadsheetToWordprocessing, wordprocessingToPresentation } from "./convert/variant-bridges.js";
29
29
  import { UnsupportedConversionError } from "./convert/capability.js";
@@ -23,7 +23,7 @@ const require_csv_write = require("./csv/write.cjs");
23
23
  const require_read$1 = require("./read-CahCZMwG.cjs");
24
24
  const require_svg_write = require("./svg/write.cjs");
25
25
  const require_ports_abort = require("./ports/abort.cjs");
26
- const require_reconstruct_presentations_prod = require("./reconstruct-presentations-prod-CsvGlvP2.cjs");
26
+ const require_reconstruct_presentations_prod = require("./reconstruct-presentations-prod-DtpKKgRY.cjs");
27
27
  const require_convert_pdf_package_tables = require("./convert/pdf-package-tables.cjs");
28
28
  const require_convert_variant_bridges = require("./convert/variant-bridges.cjs");
29
29
  const require_convert_capability = require("./convert/capability.cjs");
@@ -1,5 +1,5 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
- const require_composition_plan = require("../composition-plan-DJQgPvBj.cjs");
2
+ const require_composition_plan = require("../composition-plan-CgjS-XZU.cjs");
3
3
  exports.buildCompositionGraph = require_composition_plan.buildCompositionGraph;
4
4
  exports.isContentFormat = require_composition_plan.isContentFormat;
5
5
  exports.isSourceContentFormat = require_composition_plan.isSourceContentFormat;
@@ -1,2 +1,2 @@
1
- import { a as shortestPath, i as resolveCompositionPlan, n as isContentFormat, r as isSourceContentFormat, t as buildCompositionGraph } from "../composition-plan-DMTkmk8n.js";
1
+ import { a as shortestPath, i as resolveCompositionPlan, n as isContentFormat, r as isSourceContentFormat, t as buildCompositionGraph } from "../composition-plan-BLhc55_x.js";
2
2
  export { buildCompositionGraph, isContentFormat, isSourceContentFormat, resolveCompositionPlan, shortestPath };
@@ -7,7 +7,7 @@ const require_ports_abort = require("../ports/abort.cjs");
7
7
  const require_sheets = require("../sheets-BQCpNXsE.cjs");
8
8
  const require_layout_drawing = require("../layout/drawing.cjs");
9
9
  const require_convert_capability = require("./capability.cjs");
10
- const require_composition_plan = require("../composition-plan-DJQgPvBj.cjs");
10
+ const require_composition_plan = require("../composition-plan-CgjS-XZU.cjs");
11
11
  let document_schema_js = require("document-schema.js");
12
12
  let pdf_codec = require("pdf-codec");
13
13
  //#region src/convert/composition-to-pdf.ts
@@ -6,7 +6,7 @@ import { throwIfAborted } from "../ports/abort.js";
6
6
  import { l as convertSpreadsheetToLayout } from "../sheets-DWDvGN-g.js";
7
7
  import { convertDrawingToLayout } from "../layout/drawing.js";
8
8
  import { UnsupportedConversionError } from "./capability.js";
9
- import { _ as executeFromPdf, b as runCompositionPlan, c as LAYOUT_CAPABLE, d as READ_ONLY_FORMAT_NODES, g as executeBridge, i as resolveCompositionPlan, s as FORMAT_NODES, v as isReadOnlyContentFormat, y as isTextFormatNode } from "../composition-plan-DMTkmk8n.js";
9
+ import { _ as executeFromPdf, b as runCompositionPlan, c as LAYOUT_CAPABLE, d as READ_ONLY_FORMAT_NODES, g as executeBridge, i as resolveCompositionPlan, s as FORMAT_NODES, v as isReadOnlyContentFormat, y as isTextFormatNode } from "../composition-plan-BLhc55_x.js";
10
10
  import { assembleTree } from "document-schema.js";
11
11
  import { createFontMeasurer, createFontRegistry, loadMathFont, writePdf } from "pdf-codec";
12
12
  //#region src/convert/composition-to-pdf.ts
@@ -1,5 +1,5 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
- const require_composition_plan = require("../composition-plan-DJQgPvBj.cjs");
2
+ const require_composition_plan = require("../composition-plan-CgjS-XZU.cjs");
3
3
  exports.CONTENT_FORMATS = require_composition_plan.CONTENT_FORMATS;
4
4
  exports.FORMAT_NODES = require_composition_plan.FORMAT_NODES;
5
5
  exports.LAYOUT_CAPABLE = require_composition_plan.LAYOUT_CAPABLE;
@@ -1,2 +1,2 @@
1
- import { _ as executeFromPdf, b as runCompositionPlan, c as LAYOUT_CAPABLE, d as READ_ONLY_FORMAT_NODES, f as RECONSTRUCTORS, g as executeBridge, h as convertDocumentFromPdf, l as MAX_CONVERSION_PATH_NODES, m as TRANSFORMS, o as CONTENT_FORMATS, p as THROUGH_PDF_EDGE_COST, s as FORMAT_NODES, u as READ_ONLY_CONTENT_FORMATS, v as isReadOnlyContentFormat, y as isTextFormatNode } from "../composition-plan-DMTkmk8n.js";
1
+ import { _ as executeFromPdf, b as runCompositionPlan, c as LAYOUT_CAPABLE, d as READ_ONLY_FORMAT_NODES, f as RECONSTRUCTORS, g as executeBridge, h as convertDocumentFromPdf, l as MAX_CONVERSION_PATH_NODES, m as TRANSFORMS, o as CONTENT_FORMATS, p as THROUGH_PDF_EDGE_COST, s as FORMAT_NODES, u as READ_ONLY_CONTENT_FORMATS, v as isReadOnlyContentFormat, y as isTextFormatNode } from "../composition-plan-BLhc55_x.js";
2
2
  export { CONTENT_FORMATS, FORMAT_NODES, LAYOUT_CAPABLE, MAX_CONVERSION_PATH_NODES, READ_ONLY_CONTENT_FORMATS, READ_ONLY_FORMAT_NODES, RECONSTRUCTORS, THROUGH_PDF_EDGE_COST, TRANSFORMS, convertDocumentFromPdf, executeBridge, executeFromPdf, isReadOnlyContentFormat, isTextFormatNode, runCompositionPlan };
@@ -1,7 +1,7 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
- const require_reconstruct_presentations_prod = require("../reconstruct-presentations-prod-CsvGlvP2.cjs");
2
+ const require_reconstruct_presentations_prod = require("../reconstruct-presentations-prod-DtpKKgRY.cjs");
3
3
  const require_convert_pdf_package_tables = require("./pdf-package-tables.cjs");
4
- const require_composition_plan = require("../composition-plan-DJQgPvBj.cjs");
4
+ const require_composition_plan = require("../composition-plan-CgjS-XZU.cjs");
5
5
  const require_codecs_read = require("../codecs/read.cjs");
6
6
  let document_schema_js = require("document-schema.js");
7
7
  let pdf_codec_read = require("pdf-codec/read");
@@ -1,6 +1,6 @@
1
- import { _ as reconstructWordprocessing } from "../reconstruct-presentations-prod-D1KbGPxa.js";
1
+ import { _ as reconstructWordprocessing } from "../reconstruct-presentations-prod-DV7UQqMT.js";
2
2
  import { stampPdfPackageTables } from "./pdf-package-tables.js";
3
- import { h as convertDocumentFromPdf } from "../composition-plan-DMTkmk8n.js";
3
+ import { h as convertDocumentFromPdf } from "../composition-plan-BLhc55_x.js";
4
4
  import { CONTENT_READERS } from "../codecs/read.js";
5
5
  import { assembleTree } from "document-schema.js";
6
6
  import { readPdf } from "pdf-codec/read";
@@ -1,7 +1,7 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
2
  const require_convert_capability = require("./capability.cjs");
3
3
  const require_convert_port = require("./port.cjs");
4
- const require_composition_plan = require("../composition-plan-DJQgPvBj.cjs");
4
+ const require_composition_plan = require("../composition-plan-CgjS-XZU.cjs");
5
5
  const require_convert_composition_to_pdf = require("./composition-to-pdf.cjs");
6
6
  const require_convert_convert = require("./convert.cjs");
7
7
  //#region src/convert/local.ts
@@ -1,6 +1,6 @@
1
1
  import { UnsupportedConversionError } from "./capability.js";
2
2
  import { DOCUMENT_FORMATS } from "./port.js";
3
- import { i as resolveCompositionPlan } from "../composition-plan-DMTkmk8n.js";
3
+ import { i as resolveCompositionPlan } from "../composition-plan-BLhc55_x.js";
4
4
  import { convertDocument } from "./composition-to-pdf.js";
5
5
  import { odfToPdf } from "./convert.js";
6
6
  //#region src/convert/local.ts
package/dist/index.cjs CHANGED
@@ -93,7 +93,7 @@ const require_ports_abort = require("./ports/abort.cjs");
93
93
  const require_sheets = require("./sheets-BQCpNXsE.cjs");
94
94
  const require_layout_drawing = require("./layout/drawing.cjs");
95
95
  const require_layout_lattice = require("./layout/lattice.cjs");
96
- const require_reconstruct_presentations_prod = require("./reconstruct-presentations-prod-CsvGlvP2.cjs");
96
+ const require_reconstruct_presentations_prod = require("./reconstruct-presentations-prod-DtpKKgRY.cjs");
97
97
  const require_hsqldb_script = require("./hsqldb/script.cjs");
98
98
  const require_odb_csv = require("./odb/csv.cjs");
99
99
  const require_hsqldb_rowformat = require("./hsqldb/rowformat.cjs");
@@ -107,7 +107,7 @@ const require_firebird_backup = require("./firebird/backup.cjs");
107
107
  const require_odb_read = require("./odb/read.cjs");
108
108
  const require_odb_spreadsheet = require("./odb/spreadsheet.cjs");
109
109
  const require_convert_port = require("./convert/port.cjs");
110
- const require_composition_plan = require("./composition-plan-DJQgPvBj.cjs");
110
+ const require_composition_plan = require("./composition-plan-CgjS-XZU.cjs");
111
111
  const require_convert_composition_to_pdf = require("./convert/composition-to-pdf.cjs");
112
112
  const require_convert_convert = require("./convert/convert.cjs");
113
113
  const require_package_codec = require("./package-codec.cjs");
package/dist/index.js CHANGED
@@ -92,7 +92,7 @@ import { throwIfAborted } from "./ports/abort.js";
92
92
  import { l as convertSpreadsheetToLayout } from "./sheets-DWDvGN-g.js";
93
93
  import { convertDrawingToLayout } from "./layout/drawing.js";
94
94
  import { detectGridLattice } from "./layout/lattice.js";
95
- import { _ as reconstructWordprocessing, g as reconstructSpreadsheet, i as reconstructPresentation, r as reconstructDrawing } from "./reconstruct-presentations-prod-D1KbGPxa.js";
95
+ import { _ as reconstructWordprocessing, g as reconstructSpreadsheet, i as reconstructPresentation, r as reconstructDrawing } from "./reconstruct-presentations-prod-DV7UQqMT.js";
96
96
  import { HsqldbScriptParseError, displayTextFor, parseHsqldbScript } from "./hsqldb/script.js";
97
97
  import { OdbTableNotFoundError, OdbTableNotSpecifiedError } from "./odb/csv.js";
98
98
  import { HsqldbRowFormatError } from "./hsqldb/rowformat.js";
@@ -106,7 +106,7 @@ import { FirebirdBackupFormatError, SUPPORTED_BACKUP_FORMAT_VERSION, readFirebir
106
106
  import { OdbNoEmbeddedDataSourceError, OdbUnsupportedFormatError, readOdbTables } from "./odb/read.js";
107
107
  import { odbTablesToSpreadsheetDocument } from "./odb/spreadsheet.js";
108
108
  import { DOCUMENT_FORMATS, DocumentFormatSchema } from "./convert/port.js";
109
- import { i as resolveCompositionPlan } from "./composition-plan-DMTkmk8n.js";
109
+ import { i as resolveCompositionPlan } from "./composition-plan-BLhc55_x.js";
110
110
  import { convertDocument } from "./convert/composition-to-pdf.js";
111
111
  import { OdmUnresolvedSectionError, csvToMarkdown, csvToOds, csvToPdf, csvToXlsx, docToMarkdown, docToPdf, docxToMarkdown, docxToOdt, docxToPdf, docxToPptx, epubToPdf, markdownToCsv, markdownToDocx, markdownToOdt, markdownToPdf, markdownToXlsx, odbReportToDocx, odbReportToOdt, odbReportToPdf, odbToCsv, odbToXlsx, odfToPdf, odgToPdf, odgToSvg, odmToPdf, odpToOdt, odpToPdf, odpToPptx, odsToCsv, odsToPdf, odsToXlsx, odtToDocx, odtToMarkdown, odtToOdp, odtToPdf, pptToMarkdown, pptToPdf, pptxToDocx, pptxToOdp, pptxToPdf, rtfToPdf, svgToOdg, svgToPdf, xlsToMarkdown, xlsToPdf, xlsxToCsv, xlsxToMarkdown, xlsxToOds, xlsxToPdf } from "./convert/convert.js";
112
112
  import { UnsupportedPackageFormatError, decodeDocumentPackage, decodeOdbPackage, encodeDocumentPackage } from "./package-codec.js";
@@ -29,7 +29,7 @@ function extractLineCandidates(items) {
29
29
  continue;
30
30
  }
31
31
  if (item.kind === "rect") {
32
- const isThinEnoughToBeALine = Math.min(item.widthPt, item.heightPt) <= RECT_LINE_THICKNESS_TOLERANCE_PT;
32
+ const isThinEnoughToBeALine = Math.min(item.widthPt, item.heightPt) <= 3;
33
33
  if (isThinEnoughToBeALine && item.widthPt > item.heightPt) {
34
34
  const midYPt = item.yPt + item.heightPt / 2;
35
35
  segments.push({
@@ -123,7 +123,7 @@ function hasClosedOuterRectangle(rowLines, columnLines) {
123
123
  const rightXPt = columnLines[columnLines.length - 1].position;
124
124
  const topYPt = rowLines[0].position;
125
125
  const bottomYPt = rowLines[rowLines.length - 1].position;
126
- return unionCoverageRatio(rowLines[0].ranges, leftXPt, rightXPt) >= EDGE_COVERAGE_RATIO && unionCoverageRatio(rowLines[rowLines.length - 1].ranges, leftXPt, rightXPt) >= EDGE_COVERAGE_RATIO && unionCoverageRatio(columnLines[0].ranges, bottomYPt, topYPt) >= EDGE_COVERAGE_RATIO && unionCoverageRatio(columnLines[columnLines.length - 1].ranges, bottomYPt, topYPt) >= EDGE_COVERAGE_RATIO;
126
+ return unionCoverageRatio(rowLines[0].ranges, leftXPt, rightXPt) >= .9 && unionCoverageRatio(rowLines[rowLines.length - 1].ranges, leftXPt, rightXPt) >= .9 && unionCoverageRatio(columnLines[0].ranges, bottomYPt, topYPt) >= .9 && unionCoverageRatio(columnLines[columnLines.length - 1].ranges, bottomYPt, topYPt) >= .9;
127
127
  }
128
128
  function unionFind(size) {
129
129
  return Array.from({ length: size }, (_, i) => i);
@@ -154,7 +154,7 @@ function findCellRegions(rowLines, columnLines) {
154
154
  const rowBottomPt = rowLines[i + 1].position;
155
155
  for (let j = 0; j < colCount - 1; j++) {
156
156
  const divider = columnLines[j + 1];
157
- if (unionCoverageRatio(divider.ranges, rowBottomPt, rowTopPt) < EDGE_COVERAGE_RATIO) union(parents, indexOf(i, j), indexOf(i, j + 1));
157
+ if (unionCoverageRatio(divider.ranges, rowBottomPt, rowTopPt) < .9) union(parents, indexOf(i, j), indexOf(i, j + 1));
158
158
  }
159
159
  }
160
160
  for (let j = 0; j < colCount; j++) {
@@ -162,7 +162,7 @@ function findCellRegions(rowLines, columnLines) {
162
162
  const colRightPt = columnLines[j + 1].position;
163
163
  for (let i = 0; i < rowCount - 1; i++) {
164
164
  const divider = rowLines[i + 1];
165
- if (unionCoverageRatio(divider.ranges, colLeftPt, colRightPt) < EDGE_COVERAGE_RATIO) union(parents, indexOf(i, j), indexOf(i + 1, j));
165
+ if (unionCoverageRatio(divider.ranges, colLeftPt, colRightPt) < .9) union(parents, indexOf(i, j), indexOf(i + 1, j));
166
166
  }
167
167
  }
168
168
  const cellsByRoot = /* @__PURE__ */ new Map();
@@ -220,6 +220,25 @@ function clusterSegments(horizontal, vertical) {
220
220
  }
221
221
  return [...clusters.values()];
222
222
  }
223
+ /**
224
+ * Every horizontal rule on the page that its drawn segments cover across its own span, top to bottom.
225
+ * @param items - the page's items; lines, paths and thin rectangles are read as segments, as for a lattice
226
+ * @returns one rule per boundary position, merging collinear segments the way a lattice boundary does, and only those whose segments cover at least EDGE_COVERAGE_RATIO of the span from their leftmost to their rightmost point, so a rule broken at a table's column gutters counts while a row of separate short rules (underlines under a few words) does not
227
+ */
228
+ function horizontalRules(items) {
229
+ const horizontal = extractLineCandidates(items).map(classifyAxisLine).filter((segment) => segment?.axis === "horizontal");
230
+ if (horizontal.length === 0) return [];
231
+ return dedupeAxisLines(horizontal, true).flatMap((line) => {
232
+ const leftPt = Math.min(...line.ranges.map((range) => range.startPt));
233
+ const rightPt = Math.max(...line.ranges.map((range) => range.endPt));
234
+ return unionCoverageRatio(line.ranges, leftPt, rightPt) >= .9 ? [{
235
+ position: line.position,
236
+ leftPt,
237
+ rightPt,
238
+ items: line.items
239
+ }] : [];
240
+ });
241
+ }
223
242
  function detectGridLattice(items) {
224
243
  const horizontal = [];
225
244
  const vertical = [];
@@ -235,7 +254,7 @@ function detectGridLattice(items) {
235
254
  const clusterVertical = cluster.filter((s) => s.axis === "vertical");
236
255
  const rowLines = dedupeAxisLines(clusterHorizontal, true);
237
256
  const columnLines = dedupeAxisLines(clusterVertical, false);
238
- if (rowLines.length < MIN_GRIDLINE_COUNT_PER_AXIS || columnLines.length < MIN_GRIDLINE_COUNT_PER_AXIS) continue;
257
+ if (rowLines.length < 3 || columnLines.length < 3) continue;
239
258
  if (!hasClosedOuterRectangle(rowLines, columnLines)) continue;
240
259
  const regions = findCellRegions(rowLines, columnLines);
241
260
  if (regions === void 0) continue;
@@ -267,8 +286,12 @@ function findColumnIndex(columnBoundariesAscPt, xPt) {
267
286
  return lastIndex - 1;
268
287
  }
269
288
  //#endregion
289
+ exports.EDGE_COVERAGE_RATIO = EDGE_COVERAGE_RATIO;
290
+ exports.MIN_GRIDLINE_COUNT_PER_AXIS = MIN_GRIDLINE_COUNT_PER_AXIS;
291
+ exports.RECT_LINE_THICKNESS_TOLERANCE_PT = RECT_LINE_THICKNESS_TOLERANCE_PT;
270
292
  exports.detectGridLattice = detectGridLattice;
271
293
  exports.extractLineCandidates = extractLineCandidates;
272
294
  exports.findCellRegions = findCellRegions;
273
295
  exports.findColumnIndex = findColumnIndex;
274
296
  exports.findRowIndex = findRowIndex;
297
+ exports.horizontalRules = horizontalRules;
@@ -1,5 +1,6 @@
1
1
  import { LayoutItem } from "pdf-codec";
2
2
  //#region src/layout/lattice.d.ts
3
+ declare const RECT_LINE_THICKNESS_TOLERANCE_PT = 3;
3
4
  interface LineSegment {
4
5
  readonly item: LayoutItem;
5
6
  readonly x1Pt: number;
@@ -17,6 +18,8 @@ interface AxisLine {
17
18
  readonly ranges: readonly Range[];
18
19
  readonly items: readonly LayoutItem[];
19
20
  }
21
+ declare const MIN_GRIDLINE_COUNT_PER_AXIS = 3;
22
+ declare const EDGE_COVERAGE_RATIO = 0.9;
20
23
  interface TableRegion {
21
24
  readonly rowStart: number;
22
25
  readonly rowEnd: number;
@@ -30,8 +33,20 @@ interface GridLattice {
30
33
  readonly regions: readonly TableRegion[];
31
34
  readonly sourceItems: ReadonlySet<LayoutItem>;
32
35
  }
36
+ interface HorizontalRule {
37
+ readonly position: number;
38
+ readonly leftPt: number;
39
+ readonly rightPt: number;
40
+ readonly items: readonly LayoutItem[];
41
+ }
42
+ /**
43
+ * Every horizontal rule on the page that its drawn segments cover across its own span, top to bottom.
44
+ * @param items - the page's items; lines, paths and thin rectangles are read as segments, as for a lattice
45
+ * @returns one rule per boundary position, merging collinear segments the way a lattice boundary does, and only those whose segments cover at least EDGE_COVERAGE_RATIO of the span from their leftmost to their rightmost point, so a rule broken at a table's column gutters counts while a row of separate short rules (underlines under a few words) does not
46
+ */
47
+ declare function horizontalRules(items: readonly LayoutItem[]): HorizontalRule[];
33
48
  declare function detectGridLattice(items: readonly LayoutItem[]): GridLattice | undefined;
34
49
  declare function findRowIndex(rowBoundariesDescPt: readonly number[], yPt: number): number | undefined;
35
50
  declare function findColumnIndex(columnBoundariesAscPt: readonly number[], xPt: number): number | undefined;
36
51
  //#endregion
37
- export { GridLattice, LineSegment, TableRegion, detectGridLattice, extractLineCandidates, findCellRegions, findColumnIndex, findRowIndex };
52
+ export { EDGE_COVERAGE_RATIO, GridLattice, HorizontalRule, LineSegment, MIN_GRIDLINE_COUNT_PER_AXIS, RECT_LINE_THICKNESS_TOLERANCE_PT, TableRegion, detectGridLattice, extractLineCandidates, findCellRegions, findColumnIndex, findRowIndex, horizontalRules };
@@ -1,5 +1,6 @@
1
1
  import { LayoutItem } from "pdf-codec";
2
2
  //#region src/layout/lattice.d.ts
3
+ declare const RECT_LINE_THICKNESS_TOLERANCE_PT = 3;
3
4
  interface LineSegment {
4
5
  readonly item: LayoutItem;
5
6
  readonly x1Pt: number;
@@ -17,6 +18,8 @@ interface AxisLine {
17
18
  readonly ranges: readonly Range[];
18
19
  readonly items: readonly LayoutItem[];
19
20
  }
21
+ declare const MIN_GRIDLINE_COUNT_PER_AXIS = 3;
22
+ declare const EDGE_COVERAGE_RATIO = 0.9;
20
23
  interface TableRegion {
21
24
  readonly rowStart: number;
22
25
  readonly rowEnd: number;
@@ -30,8 +33,20 @@ interface GridLattice {
30
33
  readonly regions: readonly TableRegion[];
31
34
  readonly sourceItems: ReadonlySet<LayoutItem>;
32
35
  }
36
+ interface HorizontalRule {
37
+ readonly position: number;
38
+ readonly leftPt: number;
39
+ readonly rightPt: number;
40
+ readonly items: readonly LayoutItem[];
41
+ }
42
+ /**
43
+ * Every horizontal rule on the page that its drawn segments cover across its own span, top to bottom.
44
+ * @param items - the page's items; lines, paths and thin rectangles are read as segments, as for a lattice
45
+ * @returns one rule per boundary position, merging collinear segments the way a lattice boundary does, and only those whose segments cover at least EDGE_COVERAGE_RATIO of the span from their leftmost to their rightmost point, so a rule broken at a table's column gutters counts while a row of separate short rules (underlines under a few words) does not
46
+ */
47
+ declare function horizontalRules(items: readonly LayoutItem[]): HorizontalRule[];
33
48
  declare function detectGridLattice(items: readonly LayoutItem[]): GridLattice | undefined;
34
49
  declare function findRowIndex(rowBoundariesDescPt: readonly number[], yPt: number): number | undefined;
35
50
  declare function findColumnIndex(columnBoundariesAscPt: readonly number[], xPt: number): number | undefined;
36
51
  //#endregion
37
- export { GridLattice, LineSegment, TableRegion, detectGridLattice, extractLineCandidates, findCellRegions, findColumnIndex, findRowIndex };
52
+ export { EDGE_COVERAGE_RATIO, GridLattice, HorizontalRule, LineSegment, MIN_GRIDLINE_COUNT_PER_AXIS, RECT_LINE_THICKNESS_TOLERANCE_PT, TableRegion, detectGridLattice, extractLineCandidates, findCellRegions, findColumnIndex, findRowIndex, horizontalRules };
@@ -28,7 +28,7 @@ function extractLineCandidates(items) {
28
28
  continue;
29
29
  }
30
30
  if (item.kind === "rect") {
31
- const isThinEnoughToBeALine = Math.min(item.widthPt, item.heightPt) <= RECT_LINE_THICKNESS_TOLERANCE_PT;
31
+ const isThinEnoughToBeALine = Math.min(item.widthPt, item.heightPt) <= 3;
32
32
  if (isThinEnoughToBeALine && item.widthPt > item.heightPt) {
33
33
  const midYPt = item.yPt + item.heightPt / 2;
34
34
  segments.push({
@@ -122,7 +122,7 @@ function hasClosedOuterRectangle(rowLines, columnLines) {
122
122
  const rightXPt = columnLines[columnLines.length - 1].position;
123
123
  const topYPt = rowLines[0].position;
124
124
  const bottomYPt = rowLines[rowLines.length - 1].position;
125
- return unionCoverageRatio(rowLines[0].ranges, leftXPt, rightXPt) >= EDGE_COVERAGE_RATIO && unionCoverageRatio(rowLines[rowLines.length - 1].ranges, leftXPt, rightXPt) >= EDGE_COVERAGE_RATIO && unionCoverageRatio(columnLines[0].ranges, bottomYPt, topYPt) >= EDGE_COVERAGE_RATIO && unionCoverageRatio(columnLines[columnLines.length - 1].ranges, bottomYPt, topYPt) >= EDGE_COVERAGE_RATIO;
125
+ return unionCoverageRatio(rowLines[0].ranges, leftXPt, rightXPt) >= .9 && unionCoverageRatio(rowLines[rowLines.length - 1].ranges, leftXPt, rightXPt) >= .9 && unionCoverageRatio(columnLines[0].ranges, bottomYPt, topYPt) >= .9 && unionCoverageRatio(columnLines[columnLines.length - 1].ranges, bottomYPt, topYPt) >= .9;
126
126
  }
127
127
  function unionFind(size) {
128
128
  return Array.from({ length: size }, (_, i) => i);
@@ -153,7 +153,7 @@ function findCellRegions(rowLines, columnLines) {
153
153
  const rowBottomPt = rowLines[i + 1].position;
154
154
  for (let j = 0; j < colCount - 1; j++) {
155
155
  const divider = columnLines[j + 1];
156
- if (unionCoverageRatio(divider.ranges, rowBottomPt, rowTopPt) < EDGE_COVERAGE_RATIO) union(parents, indexOf(i, j), indexOf(i, j + 1));
156
+ if (unionCoverageRatio(divider.ranges, rowBottomPt, rowTopPt) < .9) union(parents, indexOf(i, j), indexOf(i, j + 1));
157
157
  }
158
158
  }
159
159
  for (let j = 0; j < colCount; j++) {
@@ -161,7 +161,7 @@ function findCellRegions(rowLines, columnLines) {
161
161
  const colRightPt = columnLines[j + 1].position;
162
162
  for (let i = 0; i < rowCount - 1; i++) {
163
163
  const divider = rowLines[i + 1];
164
- if (unionCoverageRatio(divider.ranges, colLeftPt, colRightPt) < EDGE_COVERAGE_RATIO) union(parents, indexOf(i, j), indexOf(i + 1, j));
164
+ if (unionCoverageRatio(divider.ranges, colLeftPt, colRightPt) < .9) union(parents, indexOf(i, j), indexOf(i + 1, j));
165
165
  }
166
166
  }
167
167
  const cellsByRoot = /* @__PURE__ */ new Map();
@@ -219,6 +219,25 @@ function clusterSegments(horizontal, vertical) {
219
219
  }
220
220
  return [...clusters.values()];
221
221
  }
222
+ /**
223
+ * Every horizontal rule on the page that its drawn segments cover across its own span, top to bottom.
224
+ * @param items - the page's items; lines, paths and thin rectangles are read as segments, as for a lattice
225
+ * @returns one rule per boundary position, merging collinear segments the way a lattice boundary does, and only those whose segments cover at least EDGE_COVERAGE_RATIO of the span from their leftmost to their rightmost point, so a rule broken at a table's column gutters counts while a row of separate short rules (underlines under a few words) does not
226
+ */
227
+ function horizontalRules(items) {
228
+ const horizontal = extractLineCandidates(items).map(classifyAxisLine).filter((segment) => segment?.axis === "horizontal");
229
+ if (horizontal.length === 0) return [];
230
+ return dedupeAxisLines(horizontal, true).flatMap((line) => {
231
+ const leftPt = Math.min(...line.ranges.map((range) => range.startPt));
232
+ const rightPt = Math.max(...line.ranges.map((range) => range.endPt));
233
+ return unionCoverageRatio(line.ranges, leftPt, rightPt) >= .9 ? [{
234
+ position: line.position,
235
+ leftPt,
236
+ rightPt,
237
+ items: line.items
238
+ }] : [];
239
+ });
240
+ }
222
241
  function detectGridLattice(items) {
223
242
  const horizontal = [];
224
243
  const vertical = [];
@@ -234,7 +253,7 @@ function detectGridLattice(items) {
234
253
  const clusterVertical = cluster.filter((s) => s.axis === "vertical");
235
254
  const rowLines = dedupeAxisLines(clusterHorizontal, true);
236
255
  const columnLines = dedupeAxisLines(clusterVertical, false);
237
- if (rowLines.length < MIN_GRIDLINE_COUNT_PER_AXIS || columnLines.length < MIN_GRIDLINE_COUNT_PER_AXIS) continue;
256
+ if (rowLines.length < 3 || columnLines.length < 3) continue;
238
257
  if (!hasClosedOuterRectangle(rowLines, columnLines)) continue;
239
258
  const regions = findCellRegions(rowLines, columnLines);
240
259
  if (regions === void 0) continue;
@@ -266,4 +285,4 @@ function findColumnIndex(columnBoundariesAscPt, xPt) {
266
285
  return lastIndex - 1;
267
286
  }
268
287
  //#endregion
269
- export { detectGridLattice, extractLineCandidates, findCellRegions, findColumnIndex, findRowIndex };
288
+ export { EDGE_COVERAGE_RATIO, MIN_GRIDLINE_COUNT_PER_AXIS, RECT_LINE_THICKNESS_TOLERANCE_PT, detectGridLattice, extractLineCandidates, findCellRegions, findColumnIndex, findRowIndex, horizontalRules };
@@ -199,4 +199,6 @@ function firstWordWidthPt(item) {
199
199
  }
200
200
  //#endregion
201
201
  exports.compareReadingPosition = compareReadingPosition;
202
+ exports.isRunningProse = isRunningProse;
202
203
  exports.readingOrder = readingOrder;
204
+ exports.spaceAdvancePt = spaceAdvancePt;
@@ -36,5 +36,7 @@ declare function compareReadingPosition(a: {
36
36
  readonly path: readonly number[];
37
37
  readonly yPt: number;
38
38
  }): number;
39
+ declare function spaceAdvancePt(item: LayoutText): number;
40
+ declare function isRunningProse(items: readonly LayoutText[], gutterWidthPt: number): boolean;
39
41
  //#endregion
40
- export { ReadingOrder, ReadingRegion, compareReadingPosition, readingOrder };
42
+ export { ReadingOrder, ReadingRegion, compareReadingPosition, isRunningProse, readingOrder, spaceAdvancePt };
@@ -36,5 +36,7 @@ declare function compareReadingPosition(a: {
36
36
  readonly path: readonly number[];
37
37
  readonly yPt: number;
38
38
  }): number;
39
+ declare function spaceAdvancePt(item: LayoutText): number;
40
+ declare function isRunningProse(items: readonly LayoutText[], gutterWidthPt: number): boolean;
39
41
  //#endregion
40
- export { ReadingOrder, ReadingRegion, compareReadingPosition, readingOrder };
42
+ export { ReadingOrder, ReadingRegion, compareReadingPosition, isRunningProse, readingOrder, spaceAdvancePt };
@@ -197,4 +197,4 @@ function firstWordWidthPt(item) {
197
197
  return text.length === 0 ? 0 : (item.widthPt ?? 0) * firstWord.length / item.text.length;
198
198
  }
199
199
  //#endregion
200
- export { compareReadingPosition, readingOrder };
200
+ export { compareReadingPosition, isRunningProse, readingOrder, spaceAdvancePt };
@@ -28,13 +28,14 @@ function dropDuplicatePaints(items) {
28
28
  return kept;
29
29
  }
30
30
  function dropOverlappingRepeatsWithinLine(items) {
31
- const rightEdgeByText = /* @__PURE__ */ new Map();
31
+ const keptByText = /* @__PURE__ */ new Map();
32
32
  const kept = [];
33
33
  for (const item of items) {
34
34
  const textKey = item.text.trim();
35
- const priorRightEdge = rightEdgeByText.get(textKey);
36
- if (priorRightEdge !== void 0 && item.xPt < priorRightEdge) continue;
37
- rightEdgeByText.set(textKey, item.xPt + (item.widthPt ?? 0));
35
+ const earlier = keptByText.get(textKey);
36
+ if (earlier?.some((prior) => item.xPt < prior.xPt + (prior.widthPt ?? 0) && Math.abs(item.yPt - prior.yPt) <= DUPLICATE_PAINT_BUCKET_PT) === true) continue;
37
+ if (earlier === void 0) keptByText.set(textKey, [item]);
38
+ else earlier.push(item);
38
39
  kept.push(item);
39
40
  }
40
41
  return kept;
@@ -127,12 +128,23 @@ function clusterIntoLines(items) {
127
128
  });
128
129
  else line.items.push(item);
129
130
  }
130
- for (const line of working) {
131
+ const lines = (0, pdf_codec_text_group.attachScriptRuns)(working, (line) => line.items).filter(({ runs }) => runs.length > 0).map(({ line, runs }) => ({
132
+ ...line,
133
+ items: runs
134
+ }));
135
+ for (const line of lines) {
131
136
  line.items.sort((a, b) => a.xPt - b.xPt);
132
137
  line.items = dropOverlappingRepeatsWithinLine(line.items);
133
138
  }
134
- working.sort((a, b) => b.baselineY - a.baselineY);
135
- return working;
139
+ lines.sort((a, b) => b.baselineY - a.baselineY);
140
+ return lines;
141
+ }
142
+ function scriptPositionOnLine(item, line) {
143
+ for (const base of line.items) {
144
+ if (base === item) continue;
145
+ const position = (0, pdf_codec_text_group.scriptPositionOf)(base, item);
146
+ if (position !== void 0) return position;
147
+ }
136
148
  }
137
149
  function textItemVerticalExtent(item) {
138
150
  const { standardName } = (0, pdf_codec_fonts.resolveStandardFont)(item.font.family, item.font.weight === "bold", item.font.style === "italic");
@@ -192,13 +204,32 @@ function pushRunsForLine(runs, line, pageIndex) {
192
204
  if (itemIndex > 0) {
193
205
  const prevItem = line.items[itemIndex - 1];
194
206
  if (gapExceeds(prevItem, item, 2 * item.sizePt)) runs.push({ text: " " });
195
- else if (gapExceeds(prevItem, item, .5)) runs[runs.length - 1].text += " ";
207
+ else if (gapExceeds(prevItem, item, .5)) appendWordSpace(runs);
196
208
  }
197
209
  const run = textItemToContentRun(item);
198
210
  require_layout_shared.stampFrame(run, pageIndex, textBoxOfItem(item, pageIndex));
199
- runs.push(run);
211
+ const verticalAlign = scriptPositionOnLine(item, line);
212
+ const core = item.text.trim();
213
+ if (verticalAlign === void 0 || core.length === 0) {
214
+ runs.push(run);
215
+ return;
216
+ }
217
+ const leading = item.text.slice(0, item.text.length - item.text.trimStart().length);
218
+ const trailing = item.text.slice(item.text.trimEnd().length);
219
+ if (leading.length > 0) runs.push({ text: leading });
220
+ runs.push({
221
+ ...run,
222
+ text: core,
223
+ verticalAlign
224
+ });
225
+ if (trailing.length > 0) runs.push({ text: trailing });
200
226
  });
201
227
  }
228
+ function appendWordSpace(runs) {
229
+ const last = runs[runs.length - 1];
230
+ if (last.verticalAlign === void 0) last.text += " ";
231
+ else runs.push({ text: " " });
232
+ }
202
233
  function bucketCounts(values, bucketSize) {
203
234
  const counts = /* @__PURE__ */ new Map();
204
235
  for (const v of values) {
@@ -281,8 +312,7 @@ function paragraphToContentParagraph(paragraph, pageIndex, headingLevel) {
281
312
  paragraph.lines.forEach((line, lineIndex) => {
282
313
  require_layout_shared.stampFrame(result, pageIndex, lineBox(line, pageIndex));
283
314
  if (lineIndex > 0) {
284
- const lastRun = result.runs[result.runs.length - 1];
285
- if (lastRun !== void 0) lastRun.text += " ";
315
+ if (result.runs.length > 0) appendWordSpace(result.runs);
286
316
  }
287
317
  pushRunsForLine(result.runs, line, pageIndex);
288
318
  });