@stll/folio-core 0.29.0 → 0.31.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/dist/controller/folioEditor.d.ts +28 -1
- package/dist/controller/folioEditor.js +37 -26
- package/dist/controller/fontReadiness.js +28 -24
- package/dist/controller/hiddenEditorManager.d.ts +2 -7
- package/dist/controller/layoutPipeline.js +33 -3
- package/dist/controller/noteEditorManager.d.ts +3 -6
- package/dist/docx/metafileSvg.d.ts +12 -0
- package/dist/docx/metafileSvg.js +462 -0
- package/dist/docx/paragraphTextBoxEnrichment.js +55 -4
- package/dist/docx/parser.d.ts +2 -2
- package/dist/docx/parser.js +26 -0
- package/dist/docx/runParser.js +2 -1
- package/dist/docx/serializer/runSerializer.js +14 -2
- package/dist/docx/server/createBilingualDocument.d.ts +20 -3
- package/dist/docx/server/createBilingualDocument.js +153 -22
- package/dist/docx/styleParser.js +2 -1
- package/dist/docx/textBoxParser.js +14 -0
- package/dist/docx/vmlImageParser.js +72 -152
- package/dist/docx/vmlPreview.d.ts +31 -0
- package/dist/docx/vmlPreview.js +535 -0
- package/dist/docx/wrapTypes.d.ts +2 -36
- package/dist/docx/wrapTypes.js +1 -39
- package/dist/fonts/fontAlternates.d.ts +8 -0
- package/dist/fonts/fontAlternates.js +16 -0
- package/dist/layout-bridge/convert/footnoteLayout.d.ts +1 -0
- package/dist/layout-bridge/convert/footnoteLayout.js +34 -3
- package/dist/layout-bridge/convert/headerFooterLayout.d.ts +1 -0
- package/dist/layout-bridge/convert/headerFooterLayout.js +5 -0
- package/dist/layout-bridge/convert/toFlowBlocks.d.ts +3 -0
- package/dist/layout-bridge/convert/toFlowBlocks.js +78 -28
- package/dist/layout-bridge/dom/noteStoryDom.d.ts +1 -1
- package/dist/layout-bridge/engine/selectionRects.js +3 -1
- package/dist/layout-engine/index.js +65 -32
- package/dist/layout-engine/justifiedLineFit.d.ts +7 -0
- package/dist/layout-engine/justifiedLineFit.js +6 -0
- package/dist/layout-engine/keep-together.js +15 -10
- package/dist/layout-engine/measure/complexScriptFormatting.d.ts +6 -1
- package/dist/layout-engine/measure/complexScriptFormatting.js +11 -2
- package/dist/layout-engine/measure/floatingTablePosition.d.ts +16 -1
- package/dist/layout-engine/measure/floatingTablePosition.js +29 -6
- package/dist/layout-engine/measure/lineBreakProvider.js +67 -8
- package/dist/layout-engine/measure/listMarkerWidth.d.ts +1 -0
- package/dist/layout-engine/measure/listMarkerWidth.js +23 -10
- package/dist/layout-engine/measure/measureBlocks.d.ts +15 -0
- package/dist/layout-engine/measure/measureBlocks.js +197 -30
- package/dist/layout-engine/measure/measureContainer.js +20 -24
- package/dist/layout-engine/measure/measureHelpers.d.ts +1 -1
- package/dist/layout-engine/measure/measureHelpers.js +14 -8
- package/dist/layout-engine/measure/measureParagraph.js +111 -37
- package/dist/layout-engine/measure/measureTypes.d.ts +3 -0
- package/dist/layout-engine/measure/tabCalculator.d.ts +123 -0
- package/dist/layout-engine/measure/tabCalculator.js +144 -0
- package/dist/layout-engine/measure/tableCellFlow.d.ts +21 -1
- package/dist/layout-engine/measure/tableCellFlow.js +45 -8
- package/dist/layout-engine/measure/tableInlinePlacement.d.ts +10 -2
- package/dist/layout-engine/measure/tableInlinePlacement.js +17 -15
- package/dist/layout-engine/paginator.d.ts +26 -2
- package/dist/layout-engine/paginator.js +76 -14
- package/dist/layout-engine/paragraphSpacing.d.ts +3 -1
- package/dist/layout-engine/paragraphSpacing.js +4 -3
- package/dist/layout-engine/renderedBreakReconciliation.d.ts +14 -3
- package/dist/layout-engine/renderedBreakReconciliation.js +45 -14
- package/dist/layout-engine/section-breaks.d.ts +3 -1
- package/dist/layout-engine/section-breaks.js +6 -2
- package/dist/layout-engine/types.d.ts +18 -1
- package/dist/layout-engine/types.js +1 -1
- package/dist/layout-painter/imageLayout.d.ts +1 -1
- package/dist/layout-painter/renderPage.js +6 -1
- package/dist/layout-painter/renderParagraph.js +169 -82
- package/dist/layout-painter/renderTable.d.ts +1 -1
- package/dist/layout-painter/renderTable.js +18 -7
- package/dist/layout-painter/renderTextBox.d.ts +1 -1
- package/dist/layout-painter/renderTextBox.js +6 -1
- package/dist/model.d.ts +3 -3
- package/dist/model.js +2 -2
- package/dist/paged-layout/sectionBlockWidths.d.ts +14 -0
- package/dist/paged-layout/sectionBlockWidths.js +103 -14
- package/dist/prosemirror/attrs/index.js +12 -1
- package/dist/prosemirror/conversion/fromProseDoc.js +6 -2
- package/dist/prosemirror/conversion/toProseDoc.js +10 -3
- package/dist/prosemirror/extensions/marks/CharacterSpacingExtension.js +9 -5
- package/dist/prosemirror/extensions/nodes/TableExtension.js +2 -0
- package/dist/prosemirror/extensions/nodes/TextBoxExtension.js +6 -2
- package/dist/prosemirror/schema/nodes.d.ts +4 -0
- package/dist/prosemirror/utils/tabCalculator.d.ts +2 -123
- package/dist/prosemirror/utils/tabCalculator.js +1 -140
- package/dist/render-dom/RemoteSelectionOverlay.d.ts +4 -4
- package/dist/server.d.ts +2 -2
- package/dist/server.js +2 -2
- package/dist/types/documentEnumValues.d.ts +4 -1
- package/dist/types/documentEnumValues.js +12 -1
- package/dist/types/editor-story.d.ts +8 -0
- package/dist/types/editor-story.js +0 -0
- package/dist/types/remote-selection.d.ts +11 -0
- package/dist/types/remote-selection.js +0 -0
- package/dist/types/wrap.d.ts +36 -0
- package/dist/types/wrap.js +40 -0
- package/dist/utils/fontResolver.d.ts +1 -1
- package/dist/utils/fontResolver.js +42 -2
- package/dist/utils/formatToStyle.js +4 -2
- package/dist/utils/horizontalScale.d.ts +28 -0
- package/dist/utils/horizontalScale.js +42 -0
- package/package.json +2 -2
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { HIGHLIGHT_COLOR_VALUES } from "../../types/documentEnumValues.js";
|
|
2
2
|
import { isValidHexColor } from "../../utils/colorResolver.js";
|
|
3
|
+
import { roundHorizontalScalePercentForSerialization } from "../../utils/horizontalScale.js";
|
|
3
4
|
import { THEME_COLOR_TO_DRAWING_SCHEME } from "../drawingUtils.js";
|
|
4
5
|
import { requiresXmlSpacePreserve } from "../textWhitespace.js";
|
|
5
6
|
import { serializeParagraph } from "./paragraphSerializer.js";
|
|
@@ -127,7 +128,8 @@ function serializeTextFormatting(formatting) {
|
|
|
127
128
|
const colorXml = serializeColorElement(formatting.color);
|
|
128
129
|
if (colorXml) parts.push(colorXml);
|
|
129
130
|
if (formatting.spacing !== void 0) parts.push(`<w:spacing w:val="${intAttr(formatting.spacing)}"/>`);
|
|
130
|
-
|
|
131
|
+
const horizontalScale = roundHorizontalScalePercentForSerialization(formatting.scale);
|
|
132
|
+
if (horizontalScale !== void 0) parts.push(`<w:w w:val="${intAttr(horizontalScale)}"/>`);
|
|
131
133
|
if (formatting.kerning !== void 0) parts.push(`<w:kern w:val="${intAttr(formatting.kerning)}"/>`);
|
|
132
134
|
if (formatting.position !== void 0) parts.push(`<w:position w:val="${intAttr(formatting.position)}"/>`);
|
|
133
135
|
if (formatting.fontSize !== void 0) parts.push(`<w:sz w:val="${intAttr(formatting.fontSize)}"/>`);
|
|
@@ -448,6 +450,16 @@ function serializeDrawingContent(content) {
|
|
|
448
450
|
function serializeShapeTextBody(blocks) {
|
|
449
451
|
return blocks.map((block) => block.type === "paragraph" ? serializeParagraph(block) : serializeTable(block, serializeParagraph)).join("");
|
|
450
452
|
}
|
|
453
|
+
const serializeTextBodyAnchor = (anchor) => {
|
|
454
|
+
switch (anchor) {
|
|
455
|
+
case "top": return "t";
|
|
456
|
+
case "middle": return "ctr";
|
|
457
|
+
case "bottom": return "b";
|
|
458
|
+
case "distributed": return "dist";
|
|
459
|
+
case "justified": return "just";
|
|
460
|
+
default: return anchor;
|
|
461
|
+
}
|
|
462
|
+
};
|
|
451
463
|
function serializeGeometryAdjustments(shape) {
|
|
452
464
|
if (!shape.geometryAdjustments || shape.geometryAdjustments.length === 0) return "<a:avLst/>";
|
|
453
465
|
return `<a:avLst>${shape.geometryAdjustments.map(({ name, formula }) => `<a:gd name="${escapeXml(name)}" fmla="${escapeXml(formula)}"/>`).join("")}</a:avLst>`;
|
|
@@ -484,7 +496,7 @@ function serializeShapeContent(content) {
|
|
|
484
496
|
const tb = shape.textBody;
|
|
485
497
|
const bpAttrs = ["rot=\"0\"", "vert=\"horz\""];
|
|
486
498
|
if (tb.textWrap) bpAttrs.push(`wrap="${tb.textWrap}"`);
|
|
487
|
-
if (tb.anchor) bpAttrs.push(`anchor="${tb.anchor
|
|
499
|
+
if (tb.anchor) bpAttrs.push(`anchor="${serializeTextBodyAnchor(tb.anchor)}"`);
|
|
488
500
|
if (tb.anchorCenter) bpAttrs.push("anchorCtr=\"1\"");
|
|
489
501
|
if (tb.margins) {
|
|
490
502
|
if (tb.margins.left != null) bpAttrs.push(`lIns="${intAttr(tb.margins.left)}"`);
|
|
@@ -16,18 +16,28 @@ type BilingualRow = ({
|
|
|
16
16
|
rowId: string;
|
|
17
17
|
} & BilingualParagraphRef) | {
|
|
18
18
|
kind: "table";
|
|
19
|
+
layout: "inline";
|
|
19
20
|
rowId: string;
|
|
20
21
|
/**
|
|
21
22
|
* Every paragraph inside the table, in document order. The table is not
|
|
22
23
|
* copied, so these are the paragraphs to translate in place.
|
|
23
24
|
*/
|
|
24
25
|
paragraphs: BilingualTableParagraphRef[];
|
|
26
|
+
} | {
|
|
27
|
+
kind: "table";
|
|
28
|
+
layout: "stacked";
|
|
29
|
+
/** Equals the first target paragraph id. */
|
|
30
|
+
rowId: string;
|
|
31
|
+
/** Source/target paragraph pairs in table document order. */
|
|
32
|
+
paragraphs: BilingualParagraphRef[];
|
|
25
33
|
};
|
|
26
34
|
type BilingualTableParagraphRef = {
|
|
27
35
|
paraId: string | undefined;
|
|
28
36
|
sourceText: string;
|
|
29
37
|
};
|
|
30
38
|
type BilingualBorders = "none" | "grid";
|
|
39
|
+
declare const BILINGUAL_TABLE_LAYOUTS: readonly ["inline", "stacked"];
|
|
40
|
+
type BilingualTableLayout = (typeof BILINGUAL_TABLE_LAYOUTS)[number];
|
|
31
41
|
type CreateBilingualDocumentOptions = {
|
|
32
42
|
/**
|
|
33
43
|
* Suffix for cloned style ids and names (for example `"en"` turns
|
|
@@ -37,6 +47,13 @@ type CreateBilingualDocumentOptions = {
|
|
|
37
47
|
targetStyleSuffix: string;
|
|
38
48
|
/** Table borders; legal practice is usually `"none"`. Default `"none"`. */
|
|
39
49
|
borders?: BilingualBorders;
|
|
50
|
+
/**
|
|
51
|
+
* Layout for source tables. `"inline"` keeps one full-width table whose
|
|
52
|
+
* paragraphs are translated in place. `"stacked"` keeps the source table
|
|
53
|
+
* and adds an independently addressable target copy below it. Default
|
|
54
|
+
* `"inline"`.
|
|
55
|
+
*/
|
|
56
|
+
tableLayout?: BilingualTableLayout;
|
|
40
57
|
/**
|
|
41
58
|
* Paragraph handles exposed by Folio's canonical AI-edit snapshot for the
|
|
42
59
|
* source DOCX. Only these paragraphs may become translation rows.
|
|
@@ -52,7 +69,7 @@ type CreateBilingualDocumentResult = {
|
|
|
52
69
|
declare const InvalidBilingualDocumentOptionsError_base: import("better-result").TaggedErrorClass<"InvalidBilingualDocumentOptionsError">;
|
|
53
70
|
declare class InvalidBilingualDocumentOptionsError extends InvalidBilingualDocumentOptionsError_base<{
|
|
54
71
|
message: string;
|
|
55
|
-
option: "targetStyleSuffix";
|
|
72
|
+
option: "tableLayout" | "targetStyleSuffix";
|
|
56
73
|
}> {}
|
|
57
74
|
declare function createBilingualDocument(source: document_d_exports.Document, options: CreateBilingualDocumentOptions): CreateBilingualDocumentResult;
|
|
58
75
|
/**
|
|
@@ -60,8 +77,8 @@ declare function createBilingualDocument(source: document_d_exports.Document, op
|
|
|
60
77
|
* {@link createBilingualDocument}. A dedicated table-style discriminator keeps
|
|
61
78
|
* ordinary two-column source tables from being mistaken for bilingual output;
|
|
62
79
|
* the expected row structure is validated as a second check. Rows are returned
|
|
63
|
-
* in document order
|
|
80
|
+
* in document order.
|
|
64
81
|
*/
|
|
65
82
|
declare function readBilingualDocument(document: document_d_exports.Document, editableParagraphIds: ReadonlySet<string>): BilingualRow[];
|
|
66
83
|
//#endregion
|
|
67
|
-
export { BilingualBorders, BilingualParagraphRef, BilingualRow, BilingualRowKind, BilingualTableParagraphRef, CreateBilingualDocumentOptions, CreateBilingualDocumentResult, InvalidBilingualDocumentOptionsError, createBilingualDocument, readBilingualDocument };
|
|
84
|
+
export { BILINGUAL_TABLE_LAYOUTS, BilingualBorders, BilingualParagraphRef, BilingualRow, BilingualRowKind, BilingualTableLayout, BilingualTableParagraphRef, CreateBilingualDocumentOptions, CreateBilingualDocumentResult, InvalidBilingualDocumentOptionsError, createBilingualDocument, readBilingualDocument };
|
|
@@ -17,13 +17,15 @@ import { TaggedError } from "better-result";
|
|
|
17
17
|
*
|
|
18
18
|
* Section breaks cannot live inside a table cell, so the body is split at
|
|
19
19
|
* paragraphs carrying `sectionProperties`: each section becomes its own table
|
|
20
|
-
* and the break paragraph stays between the tables.
|
|
21
|
-
* signature
|
|
22
|
-
*
|
|
20
|
+
* and the break paragraph stays between the tables. Source tables (parties,
|
|
21
|
+
* signature blocks) can either span both columns for inline translation or be
|
|
22
|
+
* followed by a full-width target copy, depending on `tableLayout`.
|
|
23
23
|
* Structural-only paragraphs are also kept once between tables; they have no
|
|
24
24
|
* independently editable text for a translation row to address.
|
|
25
25
|
*/
|
|
26
|
+
const BILINGUAL_TABLE_LAYOUTS = ["inline", "stacked"];
|
|
26
27
|
const STYLE_SUFFIX_PATTERN = /^[A-Za-z0-9-]+$/u;
|
|
28
|
+
const DEFAULT_TABLE_LAYOUT = BILINGUAL_TABLE_LAYOUTS[0];
|
|
27
29
|
var InvalidBilingualDocumentOptionsError = class extends TaggedError("InvalidBilingualDocumentOptionsError") {};
|
|
28
30
|
var UneditableBilingualManifestError = class extends TaggedError("UneditableBilingualManifestError") {};
|
|
29
31
|
const FULL_WIDTH_PCT = 5e3;
|
|
@@ -56,12 +58,18 @@ const TABLE_BORDERS = {
|
|
|
56
58
|
insideV: GRID_BORDER
|
|
57
59
|
}
|
|
58
60
|
};
|
|
61
|
+
const isBilingualTableLayout = (value) => typeof value === "string" && BILINGUAL_TABLE_LAYOUTS.some((layout) => layout === value);
|
|
59
62
|
function createBilingualDocument(source, options) {
|
|
60
63
|
if (!STYLE_SUFFIX_PATTERN.test(options.targetStyleSuffix)) throw new InvalidBilingualDocumentOptionsError({
|
|
61
64
|
message: `targetStyleSuffix must match ${STYLE_SUFFIX_PATTERN}; received ${JSON.stringify(options.targetStyleSuffix)}`,
|
|
62
65
|
option: "targetStyleSuffix"
|
|
63
66
|
});
|
|
67
|
+
if (options.tableLayout !== void 0 && !isBilingualTableLayout(options.tableLayout)) throw new InvalidBilingualDocumentOptionsError({
|
|
68
|
+
message: `tableLayout must be one of ${BILINGUAL_TABLE_LAYOUTS.join(", ")}; received ${JSON.stringify(options.tableLayout)}`,
|
|
69
|
+
option: "tableLayout"
|
|
70
|
+
});
|
|
64
71
|
const borders = options.borders ?? "none";
|
|
72
|
+
const tableLayout = options.tableLayout ?? DEFAULT_TABLE_LAYOUT;
|
|
65
73
|
const warnings = [];
|
|
66
74
|
const styles = source.package.styles;
|
|
67
75
|
const numbering = source.package.numbering;
|
|
@@ -128,12 +136,36 @@ function createBilingualDocument(source, options) {
|
|
|
128
136
|
content.push(block);
|
|
129
137
|
continue;
|
|
130
138
|
}
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
139
|
+
if (tableLayout === "inline") {
|
|
140
|
+
rows.push({
|
|
141
|
+
kind: "table",
|
|
142
|
+
layout: "inline",
|
|
143
|
+
rowId: paragraphs.at(0)?.paraId ?? tableRowHandle(rows.length),
|
|
144
|
+
paragraphs
|
|
145
|
+
});
|
|
146
|
+
sectionRows.push(buildInlineTableRow(block));
|
|
147
|
+
continue;
|
|
148
|
+
}
|
|
149
|
+
if (tableLayout === "stacked") {
|
|
150
|
+
const target = cloneTableForTarget({
|
|
151
|
+
table: block,
|
|
152
|
+
editableParagraphIds: options.editableParagraphIds,
|
|
153
|
+
paraIds,
|
|
154
|
+
styleCloner,
|
|
155
|
+
cloner
|
|
156
|
+
});
|
|
157
|
+
rows.push({
|
|
158
|
+
kind: "table",
|
|
159
|
+
layout: "stacked",
|
|
160
|
+
rowId: target.paragraphs.at(0)?.targetParaId ?? tableRowHandle(rows.length),
|
|
161
|
+
paragraphs: target.paragraphs
|
|
162
|
+
});
|
|
163
|
+
sectionRows.push(buildStackedTableRow({
|
|
164
|
+
source: block,
|
|
165
|
+
target: target.table
|
|
166
|
+
}));
|
|
167
|
+
continue;
|
|
168
|
+
}
|
|
137
169
|
}
|
|
138
170
|
flushSection();
|
|
139
171
|
return {
|
|
@@ -367,6 +399,33 @@ const collectTableParagraphs = (table) => {
|
|
|
367
399
|
else out.push(...collectTableParagraphs(item));
|
|
368
400
|
return out;
|
|
369
401
|
};
|
|
402
|
+
const cloneTableForTarget = ({ table, editableParagraphIds, paraIds, styleCloner, cloner }) => {
|
|
403
|
+
const paragraphs = [];
|
|
404
|
+
const cloneTable = (source) => ({
|
|
405
|
+
...structuredClone(source),
|
|
406
|
+
rows: source.rows.map((row) => ({
|
|
407
|
+
...structuredClone(row),
|
|
408
|
+
cells: row.cells.map((cell) => ({
|
|
409
|
+
...structuredClone(cell),
|
|
410
|
+
content: cell.content.map((item) => {
|
|
411
|
+
if (item.type === "table") return cloneTable(item);
|
|
412
|
+
const targetParaId = paraIds.mint(item.paraId);
|
|
413
|
+
const copy = cloneParagraphForTarget(item, targetParaId, styleCloner, cloner);
|
|
414
|
+
if (item.paraId !== void 0 && editableParagraphIds.has(item.paraId)) paragraphs.push({
|
|
415
|
+
sourceParaId: item.paraId,
|
|
416
|
+
targetParaId,
|
|
417
|
+
sourceText: getParagraphText(item)
|
|
418
|
+
});
|
|
419
|
+
return copy;
|
|
420
|
+
})
|
|
421
|
+
}))
|
|
422
|
+
}))
|
|
423
|
+
});
|
|
424
|
+
return {
|
|
425
|
+
table: cloneTable(table),
|
|
426
|
+
paragraphs
|
|
427
|
+
};
|
|
428
|
+
};
|
|
370
429
|
const buildRow = (left, right, styleById, textWidth) => {
|
|
371
430
|
const columnWidth = Math.floor(textWidth / 2);
|
|
372
431
|
const geometry = resolveHorizontalParagraphGeometry(left, styleById);
|
|
@@ -411,16 +470,21 @@ const projectParagraphIntoColumn = (paragraph, geometry, sourceWidth, columnWidt
|
|
|
411
470
|
const maxSideIndent = Math.max(0, columnWidth - MIN_COLUMN_TEXT_WIDTH_TWIPS);
|
|
412
471
|
let indentLeft = projectSideIndent(geometry.indentLeft, scale, maxSideIndent);
|
|
413
472
|
let indentRight = projectSideIndent(geometry.indentRight, scale, maxSideIndent);
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
473
|
+
const indentFirstLine = geometry.indentFirstLine === void 0 ? void 0 : Math.max(-maxSideIndent, Math.round(geometry.indentFirstLine * scale));
|
|
474
|
+
const minimumLeftIndent = Math.min(maxSideIndent, Math.max(0, -(indentFirstLine ?? 0)));
|
|
475
|
+
indentLeft = Math.max(indentLeft, minimumLeftIndent);
|
|
476
|
+
const flexibleLeft = indentLeft - minimumLeftIndent;
|
|
477
|
+
const flexibleTotal = flexibleLeft + indentRight;
|
|
478
|
+
const flexibleMaximum = maxSideIndent - minimumLeftIndent;
|
|
479
|
+
if (flexibleTotal > flexibleMaximum) {
|
|
480
|
+
indentLeft = minimumLeftIndent + Math.round(flexibleLeft / flexibleTotal * flexibleMaximum);
|
|
417
481
|
indentRight = maxSideIndent - indentLeft;
|
|
418
482
|
}
|
|
419
483
|
const formatting = {
|
|
420
484
|
...paragraph.formatting,
|
|
421
|
-
...geometry.indentLeft !== void 0 && { indentLeft },
|
|
485
|
+
...(geometry.indentLeft !== void 0 || minimumLeftIndent > 0) && { indentLeft },
|
|
422
486
|
...geometry.indentRight !== void 0 && { indentRight },
|
|
423
|
-
...
|
|
487
|
+
...indentFirstLine !== void 0 && { indentFirstLine },
|
|
424
488
|
...geometry.hangingIndent !== void 0 && { hangingIndent: geometry.hangingIndent },
|
|
425
489
|
...geometry.tabs !== void 0 && { tabs: geometry.tabs.map((tab) => ({
|
|
426
490
|
...tab,
|
|
@@ -445,7 +509,7 @@ const buildCell = (paragraph) => ({
|
|
|
445
509
|
content: [paragraph]
|
|
446
510
|
});
|
|
447
511
|
/** A source table kept once, across both columns. */
|
|
448
|
-
const
|
|
512
|
+
const buildInlineTableRow = (table) => ({
|
|
449
513
|
type: "tableRow",
|
|
450
514
|
formatting: { cantSplit: true },
|
|
451
515
|
cells: [{
|
|
@@ -464,6 +528,33 @@ const buildSpanningRow = (table) => ({
|
|
|
464
528
|
}]
|
|
465
529
|
}]
|
|
466
530
|
});
|
|
531
|
+
const buildStackedTableRow = ({ source, target }) => ({
|
|
532
|
+
type: "tableRow",
|
|
533
|
+
formatting: { cantSplit: false },
|
|
534
|
+
cells: [{
|
|
535
|
+
type: "tableCell",
|
|
536
|
+
formatting: {
|
|
537
|
+
width: {
|
|
538
|
+
value: FULL_WIDTH_PCT,
|
|
539
|
+
type: "pct"
|
|
540
|
+
},
|
|
541
|
+
gridSpan: 2,
|
|
542
|
+
verticalAlign: "top"
|
|
543
|
+
},
|
|
544
|
+
content: [
|
|
545
|
+
source,
|
|
546
|
+
{
|
|
547
|
+
type: "paragraph",
|
|
548
|
+
content: []
|
|
549
|
+
},
|
|
550
|
+
target,
|
|
551
|
+
{
|
|
552
|
+
type: "paragraph",
|
|
553
|
+
content: []
|
|
554
|
+
}
|
|
555
|
+
]
|
|
556
|
+
}]
|
|
557
|
+
});
|
|
467
558
|
const buildTable = (rows, borders, textWidth) => ({
|
|
468
559
|
type: "table",
|
|
469
560
|
formatting: {
|
|
@@ -537,7 +628,7 @@ const createParaIdMinter = (taken) => {
|
|
|
537
628
|
* {@link createBilingualDocument}. A dedicated table-style discriminator keeps
|
|
538
629
|
* ordinary two-column source tables from being mistaken for bilingual output;
|
|
539
630
|
* the expected row structure is validated as a second check. Rows are returned
|
|
540
|
-
* in document order
|
|
631
|
+
* in document order.
|
|
541
632
|
*/
|
|
542
633
|
function readBilingualDocument(document, editableParagraphIds) {
|
|
543
634
|
const styleById = new Map((document.package.styles?.styles ?? []).map((style) => [style.styleId, style]));
|
|
@@ -549,12 +640,54 @@ function readBilingualDocument(document, editableParagraphIds) {
|
|
|
549
640
|
const [left, right] = row.cells;
|
|
550
641
|
if (!left) continue;
|
|
551
642
|
if (!right) {
|
|
552
|
-
const
|
|
643
|
+
const nestedTables = left.content.filter((item) => item.type === "table");
|
|
644
|
+
const sourceTable = nestedTables.at(0);
|
|
645
|
+
if (!sourceTable) {
|
|
646
|
+
missingHandleCount += 1;
|
|
647
|
+
continue;
|
|
648
|
+
}
|
|
649
|
+
if (nestedTables.length === 2) {
|
|
650
|
+
const targetTable = nestedTables.at(1);
|
|
651
|
+
if (!targetTable) continue;
|
|
652
|
+
const sourceParagraphs = collectTableParagraphs(sourceTable);
|
|
653
|
+
const targetParagraphs = collectTableParagraphs(targetTable);
|
|
654
|
+
if (sourceParagraphs.length !== targetParagraphs.length) {
|
|
655
|
+
missingHandleCount += 1;
|
|
656
|
+
continue;
|
|
657
|
+
}
|
|
658
|
+
const paragraphs = [];
|
|
659
|
+
for (const [index, source] of sourceParagraphs.entries()) {
|
|
660
|
+
if (source.paraId === void 0 || !editableParagraphIds.has(source.paraId)) continue;
|
|
661
|
+
const target = targetParagraphs.at(index);
|
|
662
|
+
if (target?.paraId === void 0 || target.paraId === source.paraId || !editableParagraphIds.has(target.paraId)) {
|
|
663
|
+
missingHandleCount += 1;
|
|
664
|
+
continue;
|
|
665
|
+
}
|
|
666
|
+
paragraphs.push({
|
|
667
|
+
sourceParaId: source.paraId,
|
|
668
|
+
targetParaId: target.paraId,
|
|
669
|
+
sourceText: getParagraphText(source)
|
|
670
|
+
});
|
|
671
|
+
}
|
|
672
|
+
rows.push({
|
|
673
|
+
kind: "table",
|
|
674
|
+
layout: "stacked",
|
|
675
|
+
rowId: paragraphs.at(0)?.targetParaId ?? tableRowHandle(rows.length),
|
|
676
|
+
paragraphs
|
|
677
|
+
});
|
|
678
|
+
continue;
|
|
679
|
+
}
|
|
680
|
+
if (nestedTables.length !== 1) {
|
|
681
|
+
missingHandleCount += 1;
|
|
682
|
+
continue;
|
|
683
|
+
}
|
|
684
|
+
const paragraphs = collectTableParagraphs(sourceTable).filter((paragraph) => paragraph.paraId !== void 0 && editableParagraphIds.has(paragraph.paraId)).map((paragraph) => ({
|
|
553
685
|
paraId: paragraph.paraId,
|
|
554
686
|
sourceText: getParagraphText(paragraph)
|
|
555
687
|
}));
|
|
556
688
|
rows.push({
|
|
557
689
|
kind: "table",
|
|
690
|
+
layout: "inline",
|
|
558
691
|
rowId: paragraphs.at(0)?.paraId ?? tableRowHandle(rows.length),
|
|
559
692
|
paragraphs
|
|
560
693
|
});
|
|
@@ -582,26 +715,24 @@ function readBilingualDocument(document, editableParagraphIds) {
|
|
|
582
715
|
}
|
|
583
716
|
}
|
|
584
717
|
if (missingHandleCount > 0) throw new UneditableBilingualManifestError({
|
|
585
|
-
message: "Bilingual manifest
|
|
718
|
+
message: "Bilingual manifest structure or handles do not match the Folio AI-edit snapshot.",
|
|
586
719
|
missingHandleCount
|
|
587
720
|
});
|
|
588
721
|
return rows;
|
|
589
722
|
}
|
|
590
723
|
const isBilingualTable = (table) => {
|
|
591
724
|
if (table.formatting?.styleId !== BILINGUAL_TABLE_STYLE_ID) return false;
|
|
592
|
-
let pairs = 0;
|
|
593
725
|
for (const row of table.rows) {
|
|
594
726
|
const cells = row.cells;
|
|
595
727
|
if (cells.length === 2) {
|
|
596
728
|
if (!cells.every((cell) => cell.content.length === 1 && cell.content[0]?.type === "paragraph")) return false;
|
|
597
|
-
pairs += 1;
|
|
598
729
|
continue;
|
|
599
730
|
}
|
|
600
731
|
if (cells.length === 1 && cells[0]?.formatting?.gridSpan === 2) continue;
|
|
601
732
|
return false;
|
|
602
733
|
}
|
|
603
|
-
return
|
|
734
|
+
return table.rows.length > 0;
|
|
604
735
|
};
|
|
605
736
|
const isParagraphWithId = (value) => "type" in value && value.type === "paragraph" && "paraId" in value && typeof value.paraId === "string";
|
|
606
737
|
//#endregion
|
|
607
|
-
export { InvalidBilingualDocumentOptionsError, createBilingualDocument, readBilingualDocument };
|
|
738
|
+
export { BILINGUAL_TABLE_LAYOUTS, InvalidBilingualDocumentOptionsError, createBilingualDocument, readBilingualDocument };
|
package/dist/docx/styleParser.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { isValidHexColor } from "../utils/colorResolver.js";
|
|
2
|
+
import { parseHorizontalScalePercent } from "../utils/horizontalScale.js";
|
|
2
3
|
import { mergeParagraphFormatting } from "../utils/paragraphFormattingMerge.js";
|
|
3
4
|
import { mergeStyleTextFormatting } from "../utils/textFormattingMerge.js";
|
|
4
5
|
import { BorderStyleSchema, ConditionalStyleTypeSchema, EmphasisMarkSchema, FontHintSchema, FontThemeSchema, HighlightColorSchema, LineSpacingRuleSchema, ParagraphAlignmentSchema, ShadingPatternSchema, StyleTypeSchema, TabLeaderSchema, TabStopAlignmentSchema, TableCellTextDirectionSchema, TableRowHeightRuleSchema, TableWidthTypeSchema, TextEffectSchema, ThemeColorSlotSchema, UnderlineStyleSchema, narrowEnum } from "./parserEnums.js";
|
|
@@ -137,7 +138,7 @@ function parseRunProperties(rPr, theme) {
|
|
|
137
138
|
}
|
|
138
139
|
const w = findChild(rPr, "w", "w");
|
|
139
140
|
if (w) {
|
|
140
|
-
const val =
|
|
141
|
+
const val = parseHorizontalScalePercent(getAttribute(w, "w", "val"));
|
|
141
142
|
if (val !== void 0) formatting.scale = val;
|
|
142
143
|
}
|
|
143
144
|
const kern = findChild(rPr, "w", "kern");
|
|
@@ -4,6 +4,16 @@ import { findByFullName, findChildByLocalName, findChildrenByLocalName, getAttri
|
|
|
4
4
|
//#region src/docx/textBoxParser.ts
|
|
5
5
|
/** Default text box margins in EMUs (0.1 inch) */
|
|
6
6
|
const DEFAULT_MARGIN_EMU = 91440;
|
|
7
|
+
const parseTextBoxVerticalAlign = (value) => {
|
|
8
|
+
switch (value) {
|
|
9
|
+
case "t": return "top";
|
|
10
|
+
case "ctr": return "middle";
|
|
11
|
+
case "b": return "bottom";
|
|
12
|
+
case "dist": return "distributed";
|
|
13
|
+
case "just": return "justified";
|
|
14
|
+
default: return;
|
|
15
|
+
}
|
|
16
|
+
};
|
|
7
17
|
/**
|
|
8
18
|
* Parse text body properties from wps:bodyPr
|
|
9
19
|
* Returns margins/insets for the text box
|
|
@@ -16,6 +26,8 @@ function parseBodyProperties(bodyPr) {
|
|
|
16
26
|
if (findChildByLocalName(bodyPr, "spAutoFit")) result.autoFit = "shape";
|
|
17
27
|
else if (findChildByLocalName(bodyPr, "normAutofit")) result.autoFit = "normal";
|
|
18
28
|
else if (findChildByLocalName(bodyPr, "noAutofit")) result.autoFit = "none";
|
|
29
|
+
const verticalAlign = parseTextBoxVerticalAlign(getAttribute(bodyPr, null, "anchor"));
|
|
30
|
+
if (verticalAlign) result.verticalAlign = verticalAlign;
|
|
19
31
|
const lIns = parseNumericAttribute(bodyPr, null, "lIns");
|
|
20
32
|
const rIns = parseNumericAttribute(bodyPr, null, "rIns");
|
|
21
33
|
const tIns = parseNumericAttribute(bodyPr, null, "tIns");
|
|
@@ -131,6 +143,7 @@ function parseTextBox(drawingEl) {
|
|
|
131
143
|
if (bodyProps.margins) textBox.margins = bodyProps.margins;
|
|
132
144
|
if (bodyProps.autoFit) textBox.autoFit = bodyProps.autoFit;
|
|
133
145
|
if (bodyProps.textWrap) textBox.textWrap = bodyProps.textWrap;
|
|
146
|
+
if (bodyProps.verticalAlign) textBox.verticalAlign = bodyProps.verticalAlign;
|
|
134
147
|
if (isAnchor) {
|
|
135
148
|
const position = parseAnchorPosition(container);
|
|
136
149
|
if (position) textBox.position = position;
|
|
@@ -174,6 +187,7 @@ function parseTextBoxFromShape(wsp, size, position, wrap) {
|
|
|
174
187
|
if (bodyProps.margins) textBox.margins = bodyProps.margins;
|
|
175
188
|
if (bodyProps.autoFit) textBox.autoFit = bodyProps.autoFit;
|
|
176
189
|
if (bodyProps.textWrap) textBox.textWrap = bodyProps.textWrap;
|
|
190
|
+
if (bodyProps.verticalAlign) textBox.verticalAlign = bodyProps.verticalAlign;
|
|
177
191
|
if (position) textBox.position = position;
|
|
178
192
|
if (wrap) textBox.wrap = wrap;
|
|
179
193
|
return textBox;
|