js.documents 14.6.5 → 14.6.7
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.
- package/README.md +2 -2
- package/dist/{composition-plan-DMTkmk8n.js → composition-plan-BLhc55_x.js} +1 -1
- package/dist/{composition-plan-DJQgPvBj.cjs → composition-plan-CgjS-XZU.cjs} +1 -1
- package/dist/convert/composition-plan.cjs +1 -1
- package/dist/convert/composition-plan.js +1 -1
- package/dist/convert/composition-to-pdf.cjs +1 -1
- package/dist/convert/composition-to-pdf.js +1 -1
- package/dist/convert/composition.cjs +1 -1
- package/dist/convert/composition.js +1 -1
- package/dist/convert/from-pdf.cjs +2 -2
- package/dist/convert/from-pdf.js +2 -2
- package/dist/convert/local.cjs +1 -1
- package/dist/convert/local.js +1 -1
- package/dist/index.cjs +2 -2
- package/dist/index.js +2 -2
- package/dist/layout/lattice.cjs +28 -5
- package/dist/layout/lattice.d.cts +16 -1
- package/dist/layout/lattice.d.ts +16 -1
- package/dist/layout/lattice.js +25 -6
- package/dist/layout/reading-order.cjs +2 -0
- package/dist/layout/reading-order.d.cts +3 -1
- package/dist/layout/reading-order.d.ts +3 -1
- package/dist/layout/reading-order.js +1 -1
- package/dist/layout/reconstruct-presentations-prod.cjs +1 -1
- package/dist/layout/reconstruct-presentations-prod.js +1 -1
- package/dist/layout/reconstruct-tables.cjs +1 -1
- package/dist/layout/reconstruct-tables.js +1 -1
- package/dist/layout/reconstruct.cjs +1 -1
- package/dist/layout/reconstruct.js +1 -1
- package/dist/layout/ruled-rows.cjs +214 -0
- package/dist/layout/ruled-rows.d.cts +11 -0
- package/dist/layout/ruled-rows.d.ts +11 -0
- package/dist/layout/ruled-rows.js +213 -0
- package/dist/{reconstruct-presentations-prod-D1KbGPxa.js → reconstruct-presentations-prod-DV7UQqMT.js} +19 -5
- package/dist/{reconstruct-presentations-prod-CsvGlvP2.cjs → reconstruct-presentations-prod-DtpKKgRY.cjs} +19 -5
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -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
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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");
|
package/dist/convert/from-pdf.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { _ as reconstructWordprocessing } from "../reconstruct-presentations-prod-
|
|
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-
|
|
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";
|
package/dist/convert/local.cjs
CHANGED
|
@@ -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-
|
|
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
|
package/dist/convert/local.js
CHANGED
|
@@ -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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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";
|
package/dist/layout/lattice.cjs
CHANGED
|
@@ -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) <=
|
|
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) >=
|
|
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) <
|
|
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) <
|
|
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 <
|
|
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 };
|
package/dist/layout/lattice.d.ts
CHANGED
|
@@ -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 };
|
package/dist/layout/lattice.js
CHANGED
|
@@ -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) <=
|
|
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) >=
|
|
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) <
|
|
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) <
|
|
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 <
|
|
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 };
|
|
@@ -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 };
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
-
const require_reconstruct_presentations_prod = require("../reconstruct-presentations-prod-
|
|
2
|
+
const require_reconstruct_presentations_prod = require("../reconstruct-presentations-prod-DtpKKgRY.cjs");
|
|
3
3
|
exports.layoutItemToVector = require_reconstruct_presentations_prod.layoutItemToVector;
|
|
4
4
|
exports.lineToParagraph = require_reconstruct_presentations_prod.lineToParagraph;
|
|
5
5
|
exports.reconstructDrawing = require_reconstruct_presentations_prod.reconstructDrawing;
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { a as reconstructSheet, i as reconstructPresentation, n as lineToParagraph, o as recoverPageVectors, r as reconstructDrawing, s as splitLineByLargeGaps, t as layoutItemToVector } from "../reconstruct-presentations-prod-
|
|
1
|
+
import { a as reconstructSheet, i as reconstructPresentation, n as lineToParagraph, o as recoverPageVectors, r as reconstructDrawing, s as splitLineByLargeGaps, t as layoutItemToVector } from "../reconstruct-presentations-prod-DV7UQqMT.js";
|
|
2
2
|
export { layoutItemToVector, lineToParagraph, reconstructDrawing, reconstructPresentation, reconstructSheet, recoverPageVectors, splitLineByLargeGaps };
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
-
const require_reconstruct_presentations_prod = require("../reconstruct-presentations-prod-
|
|
2
|
+
const require_reconstruct_presentations_prod = require("../reconstruct-presentations-prod-DtpKKgRY.cjs");
|
|
3
3
|
exports.buildGridFromLattice = require_reconstruct_presentations_prod.buildGridFromLattice;
|
|
4
4
|
exports.buildGridFromTextClustering = require_reconstruct_presentations_prod.buildGridFromTextClustering;
|
|
5
5
|
exports.recoverTables = require_reconstruct_presentations_prod.recoverTables;
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { b as recoverTables, v as buildGridFromLattice, y as buildGridFromTextClustering } from "../reconstruct-presentations-prod-
|
|
1
|
+
import { b as recoverTables, v as buildGridFromLattice, y as buildGridFromTextClustering } from "../reconstruct-presentations-prod-DV7UQqMT.js";
|
|
2
2
|
export { buildGridFromLattice, buildGridFromTextClustering, recoverTables };
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
-
const require_reconstruct_presentations_prod = require("../reconstruct-presentations-prod-
|
|
2
|
+
const require_reconstruct_presentations_prod = require("../reconstruct-presentations-prod-DtpKKgRY.cjs");
|
|
3
3
|
exports.CELL_TYPES = require_reconstruct_presentations_prod.CELL_TYPES;
|
|
4
4
|
exports.DEFAULT_COLUMN_WIDTH_FALLBACK_PT = require_reconstruct_presentations_prod.DEFAULT_COLUMN_WIDTH_FALLBACK_PT;
|
|
5
5
|
exports.NO_ITEMS = require_reconstruct_presentations_prod.NO_ITEMS;
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { _ as reconstructWordprocessing, c as CELL_TYPES, d as ROW_TYPES, f as TABLE_TYPES, g as reconstructSpreadsheet, h as nearestOfType, l as DEFAULT_COLUMN_WIDTH_FALLBACK_PT, m as indexStructure, p as ZERO_MARGINS, u as NO_ITEMS } from "../reconstruct-presentations-prod-
|
|
1
|
+
import { _ as reconstructWordprocessing, c as CELL_TYPES, d as ROW_TYPES, f as TABLE_TYPES, g as reconstructSpreadsheet, h as nearestOfType, l as DEFAULT_COLUMN_WIDTH_FALLBACK_PT, m as indexStructure, p as ZERO_MARGINS, u as NO_ITEMS } from "../reconstruct-presentations-prod-DV7UQqMT.js";
|
|
2
2
|
export { CELL_TYPES, DEFAULT_COLUMN_WIDTH_FALLBACK_PT, NO_ITEMS, ROW_TYPES, TABLE_TYPES, ZERO_MARGINS, indexStructure, nearestOfType, reconstructSpreadsheet, reconstructWordprocessing };
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
+
const require_layout_reconstruct_lines = require("./reconstruct-lines.cjs");
|
|
3
|
+
const require_layout_lattice = require("./lattice.cjs");
|
|
4
|
+
const require_layout_reading_order = require("./reading-order.cjs");
|
|
5
|
+
//#region src/layout/ruled-rows.ts
|
|
6
|
+
/**
|
|
7
|
+
* The grid of a table ruled between its rows and set apart by gutters between its columns, when the page holds one.
|
|
8
|
+
* @param items - the page's items; rules are read from its lines, paths and thin rectangles, and text from its text items
|
|
9
|
+
* @returns a lattice whose row boundaries are the rules (and the top of an unruled heading row above them) and whose column boundaries are the table's two ends and the middle of each gutter, a row that crosses a gutter spanning the columns it covers; undefined when no set of rules of one span holds two rows of text, fewer than two rows hold text in more than one place, the rows leave no gutter they all share, or every column reads as running prose
|
|
10
|
+
*/
|
|
11
|
+
function detectRuledRowLattice(items) {
|
|
12
|
+
const rules = rulesOfOneTable(items);
|
|
13
|
+
if (rules === void 0) return;
|
|
14
|
+
const text = items.filter((item) => item.kind === "text" && item.text.trim().length > 0 && item.xPt >= rules.leftPt && item.xPt < rules.rightPt);
|
|
15
|
+
if (text.some((item) => item.widthPt === void 0 || item.writingMode !== void 0 || (item.rotationDeg ?? 0) !== 0)) return;
|
|
16
|
+
const ruledRows = rules.positionsDescPt.slice(1).map((lowerPt, row) => text.filter((item) => item.yPt > lowerPt && item.yPt < rules.positionsDescPt[row]));
|
|
17
|
+
const widestSpacePt = Math.max(...ruledRows.flat().map(require_layout_reading_order.spaceAdvancePt));
|
|
18
|
+
const shared = sharedGutters(ruledRows, widestSpacePt);
|
|
19
|
+
if (shared === void 0) return;
|
|
20
|
+
const { gutters } = shared;
|
|
21
|
+
const dividersPt = gutters.map((gutter) => (gutter.loPt + gutter.hiPt) / 2);
|
|
22
|
+
const topRulePt = rules.positionsDescPt[0];
|
|
23
|
+
const heading = headingRow(text.filter((item) => item.yPt > topRulePt), topRulePt, dividersPt, require_layout_reconstruct_lines.clusterIntoLines(ruledRows.flat()), widestSpacePt);
|
|
24
|
+
const rows = heading === void 0 ? ruledRows : [heading.items, ...ruledRows];
|
|
25
|
+
if (shared.rowsWithColumns + (heading !== void 0 && textRuns(heading.items, widestSpacePt).length > 1 ? 1 : 0) < 2) return;
|
|
26
|
+
const columnBoundariesAscPt = [
|
|
27
|
+
rules.leftPt,
|
|
28
|
+
...dividersPt,
|
|
29
|
+
rules.rightPt
|
|
30
|
+
];
|
|
31
|
+
if (holdsDrawing(items, rules, heading?.topPt ?? rules.positionsDescPt[0])) return;
|
|
32
|
+
const narrowestGutterPt = Math.min(...gutters.map((gutter) => gutter.hiPt - gutter.loPt));
|
|
33
|
+
if (columnBoundariesAscPt.slice(1).map((rightPt, column) => rows.flat().filter((item) => item.xPt >= columnBoundariesAscPt[column] && item.xPt < rightPt)).every((column) => require_layout_reading_order.isRunningProse(column, narrowestGutterPt))) return;
|
|
34
|
+
const bands = [...heading === void 0 ? [] : [heading], ...ruledRows.map((rowItems, row) => ({
|
|
35
|
+
items: rowItems,
|
|
36
|
+
topPt: rules.positionsDescPt[row]
|
|
37
|
+
}))].flatMap((band) => rowsOfBand(band.items, band.topPt, dividersPt, widestSpacePt));
|
|
38
|
+
return {
|
|
39
|
+
rowBoundariesDescPt: [...bands.map((band) => band.topPt), rules.positionsDescPt.at(-1)],
|
|
40
|
+
columnBoundariesAscPt,
|
|
41
|
+
regions: bands.flatMap((band, rowIndex) => rowRegions(band.items, rowIndex, dividersPt, widestSpacePt)),
|
|
42
|
+
sourceItems: rules.sourceItems
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
function rulesOfOneTable(items) {
|
|
46
|
+
const groups = [];
|
|
47
|
+
for (const rule of require_layout_lattice.horizontalRules(items)) {
|
|
48
|
+
const group = groups.find((members) => members.every((member) => spansOverlap(member, rule)));
|
|
49
|
+
if (group === void 0) groups.push([rule]);
|
|
50
|
+
else group.push(rule);
|
|
51
|
+
}
|
|
52
|
+
const best = groups.filter((members) => members.length >= 3).map((members) => ({
|
|
53
|
+
members,
|
|
54
|
+
leftPt: Math.min(...members.map((rule) => rule.leftPt)),
|
|
55
|
+
rightPt: Math.max(...members.map((rule) => rule.rightPt))
|
|
56
|
+
})).sort((a, b) => b.members.length - a.members.length || b.rightPt - b.leftPt - (a.rightPt - a.leftPt))[0];
|
|
57
|
+
if (best === void 0) return;
|
|
58
|
+
return {
|
|
59
|
+
positionsDescPt: best.members.map((rule) => rule.position).sort((a, b) => b - a),
|
|
60
|
+
leftPt: best.leftPt,
|
|
61
|
+
rightPt: best.rightPt,
|
|
62
|
+
sourceItems: new Set(best.members.flatMap((rule) => rule.items))
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
function holdsDrawing(items, rules, topPt) {
|
|
66
|
+
const bottomPt = rules.positionsDescPt.at(-1);
|
|
67
|
+
const widthPt = rules.rightPt - rules.leftPt;
|
|
68
|
+
const inside = (box) => box.loXPt < rules.rightPt - 3 && box.hiXPt > rules.leftPt + 3 && box.loYPt < topPt && box.hiYPt > bottomPt;
|
|
69
|
+
const spansRow = (box) => box.hiXPt - box.loXPt >= require_layout_lattice.EDGE_COVERAGE_RATIO * widthPt;
|
|
70
|
+
const horizontal = (box) => box.hiYPt - box.loYPt <= 3 && box.hiXPt - box.loXPt > box.hiYPt - box.loYPt;
|
|
71
|
+
return items.some((item) => {
|
|
72
|
+
if (rules.sourceItems.has(item)) return false;
|
|
73
|
+
const box = boxOf(item);
|
|
74
|
+
return box !== void 0 && inside(box) && !horizontal(box) && !(item.kind !== "image" && item.kind !== "ellipse" && spansRow(box));
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
function boxOf(item) {
|
|
78
|
+
switch (item.kind) {
|
|
79
|
+
case "image":
|
|
80
|
+
case "rect":
|
|
81
|
+
case "ellipse": return {
|
|
82
|
+
loXPt: item.xPt,
|
|
83
|
+
hiXPt: item.xPt + item.widthPt,
|
|
84
|
+
loYPt: item.yPt,
|
|
85
|
+
hiYPt: item.yPt + item.heightPt
|
|
86
|
+
};
|
|
87
|
+
case "line": return {
|
|
88
|
+
loXPt: Math.min(item.x1Pt, item.x2Pt),
|
|
89
|
+
hiXPt: Math.max(item.x1Pt, item.x2Pt),
|
|
90
|
+
loYPt: Math.min(item.y1Pt, item.y2Pt),
|
|
91
|
+
hiYPt: Math.max(item.y1Pt, item.y2Pt)
|
|
92
|
+
};
|
|
93
|
+
case "path": {
|
|
94
|
+
const xs = item.subpaths.flatMap((subpath) => [subpath.startXPt, ...subpath.segments.flatMap((segment) => segment.kind === "cubic" ? [
|
|
95
|
+
segment.c1xPt,
|
|
96
|
+
segment.c2xPt,
|
|
97
|
+
segment.xPt
|
|
98
|
+
] : [segment.xPt])]);
|
|
99
|
+
const ys = item.subpaths.flatMap((subpath) => [subpath.startYPt, ...subpath.segments.flatMap((segment) => segment.kind === "cubic" ? [
|
|
100
|
+
segment.c1yPt,
|
|
101
|
+
segment.c2yPt,
|
|
102
|
+
segment.yPt
|
|
103
|
+
] : [segment.yPt])]);
|
|
104
|
+
return {
|
|
105
|
+
loXPt: Math.min(...xs),
|
|
106
|
+
hiXPt: Math.max(...xs),
|
|
107
|
+
loYPt: Math.min(...ys),
|
|
108
|
+
hiYPt: Math.max(...ys)
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
case "text":
|
|
112
|
+
case "link":
|
|
113
|
+
case "internalLink": return;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
function spansOverlap(a, b) {
|
|
117
|
+
const overlapPt = Math.min(a.rightPt, b.rightPt) - Math.max(a.leftPt, b.leftPt);
|
|
118
|
+
const unionPt = Math.max(a.rightPt, b.rightPt) - Math.min(a.leftPt, b.leftPt);
|
|
119
|
+
return overlapPt >= require_layout_lattice.EDGE_COVERAGE_RATIO * unionPt;
|
|
120
|
+
}
|
|
121
|
+
function textRuns(row, widestSpacePt) {
|
|
122
|
+
const runs = [];
|
|
123
|
+
for (const item of [...row].sort((a, b) => a.xPt - b.xPt)) {
|
|
124
|
+
const last = runs.at(-1);
|
|
125
|
+
if (last !== void 0 && item.xPt - last.hiPt <= widestSpacePt) last.hiPt = Math.max(last.hiPt, rightEdge(item));
|
|
126
|
+
else runs.push({
|
|
127
|
+
loPt: item.xPt,
|
|
128
|
+
hiPt: rightEdge(item)
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
return runs;
|
|
132
|
+
}
|
|
133
|
+
function sharedGutters(rows, widestSpacePt) {
|
|
134
|
+
const gapsByRow = rows.map((row) => textRuns(row, widestSpacePt)).filter((runs) => runs.length > 1).map((runs) => runs.slice(1).map((run, index) => ({
|
|
135
|
+
loPt: runs[index].hiPt,
|
|
136
|
+
hiPt: run.loPt
|
|
137
|
+
})));
|
|
138
|
+
const [first, ...rest] = gapsByRow;
|
|
139
|
+
if (first === void 0) return;
|
|
140
|
+
const gutters = rest.reduce((shared, gaps) => shared.flatMap((gutter) => gaps.flatMap((gap) => {
|
|
141
|
+
const loPt = Math.max(gutter.loPt, gap.loPt);
|
|
142
|
+
const hiPt = Math.min(gutter.hiPt, gap.hiPt);
|
|
143
|
+
return hiPt - loPt > widestSpacePt ? [{
|
|
144
|
+
loPt,
|
|
145
|
+
hiPt
|
|
146
|
+
}] : [];
|
|
147
|
+
})), first);
|
|
148
|
+
return gutters.length > 0 ? {
|
|
149
|
+
gutters,
|
|
150
|
+
rowsWithColumns: gapsByRow.length
|
|
151
|
+
} : void 0;
|
|
152
|
+
}
|
|
153
|
+
function headingRow(above, topRulePt, dividersPt, tableLines, widestSpacePt) {
|
|
154
|
+
const reachPt = require_layout_reconstruct_lines.PARAGRAPH_GAP_MULTIPLIER * require_layout_reconstruct_lines.estimateModalLineSpacing(tableLines);
|
|
155
|
+
const items = [];
|
|
156
|
+
let lowerPt = topRulePt;
|
|
157
|
+
for (const line of [...require_layout_reconstruct_lines.clusterIntoLines(above)].reverse()) {
|
|
158
|
+
if (line.items.some((item) => dividersPt.some((dividerPt) => item.xPt < dividerPt && rightEdge(item) > dividerPt)) || line.baselineY - lowerPt > reachPt) break;
|
|
159
|
+
items.push(...line.items);
|
|
160
|
+
lowerPt = line.baselineY;
|
|
161
|
+
}
|
|
162
|
+
if (columnsHolding(items, dividersPt, widestSpacePt).size < 2) return;
|
|
163
|
+
return {
|
|
164
|
+
items,
|
|
165
|
+
topPt: Math.max(...items.map((item) => item.yPt + item.sizePt))
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
function columnsHolding(items, dividersPt, widestSpacePt) {
|
|
169
|
+
return new Set(textRuns(items, widestSpacePt).map((run) => dividersPt.filter((dividerPt) => dividerPt <= run.loPt).length));
|
|
170
|
+
}
|
|
171
|
+
function rowsOfBand(items, topPt, dividersPt, widestSpacePt) {
|
|
172
|
+
const lines = require_layout_reconstruct_lines.clusterIntoLines(items);
|
|
173
|
+
const first = lines[0];
|
|
174
|
+
const opening = first === void 0 ? /* @__PURE__ */ new Set() : columnsHolding(first.items, dividersPt, widestSpacePt);
|
|
175
|
+
if (!(lines.length > 1 && opening.size > 1 && lines.every((line) => {
|
|
176
|
+
const occupied = columnsHolding(line.items, dividersPt, widestSpacePt);
|
|
177
|
+
return [...opening].every((column) => occupied.has(column));
|
|
178
|
+
}))) return [{
|
|
179
|
+
items: [...items],
|
|
180
|
+
topPt
|
|
181
|
+
}];
|
|
182
|
+
return lines.map((line, index) => ({
|
|
183
|
+
items: [...line.items],
|
|
184
|
+
topPt: index === 0 ? topPt : (lines[index - 1].baselineY + line.baselineY) / 2
|
|
185
|
+
}));
|
|
186
|
+
}
|
|
187
|
+
function rowRegions(row, rowIndex, dividersPt, widestSpacePt) {
|
|
188
|
+
const runs = textRuns(row, widestSpacePt);
|
|
189
|
+
const regions = [];
|
|
190
|
+
let colStart = 0;
|
|
191
|
+
dividersPt.forEach((dividerPt, index) => {
|
|
192
|
+
if (!runs.some((run) => run.loPt < dividerPt && run.hiPt > dividerPt)) {
|
|
193
|
+
regions.push({
|
|
194
|
+
rowStart: rowIndex,
|
|
195
|
+
rowEnd: rowIndex + 1,
|
|
196
|
+
colStart,
|
|
197
|
+
colEnd: index + 1
|
|
198
|
+
});
|
|
199
|
+
colStart = index + 1;
|
|
200
|
+
}
|
|
201
|
+
});
|
|
202
|
+
regions.push({
|
|
203
|
+
rowStart: rowIndex,
|
|
204
|
+
rowEnd: rowIndex + 1,
|
|
205
|
+
colStart,
|
|
206
|
+
colEnd: dividersPt.length + 1
|
|
207
|
+
});
|
|
208
|
+
return regions;
|
|
209
|
+
}
|
|
210
|
+
function rightEdge(item) {
|
|
211
|
+
return item.xPt + (item.widthPt ?? 0);
|
|
212
|
+
}
|
|
213
|
+
//#endregion
|
|
214
|
+
exports.detectRuledRowLattice = detectRuledRowLattice;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { GridLattice } from "./lattice.cjs";
|
|
2
|
+
import { LayoutItem } from "pdf-codec";
|
|
3
|
+
//#region src/layout/ruled-rows.d.ts
|
|
4
|
+
/**
|
|
5
|
+
* The grid of a table ruled between its rows and set apart by gutters between its columns, when the page holds one.
|
|
6
|
+
* @param items - the page's items; rules are read from its lines, paths and thin rectangles, and text from its text items
|
|
7
|
+
* @returns a lattice whose row boundaries are the rules (and the top of an unruled heading row above them) and whose column boundaries are the table's two ends and the middle of each gutter, a row that crosses a gutter spanning the columns it covers; undefined when no set of rules of one span holds two rows of text, fewer than two rows hold text in more than one place, the rows leave no gutter they all share, or every column reads as running prose
|
|
8
|
+
*/
|
|
9
|
+
declare function detectRuledRowLattice(items: readonly LayoutItem[]): GridLattice | undefined;
|
|
10
|
+
//#endregion
|
|
11
|
+
export { detectRuledRowLattice };
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { GridLattice } from "./lattice.js";
|
|
2
|
+
import { LayoutItem } from "pdf-codec";
|
|
3
|
+
//#region src/layout/ruled-rows.d.ts
|
|
4
|
+
/**
|
|
5
|
+
* The grid of a table ruled between its rows and set apart by gutters between its columns, when the page holds one.
|
|
6
|
+
* @param items - the page's items; rules are read from its lines, paths and thin rectangles, and text from its text items
|
|
7
|
+
* @returns a lattice whose row boundaries are the rules (and the top of an unruled heading row above them) and whose column boundaries are the table's two ends and the middle of each gutter, a row that crosses a gutter spanning the columns it covers; undefined when no set of rules of one span holds two rows of text, fewer than two rows hold text in more than one place, the rows leave no gutter they all share, or every column reads as running prose
|
|
8
|
+
*/
|
|
9
|
+
declare function detectRuledRowLattice(items: readonly LayoutItem[]): GridLattice | undefined;
|
|
10
|
+
//#endregion
|
|
11
|
+
export { detectRuledRowLattice };
|
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
import { PARAGRAPH_GAP_MULTIPLIER, clusterIntoLines, estimateModalLineSpacing } from "./reconstruct-lines.js";
|
|
2
|
+
import { EDGE_COVERAGE_RATIO, horizontalRules } from "./lattice.js";
|
|
3
|
+
import { isRunningProse, spaceAdvancePt } from "./reading-order.js";
|
|
4
|
+
//#region src/layout/ruled-rows.ts
|
|
5
|
+
/**
|
|
6
|
+
* The grid of a table ruled between its rows and set apart by gutters between its columns, when the page holds one.
|
|
7
|
+
* @param items - the page's items; rules are read from its lines, paths and thin rectangles, and text from its text items
|
|
8
|
+
* @returns a lattice whose row boundaries are the rules (and the top of an unruled heading row above them) and whose column boundaries are the table's two ends and the middle of each gutter, a row that crosses a gutter spanning the columns it covers; undefined when no set of rules of one span holds two rows of text, fewer than two rows hold text in more than one place, the rows leave no gutter they all share, or every column reads as running prose
|
|
9
|
+
*/
|
|
10
|
+
function detectRuledRowLattice(items) {
|
|
11
|
+
const rules = rulesOfOneTable(items);
|
|
12
|
+
if (rules === void 0) return;
|
|
13
|
+
const text = items.filter((item) => item.kind === "text" && item.text.trim().length > 0 && item.xPt >= rules.leftPt && item.xPt < rules.rightPt);
|
|
14
|
+
if (text.some((item) => item.widthPt === void 0 || item.writingMode !== void 0 || (item.rotationDeg ?? 0) !== 0)) return;
|
|
15
|
+
const ruledRows = rules.positionsDescPt.slice(1).map((lowerPt, row) => text.filter((item) => item.yPt > lowerPt && item.yPt < rules.positionsDescPt[row]));
|
|
16
|
+
const widestSpacePt = Math.max(...ruledRows.flat().map(spaceAdvancePt));
|
|
17
|
+
const shared = sharedGutters(ruledRows, widestSpacePt);
|
|
18
|
+
if (shared === void 0) return;
|
|
19
|
+
const { gutters } = shared;
|
|
20
|
+
const dividersPt = gutters.map((gutter) => (gutter.loPt + gutter.hiPt) / 2);
|
|
21
|
+
const topRulePt = rules.positionsDescPt[0];
|
|
22
|
+
const heading = headingRow(text.filter((item) => item.yPt > topRulePt), topRulePt, dividersPt, clusterIntoLines(ruledRows.flat()), widestSpacePt);
|
|
23
|
+
const rows = heading === void 0 ? ruledRows : [heading.items, ...ruledRows];
|
|
24
|
+
if (shared.rowsWithColumns + (heading !== void 0 && textRuns(heading.items, widestSpacePt).length > 1 ? 1 : 0) < 2) return;
|
|
25
|
+
const columnBoundariesAscPt = [
|
|
26
|
+
rules.leftPt,
|
|
27
|
+
...dividersPt,
|
|
28
|
+
rules.rightPt
|
|
29
|
+
];
|
|
30
|
+
if (holdsDrawing(items, rules, heading?.topPt ?? rules.positionsDescPt[0])) return;
|
|
31
|
+
const narrowestGutterPt = Math.min(...gutters.map((gutter) => gutter.hiPt - gutter.loPt));
|
|
32
|
+
if (columnBoundariesAscPt.slice(1).map((rightPt, column) => rows.flat().filter((item) => item.xPt >= columnBoundariesAscPt[column] && item.xPt < rightPt)).every((column) => isRunningProse(column, narrowestGutterPt))) return;
|
|
33
|
+
const bands = [...heading === void 0 ? [] : [heading], ...ruledRows.map((rowItems, row) => ({
|
|
34
|
+
items: rowItems,
|
|
35
|
+
topPt: rules.positionsDescPt[row]
|
|
36
|
+
}))].flatMap((band) => rowsOfBand(band.items, band.topPt, dividersPt, widestSpacePt));
|
|
37
|
+
return {
|
|
38
|
+
rowBoundariesDescPt: [...bands.map((band) => band.topPt), rules.positionsDescPt.at(-1)],
|
|
39
|
+
columnBoundariesAscPt,
|
|
40
|
+
regions: bands.flatMap((band, rowIndex) => rowRegions(band.items, rowIndex, dividersPt, widestSpacePt)),
|
|
41
|
+
sourceItems: rules.sourceItems
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
function rulesOfOneTable(items) {
|
|
45
|
+
const groups = [];
|
|
46
|
+
for (const rule of horizontalRules(items)) {
|
|
47
|
+
const group = groups.find((members) => members.every((member) => spansOverlap(member, rule)));
|
|
48
|
+
if (group === void 0) groups.push([rule]);
|
|
49
|
+
else group.push(rule);
|
|
50
|
+
}
|
|
51
|
+
const best = groups.filter((members) => members.length >= 3).map((members) => ({
|
|
52
|
+
members,
|
|
53
|
+
leftPt: Math.min(...members.map((rule) => rule.leftPt)),
|
|
54
|
+
rightPt: Math.max(...members.map((rule) => rule.rightPt))
|
|
55
|
+
})).sort((a, b) => b.members.length - a.members.length || b.rightPt - b.leftPt - (a.rightPt - a.leftPt))[0];
|
|
56
|
+
if (best === void 0) return;
|
|
57
|
+
return {
|
|
58
|
+
positionsDescPt: best.members.map((rule) => rule.position).sort((a, b) => b - a),
|
|
59
|
+
leftPt: best.leftPt,
|
|
60
|
+
rightPt: best.rightPt,
|
|
61
|
+
sourceItems: new Set(best.members.flatMap((rule) => rule.items))
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
function holdsDrawing(items, rules, topPt) {
|
|
65
|
+
const bottomPt = rules.positionsDescPt.at(-1);
|
|
66
|
+
const widthPt = rules.rightPt - rules.leftPt;
|
|
67
|
+
const inside = (box) => box.loXPt < rules.rightPt - 3 && box.hiXPt > rules.leftPt + 3 && box.loYPt < topPt && box.hiYPt > bottomPt;
|
|
68
|
+
const spansRow = (box) => box.hiXPt - box.loXPt >= EDGE_COVERAGE_RATIO * widthPt;
|
|
69
|
+
const horizontal = (box) => box.hiYPt - box.loYPt <= 3 && box.hiXPt - box.loXPt > box.hiYPt - box.loYPt;
|
|
70
|
+
return items.some((item) => {
|
|
71
|
+
if (rules.sourceItems.has(item)) return false;
|
|
72
|
+
const box = boxOf(item);
|
|
73
|
+
return box !== void 0 && inside(box) && !horizontal(box) && !(item.kind !== "image" && item.kind !== "ellipse" && spansRow(box));
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
function boxOf(item) {
|
|
77
|
+
switch (item.kind) {
|
|
78
|
+
case "image":
|
|
79
|
+
case "rect":
|
|
80
|
+
case "ellipse": return {
|
|
81
|
+
loXPt: item.xPt,
|
|
82
|
+
hiXPt: item.xPt + item.widthPt,
|
|
83
|
+
loYPt: item.yPt,
|
|
84
|
+
hiYPt: item.yPt + item.heightPt
|
|
85
|
+
};
|
|
86
|
+
case "line": return {
|
|
87
|
+
loXPt: Math.min(item.x1Pt, item.x2Pt),
|
|
88
|
+
hiXPt: Math.max(item.x1Pt, item.x2Pt),
|
|
89
|
+
loYPt: Math.min(item.y1Pt, item.y2Pt),
|
|
90
|
+
hiYPt: Math.max(item.y1Pt, item.y2Pt)
|
|
91
|
+
};
|
|
92
|
+
case "path": {
|
|
93
|
+
const xs = item.subpaths.flatMap((subpath) => [subpath.startXPt, ...subpath.segments.flatMap((segment) => segment.kind === "cubic" ? [
|
|
94
|
+
segment.c1xPt,
|
|
95
|
+
segment.c2xPt,
|
|
96
|
+
segment.xPt
|
|
97
|
+
] : [segment.xPt])]);
|
|
98
|
+
const ys = item.subpaths.flatMap((subpath) => [subpath.startYPt, ...subpath.segments.flatMap((segment) => segment.kind === "cubic" ? [
|
|
99
|
+
segment.c1yPt,
|
|
100
|
+
segment.c2yPt,
|
|
101
|
+
segment.yPt
|
|
102
|
+
] : [segment.yPt])]);
|
|
103
|
+
return {
|
|
104
|
+
loXPt: Math.min(...xs),
|
|
105
|
+
hiXPt: Math.max(...xs),
|
|
106
|
+
loYPt: Math.min(...ys),
|
|
107
|
+
hiYPt: Math.max(...ys)
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
case "text":
|
|
111
|
+
case "link":
|
|
112
|
+
case "internalLink": return;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
function spansOverlap(a, b) {
|
|
116
|
+
const overlapPt = Math.min(a.rightPt, b.rightPt) - Math.max(a.leftPt, b.leftPt);
|
|
117
|
+
const unionPt = Math.max(a.rightPt, b.rightPt) - Math.min(a.leftPt, b.leftPt);
|
|
118
|
+
return overlapPt >= EDGE_COVERAGE_RATIO * unionPt;
|
|
119
|
+
}
|
|
120
|
+
function textRuns(row, widestSpacePt) {
|
|
121
|
+
const runs = [];
|
|
122
|
+
for (const item of [...row].sort((a, b) => a.xPt - b.xPt)) {
|
|
123
|
+
const last = runs.at(-1);
|
|
124
|
+
if (last !== void 0 && item.xPt - last.hiPt <= widestSpacePt) last.hiPt = Math.max(last.hiPt, rightEdge(item));
|
|
125
|
+
else runs.push({
|
|
126
|
+
loPt: item.xPt,
|
|
127
|
+
hiPt: rightEdge(item)
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
return runs;
|
|
131
|
+
}
|
|
132
|
+
function sharedGutters(rows, widestSpacePt) {
|
|
133
|
+
const gapsByRow = rows.map((row) => textRuns(row, widestSpacePt)).filter((runs) => runs.length > 1).map((runs) => runs.slice(1).map((run, index) => ({
|
|
134
|
+
loPt: runs[index].hiPt,
|
|
135
|
+
hiPt: run.loPt
|
|
136
|
+
})));
|
|
137
|
+
const [first, ...rest] = gapsByRow;
|
|
138
|
+
if (first === void 0) return;
|
|
139
|
+
const gutters = rest.reduce((shared, gaps) => shared.flatMap((gutter) => gaps.flatMap((gap) => {
|
|
140
|
+
const loPt = Math.max(gutter.loPt, gap.loPt);
|
|
141
|
+
const hiPt = Math.min(gutter.hiPt, gap.hiPt);
|
|
142
|
+
return hiPt - loPt > widestSpacePt ? [{
|
|
143
|
+
loPt,
|
|
144
|
+
hiPt
|
|
145
|
+
}] : [];
|
|
146
|
+
})), first);
|
|
147
|
+
return gutters.length > 0 ? {
|
|
148
|
+
gutters,
|
|
149
|
+
rowsWithColumns: gapsByRow.length
|
|
150
|
+
} : void 0;
|
|
151
|
+
}
|
|
152
|
+
function headingRow(above, topRulePt, dividersPt, tableLines, widestSpacePt) {
|
|
153
|
+
const reachPt = PARAGRAPH_GAP_MULTIPLIER * estimateModalLineSpacing(tableLines);
|
|
154
|
+
const items = [];
|
|
155
|
+
let lowerPt = topRulePt;
|
|
156
|
+
for (const line of [...clusterIntoLines(above)].reverse()) {
|
|
157
|
+
if (line.items.some((item) => dividersPt.some((dividerPt) => item.xPt < dividerPt && rightEdge(item) > dividerPt)) || line.baselineY - lowerPt > reachPt) break;
|
|
158
|
+
items.push(...line.items);
|
|
159
|
+
lowerPt = line.baselineY;
|
|
160
|
+
}
|
|
161
|
+
if (columnsHolding(items, dividersPt, widestSpacePt).size < 2) return;
|
|
162
|
+
return {
|
|
163
|
+
items,
|
|
164
|
+
topPt: Math.max(...items.map((item) => item.yPt + item.sizePt))
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
function columnsHolding(items, dividersPt, widestSpacePt) {
|
|
168
|
+
return new Set(textRuns(items, widestSpacePt).map((run) => dividersPt.filter((dividerPt) => dividerPt <= run.loPt).length));
|
|
169
|
+
}
|
|
170
|
+
function rowsOfBand(items, topPt, dividersPt, widestSpacePt) {
|
|
171
|
+
const lines = clusterIntoLines(items);
|
|
172
|
+
const first = lines[0];
|
|
173
|
+
const opening = first === void 0 ? /* @__PURE__ */ new Set() : columnsHolding(first.items, dividersPt, widestSpacePt);
|
|
174
|
+
if (!(lines.length > 1 && opening.size > 1 && lines.every((line) => {
|
|
175
|
+
const occupied = columnsHolding(line.items, dividersPt, widestSpacePt);
|
|
176
|
+
return [...opening].every((column) => occupied.has(column));
|
|
177
|
+
}))) return [{
|
|
178
|
+
items: [...items],
|
|
179
|
+
topPt
|
|
180
|
+
}];
|
|
181
|
+
return lines.map((line, index) => ({
|
|
182
|
+
items: [...line.items],
|
|
183
|
+
topPt: index === 0 ? topPt : (lines[index - 1].baselineY + line.baselineY) / 2
|
|
184
|
+
}));
|
|
185
|
+
}
|
|
186
|
+
function rowRegions(row, rowIndex, dividersPt, widestSpacePt) {
|
|
187
|
+
const runs = textRuns(row, widestSpacePt);
|
|
188
|
+
const regions = [];
|
|
189
|
+
let colStart = 0;
|
|
190
|
+
dividersPt.forEach((dividerPt, index) => {
|
|
191
|
+
if (!runs.some((run) => run.loPt < dividerPt && run.hiPt > dividerPt)) {
|
|
192
|
+
regions.push({
|
|
193
|
+
rowStart: rowIndex,
|
|
194
|
+
rowEnd: rowIndex + 1,
|
|
195
|
+
colStart,
|
|
196
|
+
colEnd: index + 1
|
|
197
|
+
});
|
|
198
|
+
colStart = index + 1;
|
|
199
|
+
}
|
|
200
|
+
});
|
|
201
|
+
regions.push({
|
|
202
|
+
rowStart: rowIndex,
|
|
203
|
+
rowEnd: rowIndex + 1,
|
|
204
|
+
colStart,
|
|
205
|
+
colEnd: dividersPt.length + 1
|
|
206
|
+
});
|
|
207
|
+
return regions;
|
|
208
|
+
}
|
|
209
|
+
function rightEdge(item) {
|
|
210
|
+
return item.xPt + (item.widthPt ?? 0);
|
|
211
|
+
}
|
|
212
|
+
//#endregion
|
|
213
|
+
export { detectRuledRowLattice };
|
|
@@ -6,6 +6,7 @@ import { throwIfAborted } from "./ports/abort.js";
|
|
|
6
6
|
import { HALF_POINT_BUCKET_PT, bucketCounts, clusterIntoLines, clusterIntoParagraphs, estimateModalLineSpacing, fontSizesClose, gapExceeds, lineBox, modeOf, paragraphToContentParagraph, pushRunsForLine, textBoxOfItem, textItemToContentRun, textItemVerticalExtent } from "./layout/reconstruct-lines.js";
|
|
7
7
|
import { detectGridLattice, findColumnIndex, findRowIndex } from "./layout/lattice.js";
|
|
8
8
|
import { compareReadingPosition, readingOrder } from "./layout/reading-order.js";
|
|
9
|
+
import { detectRuledRowLattice } from "./layout/ruled-rows.js";
|
|
9
10
|
import { denseTableRows } from "document-schema.js";
|
|
10
11
|
import { sourcePageIndex } from "pdf-codec/layout";
|
|
11
12
|
//#region src/layout/reconstruct-tables.ts
|
|
@@ -14,8 +15,23 @@ function recoverTables(page, pageIndex, structure) {
|
|
|
14
15
|
const tagged = recoverTaggedTables(page, pageIndex, structure);
|
|
15
16
|
if (tagged.length > 0) return tagged;
|
|
16
17
|
}
|
|
17
|
-
const
|
|
18
|
-
|
|
18
|
+
const grid = detectGridLattice(page.items);
|
|
19
|
+
const gridItems = grid?.sourceItems ?? NO_ITEMS;
|
|
20
|
+
return [grid, detectRuledRowLattice(page.items.filter((item) => !gridItems.has(item)))].filter((lattice) => lattice !== void 0).filter((lattice, index, lattices) => index === 0 || !latticesOverlap(lattices[0], lattice)).flatMap((lattice) => {
|
|
21
|
+
const table = recoverTable(page, pageIndex, lattice);
|
|
22
|
+
return table === void 0 ? [] : [table];
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
function latticesOverlap(a, b) {
|
|
26
|
+
const aLeft = a.columnBoundariesAscPt[0];
|
|
27
|
+
const aRight = a.columnBoundariesAscPt.at(-1);
|
|
28
|
+
const aTop = a.rowBoundariesDescPt[0];
|
|
29
|
+
const aBottom = a.rowBoundariesDescPt.at(-1);
|
|
30
|
+
const bLeft = b.columnBoundariesAscPt[0];
|
|
31
|
+
const bRight = b.columnBoundariesAscPt.at(-1);
|
|
32
|
+
const bTop = b.rowBoundariesDescPt[0];
|
|
33
|
+
const bBottom = b.rowBoundariesDescPt.at(-1);
|
|
34
|
+
return aLeft < bRight && bLeft < aRight && aBottom < bTop && bBottom < aTop;
|
|
19
35
|
}
|
|
20
36
|
function recoverTaggedTables(page, pageIndex, structure) {
|
|
21
37
|
const cellsByTable = /* @__PURE__ */ new Map();
|
|
@@ -126,9 +142,7 @@ function indexRegions(lattice, rowCount, columnCount) {
|
|
|
126
142
|
for (const region of lattice.regions) for (let i = region.rowStart; i < region.rowEnd; i++) for (let j = region.colStart; j < region.colEnd; j++) regionAt[i][j] = region;
|
|
127
143
|
return regionAt;
|
|
128
144
|
}
|
|
129
|
-
function recoverTable(page, pageIndex) {
|
|
130
|
-
const lattice = detectGridLattice(page.items);
|
|
131
|
-
if (lattice === void 0) return;
|
|
145
|
+
function recoverTable(page, pageIndex, lattice) {
|
|
132
146
|
const rowCount = lattice.rowBoundariesDescPt.length - 1;
|
|
133
147
|
const columnCount = lattice.columnBoundariesAscPt.length - 1;
|
|
134
148
|
const regionAt = indexRegions(lattice, rowCount, columnCount);
|
|
@@ -6,6 +6,7 @@ const require_ports_abort = require("./ports/abort.cjs");
|
|
|
6
6
|
const require_layout_reconstruct_lines = require("./layout/reconstruct-lines.cjs");
|
|
7
7
|
const require_layout_lattice = require("./layout/lattice.cjs");
|
|
8
8
|
const require_layout_reading_order = require("./layout/reading-order.cjs");
|
|
9
|
+
const require_layout_ruled_rows = require("./layout/ruled-rows.cjs");
|
|
9
10
|
let document_schema_js = require("document-schema.js");
|
|
10
11
|
let pdf_codec_layout = require("pdf-codec/layout");
|
|
11
12
|
//#region src/layout/reconstruct-tables.ts
|
|
@@ -14,8 +15,23 @@ function recoverTables(page, pageIndex, structure) {
|
|
|
14
15
|
const tagged = recoverTaggedTables(page, pageIndex, structure);
|
|
15
16
|
if (tagged.length > 0) return tagged;
|
|
16
17
|
}
|
|
17
|
-
const
|
|
18
|
-
|
|
18
|
+
const grid = require_layout_lattice.detectGridLattice(page.items);
|
|
19
|
+
const gridItems = grid?.sourceItems ?? NO_ITEMS;
|
|
20
|
+
return [grid, require_layout_ruled_rows.detectRuledRowLattice(page.items.filter((item) => !gridItems.has(item)))].filter((lattice) => lattice !== void 0).filter((lattice, index, lattices) => index === 0 || !latticesOverlap(lattices[0], lattice)).flatMap((lattice) => {
|
|
21
|
+
const table = recoverTable(page, pageIndex, lattice);
|
|
22
|
+
return table === void 0 ? [] : [table];
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
function latticesOverlap(a, b) {
|
|
26
|
+
const aLeft = a.columnBoundariesAscPt[0];
|
|
27
|
+
const aRight = a.columnBoundariesAscPt.at(-1);
|
|
28
|
+
const aTop = a.rowBoundariesDescPt[0];
|
|
29
|
+
const aBottom = a.rowBoundariesDescPt.at(-1);
|
|
30
|
+
const bLeft = b.columnBoundariesAscPt[0];
|
|
31
|
+
const bRight = b.columnBoundariesAscPt.at(-1);
|
|
32
|
+
const bTop = b.rowBoundariesDescPt[0];
|
|
33
|
+
const bBottom = b.rowBoundariesDescPt.at(-1);
|
|
34
|
+
return aLeft < bRight && bLeft < aRight && aBottom < bTop && bBottom < aTop;
|
|
19
35
|
}
|
|
20
36
|
function recoverTaggedTables(page, pageIndex, structure) {
|
|
21
37
|
const cellsByTable = /* @__PURE__ */ new Map();
|
|
@@ -126,9 +142,7 @@ function indexRegions(lattice, rowCount, columnCount) {
|
|
|
126
142
|
for (const region of lattice.regions) for (let i = region.rowStart; i < region.rowEnd; i++) for (let j = region.colStart; j < region.colEnd; j++) regionAt[i][j] = region;
|
|
127
143
|
return regionAt;
|
|
128
144
|
}
|
|
129
|
-
function recoverTable(page, pageIndex) {
|
|
130
|
-
const lattice = require_layout_lattice.detectGridLattice(page.items);
|
|
131
|
-
if (lattice === void 0) return;
|
|
145
|
+
function recoverTable(page, pageIndex, lattice) {
|
|
132
146
|
const rowCount = lattice.rowBoundariesDescPt.length - 1;
|
|
133
147
|
const columnCount = lattice.columnBoundariesAscPt.length - 1;
|
|
134
148
|
const regionAt = indexRegions(lattice, rowCount, columnCount);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "js.documents",
|
|
3
|
-
"version": "14.6.
|
|
3
|
+
"version": "14.6.7",
|
|
4
4
|
"description": "Converts between docx, pptx, odt, odp, ods, odg, xlsx, csv, svg, markdown, rtf, doc, xls, ppt and PDF through one shared, Zod-typed content schema (wpd read-only), plus read+write editors for OOXML and OpenDocument. Worker-isomorphic.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"repository": {
|
|
@@ -83,10 +83,10 @@
|
|
|
83
83
|
"document-schema.js": "7.16.2",
|
|
84
84
|
"epub-codec": "5.1.17",
|
|
85
85
|
"fflate": "0.8.3",
|
|
86
|
-
"markdown-codec": "11.2.
|
|
86
|
+
"markdown-codec": "11.2.1",
|
|
87
87
|
"odf.js": "11.2.3",
|
|
88
88
|
"ooxml.js": "12.2.33",
|
|
89
|
-
"pdf-codec": "5.7.
|
|
89
|
+
"pdf-codec": "5.7.1",
|
|
90
90
|
"ppt-codec": "2.2.24",
|
|
91
91
|
"rtf-codec": "8.1.25",
|
|
92
92
|
"temml": "0.13.4",
|