js.documents 4.5.0 → 4.6.0
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 +1 -1
- package/dist/convert/pdf-package-tables.cjs +15 -0
- package/dist/convert/pdf-package-tables.js +15 -0
- package/dist/layout/reconstruct.cjs +262 -46
- package/dist/layout/reconstruct.js +262 -46
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -575,7 +575,7 @@ The package is layered from generic primitives outward to the two conversion dir
|
|
|
575
575
|
- **`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 `DocumentPackage` 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`.
|
|
576
576
|
- **`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, rejecting malformed UTF-8; `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.
|
|
577
577
|
- **`src/svg/`** — fifth adapter family, sharing the drawing variant with odg. `text.ts` is the byte↔text boundary, rejecting malformed UTF-8; `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.
|
|
578
|
-
- **`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).
|
|
578
|
+
- **`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).
|
|
579
579
|
- **`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.
|
|
580
580
|
- **`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.
|
|
581
581
|
- **`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.
|
|
@@ -67,10 +67,25 @@ function stampPdfPackageTables(pkg, layout) {
|
|
|
67
67
|
};
|
|
68
68
|
});
|
|
69
69
|
});
|
|
70
|
+
if (layout.structure !== void 0) walkStructureElements(layout.structure, void 0, definitions);
|
|
70
71
|
if (Object.keys(definitions).length > 0) pkg.definitions = {
|
|
71
72
|
...pkg.definitions,
|
|
72
73
|
...definitions
|
|
73
74
|
};
|
|
74
75
|
}
|
|
76
|
+
function walkStructureElements(elements, parentKey, definitions) {
|
|
77
|
+
for (const element of elements) {
|
|
78
|
+
definitions[element.id] = {
|
|
79
|
+
kind: "structure",
|
|
80
|
+
type: element.type,
|
|
81
|
+
...element.title !== void 0 ? { title: element.title } : {},
|
|
82
|
+
...element.language !== void 0 ? { language: element.language } : {},
|
|
83
|
+
...element.alt !== void 0 ? { alt: element.alt } : {},
|
|
84
|
+
...element.actualText !== void 0 ? { actualText: element.actualText } : {},
|
|
85
|
+
...parentKey !== void 0 ? { parent: parentKey } : {}
|
|
86
|
+
};
|
|
87
|
+
walkStructureElements(element.children, element.id, definitions);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
75
90
|
//#endregion
|
|
76
91
|
exports.stampPdfPackageTables = stampPdfPackageTables;
|
|
@@ -66,10 +66,25 @@ function stampPdfPackageTables(pkg, layout) {
|
|
|
66
66
|
};
|
|
67
67
|
});
|
|
68
68
|
});
|
|
69
|
+
if (layout.structure !== void 0) walkStructureElements(layout.structure, void 0, definitions);
|
|
69
70
|
if (Object.keys(definitions).length > 0) pkg.definitions = {
|
|
70
71
|
...pkg.definitions,
|
|
71
72
|
...definitions
|
|
72
73
|
};
|
|
73
74
|
}
|
|
75
|
+
function walkStructureElements(elements, parentKey, definitions) {
|
|
76
|
+
for (const element of elements) {
|
|
77
|
+
definitions[element.id] = {
|
|
78
|
+
kind: "structure",
|
|
79
|
+
type: element.type,
|
|
80
|
+
...element.title !== void 0 ? { title: element.title } : {},
|
|
81
|
+
...element.language !== void 0 ? { language: element.language } : {},
|
|
82
|
+
...element.alt !== void 0 ? { alt: element.alt } : {},
|
|
83
|
+
...element.actualText !== void 0 ? { actualText: element.actualText } : {},
|
|
84
|
+
...parentKey !== void 0 ? { parent: parentKey } : {}
|
|
85
|
+
};
|
|
86
|
+
walkStructureElements(element.children, element.id, definitions);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
74
89
|
//#endregion
|
|
75
90
|
export { stampPdfPackageTables };
|
|
@@ -136,19 +136,20 @@ function reconstructWordprocessing(doc, options) {
|
|
|
136
136
|
const signal = options?.signal;
|
|
137
137
|
doc = dropHiddenLayerContent(doc);
|
|
138
138
|
const headingLevels = headingSizeLevels(doc);
|
|
139
|
+
const structure = indexStructure(doc);
|
|
139
140
|
const sections = [];
|
|
140
141
|
let currentGroup = [];
|
|
141
142
|
let groupStartPageIndex = 0;
|
|
142
143
|
for (const page of doc.pages) {
|
|
143
144
|
require_ports_abort.throwIfAborted(signal);
|
|
144
145
|
if (currentGroup.length > 0 && !samePageSize(currentGroup[0], page)) {
|
|
145
|
-
sections.push(buildSection(currentGroup, groupStartPageIndex, doc.images, headingLevels, doc.form));
|
|
146
|
+
sections.push(buildSection(currentGroup, groupStartPageIndex, doc.images, headingLevels, doc.form, structure));
|
|
146
147
|
groupStartPageIndex += currentGroup.length;
|
|
147
148
|
currentGroup = [];
|
|
148
149
|
}
|
|
149
150
|
currentGroup.push(page);
|
|
150
151
|
}
|
|
151
|
-
if (currentGroup.length > 0) sections.push(buildSection(currentGroup, groupStartPageIndex, doc.images, headingLevels, doc.form));
|
|
152
|
+
if (currentGroup.length > 0) sections.push(buildSection(currentGroup, groupStartPageIndex, doc.images, headingLevels, doc.form, structure));
|
|
152
153
|
return {
|
|
153
154
|
kind: "wordprocessing",
|
|
154
155
|
metadata: doc.metadata,
|
|
@@ -169,6 +170,59 @@ function dropHiddenLayerContent(doc) {
|
|
|
169
170
|
}))
|
|
170
171
|
};
|
|
171
172
|
}
|
|
173
|
+
const HEADING_LEVELS = /* @__PURE__ */ new Map([
|
|
174
|
+
["H1", 1],
|
|
175
|
+
["H2", 2],
|
|
176
|
+
["H3", 3],
|
|
177
|
+
["H4", 4],
|
|
178
|
+
["H5", 5],
|
|
179
|
+
["H6", 6]
|
|
180
|
+
]);
|
|
181
|
+
const DIVISION_TYPES = /* @__PURE__ */ new Set([
|
|
182
|
+
"Part",
|
|
183
|
+
"Sect",
|
|
184
|
+
"Div"
|
|
185
|
+
]);
|
|
186
|
+
const CELL_TYPES = /* @__PURE__ */ new Set(["TD", "TH"]);
|
|
187
|
+
const ROW_TYPES = /* @__PURE__ */ new Set(["TR"]);
|
|
188
|
+
const TABLE_TYPES = /* @__PURE__ */ new Set(["Table"]);
|
|
189
|
+
function indexStructure(doc) {
|
|
190
|
+
if (doc.structure === void 0 || doc.structure.length === 0) return;
|
|
191
|
+
const byId = /* @__PURE__ */ new Map();
|
|
192
|
+
let order = 0;
|
|
193
|
+
const walk = (element, parent) => {
|
|
194
|
+
const node = {
|
|
195
|
+
id: element.id,
|
|
196
|
+
type: element.type,
|
|
197
|
+
parent,
|
|
198
|
+
order: order++
|
|
199
|
+
};
|
|
200
|
+
byId.set(element.id, node);
|
|
201
|
+
for (const child of element.children) walk(child, node);
|
|
202
|
+
};
|
|
203
|
+
for (const root of doc.structure) walk(root, void 0);
|
|
204
|
+
return { nodeOfItem: (item) => item.kind === "link" || item.kind === "internalLink" ? void 0 : byId.get(item.structure ?? "") };
|
|
205
|
+
}
|
|
206
|
+
function nearestOfType(node, types) {
|
|
207
|
+
for (let current = node; current !== void 0; current = current.parent) if (types.has(current.type)) return current;
|
|
208
|
+
}
|
|
209
|
+
function nearestHeadingLevel(node) {
|
|
210
|
+
for (let current = node; current !== void 0; current = current.parent) {
|
|
211
|
+
const level = HEADING_LEVELS.get(current.type);
|
|
212
|
+
if (level !== void 0) return level;
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
function structureHeadingLevel(items, structure) {
|
|
216
|
+
if (items.length === 0 || items.some((item) => structure.nodeOfItem(item) === void 0)) return;
|
|
217
|
+
let level;
|
|
218
|
+
for (const item of items) {
|
|
219
|
+
const itemLevel = nearestHeadingLevel(structure.nodeOfItem(item));
|
|
220
|
+
if (itemLevel === void 0) return { level: void 0 };
|
|
221
|
+
if (level === void 0) level = itemLevel;
|
|
222
|
+
else if (level !== itemLevel) return;
|
|
223
|
+
}
|
|
224
|
+
return { level };
|
|
225
|
+
}
|
|
172
226
|
const HEADING_MIN_SIZE_DELTA_PT = 2;
|
|
173
227
|
const MAX_HEADING_LEVEL = 6;
|
|
174
228
|
function headingSizeLevels(doc) {
|
|
@@ -193,11 +247,12 @@ function headingLevelOf(paragraph, levels) {
|
|
|
193
247
|
function samePageSize(a, b) {
|
|
194
248
|
return a.widthPt === b.widthPt && a.heightPt === b.heightPt;
|
|
195
249
|
}
|
|
196
|
-
function buildSection(pages, startPageIndex, images, headingLevels, form) {
|
|
250
|
+
function buildSection(pages, startPageIndex, images, headingLevels, form, structure) {
|
|
197
251
|
const blocks = [];
|
|
252
|
+
const divisionByBlock = /* @__PURE__ */ new WeakMap();
|
|
198
253
|
pages.forEach((page, i) => {
|
|
199
254
|
if (i > 0) blocks.push({ kind: "pageBreak" });
|
|
200
|
-
blocks.push(...reconstructPageBlocks(page, startPageIndex + i, images, headingLevels, form));
|
|
255
|
+
blocks.push(...reconstructPageBlocks(page, startPageIndex + i, images, headingLevels, form, structure, divisionByBlock));
|
|
201
256
|
});
|
|
202
257
|
return {
|
|
203
258
|
pageSize: {
|
|
@@ -205,20 +260,34 @@ function buildSection(pages, startPageIndex, images, headingLevels, form) {
|
|
|
205
260
|
heightPt: pages[0].heightPt
|
|
206
261
|
},
|
|
207
262
|
margins: ZERO_MARGINS,
|
|
208
|
-
blocks
|
|
263
|
+
blocks: wrapDivisionExtents(blocks, divisionByBlock)
|
|
209
264
|
};
|
|
210
265
|
}
|
|
211
|
-
function reconstructPageBlocks(page, pageIndex, images, headingLevels, form) {
|
|
212
|
-
const
|
|
213
|
-
const consumedText =
|
|
214
|
-
const
|
|
266
|
+
function reconstructPageBlocks(page, pageIndex, images, headingLevels, form, structure, divisionByBlock) {
|
|
267
|
+
const recoveredTables = recoverTables(page, pageIndex, structure);
|
|
268
|
+
const consumedText = /* @__PURE__ */ new Set();
|
|
269
|
+
const claimedItems = /* @__PURE__ */ new Set();
|
|
270
|
+
for (const recovered of recoveredTables) {
|
|
271
|
+
for (const item of recovered.consumedText) consumedText.add(item);
|
|
272
|
+
for (const item of recovered.latticeItems) claimedItems.add(item);
|
|
273
|
+
}
|
|
274
|
+
const textItems = page.items.filter((i) => i.kind === "text" && !consumedText.has(i));
|
|
215
275
|
const imageItems = page.items.filter((i) => i.kind === "image");
|
|
216
276
|
const paragraphs = clusterIntoParagraphs(clusterIntoLines(textItems));
|
|
277
|
+
const recordDivisions = (block, items) => {
|
|
278
|
+
if (structure !== void 0) divisionByBlock.set(block, divisionChainOf(items, structure));
|
|
279
|
+
};
|
|
217
280
|
const positioned = [];
|
|
218
|
-
for (const paragraph of paragraphs)
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
281
|
+
for (const paragraph of paragraphs) {
|
|
282
|
+
const paragraphItems = paragraph.lines.flatMap((line) => line.items);
|
|
283
|
+
const structural = structure === void 0 ? void 0 : structureHeadingLevel(paragraphItems, structure);
|
|
284
|
+
const block = paragraphToContentParagraph(paragraph, pageIndex, structural !== void 0 ? structural.level : headingLevelOf(paragraph, headingLevels));
|
|
285
|
+
recordDivisions(block, paragraphItems);
|
|
286
|
+
positioned.push({
|
|
287
|
+
yPt: paragraph.lines[0].baselineY,
|
|
288
|
+
block
|
|
289
|
+
});
|
|
290
|
+
}
|
|
222
291
|
for (const img of imageItems) {
|
|
223
292
|
const asset = images[img.imageId];
|
|
224
293
|
if (asset !== void 0) {
|
|
@@ -235,21 +304,29 @@ function reconstructPageBlocks(page, pageIndex, images, headingLevels, form) {
|
|
|
235
304
|
widthPt: img.widthPt,
|
|
236
305
|
heightPt: img.heightPt
|
|
237
306
|
});
|
|
307
|
+
recordDivisions(block, [img]);
|
|
238
308
|
positioned.push({
|
|
239
309
|
yPt: img.yPt,
|
|
240
310
|
block
|
|
241
311
|
});
|
|
242
312
|
}
|
|
243
313
|
}
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
314
|
+
for (const recovered of recoveredTables) {
|
|
315
|
+
recordDivisions(recovered.table, [...recovered.consumedText]);
|
|
316
|
+
positioned.push({
|
|
317
|
+
yPt: recovered.topYPt,
|
|
318
|
+
block: recovered.table
|
|
319
|
+
});
|
|
320
|
+
}
|
|
321
|
+
const recoveredVectors = recoverPageVectors(page, pageIndex, claimedItems);
|
|
322
|
+
if (recoveredVectors !== void 0) {
|
|
323
|
+
const vectorItems = page.items.filter((item) => !claimedItems.has(item) && layoutItemToVector(item, page.heightPt) !== void 0);
|
|
324
|
+
recordDivisions(recoveredVectors.block, vectorItems);
|
|
325
|
+
positioned.push({
|
|
326
|
+
yPt: recoveredVectors.topYPt,
|
|
327
|
+
block: recoveredVectors.block
|
|
328
|
+
});
|
|
329
|
+
}
|
|
253
330
|
positioned.sort((a, b) => b.yPt - a.yPt);
|
|
254
331
|
const blocks = positioned.map((p) => p.block);
|
|
255
332
|
reconcileLinks(blocks, page, pageIndex);
|
|
@@ -257,6 +334,49 @@ function reconstructPageBlocks(page, pageIndex, images, headingLevels, form) {
|
|
|
257
334
|
appendFormFieldConstructs(blocks, form, pageIndex);
|
|
258
335
|
return blocks;
|
|
259
336
|
}
|
|
337
|
+
function divisionChainOf(items, structure) {
|
|
338
|
+
let prefix;
|
|
339
|
+
for (const item of items) {
|
|
340
|
+
const chain = [];
|
|
341
|
+
for (let current = structure.nodeOfItem(item); current !== void 0; current = current.parent) if (DIVISION_TYPES.has(current.type)) chain.push(current.id);
|
|
342
|
+
chain.reverse();
|
|
343
|
+
if (prefix === void 0) {
|
|
344
|
+
prefix = chain;
|
|
345
|
+
continue;
|
|
346
|
+
}
|
|
347
|
+
let shared = 0;
|
|
348
|
+
while (shared < prefix.length && shared < chain.length && prefix[shared] === chain[shared]) shared += 1;
|
|
349
|
+
prefix = prefix.slice(0, shared);
|
|
350
|
+
}
|
|
351
|
+
return prefix ?? [];
|
|
352
|
+
}
|
|
353
|
+
function wrapDivisionExtents(blocks, divisionByBlock) {
|
|
354
|
+
const out = [];
|
|
355
|
+
const open = [];
|
|
356
|
+
for (const block of blocks) {
|
|
357
|
+
const chain = divisionByBlock.get(block);
|
|
358
|
+
if (chain !== void 0) {
|
|
359
|
+
while (open.length > 0 && !chain.includes(open[open.length - 1])) {
|
|
360
|
+
out.push({ kind: "constructEnd" });
|
|
361
|
+
open.pop();
|
|
362
|
+
}
|
|
363
|
+
for (const id of chain) {
|
|
364
|
+
if (open.includes(id)) continue;
|
|
365
|
+
out.push({
|
|
366
|
+
kind: "constructStart",
|
|
367
|
+
descriptor: { kind: "division" }
|
|
368
|
+
});
|
|
369
|
+
open.push(id);
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
out.push(block);
|
|
373
|
+
}
|
|
374
|
+
while (open.length > 0) {
|
|
375
|
+
out.push({ kind: "constructEnd" });
|
|
376
|
+
open.pop();
|
|
377
|
+
}
|
|
378
|
+
return out;
|
|
379
|
+
}
|
|
260
380
|
function framesIntersect(a, b) {
|
|
261
381
|
return a.xPt < b.xPt + b.widthPt && b.xPt < a.xPt + a.widthPt && a.yPt < b.yPt + b.heightPt && b.yPt < a.yPt + a.heightPt;
|
|
262
382
|
}
|
|
@@ -420,9 +540,10 @@ function paragraphToContentParagraph(paragraph, pageIndex, headingLevel) {
|
|
|
420
540
|
}
|
|
421
541
|
function reconstructPresentation(doc, options) {
|
|
422
542
|
const signal = options?.signal;
|
|
543
|
+
const structure = indexStructure(doc);
|
|
423
544
|
const slides = doc.pages.map((page, pageIndex) => {
|
|
424
545
|
require_ports_abort.throwIfAborted(signal);
|
|
425
|
-
return reconstructSlide(page, pageIndex, doc.images);
|
|
546
|
+
return reconstructSlide(page, pageIndex, doc.images, structure);
|
|
426
547
|
});
|
|
427
548
|
return {
|
|
428
549
|
kind: "presentation",
|
|
@@ -430,10 +551,15 @@ function reconstructPresentation(doc, options) {
|
|
|
430
551
|
slides
|
|
431
552
|
};
|
|
432
553
|
}
|
|
433
|
-
function reconstructSlide(page, pageIndex, images) {
|
|
434
|
-
const
|
|
435
|
-
const consumedText =
|
|
436
|
-
const
|
|
554
|
+
function reconstructSlide(page, pageIndex, images, structure) {
|
|
555
|
+
const recoveredTables = recoverTables(page, pageIndex, structure);
|
|
556
|
+
const consumedText = /* @__PURE__ */ new Set();
|
|
557
|
+
const claimedItems = /* @__PURE__ */ new Set();
|
|
558
|
+
for (const recovered of recoveredTables) {
|
|
559
|
+
for (const item of recovered.consumedText) consumedText.add(item);
|
|
560
|
+
for (const item of recovered.latticeItems) claimedItems.add(item);
|
|
561
|
+
}
|
|
562
|
+
const textItems = page.items.filter((i) => i.kind === "text" && !consumedText.has(i));
|
|
437
563
|
const imageItems = page.items.filter((i) => i.kind === "image");
|
|
438
564
|
const textShapes = clusterIntoBlocks(clusterIntoLines(textItems)).map((block) => blockToShape(block, page.heightPt, pageIndex));
|
|
439
565
|
const imageShapes = [];
|
|
@@ -441,14 +567,14 @@ function reconstructSlide(page, pageIndex, images) {
|
|
|
441
567
|
const shape = imageToShape(img, page.heightPt, pageIndex, images);
|
|
442
568
|
if (shape !== void 0) imageShapes.push(shape);
|
|
443
569
|
}
|
|
444
|
-
const recoveredVectors = recoverPageVectors(page, pageIndex,
|
|
570
|
+
const recoveredVectors = recoverPageVectors(page, pageIndex, claimedItems);
|
|
445
571
|
const vectorShapes = recoveredVectors === void 0 ? [] : [wrapBlockInShape(recoveredVectors.block, {
|
|
446
572
|
xPt: 0,
|
|
447
573
|
yPt: 0,
|
|
448
574
|
widthPt: page.widthPt,
|
|
449
575
|
heightPt: page.heightPt
|
|
450
576
|
}, page.heightPt, pageIndex)];
|
|
451
|
-
const tableShapes =
|
|
577
|
+
const tableShapes = recoveredTables.map((recovered) => wrapBlockInShape(recovered.table, recovered.frame, page.heightPt, pageIndex));
|
|
452
578
|
return {
|
|
453
579
|
size: {
|
|
454
580
|
widthPt: page.widthPt,
|
|
@@ -894,6 +1020,112 @@ function recoverPageVectors(page, pageIndex, excluded) {
|
|
|
894
1020
|
topYPt: page.heightPt - topYDownPt
|
|
895
1021
|
};
|
|
896
1022
|
}
|
|
1023
|
+
function recoverTables(page, pageIndex, structure) {
|
|
1024
|
+
if (structure !== void 0) {
|
|
1025
|
+
const tagged = recoverTaggedTables(page, pageIndex, structure);
|
|
1026
|
+
if (tagged.length > 0) return tagged;
|
|
1027
|
+
}
|
|
1028
|
+
const lattice = recoverTable(page, pageIndex);
|
|
1029
|
+
return lattice === void 0 ? [] : [lattice];
|
|
1030
|
+
}
|
|
1031
|
+
function recoverTaggedTables(page, pageIndex, structure) {
|
|
1032
|
+
const cellsByTable = /* @__PURE__ */ new Map();
|
|
1033
|
+
for (const item of page.items) {
|
|
1034
|
+
if (item.kind !== "text") continue;
|
|
1035
|
+
const cell = nearestOfType(structure.nodeOfItem(item), CELL_TYPES);
|
|
1036
|
+
const row = nearestOfType(cell?.parent, ROW_TYPES);
|
|
1037
|
+
const table = nearestOfType(row?.parent, TABLE_TYPES);
|
|
1038
|
+
if (cell === void 0 || row === void 0 || table === void 0) continue;
|
|
1039
|
+
const entry = cellsByTable.get(table.id) ?? {
|
|
1040
|
+
table,
|
|
1041
|
+
cells: []
|
|
1042
|
+
};
|
|
1043
|
+
entry.cells.push({
|
|
1044
|
+
table,
|
|
1045
|
+
row,
|
|
1046
|
+
cell,
|
|
1047
|
+
items: [item]
|
|
1048
|
+
});
|
|
1049
|
+
cellsByTable.set(table.id, entry);
|
|
1050
|
+
}
|
|
1051
|
+
if (cellsByTable.size === 0) return [];
|
|
1052
|
+
const latticeItems = require_layout_lattice.detectGridLattice(page.items)?.sourceItems ?? NO_ITEMS;
|
|
1053
|
+
const recovered = [];
|
|
1054
|
+
for (const { cells } of cellsByTable.values()) {
|
|
1055
|
+
const rowsById = /* @__PURE__ */ new Map();
|
|
1056
|
+
for (const cellEntry of cells) {
|
|
1057
|
+
const rowEntry = rowsById.get(cellEntry.row.id) ?? {
|
|
1058
|
+
row: cellEntry.row,
|
|
1059
|
+
cells: []
|
|
1060
|
+
};
|
|
1061
|
+
rowEntry.cells.push(cellEntry);
|
|
1062
|
+
rowsById.set(cellEntry.row.id, rowEntry);
|
|
1063
|
+
}
|
|
1064
|
+
const rows = [...rowsById.values()].sort((a, b) => a.row.order - b.row.order);
|
|
1065
|
+
for (const row of rows) row.cells.sort((a, b) => a.cell.order - b.cell.order);
|
|
1066
|
+
const columnCount = Math.max(...rows.map((row) => row.cells.length));
|
|
1067
|
+
const columnWidthsPt = [];
|
|
1068
|
+
for (let j = 0; j < columnCount; j++) {
|
|
1069
|
+
let minX = Number.POSITIVE_INFINITY;
|
|
1070
|
+
let maxX = Number.NEGATIVE_INFINITY;
|
|
1071
|
+
for (const row of rows) {
|
|
1072
|
+
const cellEntry = row.cells[j];
|
|
1073
|
+
if (cellEntry === void 0) continue;
|
|
1074
|
+
for (const item of cellEntry.items) {
|
|
1075
|
+
minX = Math.min(minX, item.xPt);
|
|
1076
|
+
maxX = Math.max(maxX, item.xPt + (item.widthPt ?? 0));
|
|
1077
|
+
}
|
|
1078
|
+
}
|
|
1079
|
+
const width = maxX - minX;
|
|
1080
|
+
columnWidthsPt.push(width > 0 ? width : DEFAULT_COLUMN_WIDTH_FALLBACK_PT);
|
|
1081
|
+
}
|
|
1082
|
+
const allItems = cells.flatMap((cellEntry) => cellEntry.items);
|
|
1083
|
+
const contentRows = rows.map((row) => {
|
|
1084
|
+
const contentCells = [];
|
|
1085
|
+
for (let j = 0; j < columnCount; j++) {
|
|
1086
|
+
const cellEntry = row.cells[j];
|
|
1087
|
+
const cell = { blocks: cellBlocksFromItems(cellEntry?.items ?? [], pageIndex) };
|
|
1088
|
+
if (cellEntry !== void 0) require_layout_shared.stampFrame(cell, pageIndex, textItemsPdfBox(cellEntry.items));
|
|
1089
|
+
contentCells.push(cell);
|
|
1090
|
+
}
|
|
1091
|
+
return { cells: contentCells };
|
|
1092
|
+
});
|
|
1093
|
+
const box = textItemsPdfBox(allItems);
|
|
1094
|
+
const table = {
|
|
1095
|
+
kind: "table",
|
|
1096
|
+
rows: contentRows,
|
|
1097
|
+
columnWidthsPt
|
|
1098
|
+
};
|
|
1099
|
+
require_layout_shared.stampFrame(table, pageIndex, box);
|
|
1100
|
+
recovered.push({
|
|
1101
|
+
table,
|
|
1102
|
+
frame: require_model_geometry.flipY(box, page.heightPt),
|
|
1103
|
+
topYPt: box.yPt + box.heightPt,
|
|
1104
|
+
consumedText: new Set(allItems),
|
|
1105
|
+
latticeItems
|
|
1106
|
+
});
|
|
1107
|
+
}
|
|
1108
|
+
return recovered;
|
|
1109
|
+
}
|
|
1110
|
+
function textItemsPdfBox(items) {
|
|
1111
|
+
let minX = Number.POSITIVE_INFINITY;
|
|
1112
|
+
let maxX = Number.NEGATIVE_INFINITY;
|
|
1113
|
+
let minY = Number.POSITIVE_INFINITY;
|
|
1114
|
+
let maxY = Number.NEGATIVE_INFINITY;
|
|
1115
|
+
for (const item of items) {
|
|
1116
|
+
const { ascentPt, descentPt } = textItemVerticalExtent(item);
|
|
1117
|
+
minX = Math.min(minX, item.xPt);
|
|
1118
|
+
maxX = Math.max(maxX, item.xPt + (item.widthPt ?? 0));
|
|
1119
|
+
minY = Math.min(minY, item.yPt - descentPt);
|
|
1120
|
+
maxY = Math.max(maxY, item.yPt + ascentPt);
|
|
1121
|
+
}
|
|
1122
|
+
return {
|
|
1123
|
+
xPt: minX,
|
|
1124
|
+
yPt: minY,
|
|
1125
|
+
widthPt: maxX - minX,
|
|
1126
|
+
heightPt: maxY - minY
|
|
1127
|
+
};
|
|
1128
|
+
}
|
|
897
1129
|
function cellBlocksFromItems(items, pageIndex) {
|
|
898
1130
|
return clusterIntoLines(items).map((line) => lineToParagraph(line, pageIndex));
|
|
899
1131
|
}
|
|
@@ -998,23 +1230,7 @@ function buildCellsFromGroups(groups, pageIndex, context) {
|
|
|
998
1230
|
value: inferredCellValue(displayText, row, column, context),
|
|
999
1231
|
displayText
|
|
1000
1232
|
};
|
|
1001
|
-
|
|
1002
|
-
let maxX = Number.NEGATIVE_INFINITY;
|
|
1003
|
-
let minY = Number.POSITIVE_INFINITY;
|
|
1004
|
-
let maxY = Number.NEGATIVE_INFINITY;
|
|
1005
|
-
for (const item of items) {
|
|
1006
|
-
const { ascentPt, descentPt } = textItemVerticalExtent(item);
|
|
1007
|
-
minX = Math.min(minX, item.xPt);
|
|
1008
|
-
maxX = Math.max(maxX, item.xPt + (item.widthPt ?? 0));
|
|
1009
|
-
minY = Math.min(minY, item.yPt - descentPt);
|
|
1010
|
-
maxY = Math.max(maxY, item.yPt + ascentPt);
|
|
1011
|
-
}
|
|
1012
|
-
require_layout_shared.stampFrame(cell, pageIndex, {
|
|
1013
|
-
xPt: minX,
|
|
1014
|
-
yPt: minY,
|
|
1015
|
-
widthPt: maxX - minX,
|
|
1016
|
-
heightPt: maxY - minY
|
|
1017
|
-
});
|
|
1233
|
+
require_layout_shared.stampFrame(cell, pageIndex, textItemsPdfBox(items));
|
|
1018
1234
|
cells.push(cell);
|
|
1019
1235
|
}
|
|
1020
1236
|
cells.sort((a, b) => a.row - b.row || a.column - b.column);
|
|
@@ -135,19 +135,20 @@ function reconstructWordprocessing(doc, options) {
|
|
|
135
135
|
const signal = options?.signal;
|
|
136
136
|
doc = dropHiddenLayerContent(doc);
|
|
137
137
|
const headingLevels = headingSizeLevels(doc);
|
|
138
|
+
const structure = indexStructure(doc);
|
|
138
139
|
const sections = [];
|
|
139
140
|
let currentGroup = [];
|
|
140
141
|
let groupStartPageIndex = 0;
|
|
141
142
|
for (const page of doc.pages) {
|
|
142
143
|
throwIfAborted(signal);
|
|
143
144
|
if (currentGroup.length > 0 && !samePageSize(currentGroup[0], page)) {
|
|
144
|
-
sections.push(buildSection(currentGroup, groupStartPageIndex, doc.images, headingLevels, doc.form));
|
|
145
|
+
sections.push(buildSection(currentGroup, groupStartPageIndex, doc.images, headingLevels, doc.form, structure));
|
|
145
146
|
groupStartPageIndex += currentGroup.length;
|
|
146
147
|
currentGroup = [];
|
|
147
148
|
}
|
|
148
149
|
currentGroup.push(page);
|
|
149
150
|
}
|
|
150
|
-
if (currentGroup.length > 0) sections.push(buildSection(currentGroup, groupStartPageIndex, doc.images, headingLevels, doc.form));
|
|
151
|
+
if (currentGroup.length > 0) sections.push(buildSection(currentGroup, groupStartPageIndex, doc.images, headingLevels, doc.form, structure));
|
|
151
152
|
return {
|
|
152
153
|
kind: "wordprocessing",
|
|
153
154
|
metadata: doc.metadata,
|
|
@@ -168,6 +169,59 @@ function dropHiddenLayerContent(doc) {
|
|
|
168
169
|
}))
|
|
169
170
|
};
|
|
170
171
|
}
|
|
172
|
+
const HEADING_LEVELS = /* @__PURE__ */ new Map([
|
|
173
|
+
["H1", 1],
|
|
174
|
+
["H2", 2],
|
|
175
|
+
["H3", 3],
|
|
176
|
+
["H4", 4],
|
|
177
|
+
["H5", 5],
|
|
178
|
+
["H6", 6]
|
|
179
|
+
]);
|
|
180
|
+
const DIVISION_TYPES = /* @__PURE__ */ new Set([
|
|
181
|
+
"Part",
|
|
182
|
+
"Sect",
|
|
183
|
+
"Div"
|
|
184
|
+
]);
|
|
185
|
+
const CELL_TYPES = /* @__PURE__ */ new Set(["TD", "TH"]);
|
|
186
|
+
const ROW_TYPES = /* @__PURE__ */ new Set(["TR"]);
|
|
187
|
+
const TABLE_TYPES = /* @__PURE__ */ new Set(["Table"]);
|
|
188
|
+
function indexStructure(doc) {
|
|
189
|
+
if (doc.structure === void 0 || doc.structure.length === 0) return;
|
|
190
|
+
const byId = /* @__PURE__ */ new Map();
|
|
191
|
+
let order = 0;
|
|
192
|
+
const walk = (element, parent) => {
|
|
193
|
+
const node = {
|
|
194
|
+
id: element.id,
|
|
195
|
+
type: element.type,
|
|
196
|
+
parent,
|
|
197
|
+
order: order++
|
|
198
|
+
};
|
|
199
|
+
byId.set(element.id, node);
|
|
200
|
+
for (const child of element.children) walk(child, node);
|
|
201
|
+
};
|
|
202
|
+
for (const root of doc.structure) walk(root, void 0);
|
|
203
|
+
return { nodeOfItem: (item) => item.kind === "link" || item.kind === "internalLink" ? void 0 : byId.get(item.structure ?? "") };
|
|
204
|
+
}
|
|
205
|
+
function nearestOfType(node, types) {
|
|
206
|
+
for (let current = node; current !== void 0; current = current.parent) if (types.has(current.type)) return current;
|
|
207
|
+
}
|
|
208
|
+
function nearestHeadingLevel(node) {
|
|
209
|
+
for (let current = node; current !== void 0; current = current.parent) {
|
|
210
|
+
const level = HEADING_LEVELS.get(current.type);
|
|
211
|
+
if (level !== void 0) return level;
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
function structureHeadingLevel(items, structure) {
|
|
215
|
+
if (items.length === 0 || items.some((item) => structure.nodeOfItem(item) === void 0)) return;
|
|
216
|
+
let level;
|
|
217
|
+
for (const item of items) {
|
|
218
|
+
const itemLevel = nearestHeadingLevel(structure.nodeOfItem(item));
|
|
219
|
+
if (itemLevel === void 0) return { level: void 0 };
|
|
220
|
+
if (level === void 0) level = itemLevel;
|
|
221
|
+
else if (level !== itemLevel) return;
|
|
222
|
+
}
|
|
223
|
+
return { level };
|
|
224
|
+
}
|
|
171
225
|
const HEADING_MIN_SIZE_DELTA_PT = 2;
|
|
172
226
|
const MAX_HEADING_LEVEL = 6;
|
|
173
227
|
function headingSizeLevels(doc) {
|
|
@@ -192,11 +246,12 @@ function headingLevelOf(paragraph, levels) {
|
|
|
192
246
|
function samePageSize(a, b) {
|
|
193
247
|
return a.widthPt === b.widthPt && a.heightPt === b.heightPt;
|
|
194
248
|
}
|
|
195
|
-
function buildSection(pages, startPageIndex, images, headingLevels, form) {
|
|
249
|
+
function buildSection(pages, startPageIndex, images, headingLevels, form, structure) {
|
|
196
250
|
const blocks = [];
|
|
251
|
+
const divisionByBlock = /* @__PURE__ */ new WeakMap();
|
|
197
252
|
pages.forEach((page, i) => {
|
|
198
253
|
if (i > 0) blocks.push({ kind: "pageBreak" });
|
|
199
|
-
blocks.push(...reconstructPageBlocks(page, startPageIndex + i, images, headingLevels, form));
|
|
254
|
+
blocks.push(...reconstructPageBlocks(page, startPageIndex + i, images, headingLevels, form, structure, divisionByBlock));
|
|
200
255
|
});
|
|
201
256
|
return {
|
|
202
257
|
pageSize: {
|
|
@@ -204,20 +259,34 @@ function buildSection(pages, startPageIndex, images, headingLevels, form) {
|
|
|
204
259
|
heightPt: pages[0].heightPt
|
|
205
260
|
},
|
|
206
261
|
margins: ZERO_MARGINS,
|
|
207
|
-
blocks
|
|
262
|
+
blocks: wrapDivisionExtents(blocks, divisionByBlock)
|
|
208
263
|
};
|
|
209
264
|
}
|
|
210
|
-
function reconstructPageBlocks(page, pageIndex, images, headingLevels, form) {
|
|
211
|
-
const
|
|
212
|
-
const consumedText =
|
|
213
|
-
const
|
|
265
|
+
function reconstructPageBlocks(page, pageIndex, images, headingLevels, form, structure, divisionByBlock) {
|
|
266
|
+
const recoveredTables = recoverTables(page, pageIndex, structure);
|
|
267
|
+
const consumedText = /* @__PURE__ */ new Set();
|
|
268
|
+
const claimedItems = /* @__PURE__ */ new Set();
|
|
269
|
+
for (const recovered of recoveredTables) {
|
|
270
|
+
for (const item of recovered.consumedText) consumedText.add(item);
|
|
271
|
+
for (const item of recovered.latticeItems) claimedItems.add(item);
|
|
272
|
+
}
|
|
273
|
+
const textItems = page.items.filter((i) => i.kind === "text" && !consumedText.has(i));
|
|
214
274
|
const imageItems = page.items.filter((i) => i.kind === "image");
|
|
215
275
|
const paragraphs = clusterIntoParagraphs(clusterIntoLines(textItems));
|
|
276
|
+
const recordDivisions = (block, items) => {
|
|
277
|
+
if (structure !== void 0) divisionByBlock.set(block, divisionChainOf(items, structure));
|
|
278
|
+
};
|
|
216
279
|
const positioned = [];
|
|
217
|
-
for (const paragraph of paragraphs)
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
280
|
+
for (const paragraph of paragraphs) {
|
|
281
|
+
const paragraphItems = paragraph.lines.flatMap((line) => line.items);
|
|
282
|
+
const structural = structure === void 0 ? void 0 : structureHeadingLevel(paragraphItems, structure);
|
|
283
|
+
const block = paragraphToContentParagraph(paragraph, pageIndex, structural !== void 0 ? structural.level : headingLevelOf(paragraph, headingLevels));
|
|
284
|
+
recordDivisions(block, paragraphItems);
|
|
285
|
+
positioned.push({
|
|
286
|
+
yPt: paragraph.lines[0].baselineY,
|
|
287
|
+
block
|
|
288
|
+
});
|
|
289
|
+
}
|
|
221
290
|
for (const img of imageItems) {
|
|
222
291
|
const asset = images[img.imageId];
|
|
223
292
|
if (asset !== void 0) {
|
|
@@ -234,21 +303,29 @@ function reconstructPageBlocks(page, pageIndex, images, headingLevels, form) {
|
|
|
234
303
|
widthPt: img.widthPt,
|
|
235
304
|
heightPt: img.heightPt
|
|
236
305
|
});
|
|
306
|
+
recordDivisions(block, [img]);
|
|
237
307
|
positioned.push({
|
|
238
308
|
yPt: img.yPt,
|
|
239
309
|
block
|
|
240
310
|
});
|
|
241
311
|
}
|
|
242
312
|
}
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
313
|
+
for (const recovered of recoveredTables) {
|
|
314
|
+
recordDivisions(recovered.table, [...recovered.consumedText]);
|
|
315
|
+
positioned.push({
|
|
316
|
+
yPt: recovered.topYPt,
|
|
317
|
+
block: recovered.table
|
|
318
|
+
});
|
|
319
|
+
}
|
|
320
|
+
const recoveredVectors = recoverPageVectors(page, pageIndex, claimedItems);
|
|
321
|
+
if (recoveredVectors !== void 0) {
|
|
322
|
+
const vectorItems = page.items.filter((item) => !claimedItems.has(item) && layoutItemToVector(item, page.heightPt) !== void 0);
|
|
323
|
+
recordDivisions(recoveredVectors.block, vectorItems);
|
|
324
|
+
positioned.push({
|
|
325
|
+
yPt: recoveredVectors.topYPt,
|
|
326
|
+
block: recoveredVectors.block
|
|
327
|
+
});
|
|
328
|
+
}
|
|
252
329
|
positioned.sort((a, b) => b.yPt - a.yPt);
|
|
253
330
|
const blocks = positioned.map((p) => p.block);
|
|
254
331
|
reconcileLinks(blocks, page, pageIndex);
|
|
@@ -256,6 +333,49 @@ function reconstructPageBlocks(page, pageIndex, images, headingLevels, form) {
|
|
|
256
333
|
appendFormFieldConstructs(blocks, form, pageIndex);
|
|
257
334
|
return blocks;
|
|
258
335
|
}
|
|
336
|
+
function divisionChainOf(items, structure) {
|
|
337
|
+
let prefix;
|
|
338
|
+
for (const item of items) {
|
|
339
|
+
const chain = [];
|
|
340
|
+
for (let current = structure.nodeOfItem(item); current !== void 0; current = current.parent) if (DIVISION_TYPES.has(current.type)) chain.push(current.id);
|
|
341
|
+
chain.reverse();
|
|
342
|
+
if (prefix === void 0) {
|
|
343
|
+
prefix = chain;
|
|
344
|
+
continue;
|
|
345
|
+
}
|
|
346
|
+
let shared = 0;
|
|
347
|
+
while (shared < prefix.length && shared < chain.length && prefix[shared] === chain[shared]) shared += 1;
|
|
348
|
+
prefix = prefix.slice(0, shared);
|
|
349
|
+
}
|
|
350
|
+
return prefix ?? [];
|
|
351
|
+
}
|
|
352
|
+
function wrapDivisionExtents(blocks, divisionByBlock) {
|
|
353
|
+
const out = [];
|
|
354
|
+
const open = [];
|
|
355
|
+
for (const block of blocks) {
|
|
356
|
+
const chain = divisionByBlock.get(block);
|
|
357
|
+
if (chain !== void 0) {
|
|
358
|
+
while (open.length > 0 && !chain.includes(open[open.length - 1])) {
|
|
359
|
+
out.push({ kind: "constructEnd" });
|
|
360
|
+
open.pop();
|
|
361
|
+
}
|
|
362
|
+
for (const id of chain) {
|
|
363
|
+
if (open.includes(id)) continue;
|
|
364
|
+
out.push({
|
|
365
|
+
kind: "constructStart",
|
|
366
|
+
descriptor: { kind: "division" }
|
|
367
|
+
});
|
|
368
|
+
open.push(id);
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
out.push(block);
|
|
372
|
+
}
|
|
373
|
+
while (open.length > 0) {
|
|
374
|
+
out.push({ kind: "constructEnd" });
|
|
375
|
+
open.pop();
|
|
376
|
+
}
|
|
377
|
+
return out;
|
|
378
|
+
}
|
|
259
379
|
function framesIntersect(a, b) {
|
|
260
380
|
return a.xPt < b.xPt + b.widthPt && b.xPt < a.xPt + a.widthPt && a.yPt < b.yPt + b.heightPt && b.yPt < a.yPt + a.heightPt;
|
|
261
381
|
}
|
|
@@ -419,9 +539,10 @@ function paragraphToContentParagraph(paragraph, pageIndex, headingLevel) {
|
|
|
419
539
|
}
|
|
420
540
|
function reconstructPresentation(doc, options) {
|
|
421
541
|
const signal = options?.signal;
|
|
542
|
+
const structure = indexStructure(doc);
|
|
422
543
|
const slides = doc.pages.map((page, pageIndex) => {
|
|
423
544
|
throwIfAborted(signal);
|
|
424
|
-
return reconstructSlide(page, pageIndex, doc.images);
|
|
545
|
+
return reconstructSlide(page, pageIndex, doc.images, structure);
|
|
425
546
|
});
|
|
426
547
|
return {
|
|
427
548
|
kind: "presentation",
|
|
@@ -429,10 +550,15 @@ function reconstructPresentation(doc, options) {
|
|
|
429
550
|
slides
|
|
430
551
|
};
|
|
431
552
|
}
|
|
432
|
-
function reconstructSlide(page, pageIndex, images) {
|
|
433
|
-
const
|
|
434
|
-
const consumedText =
|
|
435
|
-
const
|
|
553
|
+
function reconstructSlide(page, pageIndex, images, structure) {
|
|
554
|
+
const recoveredTables = recoverTables(page, pageIndex, structure);
|
|
555
|
+
const consumedText = /* @__PURE__ */ new Set();
|
|
556
|
+
const claimedItems = /* @__PURE__ */ new Set();
|
|
557
|
+
for (const recovered of recoveredTables) {
|
|
558
|
+
for (const item of recovered.consumedText) consumedText.add(item);
|
|
559
|
+
for (const item of recovered.latticeItems) claimedItems.add(item);
|
|
560
|
+
}
|
|
561
|
+
const textItems = page.items.filter((i) => i.kind === "text" && !consumedText.has(i));
|
|
436
562
|
const imageItems = page.items.filter((i) => i.kind === "image");
|
|
437
563
|
const textShapes = clusterIntoBlocks(clusterIntoLines(textItems)).map((block) => blockToShape(block, page.heightPt, pageIndex));
|
|
438
564
|
const imageShapes = [];
|
|
@@ -440,14 +566,14 @@ function reconstructSlide(page, pageIndex, images) {
|
|
|
440
566
|
const shape = imageToShape(img, page.heightPt, pageIndex, images);
|
|
441
567
|
if (shape !== void 0) imageShapes.push(shape);
|
|
442
568
|
}
|
|
443
|
-
const recoveredVectors = recoverPageVectors(page, pageIndex,
|
|
569
|
+
const recoveredVectors = recoverPageVectors(page, pageIndex, claimedItems);
|
|
444
570
|
const vectorShapes = recoveredVectors === void 0 ? [] : [wrapBlockInShape(recoveredVectors.block, {
|
|
445
571
|
xPt: 0,
|
|
446
572
|
yPt: 0,
|
|
447
573
|
widthPt: page.widthPt,
|
|
448
574
|
heightPt: page.heightPt
|
|
449
575
|
}, page.heightPt, pageIndex)];
|
|
450
|
-
const tableShapes =
|
|
576
|
+
const tableShapes = recoveredTables.map((recovered) => wrapBlockInShape(recovered.table, recovered.frame, page.heightPt, pageIndex));
|
|
451
577
|
return {
|
|
452
578
|
size: {
|
|
453
579
|
widthPt: page.widthPt,
|
|
@@ -893,6 +1019,112 @@ function recoverPageVectors(page, pageIndex, excluded) {
|
|
|
893
1019
|
topYPt: page.heightPt - topYDownPt
|
|
894
1020
|
};
|
|
895
1021
|
}
|
|
1022
|
+
function recoverTables(page, pageIndex, structure) {
|
|
1023
|
+
if (structure !== void 0) {
|
|
1024
|
+
const tagged = recoverTaggedTables(page, pageIndex, structure);
|
|
1025
|
+
if (tagged.length > 0) return tagged;
|
|
1026
|
+
}
|
|
1027
|
+
const lattice = recoverTable(page, pageIndex);
|
|
1028
|
+
return lattice === void 0 ? [] : [lattice];
|
|
1029
|
+
}
|
|
1030
|
+
function recoverTaggedTables(page, pageIndex, structure) {
|
|
1031
|
+
const cellsByTable = /* @__PURE__ */ new Map();
|
|
1032
|
+
for (const item of page.items) {
|
|
1033
|
+
if (item.kind !== "text") continue;
|
|
1034
|
+
const cell = nearestOfType(structure.nodeOfItem(item), CELL_TYPES);
|
|
1035
|
+
const row = nearestOfType(cell?.parent, ROW_TYPES);
|
|
1036
|
+
const table = nearestOfType(row?.parent, TABLE_TYPES);
|
|
1037
|
+
if (cell === void 0 || row === void 0 || table === void 0) continue;
|
|
1038
|
+
const entry = cellsByTable.get(table.id) ?? {
|
|
1039
|
+
table,
|
|
1040
|
+
cells: []
|
|
1041
|
+
};
|
|
1042
|
+
entry.cells.push({
|
|
1043
|
+
table,
|
|
1044
|
+
row,
|
|
1045
|
+
cell,
|
|
1046
|
+
items: [item]
|
|
1047
|
+
});
|
|
1048
|
+
cellsByTable.set(table.id, entry);
|
|
1049
|
+
}
|
|
1050
|
+
if (cellsByTable.size === 0) return [];
|
|
1051
|
+
const latticeItems = detectGridLattice(page.items)?.sourceItems ?? NO_ITEMS;
|
|
1052
|
+
const recovered = [];
|
|
1053
|
+
for (const { cells } of cellsByTable.values()) {
|
|
1054
|
+
const rowsById = /* @__PURE__ */ new Map();
|
|
1055
|
+
for (const cellEntry of cells) {
|
|
1056
|
+
const rowEntry = rowsById.get(cellEntry.row.id) ?? {
|
|
1057
|
+
row: cellEntry.row,
|
|
1058
|
+
cells: []
|
|
1059
|
+
};
|
|
1060
|
+
rowEntry.cells.push(cellEntry);
|
|
1061
|
+
rowsById.set(cellEntry.row.id, rowEntry);
|
|
1062
|
+
}
|
|
1063
|
+
const rows = [...rowsById.values()].sort((a, b) => a.row.order - b.row.order);
|
|
1064
|
+
for (const row of rows) row.cells.sort((a, b) => a.cell.order - b.cell.order);
|
|
1065
|
+
const columnCount = Math.max(...rows.map((row) => row.cells.length));
|
|
1066
|
+
const columnWidthsPt = [];
|
|
1067
|
+
for (let j = 0; j < columnCount; j++) {
|
|
1068
|
+
let minX = Number.POSITIVE_INFINITY;
|
|
1069
|
+
let maxX = Number.NEGATIVE_INFINITY;
|
|
1070
|
+
for (const row of rows) {
|
|
1071
|
+
const cellEntry = row.cells[j];
|
|
1072
|
+
if (cellEntry === void 0) continue;
|
|
1073
|
+
for (const item of cellEntry.items) {
|
|
1074
|
+
minX = Math.min(minX, item.xPt);
|
|
1075
|
+
maxX = Math.max(maxX, item.xPt + (item.widthPt ?? 0));
|
|
1076
|
+
}
|
|
1077
|
+
}
|
|
1078
|
+
const width = maxX - minX;
|
|
1079
|
+
columnWidthsPt.push(width > 0 ? width : DEFAULT_COLUMN_WIDTH_FALLBACK_PT);
|
|
1080
|
+
}
|
|
1081
|
+
const allItems = cells.flatMap((cellEntry) => cellEntry.items);
|
|
1082
|
+
const contentRows = rows.map((row) => {
|
|
1083
|
+
const contentCells = [];
|
|
1084
|
+
for (let j = 0; j < columnCount; j++) {
|
|
1085
|
+
const cellEntry = row.cells[j];
|
|
1086
|
+
const cell = { blocks: cellBlocksFromItems(cellEntry?.items ?? [], pageIndex) };
|
|
1087
|
+
if (cellEntry !== void 0) stampFrame(cell, pageIndex, textItemsPdfBox(cellEntry.items));
|
|
1088
|
+
contentCells.push(cell);
|
|
1089
|
+
}
|
|
1090
|
+
return { cells: contentCells };
|
|
1091
|
+
});
|
|
1092
|
+
const box = textItemsPdfBox(allItems);
|
|
1093
|
+
const table = {
|
|
1094
|
+
kind: "table",
|
|
1095
|
+
rows: contentRows,
|
|
1096
|
+
columnWidthsPt
|
|
1097
|
+
};
|
|
1098
|
+
stampFrame(table, pageIndex, box);
|
|
1099
|
+
recovered.push({
|
|
1100
|
+
table,
|
|
1101
|
+
frame: flipY(box, page.heightPt),
|
|
1102
|
+
topYPt: box.yPt + box.heightPt,
|
|
1103
|
+
consumedText: new Set(allItems),
|
|
1104
|
+
latticeItems
|
|
1105
|
+
});
|
|
1106
|
+
}
|
|
1107
|
+
return recovered;
|
|
1108
|
+
}
|
|
1109
|
+
function textItemsPdfBox(items) {
|
|
1110
|
+
let minX = Number.POSITIVE_INFINITY;
|
|
1111
|
+
let maxX = Number.NEGATIVE_INFINITY;
|
|
1112
|
+
let minY = Number.POSITIVE_INFINITY;
|
|
1113
|
+
let maxY = Number.NEGATIVE_INFINITY;
|
|
1114
|
+
for (const item of items) {
|
|
1115
|
+
const { ascentPt, descentPt } = textItemVerticalExtent(item);
|
|
1116
|
+
minX = Math.min(minX, item.xPt);
|
|
1117
|
+
maxX = Math.max(maxX, item.xPt + (item.widthPt ?? 0));
|
|
1118
|
+
minY = Math.min(minY, item.yPt - descentPt);
|
|
1119
|
+
maxY = Math.max(maxY, item.yPt + ascentPt);
|
|
1120
|
+
}
|
|
1121
|
+
return {
|
|
1122
|
+
xPt: minX,
|
|
1123
|
+
yPt: minY,
|
|
1124
|
+
widthPt: maxX - minX,
|
|
1125
|
+
heightPt: maxY - minY
|
|
1126
|
+
};
|
|
1127
|
+
}
|
|
896
1128
|
function cellBlocksFromItems(items, pageIndex) {
|
|
897
1129
|
return clusterIntoLines(items).map((line) => lineToParagraph(line, pageIndex));
|
|
898
1130
|
}
|
|
@@ -997,23 +1229,7 @@ function buildCellsFromGroups(groups, pageIndex, context) {
|
|
|
997
1229
|
value: inferredCellValue(displayText, row, column, context),
|
|
998
1230
|
displayText
|
|
999
1231
|
};
|
|
1000
|
-
|
|
1001
|
-
let maxX = Number.NEGATIVE_INFINITY;
|
|
1002
|
-
let minY = Number.POSITIVE_INFINITY;
|
|
1003
|
-
let maxY = Number.NEGATIVE_INFINITY;
|
|
1004
|
-
for (const item of items) {
|
|
1005
|
-
const { ascentPt, descentPt } = textItemVerticalExtent(item);
|
|
1006
|
-
minX = Math.min(minX, item.xPt);
|
|
1007
|
-
maxX = Math.max(maxX, item.xPt + (item.widthPt ?? 0));
|
|
1008
|
-
minY = Math.min(minY, item.yPt - descentPt);
|
|
1009
|
-
maxY = Math.max(maxY, item.yPt + ascentPt);
|
|
1010
|
-
}
|
|
1011
|
-
stampFrame(cell, pageIndex, {
|
|
1012
|
-
xPt: minX,
|
|
1013
|
-
yPt: minY,
|
|
1014
|
-
widthPt: maxX - minX,
|
|
1015
|
-
heightPt: maxY - minY
|
|
1016
|
-
});
|
|
1232
|
+
stampFrame(cell, pageIndex, textItemsPdfBox(items));
|
|
1017
1233
|
cells.push(cell);
|
|
1018
1234
|
}
|
|
1019
1235
|
cells.sort((a, b) => a.row - b.row || a.column - b.column);
|
package/package.json
CHANGED