@stll/folio-core 0.24.0 → 0.25.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/layoutPipeline.js +3 -4
- package/dist/docx/fieldParser.d.ts +1 -1
- package/dist/docx/fieldParser.js +2 -9
- package/dist/docx/paragraphTextBoxEnrichment.js +1 -0
- package/dist/docx/sectionParser.js +2 -2
- package/dist/docx/serializer/runSerializer.js +1 -0
- package/dist/docx/textBoxParser.js +4 -0
- package/dist/fields/fieldContext.d.ts +2 -1
- package/dist/layout-bridge/convert/headerFooterLayout.d.ts +9 -1
- package/dist/layout-bridge/convert/headerFooterLayout.js +26 -1
- package/dist/layout-bridge/convert/toFlowBlocks.js +1 -0
- package/dist/layout-bridge/engine/selectionRects.js +3 -5
- package/dist/layout-engine/index.d.ts +2 -2
- package/dist/layout-engine/index.js +3 -5
- package/dist/layout-engine/measure/measureBlocks.js +27 -11
- package/dist/layout-engine/measure/measureParagraph.js +7 -1
- package/dist/layout-engine/measure/tableCellFloating.js +2 -3
- package/dist/layout-engine/renderedBreakReconciliation.js +1 -1
- package/dist/layout-engine/tableRowBreak.js +2 -2
- package/dist/layout-engine/types.d.ts +13 -8
- package/dist/layout-engine/types.js +15 -1
- package/dist/layout-painter/renderParagraph.d.ts +9 -1
- package/dist/layout-painter/renderParagraph.js +57 -35
- package/dist/layout-painter/renderTable.js +2 -5
- package/dist/layout-painter/renderTextBox.js +1 -1
- package/dist/layout-painter/renderUtils.d.ts +2 -1
- package/dist/prosemirror/attrs/index.js +2 -0
- package/dist/prosemirror/conversion/fromProseDoc.js +1 -0
- package/dist/prosemirror/conversion/toProseDoc.js +2 -0
- package/dist/prosemirror/extensions/nodes/TextBoxExtension.d.ts +2 -0
- package/dist/prosemirror/extensions/nodes/TextBoxExtension.js +7 -0
- package/dist/prosemirror/schema/nodes.d.ts +2 -0
- package/dist/utils/paragraphBaseDirection.d.ts +6 -0
- package/dist/utils/paragraphBaseDirection.js +12 -0
- package/package.json +2 -2
|
@@ -26,11 +26,12 @@ function bodyMarginsClearHeaderFooter({ authoredMargins, preparedHeader, prepare
|
|
|
26
26
|
const headerBottom = preparedHeader ? (authoredMargins.header ?? 0) + (preparedHeader.marginPushBottom ?? preparedHeader.height) : authoredMargins.top;
|
|
27
27
|
const footerClearance = preparedFooter ? (authoredMargins.footer ?? 0) + (preparedFooter.marginPushBottom ?? preparedFooter.height) : authoredMargins.bottom;
|
|
28
28
|
const top = Math.max(authoredMargins.top, headerBottom);
|
|
29
|
+
const clearedTop = Math.max(top, preparedHeader?.bodyTopClearance ?? 0);
|
|
29
30
|
const bottom = Math.max(authoredMargins.bottom, footerClearance);
|
|
30
|
-
if (
|
|
31
|
+
if (clearedTop === authoredMargins.top && bottom === authoredMargins.bottom) return authoredMargins;
|
|
31
32
|
return {
|
|
32
33
|
...authoredMargins,
|
|
33
|
-
top,
|
|
34
|
+
top: clearedTop,
|
|
34
35
|
bottom
|
|
35
36
|
};
|
|
36
37
|
}
|
|
@@ -255,7 +256,6 @@ function runLayoutPipeline(deps, state, options = {}) {
|
|
|
255
256
|
let newLayout;
|
|
256
257
|
let pageFootnoteMap = /* @__PURE__ */ new Map();
|
|
257
258
|
let footnoteContentMap = /* @__PURE__ */ new Map();
|
|
258
|
-
const bodyBreakType = finalSectionProperties?.sectionStart;
|
|
259
259
|
const buildLayoutOpts = () => {
|
|
260
260
|
const nextLayoutOpts = {
|
|
261
261
|
pageSize,
|
|
@@ -279,7 +279,6 @@ function runLayoutPipeline(deps, state, options = {}) {
|
|
|
279
279
|
nextLayoutOpts.finalPageNumbering = finalLayoutConfig.pageNumbering;
|
|
280
280
|
}
|
|
281
281
|
if (columns !== void 0) nextLayoutOpts.columns = columns;
|
|
282
|
-
if (bodyBreakType !== void 0) nextLayoutOpts.bodyBreakType = bodyBreakType;
|
|
283
282
|
if (sectionHeaderFooterRefs !== void 0) nextLayoutOpts.sectionHeaderFooterRefs = sectionHeaderFooterRefs;
|
|
284
283
|
if (sectionEvenPageMargins !== void 0) nextLayoutOpts.sectionEvenPageMargins = sectionEvenPageMargins;
|
|
285
284
|
return nextLayoutOpts;
|
|
@@ -188,7 +188,7 @@ declare function isTocField(field: document_d_exports.Field): boolean;
|
|
|
188
188
|
* @param instruction - Parsed instruction for format switches
|
|
189
189
|
* @returns Formatted page number string
|
|
190
190
|
*/
|
|
191
|
-
declare function computePageNumber(pageNumber: number, instruction?: ParsedFieldInstruction, sectionFormat?:
|
|
191
|
+
declare function computePageNumber(pageNumber: number, instruction?: ParsedFieldInstruction, sectionFormat?: document_d_exports.NumberFormat): string;
|
|
192
192
|
/**
|
|
193
193
|
* Format a date according to a format string
|
|
194
194
|
*
|
package/dist/docx/fieldParser.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { formatOoxmlCounter } from "./ooxmlCounterFormatter.js";
|
|
1
2
|
import { FieldTypeSchema, narrowEnum } from "./parserEnums.js";
|
|
2
3
|
import { parseRun } from "./runParser.js";
|
|
3
4
|
import { findChildren, getAttribute } from "./xmlParser.js";
|
|
@@ -354,15 +355,7 @@ function computePageNumber(pageNumber, instruction, sectionFormat) {
|
|
|
354
355
|
default: return String(pageNumber);
|
|
355
356
|
}
|
|
356
357
|
}
|
|
357
|
-
|
|
358
|
-
switch (format) {
|
|
359
|
-
case "upperRoman": return toRoman(pageNumber);
|
|
360
|
-
case "lowerRoman": return toRoman(pageNumber).toLowerCase();
|
|
361
|
-
case "upperLetter": return toLetter(pageNumber);
|
|
362
|
-
case "lowerLetter": return toLetter(pageNumber).toLowerCase();
|
|
363
|
-
default: return String(pageNumber);
|
|
364
|
-
}
|
|
365
|
-
}
|
|
358
|
+
const formatSectionPageNumber = (pageNumber, format) => formatOoxmlCounter(pageNumber, format);
|
|
366
359
|
/**
|
|
367
360
|
* Convert number to uppercase Roman numerals
|
|
368
361
|
*/
|
|
@@ -158,6 +158,7 @@ const enrichTextBoxRuns = ({ content, xmlChildren, styles, theme, numbering, rel
|
|
|
158
158
|
textBody: {
|
|
159
159
|
content: textBox.content,
|
|
160
160
|
...textBox.autoFit !== void 0 ? { autoFit: textBox.autoFit } : {},
|
|
161
|
+
...textBox.textWrap !== void 0 ? { textWrap: textBox.textWrap } : {},
|
|
161
162
|
...textBox.margins !== void 0 ? { margins: textBox.margins } : {}
|
|
162
163
|
}
|
|
163
164
|
};
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { parseFooterReference, parseHeaderReference } from "./headerFooterRefParser.js";
|
|
2
2
|
import { parseEndnoteProperties, parseFootnoteProperties } from "./notePropertiesParser.js";
|
|
3
|
-
import { BorderStyleSchema, ThemeColorSlotSchema, narrowEnum } from "./parserEnums.js";
|
|
3
|
+
import { BorderStyleSchema, NumberFormatSchema, ThemeColorSlotSchema, narrowEnum } from "./parserEnums.js";
|
|
4
4
|
import { findChild, findChildren, getAttribute, getChildElements, getLocalName, parseBooleanElement, parseNumericAttribute } from "./xmlParser.js";
|
|
5
5
|
import { normalizeRevisionId } from "@stll/docx-core/model";
|
|
6
6
|
//#region src/docx/sectionParser.ts
|
|
@@ -272,7 +272,7 @@ function parseSectionProperties(sectPr) {
|
|
|
272
272
|
const pgNumType = findChild(sectPr, "w", "pgNumType");
|
|
273
273
|
if (pgNumType) {
|
|
274
274
|
const pageNumbering = {};
|
|
275
|
-
const format = getAttribute(pgNumType, "w", "fmt");
|
|
275
|
+
const format = narrowEnum(getAttribute(pgNumType, "w", "fmt"), NumberFormatSchema);
|
|
276
276
|
if (format) pageNumbering.format = format;
|
|
277
277
|
const start = parseNumericAttribute(pgNumType, "w", "start");
|
|
278
278
|
if (start !== void 0) pageNumbering.start = start;
|
|
@@ -479,6 +479,7 @@ function serializeShapeContent(content) {
|
|
|
479
479
|
if (shape.textBody) {
|
|
480
480
|
const tb = shape.textBody;
|
|
481
481
|
const bpAttrs = ["rot=\"0\"", "vert=\"horz\""];
|
|
482
|
+
if (tb.textWrap) bpAttrs.push(`wrap="${tb.textWrap}"`);
|
|
482
483
|
if (tb.anchor) bpAttrs.push(`anchor="${tb.anchor === "middle" ? "ctr" : tb.anchor}"`);
|
|
483
484
|
if (tb.anchorCenter) bpAttrs.push("anchorCtr=\"1\"");
|
|
484
485
|
if (tb.margins) {
|
|
@@ -11,6 +11,8 @@ const DEFAULT_MARGIN_EMU = 91440;
|
|
|
11
11
|
function parseBodyProperties(bodyPr) {
|
|
12
12
|
if (!bodyPr) return {};
|
|
13
13
|
const result = {};
|
|
14
|
+
const textWrap = getAttribute(bodyPr, null, "wrap");
|
|
15
|
+
if (textWrap === "none" || textWrap === "square") result.textWrap = textWrap;
|
|
14
16
|
if (findChildByLocalName(bodyPr, "spAutoFit")) result.autoFit = "shape";
|
|
15
17
|
else if (findChildByLocalName(bodyPr, "normAutofit")) result.autoFit = "normal";
|
|
16
18
|
else if (findChildByLocalName(bodyPr, "noAutofit")) result.autoFit = "none";
|
|
@@ -128,6 +130,7 @@ function parseTextBox(drawingEl) {
|
|
|
128
130
|
if (outline) textBox.outline = outline;
|
|
129
131
|
if (bodyProps.margins) textBox.margins = bodyProps.margins;
|
|
130
132
|
if (bodyProps.autoFit) textBox.autoFit = bodyProps.autoFit;
|
|
133
|
+
if (bodyProps.textWrap) textBox.textWrap = bodyProps.textWrap;
|
|
131
134
|
if (isAnchor) {
|
|
132
135
|
const position = parseAnchorPosition(container);
|
|
133
136
|
if (position) textBox.position = position;
|
|
@@ -170,6 +173,7 @@ function parseTextBoxFromShape(wsp, size, position, wrap) {
|
|
|
170
173
|
if (outline) textBox.outline = outline;
|
|
171
174
|
if (bodyProps.margins) textBox.margins = bodyProps.margins;
|
|
172
175
|
if (bodyProps.autoFit) textBox.autoFit = bodyProps.autoFit;
|
|
176
|
+
if (bodyProps.textWrap) textBox.textWrap = bodyProps.textWrap;
|
|
173
177
|
if (position) textBox.position = position;
|
|
174
178
|
if (wrap) textBox.wrap = wrap;
|
|
175
179
|
return textBox;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { document_d_exports } from "../types/document.js";
|
|
1
2
|
//#region src/fields/fieldContext.d.ts
|
|
2
3
|
/**
|
|
3
4
|
* Resolution context for evaluating dynamic DOCX fields against a laid-out
|
|
@@ -10,7 +11,7 @@ type FieldContext = {
|
|
|
10
11
|
/** 1-indexed page the field is on (PAGE). */
|
|
11
12
|
pageNumber: number;
|
|
12
13
|
/** OOXML section-level format used when PAGE has no explicit format switch. */
|
|
13
|
-
pageNumberFormat?:
|
|
14
|
+
pageNumberFormat?: document_d_exports.NumberFormat;
|
|
14
15
|
/** Total pages in the document (NUMPAGES). */
|
|
15
16
|
totalPages: number;
|
|
16
17
|
/** Pages in the field's current section (SECTIONPAGES); omit until the
|
|
@@ -30,6 +30,14 @@ declare function calculateHeaderFooterMarginPushBounds(blocks: FlowBlock[], meas
|
|
|
30
30
|
top: number;
|
|
31
31
|
bottom: number;
|
|
32
32
|
};
|
|
33
|
+
/**
|
|
34
|
+
* Find the lower page edge of header `wrapTopAndBottom` artwork that overlaps
|
|
35
|
+
* the authored body start. Unlike ordinary anchored artwork, this wrap mode
|
|
36
|
+
* reserves a full-width horizontal band even when the drawing paints behind
|
|
37
|
+
* text. Bands wholly below the body start need page-local flow support and are
|
|
38
|
+
* intentionally not flattened into a top margin here.
|
|
39
|
+
*/
|
|
40
|
+
declare function calculateHeaderFooterBodyTopClearance(blocks: FlowBlock[], flowHeight: number, metrics: HeaderFooterMetrics): number | undefined;
|
|
33
41
|
type ConvertHeaderFooterOptions = {
|
|
34
42
|
styles?: document_d_exports.StyleDefinitions | null;
|
|
35
43
|
theme?: document_d_exports.Theme | null;
|
|
@@ -74,4 +82,4 @@ declare function convertHeaderFooterToContent(headerFooter: document_d_exports.H
|
|
|
74
82
|
*/
|
|
75
83
|
declare function convertHeaderFooterPmDocToContent(pmDoc: Node | null | undefined, contentWidth: number, metrics: HeaderFooterMetrics, options: Omit<ConvertHeaderFooterOptions, "styles">): HeaderFooterContent | undefined;
|
|
76
84
|
//#endregion
|
|
77
|
-
export { ConvertHeaderFooterOptions, HeaderFooterMetrics, calculateHeaderFooterMarginPushBounds, calculateHeaderFooterVisualBounds, convertHeaderFooterPmDocToContent, convertHeaderFooterToContent, normalizeHeaderFooterMeasureBlocks, resolveHeaderFooterPositionedVisualTop, resolveHeaderFooterVisualTop };
|
|
85
|
+
export { ConvertHeaderFooterOptions, HeaderFooterMetrics, calculateHeaderFooterBodyTopClearance, calculateHeaderFooterMarginPushBounds, calculateHeaderFooterVisualBounds, convertHeaderFooterPmDocToContent, convertHeaderFooterToContent, normalizeHeaderFooterMeasureBlocks, resolveHeaderFooterPositionedVisualTop, resolveHeaderFooterVisualTop };
|
|
@@ -290,6 +290,29 @@ function calculateHeaderFooterMarginPushBounds(blocks, measures, flowHeight, met
|
|
|
290
290
|
};
|
|
291
291
|
}
|
|
292
292
|
/**
|
|
293
|
+
* Find the lower page edge of header `wrapTopAndBottom` artwork that overlaps
|
|
294
|
+
* the authored body start. Unlike ordinary anchored artwork, this wrap mode
|
|
295
|
+
* reserves a full-width horizontal band even when the drawing paints behind
|
|
296
|
+
* text. Bands wholly below the body start need page-local flow support and are
|
|
297
|
+
* intentionally not flattened into a top margin here.
|
|
298
|
+
*/
|
|
299
|
+
function calculateHeaderFooterBodyTopClearance(blocks, flowHeight, metrics) {
|
|
300
|
+
if (metrics.section !== "header") return;
|
|
301
|
+
const flowTop = metrics.margins.header ?? 48;
|
|
302
|
+
let clearance = 0;
|
|
303
|
+
for (const block of blocks) {
|
|
304
|
+
if (block.kind !== "paragraph") continue;
|
|
305
|
+
for (const run of block.runs) {
|
|
306
|
+
if (run.kind !== "image" || run.wrapType !== "topAndBottom" || run.position?.vertical?.relativeTo !== "page" && run.position?.vertical?.relativeTo !== "margin") continue;
|
|
307
|
+
const pageTop = flowTop + resolveHeaderFooterVisualTop(run, 0, flowHeight, metrics) - (run.distTop ?? 0);
|
|
308
|
+
if (pageTop > metrics.margins.top) continue;
|
|
309
|
+
const pageBottom = Math.min(metrics.pageSize.h, pageTop + (run.distTop ?? 0) + run.height + (run.distBottom ?? 0));
|
|
310
|
+
clearance = Math.max(clearance, pageBottom);
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
return clearance > 0 ? clearance : void 0;
|
|
314
|
+
}
|
|
315
|
+
/**
|
|
293
316
|
* Convert HeaderFooter (document type) to HeaderFooterContent (render type).
|
|
294
317
|
*
|
|
295
318
|
* Routes through the same pipeline as the body: HF.content ->
|
|
@@ -350,6 +373,7 @@ function finalizeHeaderFooterContent(blocks, contentWidth, metrics, options) {
|
|
|
350
373
|
}
|
|
351
374
|
const { visualTop, visualBottom } = calculateHeaderFooterVisualBounds(blocks, measures, flowHeight, metrics);
|
|
352
375
|
const { top: marginPushTop, bottom: marginPushBottom } = calculateHeaderFooterMarginPushBounds(blocks, measures, flowHeight, metrics);
|
|
376
|
+
const bodyTopClearance = calculateHeaderFooterBodyTopClearance(blocks, flowHeight, metrics);
|
|
353
377
|
return {
|
|
354
378
|
blocks,
|
|
355
379
|
measures,
|
|
@@ -358,6 +382,7 @@ function finalizeHeaderFooterContent(blocks, contentWidth, metrics, options) {
|
|
|
358
382
|
visualBottom,
|
|
359
383
|
marginPushTop,
|
|
360
384
|
marginPushBottom,
|
|
385
|
+
...bodyTopClearance !== void 0 ? { bodyTopClearance } : {},
|
|
361
386
|
textSig: computeHeaderFooterTextSig(blocks),
|
|
362
387
|
...options.rId ? { rId: options.rId } : {}
|
|
363
388
|
};
|
|
@@ -489,4 +514,4 @@ function serializeRunFmt(run) {
|
|
|
489
514
|
return JSON.stringify(out);
|
|
490
515
|
}
|
|
491
516
|
//#endregion
|
|
492
|
-
export { calculateHeaderFooterMarginPushBounds, calculateHeaderFooterVisualBounds, convertHeaderFooterPmDocToContent, convertHeaderFooterToContent, normalizeHeaderFooterMeasureBlocks, resolveHeaderFooterPositionedVisualTop, resolveHeaderFooterVisualTop };
|
|
517
|
+
export { calculateHeaderFooterBodyTopClearance, calculateHeaderFooterMarginPushBounds, calculateHeaderFooterVisualBounds, convertHeaderFooterPmDocToContent, convertHeaderFooterToContent, normalizeHeaderFooterMeasureBlocks, resolveHeaderFooterPositionedVisualTop, resolveHeaderFooterVisualTop };
|
|
@@ -1221,6 +1221,7 @@ function convertTextBoxNode(node, startPos, opts) {
|
|
|
1221
1221
|
};
|
|
1222
1222
|
if (attrs.height !== void 0) textBox.height = attrs.height;
|
|
1223
1223
|
if (attrs.autoFit !== void 0) textBox.autoFit = attrs.autoFit;
|
|
1224
|
+
if (attrs.textWrap !== void 0) textBox.textWrap = attrs.textWrap;
|
|
1224
1225
|
if (attrs.fillColor !== void 0) textBox.fillColor = attrs.fillColor;
|
|
1225
1226
|
if (attrs.outlineWidth !== void 0) textBox.outlineWidth = attrs.outlineWidth;
|
|
1226
1227
|
if (attrs.outlineColor !== void 0) textBox.outlineColor = attrs.outlineColor;
|
|
@@ -4,7 +4,7 @@ import { buildRunFontStyle } from "../../layout-engine/measure/measureHelpers.js
|
|
|
4
4
|
import { measureParagraph } from "../../layout-engine/measure/measureParagraph.js";
|
|
5
5
|
import { measureRun } from "../../layout-engine/measure/measureProvider.js";
|
|
6
6
|
import { buildTableCellFloatingZones, getTableCellContentWidth, getTableCellFloatingImages } from "../../layout-engine/measure/tableCellFloating.js";
|
|
7
|
-
import { getTableRowLeadingWidth } from "../../layout-engine/types.js";
|
|
7
|
+
import { getTableRowLeadingWidth, resolveTableCellPadding } from "../../layout-engine/types.js";
|
|
8
8
|
import { inlineImageBoundingBox } from "../../utils/rotationBoundingBox.js";
|
|
9
9
|
import { getPageTop } from "./hitTest.js";
|
|
10
10
|
//#region src/layout-bridge/engine/selectionRects.ts
|
|
@@ -24,8 +24,6 @@ import { getPageTop } from "./hitTest.js";
|
|
|
24
24
|
* across blank lines visible (eigenpal/docx-editor#836).
|
|
25
25
|
*/
|
|
26
26
|
const EMPTY_PARAGRAPH_SLIVER_WIDTH = 4;
|
|
27
|
-
const DEFAULT_TABLE_CELL_PADDING_LEFT = 7;
|
|
28
|
-
const DEFAULT_TABLE_CELL_PADDING_TOP = 1;
|
|
29
27
|
/**
|
|
30
28
|
* Extract FontStyle from a run for measurement.
|
|
31
29
|
*/
|
|
@@ -49,14 +47,14 @@ function findBlockById(blocks, blockId) {
|
|
|
49
47
|
return blocks.findIndex((block) => block.id === blockId);
|
|
50
48
|
}
|
|
51
49
|
function getCellContentOffsetY(cell, cellMeasure, rowHeight) {
|
|
52
|
-
const padTop = cell
|
|
50
|
+
const { top: padTop } = resolveTableCellPadding(cell);
|
|
53
51
|
const spareHeight = Math.max(0, rowHeight - cellMeasure.height);
|
|
54
52
|
if (cell.verticalAlign === "bottom") return padTop + spareHeight;
|
|
55
53
|
if (cell.verticalAlign === "center") return padTop + spareHeight / 2;
|
|
56
54
|
return padTop;
|
|
57
55
|
}
|
|
58
56
|
function getCellContentOffsetX(cell) {
|
|
59
|
-
return cell.
|
|
57
|
+
return resolveTableCellPadding(cell).left;
|
|
60
58
|
}
|
|
61
59
|
function getMeasuredBlockHeight(measure) {
|
|
62
60
|
if (!measure) return 0;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { BlockId, BorderStyle, CellBorderSpec, CellBorders, ColumnBreakBlock, ColumnBreakMeasure, ColumnLayout, DEFAULT_TEXTBOX_MARGINS, DEFAULT_TEXTBOX_WIDTH, DocumentPosition, FOOTNOTE_ENTRY_MARGIN_BOTTOM, FOOTNOTE_FALLBACK_LINE_HEIGHT, FOOTNOTE_SEPARATOR_HEIGHT, FieldRun, FloatingTablePosition, FlowBlock, FootnoteContent, Fragment, FragmentBase, HeaderFooterContent, HeaderFooterContentHeights, HeaderFooterLayout, HitTestResult, HyperlinkInfo, ImageBlock, ImageFragment, ImageMeasure, ImageRun, ImageRunPosition, Layout, LayoutOptions, LineBreakRun, ListMarkerFormatting, ListNumPr, MathRun, Measure, MeasuredLine, Page, PageBreakBlock, PageBreakMeasure, PageHeaderFooterRefs, PageMargins, ParagraphAttrs, ParagraphBlock, ParagraphBorders, ParagraphFragment, ParagraphIndent, ParagraphMeasure, ParagraphSpacing, RenderedPageBreakRun, Run, RunFormatting, SdtGroup, SectionBreakBlock, SectionBreakMeasure, SectionPageNumbering, TabAlignment, TabRun, TabStop, TableBlock, TableCell, TableCellMeasure, TableFragment, TableMeasure, TableRow, TableRowMeasure, TextBoxBlock, TextBoxFlowAttrs, TextBoxFragment, TextBoxMeasure, TextRun, floatingTextBoxReservesBand, floatingTextBoxWrapsText, getTableRowLeadingWidth, isFloatingImageRun, isFloatingTextBoxBlock, isTextWrappingFloatingImageRun, tableColumnsArePinned } from "./types.js";
|
|
1
|
+
import { BlockId, BorderStyle, CellBorderSpec, CellBorders, ColumnBreakBlock, ColumnBreakMeasure, ColumnLayout, DEFAULT_TEXTBOX_MARGINS, DEFAULT_TEXTBOX_WIDTH, DocumentPosition, FOOTNOTE_ENTRY_MARGIN_BOTTOM, FOOTNOTE_FALLBACK_LINE_HEIGHT, FOOTNOTE_SEPARATOR_HEIGHT, FieldRun, FloatingTablePosition, FlowBlock, FootnoteContent, Fragment, FragmentBase, HeaderFooterContent, HeaderFooterContentHeights, HeaderFooterLayout, HitTestResult, HyperlinkInfo, ImageBlock, ImageFragment, ImageMeasure, ImageRun, ImageRunPosition, Layout, LayoutOptions, LineBreakRun, ListMarkerFormatting, ListNumPr, MathRun, Measure, MeasuredLine, Page, PageBreakBlock, PageBreakMeasure, PageHeaderFooterRefs, PageMargins, ParagraphAttrs, ParagraphBlock, ParagraphBorders, ParagraphFragment, ParagraphIndent, ParagraphMeasure, ParagraphSpacing, RenderedPageBreakRun, Run, RunFormatting, SdtGroup, SectionBreakBlock, SectionBreakMeasure, SectionPageNumbering, TabAlignment, TabRun, TabStop, TableBlock, TableCell, TableCellMeasure, TableFragment, TableMeasure, TableRow, TableRowMeasure, TextBoxBlock, TextBoxFlowAttrs, TextBoxFragment, TextBoxMeasure, TextRun, floatingTextBoxReservesBand, floatingTextBoxWrapsText, getTableRowLeadingWidth, isFloatingImageRun, isFloatingTextBoxBlock, isTextWrappingFloatingImageRun, resolveTableCellPadding, tableColumnsArePinned } from "./types.js";
|
|
2
2
|
import { PageState, Paginator, PaginatorOptions, createPaginator } from "./paginator.js";
|
|
3
3
|
import { KeepNextChain, calculateChainHeight, computeKeepNextChains, getMidChainIndices, hasKeepLines, hasPageBreakBefore } from "./keep-together.js";
|
|
4
4
|
import { resolveSectionHeaderFooterRefs } from "./headerFooterRefs.js";
|
|
@@ -44,4 +44,4 @@ declare function layoutDocument(blocks: FlowBlock[], measures: Measure[], option
|
|
|
44
44
|
*/
|
|
45
45
|
declare function getHeaderRowsHeight(measure: TableMeasure, headerRowCount: number): number;
|
|
46
46
|
//#endregion
|
|
47
|
-
export { BlockId, BorderStyle, type BreakDecision, CellBorderSpec, CellBorders, ColumnBreakBlock, ColumnBreakMeasure, ColumnLayout, DEFAULT_TEXTBOX_MARGINS, DEFAULT_TEXTBOX_WIDTH, DocumentPosition, FOOTNOTE_ENTRY_MARGIN_BOTTOM, FOOTNOTE_FALLBACK_LINE_HEIGHT, FOOTNOTE_SEPARATOR_HEIGHT, FieldRun, FloatingTablePosition, FlowBlock, FootnoteContent, Fragment, FragmentBase, HeaderFooterContent, HeaderFooterContentHeights, HeaderFooterLayout, HitTestResult, HyperlinkInfo, ImageBlock, ImageFragment, ImageMeasure, ImageRun, ImageRunPosition, type KeepNextChain, Layout, LayoutOptions, LineBreakRun, ListMarkerFormatting, ListNumPr, MathRun, Measure, MeasuredLine, Page, PageBreakBlock, PageBreakMeasure, PageHeaderFooterRefs, PageMargins, type PageState, type Paginator, type PaginatorOptions, ParagraphAttrs, ParagraphBlock, ParagraphBorders, ParagraphFragment, ParagraphIndent, ParagraphMeasure, ParagraphSpacing, RenderedPageBreakRun, Run, RunFormatting, SdtGroup, SectionBreakBlock, SectionBreakMeasure, SectionLayoutConfig, SectionPageNumbering, type SectionState, TabAlignment, TabRun, TabStop, TableBlock, TableCell, TableCellMeasure, TableFragment, TableMeasure, TableRow, TableRowMeasure, TextBoxBlock, TextBoxFlowAttrs, TextBoxFragment, TextBoxMeasure, TextRun, applyContextualSpacing, applyPendingToActive, assertExhaustiveFlowBlock, calculateChainHeight, collectSectionConfigs, computeKeepNextChains, createInitialSectionState, createPaginator, findPageIndexContainingPmPos, floatingTextBoxReservesBand, floatingTextBoxWrapsText, getEffectiveColumns, getEffectiveMargins, getEffectivePageSize, getHeaderRowsHeight, getMidChainIndices, getTableRowLeadingWidth, hasKeepLines, hasPageBreakBefore, isFloatingImageRun, isFloatingTextBoxBlock, isTextWrappingFloatingImageRun, layoutDocument, resolveSectionHeaderFooterRefs, scheduleSectionBreak, tableColumnsArePinned };
|
|
47
|
+
export { BlockId, BorderStyle, type BreakDecision, CellBorderSpec, CellBorders, ColumnBreakBlock, ColumnBreakMeasure, ColumnLayout, DEFAULT_TEXTBOX_MARGINS, DEFAULT_TEXTBOX_WIDTH, DocumentPosition, FOOTNOTE_ENTRY_MARGIN_BOTTOM, FOOTNOTE_FALLBACK_LINE_HEIGHT, FOOTNOTE_SEPARATOR_HEIGHT, FieldRun, FloatingTablePosition, FlowBlock, FootnoteContent, Fragment, FragmentBase, HeaderFooterContent, HeaderFooterContentHeights, HeaderFooterLayout, HitTestResult, HyperlinkInfo, ImageBlock, ImageFragment, ImageMeasure, ImageRun, ImageRunPosition, type KeepNextChain, Layout, LayoutOptions, LineBreakRun, ListMarkerFormatting, ListNumPr, MathRun, Measure, MeasuredLine, Page, PageBreakBlock, PageBreakMeasure, PageHeaderFooterRefs, PageMargins, type PageState, type Paginator, type PaginatorOptions, ParagraphAttrs, ParagraphBlock, ParagraphBorders, ParagraphFragment, ParagraphIndent, ParagraphMeasure, ParagraphSpacing, RenderedPageBreakRun, Run, RunFormatting, SdtGroup, SectionBreakBlock, SectionBreakMeasure, SectionLayoutConfig, SectionPageNumbering, type SectionState, TabAlignment, TabRun, TabStop, TableBlock, TableCell, TableCellMeasure, TableFragment, TableMeasure, TableRow, TableRowMeasure, TextBoxBlock, TextBoxFlowAttrs, TextBoxFragment, TextBoxMeasure, TextRun, applyContextualSpacing, applyPendingToActive, assertExhaustiveFlowBlock, calculateChainHeight, collectSectionConfigs, computeKeepNextChains, createInitialSectionState, createPaginator, findPageIndexContainingPmPos, floatingTextBoxReservesBand, floatingTextBoxWrapsText, getEffectiveColumns, getEffectiveMargins, getEffectivePageSize, getHeaderRowsHeight, getMidChainIndices, getTableRowLeadingWidth, hasKeepLines, hasPageBreakBefore, isFloatingImageRun, isFloatingTextBoxBlock, isTextWrappingFloatingImageRun, layoutDocument, resolveSectionHeaderFooterRefs, resolveTableCellPadding, scheduleSectionBreak, tableColumnsArePinned };
|
|
@@ -11,7 +11,7 @@ import { INITIAL_RENDERED_BREAK_STATE, reconcileAfterBlock, reconcileBreakBefore
|
|
|
11
11
|
import { applyPendingToActive, createInitialSectionState, getEffectiveColumns, getEffectiveMargins, getEffectivePageSize, scheduleSectionBreak } from "./section-breaks.js";
|
|
12
12
|
import { buildTableRowBreakInfo, getRowContinuationSkip, snapRowBreak } from "./tableRowBreak.js";
|
|
13
13
|
import { bandFragmentX, bandTopContentY, isPageFrameRelativeAnchor } from "./textBoxFlow.js";
|
|
14
|
-
import { DEFAULT_TEXTBOX_MARGINS, DEFAULT_TEXTBOX_WIDTH, FOOTNOTE_ENTRY_MARGIN_BOTTOM, FOOTNOTE_FALLBACK_LINE_HEIGHT, FOOTNOTE_SEPARATOR_HEIGHT, floatingTextBoxReservesBand, floatingTextBoxWrapsText, getTableRowLeadingWidth, isFloatingImageRun, isFloatingTextBoxBlock, isTextWrappingFloatingImageRun, tableColumnsArePinned } from "./types.js";
|
|
14
|
+
import { DEFAULT_TEXTBOX_MARGINS, DEFAULT_TEXTBOX_WIDTH, FOOTNOTE_ENTRY_MARGIN_BOTTOM, FOOTNOTE_FALLBACK_LINE_HEIGHT, FOOTNOTE_SEPARATOR_HEIGHT, floatingTextBoxReservesBand, floatingTextBoxWrapsText, getTableRowLeadingWidth, isFloatingImageRun, isFloatingTextBoxBlock, isTextWrappingFloatingImageRun, resolveTableCellPadding, tableColumnsArePinned } from "./types.js";
|
|
15
15
|
import { panic } from "better-result";
|
|
16
16
|
//#region src/layout-engine/index.ts
|
|
17
17
|
/**
|
|
@@ -257,9 +257,7 @@ function layoutDocument(blocks, measures, options) {
|
|
|
257
257
|
break;
|
|
258
258
|
case "sectionBreak": {
|
|
259
259
|
const nextSectionConfig = sectionConfigs[sectionIdx + 1] ?? initialConfig;
|
|
260
|
-
|
|
261
|
-
if (sectionIdx + 1 < sectionBreakTypes.length) nextType = sectionBreakTypes[sectionIdx + 1] ?? DEFAULT_SECTION_BREAK_TYPE;
|
|
262
|
-
handleSectionBreak(block, paginator, nextSectionConfig, nextType, sectionIdx + 1);
|
|
260
|
+
handleSectionBreak(block, paginator, nextSectionConfig, sectionBreakTypes[sectionIdx] ?? DEFAULT_SECTION_BREAK_TYPE, sectionIdx + 1);
|
|
263
261
|
const nextColumns = nextSectionConfig.columns;
|
|
264
262
|
const nextBreakIndex = breakIndices[sectionIdx + 1] ?? blocks.length;
|
|
265
263
|
const nextBreak = blocks[nextBreakIndex];
|
|
@@ -937,4 +935,4 @@ function handleSectionBreak(_block, paginator, nextSectionConfig, nextSectionTyp
|
|
|
937
935
|
paginator.updateColumns(nextSectionConfig.columns ?? DEFAULT_COLUMNS);
|
|
938
936
|
}
|
|
939
937
|
//#endregion
|
|
940
|
-
export { DEFAULT_TEXTBOX_MARGINS, DEFAULT_TEXTBOX_WIDTH, FOOTNOTE_ENTRY_MARGIN_BOTTOM, FOOTNOTE_FALLBACK_LINE_HEIGHT, FOOTNOTE_SEPARATOR_HEIGHT, applyContextualSpacing, applyPendingToActive, assertExhaustiveFlowBlock, calculateChainHeight, collectSectionConfigs, computeKeepNextChains, createInitialSectionState, createPaginator, findPageIndexContainingPmPos, floatingTextBoxReservesBand, floatingTextBoxWrapsText, getEffectiveColumns, getEffectiveMargins, getEffectivePageSize, getHeaderRowsHeight, getMidChainIndices, getTableRowLeadingWidth, hasKeepLines, hasPageBreakBefore, isFloatingImageRun, isFloatingTextBoxBlock, isTextWrappingFloatingImageRun, layoutDocument, resolveSectionHeaderFooterRefs, scheduleSectionBreak, tableColumnsArePinned };
|
|
938
|
+
export { DEFAULT_TEXTBOX_MARGINS, DEFAULT_TEXTBOX_WIDTH, FOOTNOTE_ENTRY_MARGIN_BOTTOM, FOOTNOTE_FALLBACK_LINE_HEIGHT, FOOTNOTE_SEPARATOR_HEIGHT, applyContextualSpacing, applyPendingToActive, assertExhaustiveFlowBlock, calculateChainHeight, collectSectionConfigs, computeKeepNextChains, createInitialSectionState, createPaginator, findPageIndexContainingPmPos, floatingTextBoxReservesBand, floatingTextBoxWrapsText, getEffectiveColumns, getEffectiveMargins, getEffectivePageSize, getHeaderRowsHeight, getMidChainIndices, getTableRowLeadingWidth, hasKeepLines, hasPageBreakBefore, isFloatingImageRun, isFloatingTextBoxBlock, isTextWrappingFloatingImageRun, layoutDocument, resolveSectionHeaderFooterRefs, resolveTableCellPadding, scheduleSectionBreak, tableColumnsArePinned };
|
|
@@ -2,7 +2,7 @@ import { recordMeasureBlock, recordMeasureBlockError } from "../layoutInstrument
|
|
|
2
2
|
import { isParagraphFrameTextBox } from "../paragraphFrame.js";
|
|
3
3
|
import { bandFragmentX, bandTopContentY, isPageFrameRelativeAnchor } from "../textBoxFlow.js";
|
|
4
4
|
import { getTextBoxGroupId } from "../textBoxGroup.js";
|
|
5
|
-
import { DEFAULT_TEXTBOX_MARGINS, floatingTextBoxReservesBand, floatingTextBoxWrapsText, tableColumnsArePinned } from "../types.js";
|
|
5
|
+
import { DEFAULT_TEXTBOX_MARGINS, floatingTextBoxReservesBand, floatingTextBoxWrapsText, resolveTableCellPadding, tableColumnsArePinned } from "../types.js";
|
|
6
6
|
import { getCachedParagraphMeasure, setCachedParagraphMeasure } from "./cache.js";
|
|
7
7
|
import { resolveFloatingTableX } from "./floatingTablePosition.js";
|
|
8
8
|
import { findClearLineY, measureParagraph } from "./measureParagraph.js";
|
|
@@ -63,8 +63,6 @@ function resolveTableWidthPx(width, widthType, contentWidth) {
|
|
|
63
63
|
if (widthType === "dxa" || !widthType || widthType === "auto") return Math.round(width / 20 * 1.333);
|
|
64
64
|
}
|
|
65
65
|
function measureTableBlock(tableBlock, contentWidth, fieldValues) {
|
|
66
|
-
const DEFAULT_CELL_PADDING_X = 7;
|
|
67
|
-
const DEFAULT_CELL_PADDING_Y = 0;
|
|
68
66
|
let columnWidths = tableBlock.columnWidths ?? [];
|
|
69
67
|
const explicitWidthPx = resolveTableWidthPx(tableBlock.width, tableBlock.widthType, contentWidth);
|
|
70
68
|
if (columnWidths.length === 0 && tableBlock.rows.length > 0) {
|
|
@@ -89,8 +87,7 @@ function measureTableBlock(tableBlock, contentWidth, fieldValues) {
|
|
|
89
87
|
for (let c = 0; c < colSpan && columnIndex + c < columnWidths.length; c++) cellWidth += columnWidths[columnIndex + c] ?? 0;
|
|
90
88
|
if (cellWidth === 0) cellWidth = cell.width ?? 100;
|
|
91
89
|
columnIndex = getFirstAvailableColumn(cellGrid, rowIdx, columnIndex + colSpan);
|
|
92
|
-
const padLeft = cell
|
|
93
|
-
const padRight = cell.padding?.right ?? DEFAULT_CELL_PADDING_X;
|
|
90
|
+
const { left: padLeft, right: padRight } = resolveTableCellPadding(cell);
|
|
94
91
|
const cellContentWidth = Math.max(1, cellWidth - padLeft - padRight);
|
|
95
92
|
const measureWidth = cell.noWrap === true && !columnsPinned ? NO_WRAP_MEASURE_WIDTH : cellContentWidth;
|
|
96
93
|
const cellMeasure = {
|
|
@@ -132,8 +129,7 @@ function measureTableBlock(tableBlock, contentWidth, fieldValues) {
|
|
|
132
129
|
placeTableCellBlock(flowState, sourceBlock, blockMeasure);
|
|
133
130
|
}
|
|
134
131
|
cell.height = finishTableCellFlow(flowState);
|
|
135
|
-
const padTop = sourceCell
|
|
136
|
-
const padBottom = sourceCell?.padding?.bottom ?? DEFAULT_CELL_PADDING_Y;
|
|
132
|
+
const { top: padTop, bottom: padBottom } = resolveTableCellPadding(sourceCell);
|
|
137
133
|
cell.height += padTop + padBottom;
|
|
138
134
|
if ((sourceCell?.rowSpan ?? 1) > 1) continue;
|
|
139
135
|
const borderHeight = getTableCellVerticalBorderHeight(cellGrid, sourceCell, rowIdx);
|
|
@@ -444,14 +440,34 @@ function measureTextBoxBlock(tb, fieldValues) {
|
|
|
444
440
|
const innerWidth = tb.width - margins.left - margins.right;
|
|
445
441
|
const innerMeasures = tb.content.map((block) => {
|
|
446
442
|
if (block.kind === "table") return measureTableBlock(block, innerWidth, fieldValues);
|
|
447
|
-
return measureParagraph(block, innerWidth, fieldValues ? { fieldValues } : void 0);
|
|
443
|
+
return measureParagraph(block, tb.textWrap === "none" ? NO_WRAP_MEASURE_WIDTH : innerWidth, fieldValues ? { fieldValues } : void 0);
|
|
448
444
|
});
|
|
445
|
+
let naturalContentWidth = 0;
|
|
446
|
+
for (let index = 0; index < innerMeasures.length; index++) {
|
|
447
|
+
const measure = innerMeasures[index];
|
|
448
|
+
if (measure.kind === "table") {
|
|
449
|
+
naturalContentWidth = Math.max(naturalContentWidth, measure.totalWidth);
|
|
450
|
+
continue;
|
|
451
|
+
}
|
|
452
|
+
const block = tb.content[index];
|
|
453
|
+
if (!block || block.kind !== "paragraph") continue;
|
|
454
|
+
const indent = block.attrs?.indent;
|
|
455
|
+
const left = indent?.left ?? 0;
|
|
456
|
+
const right = indent?.right ?? 0;
|
|
457
|
+
for (let lineIndex = 0; lineIndex < measure.lines.length; lineIndex++) {
|
|
458
|
+
const line = measure.lines[lineIndex];
|
|
459
|
+
const start = left + (lineIndex === 0 ? (indent?.firstLine ?? 0) - (indent?.hanging ?? 0) : 0);
|
|
460
|
+
const end = start + line.width + right;
|
|
461
|
+
naturalContentWidth = Math.max(naturalContentWidth, Math.max(0, end) - Math.min(0, start));
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
const fittedWidth = naturalContentWidth + margins.left + margins.right;
|
|
465
|
+
const totalWidth = tb.autoFit === "shape" && tb.textWrap === "none" ? Math.max(tb.width, fittedWidth) : tb.width;
|
|
449
466
|
const contentBoxHeight = layoutTextBoxContent(tb.content, innerMeasures).totalHeight + margins.top + margins.bottom;
|
|
450
|
-
const totalHeight = tb.autoFit === "shape" ? Math.max(tb.height ?? 0, contentBoxHeight) : tb.height ?? contentBoxHeight;
|
|
451
467
|
return {
|
|
452
468
|
kind: "textBox",
|
|
453
|
-
width:
|
|
454
|
-
height:
|
|
469
|
+
width: totalWidth,
|
|
470
|
+
height: tb.autoFit === "shape" ? Math.max(tb.height ?? 0, contentBoxHeight) : tb.height ?? contentBoxHeight,
|
|
455
471
|
innerMeasures
|
|
456
472
|
};
|
|
457
473
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { calculateTabWidth, pixelsToTwips } from "../../prosemirror/utils/tabCalculator.js";
|
|
2
2
|
import { CJK_FALLBACK_FONT_FAMILY, isCjkFont } from "../../utils/fontResolver.js";
|
|
3
|
+
import { isRtlParagraph } from "../../utils/paragraphBaseDirection.js";
|
|
3
4
|
import { inlineImageBoundingBox } from "../../utils/rotationBoundingBox.js";
|
|
4
5
|
import { hasCjk, hasComplexScript } from "../../utils/scriptSegments.js";
|
|
5
6
|
import { measuredLineAdvance } from "../lineFlow.js";
|
|
@@ -668,6 +669,7 @@ function findClearLineY(startY, lineHeight, zones, contentWidth, minWidth) {
|
|
|
668
669
|
function measureParagraph(block, maxWidth, options) {
|
|
669
670
|
const runs = block.runs;
|
|
670
671
|
const attrs = block.attrs;
|
|
672
|
+
const isRtl = isRtlParagraph(block);
|
|
671
673
|
const spacing = attrs?.spacing;
|
|
672
674
|
const isJustifiedParagraph = attrs?.alignment === "justify";
|
|
673
675
|
const justificationProfile = {
|
|
@@ -936,9 +938,13 @@ function measureParagraph(block, maxWidth, options) {
|
|
|
936
938
|
decimalPrefixWidth
|
|
937
939
|
});
|
|
938
940
|
let tabWidth = tabResult.width;
|
|
941
|
+
const authoredEndpoint = contentX + tabWidth + followingWidth;
|
|
942
|
+
const activeContentRightEdge = maxWidth - currentLine.rightOffset;
|
|
943
|
+
const preservesLogicalRtlEndStop = isRtl && tabResult.alignment === "end" && authoredEndpoint <= activeContentRightEdge + WIDTH_TOLERANCE;
|
|
939
944
|
const landsOnLeftIndent = tabResult.alignment === "start" && indentLeft > 0 && Math.abs(contentX + tabWidth - indentLeft) <= WIDTH_TOLERANCE;
|
|
940
945
|
const lineRightEdgeX = indentLeft + (isFirstLine ? firstLineOffset + markerInlineWidth : 0) + currentLine.availableWidth + currentLine.leftOffset;
|
|
941
|
-
if (!landsOnLeftIndent && !hasFollowingTabOnLine(runs, runIndex) && canClampTabToRightEdge(tabResult.alignment, currentLine.width, hasPriorTabOnLine(runs, runIndex), followingWidth, currentLine.availableWidth) && (tabWidth > 0 || followingWidth > 0) && contentX + tabWidth + followingWidth > lineRightEdgeX + WIDTH_TOLERANCE) tabWidth = Math.max(1, lineRightEdgeX - contentX - followingWidth);
|
|
946
|
+
if (!preservesLogicalRtlEndStop && !landsOnLeftIndent && !hasFollowingTabOnLine(runs, runIndex) && canClampTabToRightEdge(tabResult.alignment, currentLine.width, hasPriorTabOnLine(runs, runIndex), followingWidth, currentLine.availableWidth) && (tabWidth > 0 || followingWidth > 0) && contentX + tabWidth + followingWidth > lineRightEdgeX + WIDTH_TOLERANCE) tabWidth = Math.max(1, lineRightEdgeX - contentX - followingWidth);
|
|
947
|
+
if (preservesLogicalRtlEndStop) currentLine.availableWidth = Math.max(currentLine.availableWidth, currentLine.width + tabWidth + followingWidth);
|
|
942
948
|
if (currentLine.width + tabWidth > currentLine.availableWidth + WIDTH_TOLERANCE) {
|
|
943
949
|
startNewLine(runIndex, 0);
|
|
944
950
|
updateMaxFont(style);
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { emuToPixels } from "../../utils/units.js";
|
|
2
|
-
import { isFloatingImageRun } from "../types.js";
|
|
2
|
+
import { isFloatingImageRun, resolveTableCellPadding } from "../types.js";
|
|
3
3
|
import { clampFloatingWrapMargins } from "./clampFloatingWrapMargins.js";
|
|
4
4
|
import { createTableCellFlowState, placeTableCellBlock } from "./tableCellFlow.js";
|
|
5
5
|
//#region src/layout-engine/measure/tableCellFloating.ts
|
|
@@ -34,8 +34,7 @@ const resolveCellScopedPosition = ({ run, paragraphY, contentWidth }) => {
|
|
|
34
34
|
};
|
|
35
35
|
};
|
|
36
36
|
function getTableCellContentWidth(cell, cellMeasure) {
|
|
37
|
-
const padLeft = cell
|
|
38
|
-
const padRight = cell?.padding?.right ?? 7;
|
|
37
|
+
const { left: padLeft, right: padRight } = resolveTableCellPadding(cell);
|
|
39
38
|
return Math.max(0, cellMeasure.width - padLeft - padRight);
|
|
40
39
|
}
|
|
41
40
|
function getTableCellFloatingImages(cell, cellMeasure, contentWidth, resolvePosition) {
|
|
@@ -11,7 +11,7 @@ const INITIAL_RENDERED_BREAK_STATE = { type: "noPageAdvance" };
|
|
|
11
11
|
*/
|
|
12
12
|
const reconcileBreakBeforeBlock = ({ state, block, previousBlock, page, blocksById, hasExplicitPageBreak, renderedBreakNeedsSnap }) => {
|
|
13
13
|
if (hasExplicitPageBreak) return {
|
|
14
|
-
forcePageBreak:
|
|
14
|
+
forcePageBreak: !(state.type === "pageAdvance" && state.reason === "authoredBoundary" && state.boundary === "sectionBreak" && previousBlock?.kind === "sectionBreak" && previousBlock.type !== "continuous"),
|
|
15
15
|
suppressSpaceBefore: false,
|
|
16
16
|
state: INITIAL_RENDERED_BREAK_STATE
|
|
17
17
|
};
|
|
@@ -2,6 +2,7 @@ import { measureParagraph } from "./measure/measureParagraph.js";
|
|
|
2
2
|
import { buildTableCellFloatingZones, getTableCellContentWidth, getTableCellFloatingImages } from "./measure/tableCellFloating.js";
|
|
3
3
|
import { createTableCellFlowState, placeTableCellBlock } from "./measure/tableCellFlow.js";
|
|
4
4
|
import { isEmptyParagraph } from "./paragraphSpacing.js";
|
|
5
|
+
import { resolveTableCellPadding } from "./types.js";
|
|
5
6
|
//#region src/layout-engine/tableRowBreak.ts
|
|
6
7
|
/**
|
|
7
8
|
* Table row-break geometry. Ported from eigenpal/docx-editor#698 (folio subset).
|
|
@@ -18,7 +19,6 @@ import { isEmptyParagraph } from "./paragraphSpacing.js";
|
|
|
18
19
|
* here yet (a separate follow-up); each row uses its own cells' content.
|
|
19
20
|
*/
|
|
20
21
|
const BREAK_OFFSET_EPSILON = .01;
|
|
21
|
-
const DEFAULT_TABLE_CELL_PADDING_TOP = 1;
|
|
22
22
|
function isInsideRange(offset, range) {
|
|
23
23
|
return offset > range.top && offset < range.bottom;
|
|
24
24
|
}
|
|
@@ -49,7 +49,7 @@ function cellBreakGeometry(cell, measure) {
|
|
|
49
49
|
const suppressibleLeadingRanges = [];
|
|
50
50
|
const cellBlocks = cell?.blocks;
|
|
51
51
|
const blockMeasures = measure.blocks;
|
|
52
|
-
const padTop = cell
|
|
52
|
+
const { top: padTop } = resolveTableCellPadding(cell);
|
|
53
53
|
const contentWidth = getTableCellContentWidth(cell, measure);
|
|
54
54
|
const floatingZones = buildTableCellFloatingZones(cell !== void 0 ? getTableCellFloatingImages(cell, measure, contentWidth) : [], contentWidth);
|
|
55
55
|
const flowState = createTableCellFlowState();
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { OutlineStyleAttr } from "../types/documentEnumValues.js";
|
|
2
|
-
import { ImagePosition, ImageWrap, SdtProperties, SdtType, ShapeTextBody, TableCellFormatting, TableWidthType } from "@stll/docx-core/model";
|
|
2
|
+
import { ImagePosition, ImageWrap, NumberFormat, SdtProperties, SdtType, ShapeTextBody, TableCellFormatting, TableWidthType } from "@stll/docx-core/model";
|
|
3
3
|
declare namespace types_d_exports {
|
|
4
|
-
export { BlockId, BorderStyle, CellBorderSpec, CellBorders, ColumnBreakBlock, ColumnBreakMeasure, ColumnLayout, DEFAULT_TEXTBOX_MARGINS, DEFAULT_TEXTBOX_WIDTH, DocumentPosition, FOOTNOTE_ENTRY_MARGIN_BOTTOM, FOOTNOTE_FALLBACK_LINE_HEIGHT, FOOTNOTE_SEPARATOR_HEIGHT, FieldRun, FloatingTablePosition, FlowBlock, FootnoteContent, Fragment, FragmentBase, HeaderFooterContent, HeaderFooterContentHeights, HeaderFooterLayout, HitTestResult, HyperlinkInfo, ImageBlock, ImageFragment, ImageMeasure, ImageRun, ImageRunPosition, Layout, LayoutOptions, LineBreakRun, ListMarkerFormatting, ListNumPr, MathRun, Measure, MeasuredLine, Page, PageBreakBlock, PageBreakMeasure, PageHeaderFooterRefs, PageMargins, ParagraphAttrs, ParagraphBlock, ParagraphBorders, ParagraphFragment, ParagraphIndent, ParagraphMeasure, ParagraphSpacing, RenderedPageBreakRun, Run, RunFormatting, SdtGroup, SectionBreakBlock, SectionBreakMeasure, SectionPageNumbering, TabAlignment, TabRun, TabStop, TableBlock, TableCell, TableCellMeasure, TableFragment, TableMeasure, TableRow, TableRowMeasure, TextBoxBlock, TextBoxFlowAttrs, TextBoxFragment, TextBoxMeasure, TextRun, floatingTextBoxReservesBand, floatingTextBoxWrapsText, getTableRowLeadingWidth, isFloatingImageRun, isFloatingTextBoxBlock, isTextWrappingFloatingImageRun, tableColumnsArePinned };
|
|
4
|
+
export { BlockId, BorderStyle, CellBorderSpec, CellBorders, ColumnBreakBlock, ColumnBreakMeasure, ColumnLayout, DEFAULT_TEXTBOX_MARGINS, DEFAULT_TEXTBOX_WIDTH, DocumentPosition, FOOTNOTE_ENTRY_MARGIN_BOTTOM, FOOTNOTE_FALLBACK_LINE_HEIGHT, FOOTNOTE_SEPARATOR_HEIGHT, FieldRun, FloatingTablePosition, FlowBlock, FootnoteContent, Fragment, FragmentBase, HeaderFooterContent, HeaderFooterContentHeights, HeaderFooterLayout, HitTestResult, HyperlinkInfo, ImageBlock, ImageFragment, ImageMeasure, ImageRun, ImageRunPosition, Layout, LayoutOptions, LineBreakRun, ListMarkerFormatting, ListNumPr, MathRun, Measure, MeasuredLine, Page, PageBreakBlock, PageBreakMeasure, PageHeaderFooterRefs, PageMargins, ParagraphAttrs, ParagraphBlock, ParagraphBorders, ParagraphFragment, ParagraphIndent, ParagraphMeasure, ParagraphSpacing, RenderedPageBreakRun, Run, RunFormatting, SdtGroup, SectionBreakBlock, SectionBreakMeasure, SectionPageNumbering, TabAlignment, TabRun, TabStop, TableBlock, TableCell, TableCellMeasure, TableFragment, TableMeasure, TableRow, TableRowMeasure, TextBoxBlock, TextBoxFlowAttrs, TextBoxFragment, TextBoxMeasure, TextRun, floatingTextBoxReservesBand, floatingTextBoxWrapsText, getTableRowLeadingWidth, isFloatingImageRun, isFloatingTextBoxBlock, isTextWrappingFloatingImageRun, resolveTableCellPadding, tableColumnsArePinned };
|
|
5
5
|
}
|
|
6
6
|
/**
|
|
7
7
|
* Unique identifier for a block in the document.
|
|
@@ -578,6 +578,9 @@ type TableCell = {
|
|
|
578
578
|
*/
|
|
579
579
|
noWrap?: boolean;
|
|
580
580
|
};
|
|
581
|
+
type TableCellPadding = NonNullable<TableCell["padding"]>;
|
|
582
|
+
/** Resolve authored cell margins against the TableNormal defaults. */
|
|
583
|
+
declare const resolveTableCellPadding: (cell: Pick<TableCell, "padding"> | undefined) => TableCellPadding;
|
|
581
584
|
/**
|
|
582
585
|
* A table row containing cells.
|
|
583
586
|
*/
|
|
@@ -705,11 +708,11 @@ type SectionBreakBlock = {
|
|
|
705
708
|
/** Normalized section page-number policy. An omitted OOXML start continues numbering. */
|
|
706
709
|
type SectionPageNumbering = {
|
|
707
710
|
type: "continue";
|
|
708
|
-
format?:
|
|
711
|
+
format?: NumberFormat;
|
|
709
712
|
} | {
|
|
710
713
|
type: "restart";
|
|
711
714
|
start: number;
|
|
712
|
-
format?:
|
|
715
|
+
format?: NumberFormat;
|
|
713
716
|
};
|
|
714
717
|
type PageHeaderFooterRefs = {
|
|
715
718
|
titlePg?: boolean;
|
|
@@ -760,6 +763,8 @@ type TextBoxBlock = {
|
|
|
760
763
|
height?: number;
|
|
761
764
|
/** Text fitting behavior */
|
|
762
765
|
autoFit?: ShapeTextBody["autoFit"];
|
|
766
|
+
/** Horizontal text wrapping inside the box */
|
|
767
|
+
textWrap?: ShapeTextBody["textWrap"];
|
|
763
768
|
/** Fill/background color */
|
|
764
769
|
fillColor?: string;
|
|
765
770
|
/** Border width in pixels */
|
|
@@ -1081,7 +1086,7 @@ type Page = {
|
|
|
1081
1086
|
/** Authored page number shown by PAGE fields. */
|
|
1082
1087
|
logicalNumber: number;
|
|
1083
1088
|
/** OOXML number format for this page's section. */
|
|
1084
|
-
logicalNumberFormat?:
|
|
1089
|
+
logicalNumberFormat?: NumberFormat;
|
|
1085
1090
|
/** Fragments positioned on this page. */
|
|
1086
1091
|
fragments: Fragment[];
|
|
1087
1092
|
/** Page margins. */
|
|
@@ -1235,8 +1240,6 @@ type LayoutOptions = {
|
|
|
1235
1240
|
* page.
|
|
1236
1241
|
*/
|
|
1237
1242
|
footnoteHeightById?: Map<number, number>;
|
|
1238
|
-
/** Section break type for the body-level (final) section (for section transition logic). */
|
|
1239
|
-
bodyBreakType?: "continuous" | "nextPage" | "evenPage" | "oddPage";
|
|
1240
1243
|
/** Header/footer references for each document section, by section index. */
|
|
1241
1244
|
sectionHeaderFooterRefs?: PageHeaderFooterRefs[];
|
|
1242
1245
|
};
|
|
@@ -1308,6 +1311,8 @@ type HeaderFooterContent = {
|
|
|
1308
1311
|
*/
|
|
1309
1312
|
marginPushTop?: number;
|
|
1310
1313
|
marginPushBottom?: number;
|
|
1314
|
+
/** Page-coordinate lower edge of a header wrap band overlapping the body top. */
|
|
1315
|
+
bodyTopClearance?: number;
|
|
1311
1316
|
/**
|
|
1312
1317
|
* Relationship id (`rId`) of the source HF part. Emitted as `data-rid`
|
|
1313
1318
|
* on the painted `.layout-page-header` / `.layout-page-footer` so the
|
|
@@ -1380,4 +1385,4 @@ declare function tableColumnsArePinned(table: TableBlock): boolean;
|
|
|
1380
1385
|
/** Return the leading visual offset for a row with omitted grid columns. */
|
|
1381
1386
|
declare const getTableRowLeadingWidth: (row: TableRow, columnWidths: readonly number[]) => number;
|
|
1382
1387
|
//#endregion
|
|
1383
|
-
export { BlockId, BorderStyle, CellBorderSpec, CellBorders, ColumnBreakBlock, ColumnBreakMeasure, ColumnLayout, DEFAULT_TEXTBOX_MARGINS, DEFAULT_TEXTBOX_WIDTH, DocumentPosition, FOOTNOTE_ENTRY_MARGIN_BOTTOM, FOOTNOTE_FALLBACK_LINE_HEIGHT, FOOTNOTE_SEPARATOR_HEIGHT, FieldRun, FloatingTablePosition, FlowBlock, FootnoteContent, Fragment, FragmentBase, HeaderFooterContent, HeaderFooterContentHeights, HeaderFooterLayout, HitTestResult, HyperlinkInfo, ImageBlock, ImageFragment, ImageMeasure, ImageRun, ImageRunPosition, Layout, LayoutOptions, LineBreakRun, ListMarkerFormatting, ListNumPr, MathRun, Measure, MeasuredLine, Page, PageBreakBlock, PageBreakMeasure, PageHeaderFooterRefs, PageMargins, ParagraphAttrs, ParagraphBlock, ParagraphBorders, ParagraphFragment, ParagraphIndent, ParagraphMeasure, ParagraphSpacing, RenderedPageBreakRun, Run, RunFormatting, SdtGroup, SectionBreakBlock, SectionBreakMeasure, SectionPageNumbering, TabAlignment, TabRun, TabStop, TableBlock, TableCell, TableCellMeasure, TableFragment, TableMeasure, TableRow, TableRowMeasure, TextBoxBlock, TextBoxFlowAttrs, TextBoxFragment, TextBoxMeasure, TextRun, floatingTextBoxReservesBand, floatingTextBoxWrapsText, getTableRowLeadingWidth, isFloatingImageRun, isFloatingTextBoxBlock, isTextWrappingFloatingImageRun, tableColumnsArePinned, types_d_exports };
|
|
1388
|
+
export { BlockId, BorderStyle, CellBorderSpec, CellBorders, ColumnBreakBlock, ColumnBreakMeasure, ColumnLayout, DEFAULT_TEXTBOX_MARGINS, DEFAULT_TEXTBOX_WIDTH, DocumentPosition, FOOTNOTE_ENTRY_MARGIN_BOTTOM, FOOTNOTE_FALLBACK_LINE_HEIGHT, FOOTNOTE_SEPARATOR_HEIGHT, FieldRun, FloatingTablePosition, FlowBlock, FootnoteContent, Fragment, FragmentBase, HeaderFooterContent, HeaderFooterContentHeights, HeaderFooterLayout, HitTestResult, HyperlinkInfo, ImageBlock, ImageFragment, ImageMeasure, ImageRun, ImageRunPosition, Layout, LayoutOptions, LineBreakRun, ListMarkerFormatting, ListNumPr, MathRun, Measure, MeasuredLine, Page, PageBreakBlock, PageBreakMeasure, PageHeaderFooterRefs, PageMargins, ParagraphAttrs, ParagraphBlock, ParagraphBorders, ParagraphFragment, ParagraphIndent, ParagraphMeasure, ParagraphSpacing, RenderedPageBreakRun, Run, RunFormatting, SdtGroup, SectionBreakBlock, SectionBreakMeasure, SectionPageNumbering, TabAlignment, TabRun, TabStop, TableBlock, TableCell, TableCellMeasure, TableFragment, TableMeasure, TableRow, TableRowMeasure, TextBoxBlock, TextBoxFlowAttrs, TextBoxFragment, TextBoxMeasure, TextRun, floatingTextBoxReservesBand, floatingTextBoxWrapsText, getTableRowLeadingWidth, isFloatingImageRun, isFloatingTextBoxBlock, isTextWrappingFloatingImageRun, resolveTableCellPadding, tableColumnsArePinned, types_d_exports };
|
|
@@ -13,8 +13,22 @@ var types_exports = /* @__PURE__ */ __exportAll({
|
|
|
13
13
|
isFloatingImageRun: () => isFloatingImageRun,
|
|
14
14
|
isFloatingTextBoxBlock: () => isFloatingTextBoxBlock,
|
|
15
15
|
isTextWrappingFloatingImageRun: () => isTextWrappingFloatingImageRun,
|
|
16
|
+
resolveTableCellPadding: () => resolveTableCellPadding,
|
|
16
17
|
tableColumnsArePinned: () => tableColumnsArePinned
|
|
17
18
|
});
|
|
19
|
+
const DEFAULT_TABLE_CELL_PADDING = {
|
|
20
|
+
top: 0,
|
|
21
|
+
right: 7,
|
|
22
|
+
bottom: 0,
|
|
23
|
+
left: 7
|
|
24
|
+
};
|
|
25
|
+
/** Resolve authored cell margins against the TableNormal defaults. */
|
|
26
|
+
const resolveTableCellPadding = (cell) => ({
|
|
27
|
+
top: cell?.padding?.top ?? DEFAULT_TABLE_CELL_PADDING.top,
|
|
28
|
+
right: cell?.padding?.right ?? DEFAULT_TABLE_CELL_PADDING.right,
|
|
29
|
+
bottom: cell?.padding?.bottom ?? DEFAULT_TABLE_CELL_PADDING.bottom,
|
|
30
|
+
left: cell?.padding?.left ?? DEFAULT_TABLE_CELL_PADDING.left
|
|
31
|
+
});
|
|
18
32
|
/** Default internal margins for text boxes (OOXML defaults in pixels) */
|
|
19
33
|
const DEFAULT_TEXTBOX_MARGINS = {
|
|
20
34
|
top: 4,
|
|
@@ -128,4 +142,4 @@ const getTableRowLeadingWidth = (row, columnWidths) => {
|
|
|
128
142
|
return columnWidths.slice(0, row.gridBefore ?? 0).reduce((sum, columnWidth) => sum + columnWidth, 0);
|
|
129
143
|
};
|
|
130
144
|
//#endregion
|
|
131
|
-
export { DEFAULT_TEXTBOX_MARGINS, DEFAULT_TEXTBOX_WIDTH, FOOTNOTE_ENTRY_MARGIN_BOTTOM, FOOTNOTE_FALLBACK_LINE_HEIGHT, FOOTNOTE_SEPARATOR_HEIGHT, floatingTextBoxReservesBand, floatingTextBoxWrapsText, getTableRowLeadingWidth, isFloatingImageRun, isFloatingTextBoxBlock, isTextWrappingFloatingImageRun, tableColumnsArePinned, types_exports };
|
|
145
|
+
export { DEFAULT_TEXTBOX_MARGINS, DEFAULT_TEXTBOX_WIDTH, FOOTNOTE_ENTRY_MARGIN_BOTTOM, FOOTNOTE_FALLBACK_LINE_HEIGHT, FOOTNOTE_SEPARATOR_HEIGHT, floatingTextBoxReservesBand, floatingTextBoxWrapsText, getTableRowLeadingWidth, isFloatingImageRun, isFloatingTextBoxBlock, isTextWrappingFloatingImageRun, resolveTableCellPadding, tableColumnsArePinned, types_exports };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { MeasuredLine, ParagraphBlock, ParagraphBorders, ParagraphFragment, ParagraphMeasure, Run, TabStop } from "../layout-engine/types.js";
|
|
1
|
+
import { HyperlinkInfo, MeasuredLine, ParagraphBlock, ParagraphBorders, ParagraphFragment, ParagraphMeasure, Run, TabStop } from "../layout-engine/types.js";
|
|
2
2
|
import { RenderContext } from "./renderUtils.js";
|
|
3
3
|
//#region src/layout-painter/renderParagraph.d.ts
|
|
4
4
|
/**
|
|
@@ -64,8 +64,14 @@ type RenderLineOptions = {
|
|
|
64
64
|
context?: RenderContext;
|
|
65
65
|
/** Left indent in pixels */
|
|
66
66
|
leftIndentPx?: number;
|
|
67
|
+
/** Authored OOXML left indent used by logical tab-stop calculations. */
|
|
68
|
+
tabLeftIndentPx?: number;
|
|
67
69
|
/** First line indent in pixels (positive) or hanging indent (negative) */
|
|
68
70
|
firstLineIndentPx?: number;
|
|
71
|
+
/** Paragraph base direction for logical first-line indentation. */
|
|
72
|
+
isRtl?: boolean;
|
|
73
|
+
/** Full paragraph content-box width before physical indents. */
|
|
74
|
+
contentWidthPx?: number;
|
|
69
75
|
/** Line-specific floating image margins (calculated per-line based on Y overlap) */
|
|
70
76
|
floatingMargins?: {
|
|
71
77
|
leftMargin: number;
|
|
@@ -73,6 +79,8 @@ type RenderLineOptions = {
|
|
|
73
79
|
};
|
|
74
80
|
/** Track inline image runs already rendered in this paragraph fragment to prevent duplicates */
|
|
75
81
|
renderedInlineImageKeys?: Set<string>;
|
|
82
|
+
/** Hyperlink fragments whose complete displayed text is a URL. */
|
|
83
|
+
leftToRightDisplayedUrlHyperlinks?: ReadonlySet<HyperlinkInfo>;
|
|
76
84
|
/**
|
|
77
85
|
* Rightmost x where inline content may render, in content-area coords. Used
|
|
78
86
|
* by the right-tab anchor (TOC pattern); passed in directly rather than
|
|
@@ -8,9 +8,9 @@ import { FONT_KERNING_MODE, countCompressibleSpaces, getFontKerningMode, getRunF
|
|
|
8
8
|
import { isFloatingImageRun } from "../layout-engine/types.js";
|
|
9
9
|
import { calculateTabWidth } from "../prosemirror/utils/tabCalculator.js";
|
|
10
10
|
import { AUTHOR_COLORS, getAuthorColorIdx } from "../utils/authorColors.js";
|
|
11
|
-
import { detectBaseDirection } from "../utils/baseDirection.js";
|
|
12
11
|
import { resolveFontFamily } from "../utils/fontResolver.js";
|
|
13
12
|
import "../utils/fontWeights.js";
|
|
13
|
+
import { isRtlParagraph } from "../utils/paragraphBaseDirection.js";
|
|
14
14
|
import { inlineImageBoundingBox, parseRotationDegrees, rotatedBoundingBox } from "../utils/rotationBoundingBox.js";
|
|
15
15
|
import { applySanitizedImageSrc } from "../utils/sanitizeImageSrc.js";
|
|
16
16
|
import { SCRIPT_CLASS, hasCjk, hasComplexScript, segmentByScript } from "../utils/scriptSegments.js";
|
|
@@ -39,12 +39,42 @@ const PARAGRAPH_CLASS_NAMES = {
|
|
|
39
39
|
image: "layout-run-image",
|
|
40
40
|
lineBreak: "layout-run-linebreak"
|
|
41
41
|
};
|
|
42
|
+
const LEFT_TO_RIGHT_DIRECTION = "ltr";
|
|
43
|
+
const RIGHT_TO_LEFT_DIRECTION = "rtl";
|
|
44
|
+
const DISPLAYED_URL_PATTERN = /^(?:https?:\/\/|www\.)\S+$/iu;
|
|
42
45
|
/**
|
|
43
46
|
* Check if run is a text run
|
|
44
47
|
*/
|
|
45
48
|
function isTextRun(run) {
|
|
46
49
|
return run.kind === "text";
|
|
47
50
|
}
|
|
51
|
+
function hyperlinksMatch(left, right) {
|
|
52
|
+
return left.href === right.href && left.tooltip === right.tooltip && left.noDefaultStyle === right.noDefaultStyle;
|
|
53
|
+
}
|
|
54
|
+
function collectLeftToRightDisplayedUrlHyperlinks(runs) {
|
|
55
|
+
const result = /* @__PURE__ */ new Set();
|
|
56
|
+
let index = 0;
|
|
57
|
+
while (index < runs.length) {
|
|
58
|
+
const first = runs[index];
|
|
59
|
+
if (!first || !isTextRun(first) || !first.hyperlink) {
|
|
60
|
+
index += 1;
|
|
61
|
+
continue;
|
|
62
|
+
}
|
|
63
|
+
const hyperlinks = [];
|
|
64
|
+
let displayedText = "";
|
|
65
|
+
let groupEnd = index;
|
|
66
|
+
while (groupEnd < runs.length) {
|
|
67
|
+
const run = runs[groupEnd];
|
|
68
|
+
if (!run || !isTextRun(run) || !run.hyperlink || !hyperlinksMatch(first.hyperlink, run.hyperlink)) break;
|
|
69
|
+
displayedText += toPaintedText(run.text);
|
|
70
|
+
hyperlinks.push(run.hyperlink);
|
|
71
|
+
groupEnd += 1;
|
|
72
|
+
}
|
|
73
|
+
if (DISPLAYED_URL_PATTERN.test(displayedText.trim())) for (const hyperlink of hyperlinks) result.add(hyperlink);
|
|
74
|
+
index = groupEnd;
|
|
75
|
+
}
|
|
76
|
+
return result;
|
|
77
|
+
}
|
|
48
78
|
/**
|
|
49
79
|
* Check if run is a tab run
|
|
50
80
|
*/
|
|
@@ -144,8 +174,8 @@ function applyRunStyles(element, run) {
|
|
|
144
174
|
element.style.webkitTextStroke = "1px currentColor";
|
|
145
175
|
element.style.webkitTextFillColor = "transparent";
|
|
146
176
|
}
|
|
147
|
-
if (run.rtl === true) element.dir =
|
|
148
|
-
else if (run.rtl === false) element.dir =
|
|
177
|
+
if (run.rtl === true) element.dir = RIGHT_TO_LEFT_DIRECTION;
|
|
178
|
+
else if (run.rtl === false) element.dir = LEFT_TO_RIGHT_DIRECTION;
|
|
149
179
|
if (run.textEffect) {
|
|
150
180
|
element.classList.add("docx-text-effect", `docx-text-effect-${run.textEffect}`);
|
|
151
181
|
element.dataset["effect"] = run.textEffect;
|
|
@@ -264,7 +294,7 @@ function applyPmPositions(element, pmStart, pmEnd) {
|
|
|
264
294
|
/**
|
|
265
295
|
* Render a text run
|
|
266
296
|
*/
|
|
267
|
-
function renderTextRun(run, doc) {
|
|
297
|
+
function renderTextRun(run, doc, hyperlinkDirection) {
|
|
268
298
|
const span = doc.createElement("span");
|
|
269
299
|
span.className = `${PARAGRAPH_CLASS_NAMES.run} ${PARAGRAPH_CLASS_NAMES.text}`;
|
|
270
300
|
if (run.footnoteRefId !== void 0) {
|
|
@@ -284,6 +314,7 @@ function renderTextRun(run, doc) {
|
|
|
284
314
|
if (run.hyperlink) {
|
|
285
315
|
const anchor = doc.createElement("a");
|
|
286
316
|
anchor.href = run.hyperlink.href;
|
|
317
|
+
if (hyperlinkDirection || DISPLAYED_URL_PATTERN.test(paintedText.trim())) anchor.dir = LEFT_TO_RIGHT_DIRECTION;
|
|
287
318
|
if (!run.hyperlink.href.startsWith("#")) {
|
|
288
319
|
anchor.target = "_blank";
|
|
289
320
|
anchor.rel = "noopener noreferrer";
|
|
@@ -1053,7 +1084,7 @@ function renderLine(block, line, alignment, doc, options) {
|
|
|
1053
1084
|
return withCursiveJoiners(runEl, run, cursiveJoinerPlan, applyJoinerRunStyles, doc);
|
|
1054
1085
|
};
|
|
1055
1086
|
const renderLineTextRun = (run) => {
|
|
1056
|
-
const runEl = renderTextRun(run, doc);
|
|
1087
|
+
const runEl = renderTextRun(run, doc, run.hyperlink && options?.leftToRightDisplayedUrlHyperlinks?.has(run.hyperlink) ? LEFT_TO_RIGHT_DIRECTION : void 0);
|
|
1057
1088
|
if (collapsedLeadingSpaceRuns.has(run)) runEl.dataset["collapsedLeadingSpaces"] = "true";
|
|
1058
1089
|
if (collapsedTrailingSpaceRuns.has(run)) runEl.dataset["collapsedTrailingSpaces"] = "true";
|
|
1059
1090
|
if (isCollapsedLineEdgeSpaceRun(run)) {
|
|
@@ -1125,7 +1156,8 @@ function renderLine(block, line, alignment, doc, options) {
|
|
|
1125
1156
|
const measureText = collapsedSpaceMeasureText ?? (hasTabRuns || hasScaledTextRun ? createTextMeasurer(doc) : void 0);
|
|
1126
1157
|
if (hasTabRuns) {
|
|
1127
1158
|
const explicitStops = options?.tabStops?.map(convertTabStopToCalc);
|
|
1128
|
-
const
|
|
1159
|
+
const tabLeftIndentPx = options?.tabLeftIndentPx ?? options?.leftIndentPx ?? 0;
|
|
1160
|
+
const leftIndentTwips = Math.round(tabLeftIndentPx * 15);
|
|
1129
1161
|
tabContext = {
|
|
1130
1162
|
...explicitStops !== void 0 ? { explicitStops } : {},
|
|
1131
1163
|
...block.attrs?.defaultTabStopTwips !== void 0 ? { defaultTabInterval: block.attrs.defaultTabStopTwips } : {},
|
|
@@ -1134,11 +1166,13 @@ function renderLine(block, line, alignment, doc, options) {
|
|
|
1134
1166
|
}
|
|
1135
1167
|
let currentX;
|
|
1136
1168
|
const leftIndentPx = options?.leftIndentPx ?? 0;
|
|
1169
|
+
const tabLeftIndentPx = options?.tabLeftIndentPx ?? leftIndentPx;
|
|
1170
|
+
const floatingLeftOffsetPx = options?.floatingMargins?.leftMargin ?? 0;
|
|
1137
1171
|
if (options?.isFirstLine) {
|
|
1138
1172
|
const firstLineIndentPx = options.firstLineIndentPx ?? 0;
|
|
1139
1173
|
const markerInlineWidth = getListMarkerInlineWidth(block);
|
|
1140
|
-
currentX =
|
|
1141
|
-
} else currentX =
|
|
1174
|
+
currentX = tabLeftIndentPx + firstLineIndentPx + markerInlineWidth + floatingLeftOffsetPx;
|
|
1175
|
+
} else currentX = tabLeftIndentPx + floatingLeftOffsetPx;
|
|
1142
1176
|
for (let i = 0; i < runsForLine.length; i++) {
|
|
1143
1177
|
const run = runsForLine[i];
|
|
1144
1178
|
if (isTabRun(run) && tabContext && measureText) {
|
|
@@ -1160,7 +1194,7 @@ function renderLine(block, line, alignment, doc, options) {
|
|
|
1160
1194
|
break;
|
|
1161
1195
|
}
|
|
1162
1196
|
}
|
|
1163
|
-
if (lineRightEdgeX !== void 0 && tabResult.alignment === "end" && !hasFollowingTab && currentX + tabResult.width + followingWidthForCheck >= lineRightEdgeX - RIGHT_EDGE_EPSILON_PX) {
|
|
1197
|
+
if (lineRightEdgeX !== void 0 && options?.isRtl !== true && tabResult.alignment === "end" && !hasFollowingTab && currentX + tabResult.width + followingWidthForCheck >= lineRightEdgeX - RIGHT_EDGE_EPSILON_PX) {
|
|
1164
1198
|
lineEl.style.display = "flex";
|
|
1165
1199
|
lineEl.style.alignItems = "baseline";
|
|
1166
1200
|
lineEl.style.whiteSpace = "pre";
|
|
@@ -1173,7 +1207,8 @@ function renderLine(block, line, alignment, doc, options) {
|
|
|
1173
1207
|
tabEl.style.minWidth = "0";
|
|
1174
1208
|
tabEl.style.width = "auto";
|
|
1175
1209
|
lineEl.append(tabEl);
|
|
1176
|
-
|
|
1210
|
+
const firstFlexChild = lineEl.firstElementChild ?? void 0;
|
|
1211
|
+
if (options?.isFirstLine && options.firstLineIndentPx !== void 0 && options.firstLineIndentPx !== 0 && isStyleableHTMLElement(firstFlexChild)) firstFlexChild.style.marginLeft = `${options.firstLineIndentPx}px`;
|
|
1177
1212
|
for (let j = i + 1; j < runsForLine.length; j++) {
|
|
1178
1213
|
const next = runsForLine[j];
|
|
1179
1214
|
if (isTabRun(next) || isLineBreakRun(next)) break;
|
|
@@ -1191,7 +1226,10 @@ function renderLine(block, line, alignment, doc, options) {
|
|
|
1191
1226
|
break;
|
|
1192
1227
|
}
|
|
1193
1228
|
let tabWidth = tabResult.width;
|
|
1194
|
-
|
|
1229
|
+
const activeContentRightEdge = options?.contentWidthPx === void 0 ? void 0 : options.contentWidthPx - (options.floatingMargins?.rightMargin ?? 0);
|
|
1230
|
+
const preservesLogicalRtlEndStop = options?.isRtl === true && tabResult.alignment === "end" && activeContentRightEdge !== void 0 && currentX + tabWidth + followingWidthForCheck <= activeContentRightEdge + RIGHT_EDGE_EPSILON_PX;
|
|
1231
|
+
const landsOnLeftIndent = tabResult.alignment === "start" && tabLeftIndentPx > 0 && Math.abs(currentX + tabWidth - tabLeftIndentPx) <= RIGHT_EDGE_EPSILON_PX;
|
|
1232
|
+
if (!preservesLogicalRtlEndStop && !landsOnLeftIndent && lineRightEdgeX !== void 0 && canClampTabToRightEdge(tabResult.alignment, i > 0, runsForLine.slice(0, i).some(isTabRun), options?.isLastLine === true) && currentX + tabWidth + followingWidthForCheck > lineRightEdgeX) tabWidth = Math.max(1, lineRightEdgeX - currentX - followingWidthForCheck);
|
|
1195
1233
|
const tabEl = renderTabRun(run, doc, tabWidth, tabResult.leader);
|
|
1196
1234
|
lineEl.append(tabEl);
|
|
1197
1235
|
currentX += tabWidth;
|
|
@@ -1281,22 +1319,6 @@ function bordersFormGroup(a, b) {
|
|
|
1281
1319
|
return bordersEqual(a.top, b.top) && bordersEqual(a.bottom, b.bottom) && bordersEqual(a.left, b.left) && bordersEqual(a.right, b.right) && bordersEqual(a.between, b.between);
|
|
1282
1320
|
}
|
|
1283
1321
|
/**
|
|
1284
|
-
* Decide whether a paragraph without an explicit `w:bidi` flag should still be
|
|
1285
|
-
* laid out right-to-left. Only paragraphs that carry at least one `w:rtl` run
|
|
1286
|
-
* are candidates; among those the base direction follows the first strong
|
|
1287
|
-
* directional character (the `dir="auto"` rule), so Hebrew/Arabic-led lines
|
|
1288
|
-
* order RTL while an English- (or CJK-/Devanagari-/…) led line stays LTR.
|
|
1289
|
-
* eigenpal #723 (#719).
|
|
1290
|
-
*/
|
|
1291
|
-
function paragraphBaseIsRtl(block) {
|
|
1292
|
-
const runs = block.runs.filter((r) => isTextRun(r) || isFieldRun(r));
|
|
1293
|
-
if (!runs.some((r) => r.rtl)) return false;
|
|
1294
|
-
return detectBaseDirection(runs.map((r) => {
|
|
1295
|
-
if (isTextRun(r)) return r.text;
|
|
1296
|
-
return isFieldRun(r) ? r.fallback ?? "" : "";
|
|
1297
|
-
}).join("")) !== "ltr";
|
|
1298
|
-
}
|
|
1299
|
-
/**
|
|
1300
1322
|
* Render a paragraph fragment
|
|
1301
1323
|
*
|
|
1302
1324
|
* @param fragment - The fragment to render
|
|
@@ -1322,7 +1344,7 @@ function renderParagraphFragment(fragment, block, measure, context, options = {}
|
|
|
1322
1344
|
const lines = measure.lines.slice(fragment.fromLine, fragment.toLine);
|
|
1323
1345
|
const alignment = block.attrs?.alignment;
|
|
1324
1346
|
if (block.attrs?.styleId) fragmentEl.dataset["styleId"] = block.attrs.styleId;
|
|
1325
|
-
const isRtl =
|
|
1347
|
+
const isRtl = isRtlParagraph(block);
|
|
1326
1348
|
if (isRtl) fragmentEl.dir = "rtl";
|
|
1327
1349
|
if (alignment) if (alignment === "center") fragmentEl.style.textAlign = "center";
|
|
1328
1350
|
else if (alignment === "right") fragmentEl.style.textAlign = "right";
|
|
@@ -1407,6 +1429,7 @@ function renderParagraphFragment(fragment, block, measure, context, options = {}
|
|
|
1407
1429
|
else if (indent?.firstLine && indent.firstLine > 0) firstLineIndentPx = indent.firstLine;
|
|
1408
1430
|
let _cumulativeLineY = 0;
|
|
1409
1431
|
const renderedInlineImageKeys = options.renderedInlineImageKeys ?? /* @__PURE__ */ new Set();
|
|
1432
|
+
const leftToRightDisplayedUrlHyperlinks = isRtl ? collectLeftToRightDisplayedUrlHyperlinks(block.runs) : void 0;
|
|
1410
1433
|
for (let i = 0; i < lines.length; i++) {
|
|
1411
1434
|
const line = lines[i];
|
|
1412
1435
|
const lineIndex = fragment.fromLine + i;
|
|
@@ -1422,13 +1445,17 @@ function renderParagraphFragment(fragment, block, measure, context, options = {}
|
|
|
1422
1445
|
paragraphEndsWithLineBreak,
|
|
1423
1446
|
...block.attrs?.tabs !== void 0 ? { tabStops: block.attrs.tabs } : {},
|
|
1424
1447
|
leftIndentPx: indentLeft,
|
|
1448
|
+
tabLeftIndentPx: indent?.left ?? 0,
|
|
1425
1449
|
firstLineIndentPx: isFirstLine ? firstLineIndentPx : 0,
|
|
1450
|
+
isRtl,
|
|
1451
|
+
contentWidthPx: fragment.width,
|
|
1426
1452
|
context,
|
|
1427
1453
|
floatingMargins: {
|
|
1428
1454
|
leftMargin: lineLeftOffset,
|
|
1429
1455
|
rightMargin: lineRightOffset
|
|
1430
1456
|
},
|
|
1431
1457
|
renderedInlineImageKeys,
|
|
1458
|
+
...leftToRightDisplayedUrlHyperlinks === void 0 ? {} : { leftToRightDisplayedUrlHyperlinks },
|
|
1432
1459
|
lineRightEdgePx: fragment.width - indentRight - lineRightOffset
|
|
1433
1460
|
});
|
|
1434
1461
|
const isFlexLine = lineEl.dataset["flexLine"] === "true";
|
|
@@ -1447,13 +1474,8 @@ function renderParagraphFragment(fragment, block, measure, context, options = {}
|
|
|
1447
1474
|
const hasHanging = indent?.hanging && indent.hanging > 0;
|
|
1448
1475
|
const hasFirstLine = indent?.firstLine && indent.firstLine > 0;
|
|
1449
1476
|
if (isFirstLine) {
|
|
1450
|
-
if (indentLeft
|
|
1451
|
-
|
|
1452
|
-
if (!isFlexLine) lineEl.style.textIndent = `-${indent.hanging ?? 0}px`;
|
|
1453
|
-
} else if (indentLeft !== 0 && hasFirstLine) {
|
|
1454
|
-
lineEl.style.paddingLeft = `${Math.max(indentLeft, 0)}px`;
|
|
1455
|
-
if (!isFlexLine) lineEl.style.textIndent = `${indent.firstLine ?? 0}px`;
|
|
1456
|
-
} else if (indentLeft > 0) lineEl.style.paddingLeft = `${indentLeft}px`;
|
|
1477
|
+
if (indentLeft > 0) lineEl.style.paddingLeft = `${indentLeft}px`;
|
|
1478
|
+
if (hasHanging && !isFlexLine) lineEl.style.textIndent = `-${indent.hanging ?? 0}px`;
|
|
1457
1479
|
else if (hasFirstLine && !isFlexLine) lineEl.style.textIndent = `${indent.firstLine ?? 0}px`;
|
|
1458
1480
|
} else if (indentLeft > 0) lineEl.style.paddingLeft = `${indentLeft}px`;
|
|
1459
1481
|
if (indentRight > 0) lineEl.style.paddingRight = `${indentRight}px`;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { measureParagraph } from "../layout-engine/measure/measureParagraph.js";
|
|
2
2
|
import { buildTableCellFloatingZones, getTableCellContentWidth, getTableCellFloatingImages } from "../layout-engine/measure/tableCellFloating.js";
|
|
3
3
|
import { createTableCellFlowState, finishTableCellFlow, placeTableCellBlock } from "../layout-engine/measure/tableCellFlow.js";
|
|
4
|
-
import { getTableRowLeadingWidth, isFloatingImageRun, isFloatingTextBoxBlock, tableColumnsArePinned } from "../layout-engine/types.js";
|
|
4
|
+
import { getTableRowLeadingWidth, isFloatingImageRun, isFloatingTextBoxBlock, resolveTableCellPadding, tableColumnsArePinned } from "../layout-engine/types.js";
|
|
5
5
|
import { applySanitizedImageSrc } from "../utils/sanitizeImageSrc.js";
|
|
6
6
|
import { emuToPixels } from "../utils/units.js";
|
|
7
7
|
import { resolveAnchoredImagePosition } from "./anchoredImagePosition.js";
|
|
@@ -330,10 +330,7 @@ function renderTableCell({ cell, cellMeasure, x, rowHeight, borderFlags, columns
|
|
|
330
330
|
cellEl.style.height = `${rowHeight}px`;
|
|
331
331
|
cellEl.style.overflow = "hidden";
|
|
332
332
|
cellEl.style.boxSizing = "border-box";
|
|
333
|
-
const padTop = cell
|
|
334
|
-
const padRight = cell.padding?.right ?? 7;
|
|
335
|
-
const padBottom = cell.padding?.bottom ?? 1;
|
|
336
|
-
const padLeft = cell.padding?.left ?? 7;
|
|
333
|
+
const { top: padTop, right: padRight, bottom: padBottom, left: padLeft } = resolveTableCellPadding(cell);
|
|
337
334
|
cellEl.style.padding = `${padTop}px ${padRight}px ${padBottom}px ${padLeft}px`;
|
|
338
335
|
if (cell.borders) {
|
|
339
336
|
if (borderFlags.drawTop) applyBorder(cellEl, "top", cell.borders.top);
|
|
@@ -26,7 +26,7 @@ function renderTextBoxFragment(fragment, block, measure, context, options = {})
|
|
|
26
26
|
containerEl.style.position = "absolute";
|
|
27
27
|
containerEl.style.width = `${fragment.width}px`;
|
|
28
28
|
containerEl.style.height = `${fragment.height}px`;
|
|
29
|
-
containerEl.style.overflow = "hidden";
|
|
29
|
+
containerEl.style.overflow = block.textWrap === "none" ? "visible" : "hidden";
|
|
30
30
|
containerEl.style.boxSizing = "border-box";
|
|
31
31
|
if (block.fillColor) containerEl.style.backgroundColor = block.fillColor;
|
|
32
32
|
if (block.outlineWidth && block.outlineWidth > 0) {
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { document_d_exports } from "../types/document.js";
|
|
1
2
|
import { ImageRun, isFloatingImageRun, isTextWrappingFloatingImageRun } from "../layout-engine/types.js";
|
|
2
3
|
//#region src/layout-painter/renderUtils.d.ts
|
|
3
4
|
/**
|
|
@@ -17,7 +18,7 @@ type RenderContext = {
|
|
|
17
18
|
/** Current page number (1-indexed) */
|
|
18
19
|
pageNumber: number;
|
|
19
20
|
/** OOXML section-level format for the current logical page number. */
|
|
20
|
-
pageNumberFormat?:
|
|
21
|
+
pageNumberFormat?: document_d_exports.NumberFormat;
|
|
21
22
|
/** Total number of pages */
|
|
22
23
|
totalPages: number;
|
|
23
24
|
/** Which section is being rendered */
|
|
@@ -25,6 +25,7 @@ const TEXT_BOX_AUTO_FIT_VALUES = [
|
|
|
25
25
|
"normal",
|
|
26
26
|
"shape"
|
|
27
27
|
];
|
|
28
|
+
const TEXT_BOX_TEXT_WRAP_VALUES = ["square", "none"];
|
|
28
29
|
const FIELD_KINDS = ["simple", "complex"];
|
|
29
30
|
const MATH_DISPLAYS = ["inline", "block"];
|
|
30
31
|
const SHAPE_FILL_TYPES = [
|
|
@@ -463,6 +464,7 @@ const readTextBoxAttrs = (node) => {
|
|
|
463
464
|
optionalNumber(attrs, "width", "textBox.attrs.width", issues);
|
|
464
465
|
optionalNumber(attrs, "height", "textBox.attrs.height", issues);
|
|
465
466
|
optionalOneOf(attrs, "autoFit", "textBox.attrs.autoFit", issues, TEXT_BOX_AUTO_FIT_VALUES);
|
|
467
|
+
optionalOneOf(attrs, "textWrap", "textBox.attrs.textWrap", issues, TEXT_BOX_TEXT_WRAP_VALUES);
|
|
466
468
|
optionalString(attrs, "textBoxId", "textBox.attrs.textBoxId", issues);
|
|
467
469
|
optionalString(attrs, "fillColor", "textBox.attrs.fillColor", issues);
|
|
468
470
|
optionalNumber(attrs, "outlineWidth", "textBox.attrs.outlineWidth", issues);
|
|
@@ -1878,6 +1878,7 @@ function convertPMTextBox(node) {
|
|
|
1878
1878
|
content: []
|
|
1879
1879
|
}],
|
|
1880
1880
|
...attrs.autoFit !== void 0 ? { autoFit: attrs.autoFit } : {},
|
|
1881
|
+
...attrs.textWrap !== void 0 ? { textWrap: attrs.textWrap } : {},
|
|
1881
1882
|
margins: (() => {
|
|
1882
1883
|
const m = {};
|
|
1883
1884
|
if (typeof attrs.marginTop === "number") m.top = pixelsToEmu(attrs.marginTop);
|
|
@@ -1523,6 +1523,7 @@ function textBoxFromShape(shape, textBody) {
|
|
|
1523
1523
|
if (shape.outline) textBox.outline = shape.outline;
|
|
1524
1524
|
if (textBody.margins) textBox.margins = textBody.margins;
|
|
1525
1525
|
if (textBody.autoFit) textBox.autoFit = textBody.autoFit;
|
|
1526
|
+
if (textBody.textWrap) textBox.textWrap = textBody.textWrap;
|
|
1526
1527
|
return textBox;
|
|
1527
1528
|
}
|
|
1528
1529
|
/**
|
|
@@ -1597,6 +1598,7 @@ function convertTextBox(textBox, styleResolver, options) {
|
|
|
1597
1598
|
width: widthPx,
|
|
1598
1599
|
height: heightPx,
|
|
1599
1600
|
autoFit: textBox.autoFit,
|
|
1601
|
+
textWrap: textBox.textWrap,
|
|
1600
1602
|
textBoxId: textBox.id,
|
|
1601
1603
|
fillColor,
|
|
1602
1604
|
outlineWidth,
|
|
@@ -10,6 +10,8 @@ type TextBoxAttrs = {
|
|
|
10
10
|
height?: number;
|
|
11
11
|
/** Text fitting behavior */
|
|
12
12
|
autoFit?: document_d_exports.ShapeTextBody["autoFit"];
|
|
13
|
+
/** Horizontal text wrapping inside the box */
|
|
14
|
+
textWrap?: document_d_exports.ShapeTextBody["textWrap"];
|
|
13
15
|
/** Unique identifier */
|
|
14
16
|
textBoxId?: string;
|
|
15
17
|
/** Fill color as CSS color */
|
|
@@ -18,6 +18,9 @@ function parseTextBoxWrapType(raw) {
|
|
|
18
18
|
function parseTextBoxAutoFit(raw) {
|
|
19
19
|
if (raw === "none" || raw === "normal" || raw === "shape") return raw;
|
|
20
20
|
}
|
|
21
|
+
function parseTextBoxTextWrap(raw) {
|
|
22
|
+
if (raw === "none" || raw === "square") return raw;
|
|
23
|
+
}
|
|
21
24
|
const TextBoxExtension = createNodeExtension({
|
|
22
25
|
name: "textBox",
|
|
23
26
|
schemaNodeName: "textBox",
|
|
@@ -30,6 +33,7 @@ const TextBoxExtension = createNodeExtension({
|
|
|
30
33
|
width: { default: 200 },
|
|
31
34
|
height: { default: null },
|
|
32
35
|
autoFit: { default: null },
|
|
36
|
+
textWrap: { default: null },
|
|
33
37
|
textBoxId: { default: null },
|
|
34
38
|
fillColor: { default: null },
|
|
35
39
|
outlineWidth: { default: null },
|
|
@@ -63,10 +67,12 @@ const TextBoxExtension = createNodeExtension({
|
|
|
63
67
|
const position = parseTextBoxPosition(d["position"]);
|
|
64
68
|
const wrapType = parseTextBoxWrapType(d["wrapType"]);
|
|
65
69
|
const autoFit = parseTextBoxAutoFit(d["autoFit"]);
|
|
70
|
+
const textWrap = parseTextBoxTextWrap(d["textWrap"]);
|
|
66
71
|
return {
|
|
67
72
|
...d["width"] ? { width: Number(d["width"]) } : {},
|
|
68
73
|
...d["height"] ? { height: Number(d["height"]) } : {},
|
|
69
74
|
...autoFit ? { autoFit } : {},
|
|
75
|
+
...textWrap ? { textWrap } : {},
|
|
70
76
|
...d["textboxId"] ? { textBoxId: d["textboxId"] } : {},
|
|
71
77
|
...d["fillColor"] ? { fillColor: d["fillColor"] } : {},
|
|
72
78
|
...d["outlineWidth"] ? { outlineWidth: Number(d["outlineWidth"]) } : {},
|
|
@@ -95,6 +101,7 @@ const TextBoxExtension = createNodeExtension({
|
|
|
95
101
|
if (attrs.width) domAttrs["data-width"] = String(attrs.width);
|
|
96
102
|
if (attrs.height) domAttrs["data-height"] = String(attrs.height);
|
|
97
103
|
if (attrs.autoFit) domAttrs["data-auto-fit"] = attrs.autoFit;
|
|
104
|
+
if (attrs.textWrap) domAttrs["data-text-wrap"] = attrs.textWrap;
|
|
98
105
|
if (attrs.textBoxId) domAttrs["data-textbox-id"] = attrs.textBoxId;
|
|
99
106
|
if (attrs.fillColor) domAttrs["data-fill-color"] = attrs.fillColor;
|
|
100
107
|
if (attrs.outlineWidth) domAttrs["data-outline-width"] = String(attrs.outlineWidth);
|
|
@@ -457,6 +457,8 @@ type TextBoxAttrs = {
|
|
|
457
457
|
height?: number;
|
|
458
458
|
/** Text fitting behavior */
|
|
459
459
|
autoFit?: document_d_exports.ShapeTextBody["autoFit"];
|
|
460
|
+
/** Horizontal text wrapping inside the box */
|
|
461
|
+
textWrap?: document_d_exports.ShapeTextBody["textWrap"];
|
|
460
462
|
/** Unique identifier */
|
|
461
463
|
textBoxId?: string;
|
|
462
464
|
/** Fill color as CSS color */
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { ParagraphBlock } from "../layout-engine/types.js";
|
|
2
|
+
//#region src/utils/paragraphBaseDirection.d.ts
|
|
3
|
+
/** Resolve the paragraph direction used by both measurement and painting. */
|
|
4
|
+
declare const isRtlParagraph: (block: ParagraphBlock) => boolean;
|
|
5
|
+
//#endregion
|
|
6
|
+
export { isRtlParagraph };
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { detectBaseDirection } from "./baseDirection.js";
|
|
2
|
+
//#region src/utils/paragraphBaseDirection.ts
|
|
3
|
+
const isDirectionalTextRun = (run) => run.kind === "text" || run.kind === "field";
|
|
4
|
+
/** Resolve the paragraph direction used by both measurement and painting. */
|
|
5
|
+
const isRtlParagraph = (block) => {
|
|
6
|
+
if (block.attrs?.bidi !== void 0) return block.attrs.bidi;
|
|
7
|
+
const runs = block.runs.filter(isDirectionalTextRun);
|
|
8
|
+
if (!runs.some(({ rtl }) => rtl === true)) return false;
|
|
9
|
+
return detectBaseDirection(runs.map((run) => run.kind === "text" ? run.text : run.fallback ?? "").join("")) !== "ltr";
|
|
10
|
+
};
|
|
11
|
+
//#endregion
|
|
12
|
+
export { isRtlParagraph };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@stll/folio-core",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.25.0",
|
|
4
4
|
"description": "Headless, framework-neutral core of folio: the OOXML (.docx) parser, document model, ProseMirror integration, and page-layout engine. No React.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"document-model",
|
|
@@ -113,7 +113,7 @@
|
|
|
113
113
|
"perf": "bun scripts/profile-editor.ts"
|
|
114
114
|
},
|
|
115
115
|
"dependencies": {
|
|
116
|
-
"@stll/docx-core": "^0.
|
|
116
|
+
"@stll/docx-core": "^0.17.0",
|
|
117
117
|
"@stll/docx-utils": "^0.1.0",
|
|
118
118
|
"@stll/template-conditions": "^0.1.0",
|
|
119
119
|
"better-result": "3.0.1",
|