@stll/folio-core 0.23.1 → 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/fontReadiness.js +12 -7
- 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/numberingParser.d.ts +2 -1
- package/dist/docx/numberingParser.js +24 -80
- package/dist/docx/paragraphParser.js +3 -4
- 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 +27 -2
- package/dist/layout-bridge/convert/toFlowBlocks.js +12 -16
- 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/cache.js +4 -0
- package/dist/layout-engine/measure/listMarkerWidth.d.ts +3 -1
- package/dist/layout-engine/measure/listMarkerWidth.js +25 -21
- 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 +16 -11
- package/dist/layout-engine/types.js +15 -1
- package/dist/layout-painter/renderParagraph.d.ts +9 -1
- package/dist/layout-painter/renderParagraph.js +65 -52
- 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 +4 -6
- package/dist/prosemirror/conversion/fromProseDoc.js +2 -3
- package/dist/prosemirror/conversion/toProseDoc.js +3 -3
- package/dist/prosemirror/extensions/core/ParagraphExtension.js +1 -3
- package/dist/prosemirror/extensions/features/ListExtension.js +1 -3
- 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 +5 -7
- package/dist/prosemirror/styles/resolvedStyleAttrs.js +1 -3
- package/dist/utils/paragraphBaseDirection.d.ts +6 -0
- package/dist/utils/paragraphBaseDirection.js +12 -0
- package/package.json +2 -2
|
@@ -2,15 +2,15 @@ import { ommlToMathml } from "../docx/mathToMathml.js";
|
|
|
2
2
|
import { parseXmlDocument } from "../docx/xmlParser.js";
|
|
3
3
|
import { evaluateFieldInstruction } from "../fields/evaluateField.js";
|
|
4
4
|
import { hasComplexScriptFormatting, resolveComplexScriptFormatting } from "../layout-engine/measure/complexScriptFormatting.js";
|
|
5
|
-
import { getListMarkerInlineWidth, getListMarkerVisualOffset } from "../layout-engine/measure/listMarkerWidth.js";
|
|
5
|
+
import { getListMarkerInlineWidth, getListMarkerVisualOffset, resolveListMarkerFont } from "../layout-engine/measure/listMarkerWidth.js";
|
|
6
6
|
import { DOCX_SCRIPT_FONT_SCALE } from "../layout-engine/measure/measureHelpers.js";
|
|
7
7
|
import { FONT_KERNING_MODE, countCompressibleSpaces, getFontKerningMode, getRunFontKerningMode, toPaintedText } from "../layout-engine/measure/textMeasurementPolicy.js";
|
|
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`;
|
|
@@ -1463,18 +1485,7 @@ function renderParagraphFragment(fragment, block, measure, context, options = {}
|
|
|
1463
1485
|
const markerStart = hanging > 0 ? indentLeft - hanging : indentLeft + firstLine;
|
|
1464
1486
|
lineEl.style.paddingLeft = `${Math.max(0, markerStart)}px`;
|
|
1465
1487
|
lineEl.style.textIndent = "0";
|
|
1466
|
-
|
|
1467
|
-
if (!block.attrs.listMarkerFontFamily || !block.attrs.listMarkerFontSize || block.attrs.listMarkerBold === void 0) for (let ri = line.fromRun; ri <= line.toRun; ri++) {
|
|
1468
|
-
const r = block.runs[ri];
|
|
1469
|
-
if (r && r.kind === "text") {
|
|
1470
|
-
firstTextRun = r;
|
|
1471
|
-
break;
|
|
1472
|
-
}
|
|
1473
|
-
}
|
|
1474
|
-
const markerFontFamily = block.attrs.listMarkerFontFamily ?? firstTextRun?.fontFamily ?? block.attrs.defaultFontFamily;
|
|
1475
|
-
const markerFontSize = block.attrs.listMarkerFontSize ?? firstTextRun?.fontSize ?? block.attrs.defaultFontSize;
|
|
1476
|
-
const markerBold = block.attrs.listMarkerBold ?? firstTextRun?.bold;
|
|
1477
|
-
const marker = renderListMarker(block.attrs.listMarker, getListMarkerInlineWidth(block), getListMarkerVisualOffset(block), doc, markerFontFamily, markerFontSize, markerBold, block.attrs.listMarkerRevision, block.attrs.listMarkerSecondSlotOffsetTwips);
|
|
1488
|
+
const marker = renderListMarker(block.attrs.listMarker, getListMarkerInlineWidth(block), getListMarkerVisualOffset(block), doc, resolveListMarkerFont(block), block.attrs.listMarkerRevision, block.attrs.listMarkerSecondSlotOffsetTwips);
|
|
1478
1489
|
const markerMarginLeft = markerStart - Math.min(indentLeft, 0);
|
|
1479
1490
|
if (markerMarginLeft < 0) marker.style.marginLeft = `${markerMarginLeft}px`;
|
|
1480
1491
|
lineEl.prepend(marker);
|
|
@@ -1491,13 +1502,15 @@ function renderParagraphFragment(fragment, block, measure, context, options = {}
|
|
|
1491
1502
|
* tab grid. Long markers like "1.1.1." therefore grow to the next stop
|
|
1492
1503
|
* instead of butting against the body text.
|
|
1493
1504
|
*/
|
|
1494
|
-
function renderListMarker(marker, inlineWidth, visualOffset, doc,
|
|
1505
|
+
function renderListMarker(marker, inlineWidth, visualOffset, doc, formatting, revision, secondSlotOffsetTwips) {
|
|
1495
1506
|
const span = doc.createElement("span");
|
|
1496
1507
|
span.className = "layout-list-marker";
|
|
1497
1508
|
span.style.display = "inline-block";
|
|
1498
|
-
|
|
1499
|
-
if (fontSize) span.style.fontSize = `${fontSize * 96 / 72}px`;
|
|
1500
|
-
if (bold !== void 0) span.style.fontWeight = bold ? "700" : "normal";
|
|
1509
|
+
span.style.fontFamily = resolveFontFamily(formatting.fontFamily).cssFallback;
|
|
1510
|
+
if (formatting.fontSize) span.style.fontSize = `${formatting.fontSize * 96 / 72}px`;
|
|
1511
|
+
if (formatting.bold !== void 0) span.style.fontWeight = formatting.bold ? "700" : "normal";
|
|
1512
|
+
if (formatting.italic !== void 0) span.style.fontStyle = formatting.italic ? "italic" : "normal";
|
|
1513
|
+
if (formatting.rtl !== void 0) span.dir = formatting.rtl ? "rtl" : "ltr";
|
|
1501
1514
|
span.style.textAlign = "left";
|
|
1502
1515
|
span.style.textAlignLast = "left";
|
|
1503
1516
|
span.style.boxSizing = "border-box";
|
|
@@ -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 = [
|
|
@@ -162,9 +163,7 @@ const readParagraphAttrs = (node) => {
|
|
|
162
163
|
optionalBoolean(attrs, "listIsLegal", "paragraph.attrs.listIsLegal", issues);
|
|
163
164
|
optionalString(attrs, "listMarker", "paragraph.attrs.listMarker", issues);
|
|
164
165
|
optionalBoolean(attrs, "listMarkerHidden", "paragraph.attrs.listMarkerHidden", issues);
|
|
165
|
-
|
|
166
|
-
optionalNumber(attrs, "listMarkerFontSize", "paragraph.attrs.listMarkerFontSize", issues);
|
|
167
|
-
optionalBoolean(attrs, "listMarkerBold", "paragraph.attrs.listMarkerBold", issues);
|
|
166
|
+
optionalTextFormatting(attrs, "listMarkerFormatting", "paragraph.attrs.listMarkerFormatting", issues);
|
|
168
167
|
optionalOneOf(attrs, "listMarkerAlignment", "paragraph.attrs.listMarkerAlignment", issues, [
|
|
169
168
|
"left",
|
|
170
169
|
"center",
|
|
@@ -465,6 +464,7 @@ const readTextBoxAttrs = (node) => {
|
|
|
465
464
|
optionalNumber(attrs, "width", "textBox.attrs.width", issues);
|
|
466
465
|
optionalNumber(attrs, "height", "textBox.attrs.height", issues);
|
|
467
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);
|
|
468
468
|
optionalString(attrs, "textBoxId", "textBox.attrs.textBoxId", issues);
|
|
469
469
|
optionalString(attrs, "fillColor", "textBox.attrs.fillColor", issues);
|
|
470
470
|
optionalNumber(attrs, "outlineWidth", "textBox.attrs.outlineWidth", issues);
|
|
@@ -1397,12 +1397,10 @@ const validateParagraphFormatting = (value, path, issues) => {
|
|
|
1397
1397
|
"listIsBullet",
|
|
1398
1398
|
"listIsLegal",
|
|
1399
1399
|
"listMarkerHidden",
|
|
1400
|
-
"listMarkerBold",
|
|
1401
1400
|
"listMarkerAllCaps"
|
|
1402
1401
|
]) optionalBoolean(value, key, `${path}.${key}`, issues);
|
|
1403
1402
|
for (const key of PARAGRAPH_FORMATTING_NUMBER_KEYS) optionalNumber(value, key, `${path}.${key}`, issues);
|
|
1404
1403
|
for (const key of [
|
|
1405
|
-
"listMarkerFontSize",
|
|
1406
1404
|
"listImplicitChildLevelAdvances",
|
|
1407
1405
|
"listMarkerSecondSlotOffsetTwips",
|
|
1408
1406
|
"listAbstractNumId",
|
|
@@ -1412,7 +1410,7 @@ const validateParagraphFormatting = (value, path, issues) => {
|
|
|
1412
1410
|
optionalString(value, "styleId", `${path}.styleId`, issues);
|
|
1413
1411
|
optionalOneOf(value, "listNumFmt", `${path}.listNumFmt`, issues, NUMBER_FORMAT_VALUES);
|
|
1414
1412
|
optionalString(value, "listMarker", `${path}.listMarker`, issues);
|
|
1415
|
-
|
|
1413
|
+
optionalTextFormatting(value, "listMarkerFormatting", `${path}.listMarkerFormatting`, issues);
|
|
1416
1414
|
optionalOneOf(value, "listMarkerAlignment", `${path}.listMarkerAlignment`, issues, [
|
|
1417
1415
|
"left",
|
|
1418
1416
|
"center",
|
|
@@ -421,9 +421,7 @@ function listRenderingFromAttrs(attrs) {
|
|
|
421
421
|
...attrs.listIsLegal != null && { isLegal: attrs.listIsLegal },
|
|
422
422
|
...attrs.listNumFmt != null && { numFmt: attrs.listNumFmt },
|
|
423
423
|
...attrs.listMarkerHidden != null && { markerHidden: attrs.listMarkerHidden },
|
|
424
|
-
...attrs.
|
|
425
|
-
...attrs.listMarkerFontSize != null && { markerFontSize: attrs.listMarkerFontSize },
|
|
426
|
-
...attrs.listMarkerBold != null && { markerBold: attrs.listMarkerBold },
|
|
424
|
+
...attrs.listMarkerFormatting != null && { markerFormatting: attrs.listMarkerFormatting },
|
|
427
425
|
...attrs.listMarkerAlignment != null && { markerAlignment: attrs.listMarkerAlignment },
|
|
428
426
|
...attrs.listMarkerSuffix != null && { markerSuffix: attrs.listMarkerSuffix },
|
|
429
427
|
...attrs.listMarkerAllCaps != null && { markerAllCaps: attrs.listMarkerAllCaps },
|
|
@@ -1880,6 +1878,7 @@ function convertPMTextBox(node) {
|
|
|
1880
1878
|
content: []
|
|
1881
1879
|
}],
|
|
1882
1880
|
...attrs.autoFit !== void 0 ? { autoFit: attrs.autoFit } : {},
|
|
1881
|
+
...attrs.textWrap !== void 0 ? { textWrap: attrs.textWrap } : {},
|
|
1883
1882
|
margins: (() => {
|
|
1884
1883
|
const m = {};
|
|
1885
1884
|
if (typeof attrs.marginTop === "number") m.top = pixelsToEmu(attrs.marginTop);
|
|
@@ -290,9 +290,7 @@ function paragraphFormattingToAttrs(paragraph, styleResolver, tableParagraphOver
|
|
|
290
290
|
if (paragraph.listRendering?.isLegal) attrs.listIsLegal = paragraph.listRendering.isLegal;
|
|
291
291
|
if (paragraph.listRendering?.marker) attrs.listMarker = paragraph.listRendering.marker;
|
|
292
292
|
if (paragraph.listRendering?.markerHidden) attrs.listMarkerHidden = paragraph.listRendering.markerHidden;
|
|
293
|
-
if (paragraph.listRendering?.
|
|
294
|
-
if (paragraph.listRendering?.markerFontSize) attrs.listMarkerFontSize = paragraph.listRendering.markerFontSize;
|
|
295
|
-
if (paragraph.listRendering?.markerBold !== void 0) attrs.listMarkerBold = paragraph.listRendering.markerBold;
|
|
293
|
+
if (paragraph.listRendering?.markerFormatting) attrs.listMarkerFormatting = paragraph.listRendering.markerFormatting;
|
|
296
294
|
if (paragraph.listRendering?.markerAlignment) attrs.listMarkerAlignment = paragraph.listRendering.markerAlignment;
|
|
297
295
|
if (paragraph.listRendering?.markerSuffix) attrs.listMarkerSuffix = paragraph.listRendering.markerSuffix;
|
|
298
296
|
if (paragraph.listRendering?.markerAllCaps) attrs.listMarkerAllCaps = paragraph.listRendering.markerAllCaps;
|
|
@@ -1525,6 +1523,7 @@ function textBoxFromShape(shape, textBody) {
|
|
|
1525
1523
|
if (shape.outline) textBox.outline = shape.outline;
|
|
1526
1524
|
if (textBody.margins) textBox.margins = textBody.margins;
|
|
1527
1525
|
if (textBody.autoFit) textBox.autoFit = textBody.autoFit;
|
|
1526
|
+
if (textBody.textWrap) textBox.textWrap = textBody.textWrap;
|
|
1528
1527
|
return textBox;
|
|
1529
1528
|
}
|
|
1530
1529
|
/**
|
|
@@ -1599,6 +1598,7 @@ function convertTextBox(textBox, styleResolver, options) {
|
|
|
1599
1598
|
width: widthPx,
|
|
1600
1599
|
height: heightPx,
|
|
1601
1600
|
autoFit: textBox.autoFit,
|
|
1601
|
+
textWrap: textBox.textWrap,
|
|
1602
1602
|
textBoxId: textBox.id,
|
|
1603
1603
|
fillColor,
|
|
1604
1604
|
outlineWidth,
|
|
@@ -204,9 +204,7 @@ const paragraphNodeSpec = {
|
|
|
204
204
|
listIsLegal: { default: null },
|
|
205
205
|
listMarker: { default: null },
|
|
206
206
|
listMarkerHidden: { default: null },
|
|
207
|
-
|
|
208
|
-
listMarkerFontSize: { default: null },
|
|
209
|
-
listMarkerBold: { default: null },
|
|
207
|
+
listMarkerFormatting: { default: null },
|
|
210
208
|
listMarkerAlignment: { default: null },
|
|
211
209
|
listMarkerSuffix: { default: null },
|
|
212
210
|
listMarkerAllCaps: { default: null },
|
|
@@ -32,9 +32,7 @@ const LIST_FORMATTING_ATTRS = [
|
|
|
32
32
|
"listNumFmt",
|
|
33
33
|
"listMarker",
|
|
34
34
|
"listMarkerHidden",
|
|
35
|
-
"
|
|
36
|
-
"listMarkerFontSize",
|
|
37
|
-
"listMarkerBold",
|
|
35
|
+
"listMarkerFormatting",
|
|
38
36
|
"listMarkerAlignment",
|
|
39
37
|
"listMarkerSuffix",
|
|
40
38
|
"listLevelNumFmts",
|
|
@@ -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);
|
|
@@ -69,12 +69,8 @@ type ParagraphAttrs = {
|
|
|
69
69
|
listMarker?: string;
|
|
70
70
|
/** Whether the list marker is hidden (w:vanish on numbering level rPr) */
|
|
71
71
|
listMarkerHidden?: boolean;
|
|
72
|
-
/**
|
|
73
|
-
|
|
74
|
-
/** Marker font size from numbering level rPr, in points */
|
|
75
|
-
listMarkerFontSize?: number;
|
|
76
|
-
/** Marker bold state from numbering level rPr */
|
|
77
|
-
listMarkerBold?: boolean;
|
|
72
|
+
/** Canonical numbering-level marker typography, in OOXML units. */
|
|
73
|
+
listMarkerFormatting?: document_d_exports.ListMarkerFormatting;
|
|
78
74
|
/** Horizontal alignment of the marker around the paragraph's list anchor. */
|
|
79
75
|
listMarkerAlignment?: "left" | "center" | "right";
|
|
80
76
|
/**
|
|
@@ -200,7 +196,7 @@ type ParagraphAttrs = {
|
|
|
200
196
|
type ParagraphPropertyChangeAttrs = Omit<document_d_exports.ParagraphPropertyChange, "previousFormatting" | "currentFormatting"> & {
|
|
201
197
|
previousFormatting?: Omit<document_d_exports.ParagraphFormatting, "numPr"> & {
|
|
202
198
|
numPr?: document_d_exports.ParagraphFormatting["numPr"] | null;
|
|
203
|
-
} & Partial<Pick<ParagraphAttrs, "listIsBullet" | "listIsLegal" | "listNumFmt" | "listMarker" | "listMarkerHidden" | "
|
|
199
|
+
} & Partial<Pick<ParagraphAttrs, "listIsBullet" | "listIsLegal" | "listNumFmt" | "listMarker" | "listMarkerHidden" | "listMarkerFormatting" | "listMarkerAlignment" | "listMarkerSuffix" | "listLevelNumFmts" | "listLevelStarts" | "listAbstractNumId" | "listStartOverride" | "lineSpacingExplicit" | "direction" | "_autospacingBase">>;
|
|
204
200
|
currentFormatting?: document_d_exports.ParagraphFormatting;
|
|
205
201
|
};
|
|
206
202
|
/**
|
|
@@ -461,6 +457,8 @@ type TextBoxAttrs = {
|
|
|
461
457
|
height?: number;
|
|
462
458
|
/** Text fitting behavior */
|
|
463
459
|
autoFit?: document_d_exports.ShapeTextBody["autoFit"];
|
|
460
|
+
/** Horizontal text wrapping inside the box */
|
|
461
|
+
textWrap?: document_d_exports.ShapeTextBody["textWrap"];
|
|
464
462
|
/** Unique identifier */
|
|
465
463
|
textBoxId?: string;
|
|
466
464
|
/** Fill color as CSS color */
|
|
@@ -86,9 +86,7 @@ function listAttrsFromResolvedStyle(resolved, numbering) {
|
|
|
86
86
|
listIsLegal: rendering?.isLegal ?? null,
|
|
87
87
|
listMarker: rendering?.marker ?? null,
|
|
88
88
|
listMarkerHidden: rendering?.markerHidden ?? null,
|
|
89
|
-
|
|
90
|
-
listMarkerFontSize: rendering?.markerFontSize ?? null,
|
|
91
|
-
listMarkerBold: rendering?.markerBold ?? null,
|
|
89
|
+
listMarkerFormatting: rendering?.markerFormatting ?? null,
|
|
92
90
|
listMarkerAlignment: rendering?.markerAlignment ?? null,
|
|
93
91
|
listMarkerSuffix: rendering?.markerSuffix ?? null,
|
|
94
92
|
listMarkerAllCaps: rendering?.markerAllCaps ?? null,
|
|
@@ -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",
|