@stll/folio-core 0.7.0 → 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +39 -0
- package/dist/ai-edits/apply.js +18 -4
- package/dist/ai-edits/blockRange.d.ts +13 -2
- package/dist/ai-edits/blockRange.js +23 -2
- package/dist/ai-edits/headless.d.ts +31 -4
- package/dist/ai-edits/headless.js +148 -35
- package/dist/ai-edits/index.d.ts +6 -4
- package/dist/ai-edits/index.js +4 -1
- package/dist/ai-edits/scoped-reading.d.ts +9 -0
- package/dist/ai-edits/scoped-reading.js +60 -0
- package/dist/ai-edits/snapshot.js +53 -7
- package/dist/ai-edits/types.d.ts +46 -3
- package/dist/compat/eigenpal.d.ts +8 -3
- package/dist/compat/eigenpal.js +7 -2
- package/dist/controller/fontReadiness.d.ts +29 -0
- package/dist/controller/fontReadiness.js +139 -0
- package/dist/controller/latestRequestGate.d.ts +13 -0
- package/dist/controller/latestRequestGate.js +16 -0
- package/dist/controller/layoutPipeline.js +88 -10
- package/dist/document-operations.d.ts +10 -3
- package/dist/document-operations.js +29 -14
- package/dist/docx/blockContentParser.js +53 -10
- package/dist/docx/commentParser.js +1 -1
- package/dist/docx/compatibility.d.ts +30 -2
- package/dist/docx/compatibility.js +118 -39
- package/dist/docx/conformance.d.ts +6 -0
- package/dist/docx/conformance.js +18 -0
- package/dist/docx/encryption/agileDecryption.js +1 -1
- package/dist/docx/encryption/encryptionInfo.js +1 -1
- package/dist/docx/encryption/openEncryptedDocx.js +1 -1
- package/dist/docx/ensureParaIds.d.ts +29 -0
- package/dist/docx/ensureParaIds.js +423 -0
- package/dist/docx/fontTableParser.d.ts +6 -0
- package/dist/docx/fontTableParser.js +72 -0
- package/dist/docx/groupDrawingParser.js +7 -2
- package/dist/docx/headerFooterParser.js +1 -1
- package/dist/docx/normalizeBaseDirection.js +1 -1
- package/dist/docx/numberingParser.js +2 -0
- package/dist/docx/paragraphParser.js +21 -1
- package/dist/docx/paragraphTextBoxEnrichment.js +141 -2
- package/dist/docx/parser.d.ts +1 -1
- package/dist/docx/parser.js +14 -9
- package/dist/docx/rezip.js +70 -5
- package/dist/docx/runConsolidator.js +4 -0
- package/dist/docx/runParser.js +19 -1
- package/dist/docx/selectiveSave.js +3 -3
- package/dist/docx/selectiveXmlPatch.d.ts +10 -1
- package/dist/docx/selectiveXmlPatch.js +1 -1
- package/dist/docx/serializer/fontTableSerializer.d.ts +6 -0
- package/dist/docx/serializer/fontTableSerializer.js +26 -0
- package/dist/docx/serializer/headerFooterSerializer.js +1 -1
- package/dist/docx/serializer/paragraphSerializer.js +8 -0
- package/dist/docx/serializer/runSerializer.js +14 -2
- package/dist/docx/serializer/settingsSerializer.d.ts +6 -0
- package/dist/docx/serializer/settingsSerializer.js +16 -0
- package/dist/docx/serializer/stylesSerializer.d.ts +6 -0
- package/dist/docx/serializer/stylesSerializer.js +56 -0
- package/dist/docx/serializer/tableSerializer.js +29 -26
- package/dist/docx/serializer/themeSerializer.d.ts +6 -0
- package/dist/docx/serializer/themeSerializer.js +36 -0
- package/dist/docx/settingsParser.js +46 -0
- package/dist/docx/styleParser.js +26 -1
- package/dist/docx/tableParser.js +19 -1
- package/dist/docx/textBoxParser.js +6 -1
- package/dist/docx/vmlImageParser.js +146 -1
- package/dist/i18n/messages/catalogs.gen.d.ts +5916 -5916
- package/dist/i18n/messages/catalogs.gen.js +5910 -5910
- package/dist/index.d.ts +8 -3
- package/dist/index.js +7 -2
- package/dist/layout-bridge/convert/footnoteLayout.d.ts +3 -0
- package/dist/layout-bridge/convert/footnoteLayout.js +3 -1
- package/dist/layout-bridge/convert/headerFooterLayout.d.ts +3 -0
- package/dist/layout-bridge/convert/headerFooterLayout.js +22 -3
- package/dist/layout-bridge/convert/paragraphFrames.d.ts +7 -0
- package/dist/layout-bridge/convert/paragraphFrames.js +136 -0
- package/dist/layout-bridge/convert/toFlowBlocks.d.ts +14 -2
- package/dist/layout-bridge/convert/toFlowBlocks.js +93 -17
- package/dist/layout-bridge/engine/hitTest.js +2 -1
- package/dist/layout-bridge/engine/measuring/index.d.ts +2 -1
- package/dist/layout-bridge/engine/measuring/index.js +2 -1
- package/dist/layout-bridge/engine/selectionRects.js +2 -1
- package/dist/layout-engine/headerFooterRefs.d.ts +7 -0
- package/dist/layout-engine/headerFooterRefs.js +40 -0
- package/dist/layout-engine/index.d.ts +3 -2
- package/dist/layout-engine/index.js +68 -106
- package/dist/layout-engine/measure/cache.js +11 -3
- package/dist/layout-engine/measure/font-metrics.worker.js +1 -0
- package/dist/layout-engine/measure/index.d.ts +2 -1
- package/dist/layout-engine/measure/index.js +2 -1
- package/dist/layout-engine/measure/lineBreakProvider.d.ts +33 -0
- package/dist/layout-engine/measure/lineBreakProvider.js +252 -0
- package/dist/layout-engine/measure/lineBreaks.d.ts +7 -2
- package/dist/layout-engine/measure/lineBreaks.js +11 -18
- package/dist/layout-engine/measure/listMarkerWidth.d.ts +4 -1
- package/dist/layout-engine/measure/listMarkerWidth.js +33 -8
- package/dist/layout-engine/measure/measureBlocks.d.ts +4 -2
- package/dist/layout-engine/measure/measureBlocks.js +138 -86
- package/dist/layout-engine/measure/measureContainer.js +32 -9
- package/dist/layout-engine/measure/measureHelpers.js +5 -2
- package/dist/layout-engine/measure/measureParagraph.js +346 -74
- package/dist/layout-engine/measure/measureTypes.d.ts +2 -1
- package/dist/layout-engine/measure/measureWorker.d.ts +2 -2
- package/dist/layout-engine/measure/measureWorker.js +8 -19
- package/dist/layout-engine/measure/measureWorkerProtocol.d.ts +3 -14
- package/dist/layout-engine/measure/tableCellFloating.d.ts +10 -3
- package/dist/layout-engine/measure/tableCellFloating.js +37 -29
- package/dist/layout-engine/measure/tableCellFlow.d.ts +27 -0
- package/dist/layout-engine/measure/tableCellFlow.js +73 -0
- package/dist/layout-engine/measure/tableCellGrid.d.ts +15 -0
- package/dist/layout-engine/measure/tableCellGrid.js +62 -0
- package/dist/layout-engine/measure/textBoxParagraphLayout.d.ts +18 -0
- package/dist/layout-engine/measure/textBoxParagraphLayout.js +32 -0
- package/dist/layout-engine/measure/textMeasurementPolicy.d.ts +18 -0
- package/dist/layout-engine/measure/textMeasurementPolicy.js +24 -0
- package/dist/layout-engine/paginator.d.ts +2 -1
- package/dist/layout-engine/paginator.js +7 -6
- package/dist/layout-engine/paragraphFrame.d.ts +22 -0
- package/dist/layout-engine/paragraphFrame.js +17 -0
- package/dist/layout-engine/paragraphSequence.d.ts +7 -0
- package/dist/layout-engine/paragraphSequence.js +25 -0
- package/dist/layout-engine/paragraphSpacing.d.ts +17 -0
- package/dist/layout-engine/paragraphSpacing.js +30 -0
- package/dist/layout-engine/renderedBreakReconciliation.d.ts +63 -0
- package/dist/layout-engine/renderedBreakReconciliation.js +86 -0
- package/dist/layout-engine/tableRowBreak.d.ts +5 -2
- package/dist/layout-engine/tableRowBreak.js +62 -29
- package/dist/layout-engine/types.d.ts +54 -12
- package/dist/layout-engine/types.js +15 -2
- package/dist/layout-painter/anchoredImagePosition.d.ts +23 -0
- package/dist/layout-painter/anchoredImagePosition.js +95 -0
- package/dist/layout-painter/borderStroke.d.ts +23 -0
- package/dist/layout-painter/borderStroke.js +39 -0
- package/dist/layout-painter/renderImage.d.ts +5 -4
- package/dist/layout-painter/renderImage.js +18 -4
- package/dist/layout-painter/renderPage.d.ts +2 -27
- package/dist/layout-painter/renderPage.js +15 -101
- package/dist/layout-painter/renderParagraph.js +45 -26
- package/dist/layout-painter/renderTable.d.ts +2 -0
- package/dist/layout-painter/renderTable.js +257 -47
- package/dist/layout-painter/renderTextBox.js +17 -7
- package/dist/managers/DocumentLoaderManager.d.ts +1 -1
- package/dist/managers/DocumentLoaderManager.js +2 -2
- package/dist/paged-layout/sectionBlockWidths.d.ts +3 -0
- package/dist/paged-layout/sectionBlockWidths.js +9 -0
- package/dist/paged-layout/sectionGeometry.js +1 -1
- package/dist/prosemirror/attrs/index.d.ts +4 -2
- package/dist/prosemirror/attrs/index.js +32 -2
- package/dist/prosemirror/commands/propertyChangeScope.js +1 -1
- package/dist/prosemirror/conversion/fromProseDoc.js +35 -18
- package/dist/prosemirror/conversion/toProseDoc.js +73 -37
- package/dist/prosemirror/extensions/StarterKit.js +2 -0
- package/dist/prosemirror/extensions/core/ParagraphExtension.js +8 -2
- package/dist/prosemirror/extensions/features/AutoBidiDetectionExtension.js +1 -1
- package/dist/prosemirror/extensions/features/ListExtension.js +2 -0
- package/dist/prosemirror/extensions/features/ParaIdAllocatorExtension.js +68 -16
- package/dist/prosemirror/extensions/marks/FootnoteRefExtension.js +1 -0
- package/dist/prosemirror/extensions/marks/HighlightExtension.js +1 -1
- package/dist/prosemirror/extensions/marks/LanguageExtension.d.ts +7 -0
- package/dist/prosemirror/extensions/marks/LanguageExtension.js +30 -0
- package/dist/prosemirror/extensions/marks/RunShadingExtension.js +1 -1
- package/dist/prosemirror/extensions/marks/TextColorExtension.js +1 -1
- package/dist/prosemirror/extensions/marks/TrackedChangeExtensions.js +1 -1
- package/dist/prosemirror/extensions/marks/markUtils.js +12 -0
- package/dist/prosemirror/extensions/nodes/ImageExtension.js +2 -1
- package/dist/prosemirror/extensions/nodes/TableExtension.js +4 -2
- package/dist/prosemirror/extensions/nodes/TextBoxExtension.d.ts +2 -1
- package/dist/prosemirror/extensions/nodes/TextBoxExtension.js +7 -0
- package/dist/prosemirror/schema/index.d.ts +2 -2
- package/dist/prosemirror/schema/marks.d.ts +6 -1
- package/dist/prosemirror/schema/nodes.d.ts +16 -12
- package/dist/prosemirror/styles/resolvedStyleAttrs.js +2 -0
- package/dist/prosemirror/utils/tabCalculator.d.ts +3 -1
- package/dist/prosemirror/utils/tabCalculator.js +12 -10
- package/dist/prosemirror/validation.js +4 -1
- package/dist/redline.js +13 -3
- package/dist/server.d.ts +9 -4
- package/dist/server.js +7 -2
- package/dist/style-sets/extract.d.ts +35 -0
- package/dist/style-sets/extract.js +123 -0
- package/dist/style-sets/stellaStyle.d.ts +13 -0
- package/dist/style-sets/stellaStyle.js +516 -0
- package/dist/style-sets/types.d.ts +31 -0
- package/dist/style-sets/types.js +5 -0
- package/dist/types/index.d.ts +2 -1
- package/dist/utils/createDocument.d.ts +12 -2
- package/dist/utils/createDocument.js +55 -36
- package/dist/utils/fontResolver.js +6 -6
- package/dist/utils/formatToStyle.js +1 -1
- package/dist/utils/hexId.d.ts +8 -1
- package/dist/utils/hexId.js +11 -3
- package/dist/utils/paragraphFormattingMerge.d.ts +14 -3
- package/dist/utils/paragraphFormattingMerge.js +14 -4
- package/package.json +5 -1
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { emuToPixels } from "./renderUtils.js";
|
|
2
|
+
//#region src/layout-painter/anchoredImagePosition.ts
|
|
3
|
+
function resolveHorizontalBand(relativeTo, geometry) {
|
|
4
|
+
switch (relativeTo) {
|
|
5
|
+
case "page": return {
|
|
6
|
+
baseX: -geometry.marginLeft,
|
|
7
|
+
bandWidth: geometry.pageWidth
|
|
8
|
+
};
|
|
9
|
+
case "leftMargin":
|
|
10
|
+
case "insideMargin": return {
|
|
11
|
+
baseX: -geometry.marginLeft,
|
|
12
|
+
bandWidth: geometry.marginLeft
|
|
13
|
+
};
|
|
14
|
+
case "rightMargin":
|
|
15
|
+
case "outsideMargin": return {
|
|
16
|
+
baseX: geometry.contentWidth,
|
|
17
|
+
bandWidth: geometry.marginRight
|
|
18
|
+
};
|
|
19
|
+
case "character": return {
|
|
20
|
+
baseX: 0,
|
|
21
|
+
bandWidth: 0
|
|
22
|
+
};
|
|
23
|
+
default: return {
|
|
24
|
+
baseX: 0,
|
|
25
|
+
bandWidth: geometry.contentWidth
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
function resolveVerticalBand(relativeTo, fragmentY, geometry) {
|
|
30
|
+
switch (relativeTo) {
|
|
31
|
+
case "page": return {
|
|
32
|
+
baseY: -geometry.marginTop,
|
|
33
|
+
bandHeight: geometry.pageHeight
|
|
34
|
+
};
|
|
35
|
+
case "topMargin": return {
|
|
36
|
+
baseY: -geometry.marginTop,
|
|
37
|
+
bandHeight: geometry.marginTop
|
|
38
|
+
};
|
|
39
|
+
case "bottomMargin": return {
|
|
40
|
+
baseY: geometry.contentHeight,
|
|
41
|
+
bandHeight: geometry.marginBottom
|
|
42
|
+
};
|
|
43
|
+
case "paragraph":
|
|
44
|
+
case "line": return {
|
|
45
|
+
baseY: fragmentY,
|
|
46
|
+
bandHeight: 0
|
|
47
|
+
};
|
|
48
|
+
default: return {
|
|
49
|
+
baseY: 0,
|
|
50
|
+
bandHeight: geometry.contentHeight
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
/** Resolve an anchored image into page-content-relative coordinates. */
|
|
55
|
+
function resolveAnchoredImagePosition(imgRun, fragmentY, geometry) {
|
|
56
|
+
const position = imgRun.position;
|
|
57
|
+
const contentWidth = geometry.contentWidth;
|
|
58
|
+
let side = "left";
|
|
59
|
+
let x = 0;
|
|
60
|
+
if (position?.horizontal) {
|
|
61
|
+
const h = position.horizontal;
|
|
62
|
+
const { baseX, bandWidth } = resolveHorizontalBand(h.relativeTo, geometry);
|
|
63
|
+
const horizontalHasBand = h.relativeTo !== "character";
|
|
64
|
+
if (h.align === "right" || h.align === "outside") {
|
|
65
|
+
side = "right";
|
|
66
|
+
x = horizontalHasBand ? baseX + bandWidth - imgRun.width : 0;
|
|
67
|
+
} else if (h.align === "left" || h.align === "inside") x = baseX;
|
|
68
|
+
else if (h.align === "center") x = horizontalHasBand ? baseX + (bandWidth - imgRun.width) / 2 : 0;
|
|
69
|
+
else if (h.posOffset !== void 0) {
|
|
70
|
+
x = baseX + emuToPixels(h.posOffset);
|
|
71
|
+
side = x > contentWidth / 2 ? "right" : "left";
|
|
72
|
+
} else x = baseX;
|
|
73
|
+
} else if (imgRun.cssFloat === "right") {
|
|
74
|
+
side = "right";
|
|
75
|
+
x = contentWidth - imgRun.width;
|
|
76
|
+
}
|
|
77
|
+
let y;
|
|
78
|
+
if (position?.vertical) {
|
|
79
|
+
const v = position.vertical;
|
|
80
|
+
const { baseY, bandHeight } = resolveVerticalBand(v.relativeTo, fragmentY, geometry);
|
|
81
|
+
const verticalHasBand = v.relativeTo !== "paragraph" && v.relativeTo !== "line";
|
|
82
|
+
if (v.align === "top" || v.align === "inside") y = baseY;
|
|
83
|
+
else if (v.align === "center") y = verticalHasBand ? baseY + (bandHeight - imgRun.height) / 2 : fragmentY;
|
|
84
|
+
else if (v.align === "bottom" || v.align === "outside") y = verticalHasBand ? baseY + bandHeight - imgRun.height : fragmentY;
|
|
85
|
+
else if (v.posOffset !== void 0) y = baseY + emuToPixels(v.posOffset);
|
|
86
|
+
else y = verticalHasBand ? baseY : fragmentY;
|
|
87
|
+
} else y = fragmentY;
|
|
88
|
+
return {
|
|
89
|
+
x,
|
|
90
|
+
y,
|
|
91
|
+
side
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
//#endregion
|
|
95
|
+
export { resolveAnchoredImagePosition };
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { BorderStyle, ParagraphBorders } from "../layout-engine/types.js";
|
|
2
|
+
|
|
3
|
+
//#region src/layout-painter/borderStroke.d.ts
|
|
4
|
+
type CssBorderStroke = {
|
|
5
|
+
color: string;
|
|
6
|
+
style: string;
|
|
7
|
+
width: number;
|
|
8
|
+
};
|
|
9
|
+
/**
|
|
10
|
+
* Preserve the optical weight of authored subpixel borders. CSS quantizes a
|
|
11
|
+
* fractional border width to one device pixel, so its color carries the
|
|
12
|
+
* fractional coverage while layout keeps the authored width.
|
|
13
|
+
*/
|
|
14
|
+
declare const resolveCssBorderStroke: (border: Pick<BorderStyle, "color" | "style" | "width">) => CssBorderStroke;
|
|
15
|
+
declare const borderStrokeToCss: (border: Pick<BorderStyle, "color" | "style" | "width">) => string;
|
|
16
|
+
type ParagraphBorderHorizontalOutsets = {
|
|
17
|
+
left: number;
|
|
18
|
+
right: number;
|
|
19
|
+
};
|
|
20
|
+
/** Resolve the outer horizontal edges of a paragraph border box. */
|
|
21
|
+
declare const resolveParagraphBorderHorizontalOutsets: (borders: ParagraphBorders, hasHorizontalRule: boolean) => ParagraphBorderHorizontalOutsets;
|
|
22
|
+
//#endregion
|
|
23
|
+
export { borderStrokeToCss, resolveCssBorderStroke, resolveParagraphBorderHorizontalOutsets };
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { pointsToPixels } from "../utils/units.js";
|
|
2
|
+
//#region src/layout-painter/borderStroke.ts
|
|
3
|
+
const DEFAULT_BORDER_COLOR = "#000000";
|
|
4
|
+
const DEFAULT_BORDER_STYLE = "solid";
|
|
5
|
+
const CSS_HAIRLINE_WIDTH = 1;
|
|
6
|
+
const PARAGRAPH_RULE_ENDPOINT_OUTSET = pointsToPixels(1.5);
|
|
7
|
+
/**
|
|
8
|
+
* Preserve the optical weight of authored subpixel borders. CSS quantizes a
|
|
9
|
+
* fractional border width to one device pixel, so its color carries the
|
|
10
|
+
* fractional coverage while layout keeps the authored width.
|
|
11
|
+
*/
|
|
12
|
+
const resolveCssBorderStroke = (border) => {
|
|
13
|
+
const authoredWidth = border.width ?? CSS_HAIRLINE_WIDTH;
|
|
14
|
+
if (authoredWidth <= 0 || authoredWidth >= CSS_HAIRLINE_WIDTH) return {
|
|
15
|
+
color: border.color ?? DEFAULT_BORDER_COLOR,
|
|
16
|
+
style: border.style ?? DEFAULT_BORDER_STYLE,
|
|
17
|
+
width: Math.max(CSS_HAIRLINE_WIDTH, authoredWidth)
|
|
18
|
+
};
|
|
19
|
+
const coveragePercent = Number((authoredWidth * 100).toFixed(4));
|
|
20
|
+
return {
|
|
21
|
+
color: `color-mix(in srgb, ${border.color ?? DEFAULT_BORDER_COLOR} ${coveragePercent}%, transparent)`,
|
|
22
|
+
style: border.style ?? DEFAULT_BORDER_STYLE,
|
|
23
|
+
width: CSS_HAIRLINE_WIDTH
|
|
24
|
+
};
|
|
25
|
+
};
|
|
26
|
+
const borderStrokeToCss = (border) => {
|
|
27
|
+
const stroke = resolveCssBorderStroke(border);
|
|
28
|
+
return `${stroke.width}px ${stroke.style} ${stroke.color}`;
|
|
29
|
+
};
|
|
30
|
+
/** Resolve the outer horizontal edges of a paragraph border box. */
|
|
31
|
+
const resolveParagraphBorderHorizontalOutsets = (borders, hasHorizontalRule) => {
|
|
32
|
+
const ruleOutset = hasHorizontalRule ? PARAGRAPH_RULE_ENDPOINT_OUTSET : 0;
|
|
33
|
+
return {
|
|
34
|
+
left: borders.left ? (borders.left.space ?? 0) + (borders.left.width ?? 0) : ruleOutset,
|
|
35
|
+
right: borders.right ? (borders.right.space ?? 0) + (borders.right.width ?? 0) : ruleOutset
|
|
36
|
+
};
|
|
37
|
+
};
|
|
38
|
+
//#endregion
|
|
39
|
+
export { borderStrokeToCss, resolveCssBorderStroke, resolveParagraphBorderHorizontalOutsets };
|
|
@@ -10,7 +10,7 @@ declare const IMAGE_CLASS_NAMES: {
|
|
|
10
10
|
imageAnchored: string;
|
|
11
11
|
};
|
|
12
12
|
/**
|
|
13
|
-
* Structural shape required to apply
|
|
13
|
+
* Structural shape required to apply OOXML per-image visual attributes:
|
|
14
14
|
* `wp:srcRect` crop fractions and `a:alphaModFix` opacity. `ImageRun` and
|
|
15
15
|
* `ImageBlock` both satisfy this, so callers don't need an adapter.
|
|
16
16
|
*/
|
|
@@ -32,10 +32,11 @@ type ImageVisualAttrs = {
|
|
|
32
32
|
* `0`) — that bug otherwise hides every plain image behind `opacity: 0`.
|
|
33
33
|
*/
|
|
34
34
|
declare function hasImageVisualAttrs(v: ImageVisualAttrs): boolean;
|
|
35
|
+
declare function hasImageCrop(v: ImageVisualAttrs): boolean;
|
|
35
36
|
/**
|
|
36
|
-
* Apply
|
|
37
|
+
* Apply an OOXML `<a:srcRect>` crop to an `<img>` whose parent already has
|
|
37
38
|
* `overflow: hidden` and is sized to the *visible* (cropped) dimensions
|
|
38
|
-
* (this matches `wp:extent` semantics:
|
|
39
|
+
* (this matches `wp:extent` semantics: the extent is the cropped
|
|
39
40
|
* frame, with `<a:stretch><a:fillRect/>` stretching the cropped source to
|
|
40
41
|
* fill it).
|
|
41
42
|
*
|
|
@@ -85,4 +86,4 @@ type RenderImageFragmentOptions = {
|
|
|
85
86
|
*/
|
|
86
87
|
declare function renderImageFragment(fragment: ImageFragment, block: ImageBlock, _measure: ImageMeasure, _context: RenderContext, options?: RenderImageFragmentOptions): HTMLElement;
|
|
87
88
|
//#endregion
|
|
88
|
-
export { IMAGE_CLASS_NAMES, ImageVisualAttrs, RenderImageFragmentOptions, applyImageVisualAttrs, hasImageVisualAttrs, renderImageFragment, wrapImageWithCrop };
|
|
89
|
+
export { IMAGE_CLASS_NAMES, ImageVisualAttrs, RenderImageFragmentOptions, applyImageVisualAttrs, hasImageCrop, hasImageVisualAttrs, renderImageFragment, wrapImageWithCrop };
|
|
@@ -18,12 +18,15 @@ const IMAGE_CLASS_NAMES = {
|
|
|
18
18
|
*/
|
|
19
19
|
function hasImageVisualAttrs(v) {
|
|
20
20
|
if (v.opacity != null && v.opacity < 1) return true;
|
|
21
|
+
return hasImageCrop(v);
|
|
22
|
+
}
|
|
23
|
+
function hasImageCrop(v) {
|
|
21
24
|
return Boolean(v.cropTop || v.cropRight || v.cropBottom || v.cropLeft);
|
|
22
25
|
}
|
|
23
26
|
/**
|
|
24
|
-
* Apply
|
|
27
|
+
* Apply an OOXML `<a:srcRect>` crop to an `<img>` whose parent already has
|
|
25
28
|
* `overflow: hidden` and is sized to the *visible* (cropped) dimensions
|
|
26
|
-
* (this matches `wp:extent` semantics:
|
|
29
|
+
* (this matches `wp:extent` semantics: the extent is the cropped
|
|
27
30
|
* frame, with `<a:stretch><a:fillRect/>` stretching the cropped source to
|
|
28
31
|
* fill it).
|
|
29
32
|
*
|
|
@@ -54,8 +57,19 @@ function applyImageVisualAttrs(img, v) {
|
|
|
54
57
|
const fh = 1 / remainingH;
|
|
55
58
|
img.style.width = `${fw * 100}%`;
|
|
56
59
|
img.style.height = `${fh * 100}%`;
|
|
60
|
+
img.style.maxWidth = "none";
|
|
61
|
+
img.style.maxHeight = "none";
|
|
57
62
|
img.style.marginLeft = `${-left * fw * 100}%`;
|
|
58
|
-
img.style.
|
|
63
|
+
const existingTransform = img.style.transform;
|
|
64
|
+
if (existingTransform) {
|
|
65
|
+
const visibleCenterX = (left + 1 - right) * 50;
|
|
66
|
+
const visibleCenterY = (top + 1 - bottom) * 50;
|
|
67
|
+
img.style.transformOrigin = `${visibleCenterX}% ${visibleCenterY}%`;
|
|
68
|
+
}
|
|
69
|
+
if (top !== 0) {
|
|
70
|
+
const cropTransform = `translateY(${-top * 100}%)`;
|
|
71
|
+
img.style.transform = existingTransform ? `${existingTransform} ${cropTransform}` : cropTransform;
|
|
72
|
+
}
|
|
59
73
|
img.style.objectFit = "fill";
|
|
60
74
|
}
|
|
61
75
|
/**
|
|
@@ -127,4 +141,4 @@ function renderImageFragment(fragment, block, _measure, _context, options = {})
|
|
|
127
141
|
return containerEl;
|
|
128
142
|
}
|
|
129
143
|
//#endregion
|
|
130
|
-
export { IMAGE_CLASS_NAMES, applyImageVisualAttrs, hasImageVisualAttrs, renderImageFragment, wrapImageWithCrop };
|
|
144
|
+
export { IMAGE_CLASS_NAMES, applyImageVisualAttrs, hasImageCrop, hasImageVisualAttrs, renderImageFragment, wrapImageWithCrop };
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { document_d_exports } from "../types/document.js";
|
|
2
2
|
import { FootnoteContent, HeaderFooterContent, ImageRun, Page, isFloatingImageRun } from "../layout-engine/types.js";
|
|
3
3
|
import { RenderContext, emuToPixels } from "./renderUtils.js";
|
|
4
|
+
import { AnchoredImagePosition, PageGeometry } from "./anchoredImagePosition.js";
|
|
4
5
|
import { BlockLookup } from "./index.js";
|
|
5
6
|
|
|
6
7
|
//#region src/layout-painter/renderPage.d.ts
|
|
@@ -157,32 +158,6 @@ declare function resolveHeaderFooterFloatLeft(width: number, h: {
|
|
|
157
158
|
align?: string;
|
|
158
159
|
alignment?: string;
|
|
159
160
|
} | undefined, layout: HeaderFooterLayoutInfo): string;
|
|
160
|
-
/**
|
|
161
|
-
* Page geometry needed to translate OOXML `relativeFrom` anchors into
|
|
162
|
-
* painter coordinates. All values are in CSS pixels. Mirrors the upstream
|
|
163
|
-
* (eigenpal docx-editor) `PageGeometry` shape introduced in #424 so the
|
|
164
|
-
* anchor math stays identical across the two codebases.
|
|
165
|
-
*/
|
|
166
|
-
type PageGeometry = {
|
|
167
|
-
pageWidth: number;
|
|
168
|
-
pageHeight: number;
|
|
169
|
-
marginLeft: number;
|
|
170
|
-
marginTop: number;
|
|
171
|
-
marginRight: number;
|
|
172
|
-
marginBottom: number;
|
|
173
|
-
contentWidth: number;
|
|
174
|
-
contentHeight: number;
|
|
175
|
-
};
|
|
176
|
-
/**
|
|
177
|
-
* Resolved on-page coordinates for an anchored floating image.
|
|
178
|
-
* `x` / `y` are in content-area-relative pixels (content origin = (0, 0)).
|
|
179
|
-
* `side` feeds the text-wrap exclusion zone helper.
|
|
180
|
-
*/
|
|
181
|
-
type AnchoredImagePosition = {
|
|
182
|
-
x: number;
|
|
183
|
-
y: number;
|
|
184
|
-
side: "left" | "right";
|
|
185
|
-
};
|
|
186
161
|
/**
|
|
187
162
|
* Resolve the on-page coordinates of an anchored floating image.
|
|
188
163
|
*
|
|
@@ -290,4 +265,4 @@ declare function findPageShellForPmPos(container: HTMLElement, pmPos: number): {
|
|
|
290
265
|
isExact: boolean;
|
|
291
266
|
} | null;
|
|
292
267
|
//#endregion
|
|
293
|
-
export { AnchoredImagePosition, FootnoteRenderItem, type HeaderFooterContent, HeaderFooterLayoutInfo, PAGE_CLASS_NAMES, PAINTER_PAINTED_EVENT, PageFloatingImage, PageGeometry, PainterPaintedDetail, type RenderContext, RenderPageOptions, applySectionHeaderFooterOptions, calculateFootnoteAreaRenderHeight, computePageFingerprint, emuToPixels, findPageShellForPmPos, floatingTableReservesBand, getDefaultPageFontFamily, isFloatingImageRun, renderAllPagesNow, renderFloatingImagesLayer, renderFootnoteArea, renderPage, renderPages, resolveAnchoredImagePosition, resolveHeaderFooterFloatLeft };
|
|
268
|
+
export { type AnchoredImagePosition, FootnoteRenderItem, type HeaderFooterContent, HeaderFooterLayoutInfo, PAGE_CLASS_NAMES, PAINTER_PAINTED_EVENT, PageFloatingImage, type PageGeometry, PainterPaintedDetail, type RenderContext, RenderPageOptions, applySectionHeaderFooterOptions, calculateFootnoteAreaRenderHeight, computePageFingerprint, emuToPixels, findPageShellForPmPos, floatingTableReservesBand, getDefaultPageFontFamily, isFloatingImageRun, renderAllPagesNow, renderFloatingImagesLayer, renderFootnoteArea, renderPage, renderPages, resolveAnchoredImagePosition, resolveHeaderFooterFloatLeft };
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
import { eighthsToPixels, pointsToPixels } from "../utils/units.js";
|
|
2
2
|
import { resolveFontFamily } from "../utils/fontResolver.js";
|
|
3
|
+
import { borderToStyle } from "../utils/formatToStyle.js";
|
|
3
4
|
import { floatingTextBoxReservesBand, floatingTextBoxWrapsText, isFloatingImageRun, isFloatingTextBoxBlock, isTextWrappingFloatingImageRun } from "../layout-engine/types.js";
|
|
4
5
|
import { rectsToFloatingZones } from "../layout-engine/measure/floatingZones.js";
|
|
5
6
|
import { measureParagraph } from "../layout-engine/measure/measureParagraph.js";
|
|
6
|
-
import {
|
|
7
|
+
import { emuToPixels } from "./renderUtils.js";
|
|
8
|
+
import { resolveAnchoredImagePosition as resolveAnchoredImagePosition$1 } from "./anchoredImagePosition.js";
|
|
7
9
|
import { renderFragment } from "./renderFragment.js";
|
|
8
10
|
import { applyImageVisualAttrs, hasImageVisualAttrs, renderImageFragment } from "./renderImage.js";
|
|
9
|
-
import { emuToPixels } from "./renderUtils.js";
|
|
10
11
|
import { renderParagraphFragment } from "./renderParagraph.js";
|
|
11
12
|
import { renderTextBoxFragment } from "./renderTextBox.js";
|
|
12
13
|
import { renderTableFragment } from "./renderTable.js";
|
|
@@ -33,6 +34,7 @@ const PAGE_CLASS_NAMES = {
|
|
|
33
34
|
header: "layout-page-header",
|
|
34
35
|
footer: "layout-page-footer"
|
|
35
36
|
};
|
|
37
|
+
const HEADER_FOOTER_RULE_CLIP_MARGIN = pointsToPixels(1.5);
|
|
36
38
|
const getDefaultPageFontFamily = () => resolveFontFamily("Calibri").cssFallback;
|
|
37
39
|
/**
|
|
38
40
|
* Apply page styles to an element
|
|
@@ -271,57 +273,6 @@ function applyFragmentStyles(element, fragment, margins) {
|
|
|
271
273
|
element.style.width = `${fragment.width}px`;
|
|
272
274
|
if ("height" in fragment) element.style.height = `${fragment.height}px`;
|
|
273
275
|
}
|
|
274
|
-
function resolveHorizontalBand(relativeTo, geometry) {
|
|
275
|
-
switch (relativeTo) {
|
|
276
|
-
case "page": return {
|
|
277
|
-
baseX: -geometry.marginLeft,
|
|
278
|
-
bandWidth: geometry.pageWidth
|
|
279
|
-
};
|
|
280
|
-
case "leftMargin":
|
|
281
|
-
case "insideMargin": return {
|
|
282
|
-
baseX: -geometry.marginLeft,
|
|
283
|
-
bandWidth: geometry.marginLeft
|
|
284
|
-
};
|
|
285
|
-
case "rightMargin":
|
|
286
|
-
case "outsideMargin": return {
|
|
287
|
-
baseX: geometry.contentWidth,
|
|
288
|
-
bandWidth: geometry.marginRight
|
|
289
|
-
};
|
|
290
|
-
case "character": return {
|
|
291
|
-
baseX: 0,
|
|
292
|
-
bandWidth: 0
|
|
293
|
-
};
|
|
294
|
-
default: return {
|
|
295
|
-
baseX: 0,
|
|
296
|
-
bandWidth: geometry.contentWidth
|
|
297
|
-
};
|
|
298
|
-
}
|
|
299
|
-
}
|
|
300
|
-
function resolveVerticalBand(relativeTo, fragmentY, geometry) {
|
|
301
|
-
switch (relativeTo) {
|
|
302
|
-
case "page": return {
|
|
303
|
-
baseY: -geometry.marginTop,
|
|
304
|
-
bandHeight: geometry.pageHeight
|
|
305
|
-
};
|
|
306
|
-
case "topMargin": return {
|
|
307
|
-
baseY: -geometry.marginTop,
|
|
308
|
-
bandHeight: geometry.marginTop
|
|
309
|
-
};
|
|
310
|
-
case "bottomMargin": return {
|
|
311
|
-
baseY: geometry.contentHeight,
|
|
312
|
-
bandHeight: geometry.marginBottom
|
|
313
|
-
};
|
|
314
|
-
case "paragraph":
|
|
315
|
-
case "line": return {
|
|
316
|
-
baseY: fragmentY,
|
|
317
|
-
bandHeight: 0
|
|
318
|
-
};
|
|
319
|
-
default: return {
|
|
320
|
-
baseY: 0,
|
|
321
|
-
bandHeight: geometry.contentHeight
|
|
322
|
-
};
|
|
323
|
-
}
|
|
324
|
-
}
|
|
325
276
|
/**
|
|
326
277
|
* Resolve the on-page coordinates of an anchored floating image.
|
|
327
278
|
*
|
|
@@ -337,47 +288,7 @@ function resolveVerticalBand(relativeTo, fragmentY, geometry) {
|
|
|
337
288
|
* picked by `relativeFrom`.
|
|
338
289
|
*/
|
|
339
290
|
function resolveAnchoredImagePosition(imgRun, fragmentY, geometry) {
|
|
340
|
-
|
|
341
|
-
const contentWidth = geometry.contentWidth;
|
|
342
|
-
let side = "left";
|
|
343
|
-
let x = 0;
|
|
344
|
-
if (position?.horizontal) {
|
|
345
|
-
const h = position.horizontal;
|
|
346
|
-
const { baseX, bandWidth } = resolveHorizontalBand(h.relativeTo, geometry);
|
|
347
|
-
const horizontalHasBand = h.relativeTo !== "character";
|
|
348
|
-
if (h.align === "right" || h.align === "outside") {
|
|
349
|
-
side = "right";
|
|
350
|
-
x = horizontalHasBand ? baseX + bandWidth - imgRun.width : 0;
|
|
351
|
-
} else if (h.align === "left" || h.align === "inside") {
|
|
352
|
-
side = "left";
|
|
353
|
-
x = baseX;
|
|
354
|
-
} else if (h.align === "center") {
|
|
355
|
-
side = "left";
|
|
356
|
-
x = horizontalHasBand ? baseX + (bandWidth - imgRun.width) / 2 : 0;
|
|
357
|
-
} else if (h.posOffset !== void 0) {
|
|
358
|
-
x = baseX + emuToPixels(h.posOffset);
|
|
359
|
-
side = x > contentWidth / 2 ? "right" : "left";
|
|
360
|
-
} else x = baseX;
|
|
361
|
-
} else if (imgRun.cssFloat === "right") {
|
|
362
|
-
side = "right";
|
|
363
|
-
x = contentWidth - imgRun.width;
|
|
364
|
-
}
|
|
365
|
-
let y;
|
|
366
|
-
if (position?.vertical) {
|
|
367
|
-
const v = position.vertical;
|
|
368
|
-
const { baseY, bandHeight } = resolveVerticalBand(v.relativeTo, fragmentY, geometry);
|
|
369
|
-
const verticalHasBand = v.relativeTo !== "paragraph" && v.relativeTo !== "line";
|
|
370
|
-
if (v.align === "top" || v.align === "inside") y = baseY;
|
|
371
|
-
else if (v.align === "center") y = verticalHasBand ? baseY + (bandHeight - imgRun.height) / 2 : fragmentY;
|
|
372
|
-
else if (v.align === "bottom" || v.align === "outside") y = verticalHasBand ? baseY + bandHeight - imgRun.height : fragmentY;
|
|
373
|
-
else if (v.posOffset !== void 0) y = baseY + emuToPixels(v.posOffset);
|
|
374
|
-
else y = verticalHasBand ? baseY : fragmentY;
|
|
375
|
-
} else y = fragmentY;
|
|
376
|
-
return {
|
|
377
|
-
x,
|
|
378
|
-
y,
|
|
379
|
-
side
|
|
380
|
-
};
|
|
291
|
+
return resolveAnchoredImagePosition$1(imgRun, fragmentY, geometry);
|
|
381
292
|
}
|
|
382
293
|
/**
|
|
383
294
|
* Extract floating images from a paragraph block and determine their page-level positions.
|
|
@@ -1011,7 +922,10 @@ function renderPage(page, context, options = {}) {
|
|
|
1011
922
|
});
|
|
1012
923
|
prevParagraphBorders = paragraphBlock.attrs?.borders;
|
|
1013
924
|
} else if (fragment.kind === "table" && blockData?.block.kind === "table" && blockData.measure.kind === "table") {
|
|
1014
|
-
fragmentEl = renderTableFragment(fragment, blockData.block, blockData.measure, fragmentContext, {
|
|
925
|
+
fragmentEl = renderTableFragment(fragment, blockData.block, blockData.measure, fragmentContext, {
|
|
926
|
+
document: doc,
|
|
927
|
+
pageGeometry
|
|
928
|
+
});
|
|
1015
929
|
prevParagraphBorders = void 0;
|
|
1016
930
|
} else if (fragment.kind === "image" && blockData?.block.kind === "image" && blockData.measure.kind === "image") {
|
|
1017
931
|
fragmentEl = renderImageFragment(fragment, blockData.block, blockData.measure, fragmentContext, { document: doc });
|
|
@@ -1086,7 +1000,6 @@ function renderPage(page, context, options = {}) {
|
|
|
1086
1000
|
headerEl.style.width = `${headerContentWidth}px`;
|
|
1087
1001
|
headerEl.style.height = `${interactiveHeaderHeight}px`;
|
|
1088
1002
|
headerEl.style.minHeight = `${interactiveHeaderHeight}px`;
|
|
1089
|
-
headerEl.style.opacity = "0.62";
|
|
1090
1003
|
let shouldClipHeader = !headerOverflows && headerContentFitsBox;
|
|
1091
1004
|
if (options.headerContent && options.headerContent.blocks.length > 0) {
|
|
1092
1005
|
const headerContentEl = renderHeaderFooterContent(options.headerContent, {
|
|
@@ -1107,7 +1020,8 @@ function renderPage(page, context, options = {}) {
|
|
|
1107
1020
|
}
|
|
1108
1021
|
if (shouldClipHeader) {
|
|
1109
1022
|
headerEl.style.maxHeight = `${availableHeaderHeight}px`;
|
|
1110
|
-
headerEl.style.overflow = "
|
|
1023
|
+
headerEl.style.overflow = "clip";
|
|
1024
|
+
headerEl.style.overflowClipMargin = `${HEADER_FOOTER_RULE_CLIP_MARGIN}px`;
|
|
1111
1025
|
}
|
|
1112
1026
|
pageEl.append(headerEl);
|
|
1113
1027
|
moveBehindHeaderFooterElementsToPage(headerEl, pageEl, headerDistance, page.margins.left);
|
|
@@ -1132,7 +1046,6 @@ function renderPage(page, context, options = {}) {
|
|
|
1132
1046
|
footerEl.style.width = `${footerContentWidth}px`;
|
|
1133
1047
|
footerEl.style.height = `${interactiveFooterHeight}px`;
|
|
1134
1048
|
footerEl.style.minHeight = `${interactiveFooterHeight}px`;
|
|
1135
|
-
footerEl.style.opacity = "0.62";
|
|
1136
1049
|
const footerContentFitsBox = footerVisualBottom - footerVisualTop <= interactiveFooterHeight;
|
|
1137
1050
|
let shouldClipFooter = !footerOverflows && footerContentFitsBox;
|
|
1138
1051
|
if (options.footerContent && options.footerContent.blocks.length > 0) {
|
|
@@ -1154,7 +1067,8 @@ function renderPage(page, context, options = {}) {
|
|
|
1154
1067
|
}
|
|
1155
1068
|
if (shouldClipFooter) {
|
|
1156
1069
|
footerEl.style.maxHeight = `${availableFooterHeight}px`;
|
|
1157
|
-
footerEl.style.overflow = "
|
|
1070
|
+
footerEl.style.overflow = "clip";
|
|
1071
|
+
footerEl.style.overflowClipMargin = `${HEADER_FOOTER_RULE_CLIP_MARGIN}px`;
|
|
1158
1072
|
}
|
|
1159
1073
|
pageEl.append(footerEl);
|
|
1160
1074
|
moveBehindHeaderFooterElementsToPage(footerEl, pageEl, footerNaturalTop, page.margins.left);
|
|
@@ -1188,12 +1102,12 @@ function applySectionHeaderFooterOptions(page, pageOptions, options) {
|
|
|
1188
1102
|
const useEven = refs.evenAndOddHeaders === true && sectionPageNumber % 2 === 0;
|
|
1189
1103
|
const headerRId = (() => {
|
|
1190
1104
|
if (useFirst) return refs.headerFirst;
|
|
1191
|
-
if (useEven
|
|
1105
|
+
if (useEven) return refs.headerEven;
|
|
1192
1106
|
return refs.headerDefault;
|
|
1193
1107
|
})();
|
|
1194
1108
|
const footerRId = (() => {
|
|
1195
1109
|
if (useFirst) return refs.footerFirst;
|
|
1196
|
-
if (useEven
|
|
1110
|
+
if (useEven) return refs.footerEven;
|
|
1197
1111
|
return refs.footerDefault;
|
|
1198
1112
|
})();
|
|
1199
1113
|
if (headerRId) {
|
|
@@ -1,19 +1,22 @@
|
|
|
1
1
|
import { parseXmlDocument } from "../docx/xmlParser.js";
|
|
2
2
|
import { resolveFontFamily } from "../utils/fontResolver.js";
|
|
3
|
+
import { detectBaseDirection } from "../utils/baseDirection.js";
|
|
4
|
+
import { AUTHOR_COLORS, getAuthorColorIdx } from "../utils/authorColors.js";
|
|
3
5
|
import "../utils/fontWeights.js";
|
|
6
|
+
import { FONT_KERNING_MODE, countCompressibleSpaces, getFontKerningMode, getRunFontKerningMode } from "../layout-engine/measure/textMeasurementPolicy.js";
|
|
7
|
+
import "../layout-engine/measure/measureHelpers.js";
|
|
4
8
|
import { calculateTabWidth } from "../prosemirror/utils/tabCalculator.js";
|
|
5
9
|
import { inlineImageBoundingBox, parseRotationDegrees, rotatedBoundingBox } from "../utils/rotationBoundingBox.js";
|
|
6
10
|
import { hasCjk, segmentByScript } from "../utils/scriptSegments.js";
|
|
7
11
|
import { isFloatingImageRun } from "../layout-engine/types.js";
|
|
8
|
-
import { getListMarkerInlineWidth } from "../layout-engine/measure/listMarkerWidth.js";
|
|
12
|
+
import { getListMarkerInlineWidth, getListMarkerVisualOffset } from "../layout-engine/measure/listMarkerWidth.js";
|
|
13
|
+
import { resolveImageLineAlign } from "./renderUtils.js";
|
|
9
14
|
import { applySdtDataAttrs } from "./sdtBoundary.js";
|
|
10
15
|
import { applyImageVisualAttrs, hasImageVisualAttrs, wrapImageWithCrop } from "./renderImage.js";
|
|
11
16
|
import { ommlToMathml } from "../docx/mathToMathml.js";
|
|
12
17
|
import { evaluateFieldInstruction } from "../fields/evaluateField.js";
|
|
13
|
-
import {
|
|
14
|
-
import { detectBaseDirection } from "../utils/baseDirection.js";
|
|
18
|
+
import { borderStrokeToCss, resolveParagraphBorderHorizontalOutsets } from "./borderStroke.js";
|
|
15
19
|
import { getAutomaticTextColorForBackground } from "./documentColors.js";
|
|
16
|
-
import { resolveImageLineAlign } from "./renderUtils.js";
|
|
17
20
|
//#region src/layout-painter/renderParagraph.ts
|
|
18
21
|
/**
|
|
19
22
|
* Paragraph Fragment Renderer
|
|
@@ -128,9 +131,7 @@ function applyRunStyles(element, run) {
|
|
|
128
131
|
element.style.transform = `scaleX(${run.horizontalScale / 100})`;
|
|
129
132
|
element.style.transformOrigin = "left center";
|
|
130
133
|
}
|
|
131
|
-
|
|
132
|
-
if ((run.fontSize ?? 11) >= run.kerningMinPt) element.style.fontKerning = "normal";
|
|
133
|
-
}
|
|
134
|
+
element.style.fontKerning = getRunFontKerningMode(run, 11);
|
|
134
135
|
if (run.emboss) element.style.textShadow = "1px 1px 1px rgba(255,255,255,0.5), -1px -1px 1px rgba(0,0,0,0.3)";
|
|
135
136
|
if (run.imprint) element.style.textShadow = "-1px -1px 1px rgba(255,255,255,0.5), 1px 1px 1px rgba(0,0,0,0.3)";
|
|
136
137
|
if (run.textShadow && !run.emboss && !run.imprint) element.style.textShadow = "1px 1px 2px rgba(0,0,0,0.3)";
|
|
@@ -785,19 +786,14 @@ const startsAfterSoftWrap = (block, line) => {
|
|
|
785
786
|
* must trigger within this slack.
|
|
786
787
|
*/
|
|
787
788
|
const RIGHT_EDGE_EPSILON_PX = .5;
|
|
788
|
-
function countShrinkableSpaces(runs) {
|
|
789
|
+
function countShrinkableSpaces(runs, context) {
|
|
789
790
|
let count = 0;
|
|
790
|
-
for (const run of runs) if (isTextRun(run))
|
|
791
|
-
|
|
792
|
-
} else if (isFieldRun(run)) {
|
|
793
|
-
for (const char of run.fallback ?? "") if (char === " ") count++;
|
|
794
|
-
} else if (isMathRun(run)) {
|
|
795
|
-
for (const char of run.plainText ?? "") if (char === " ") count++;
|
|
796
|
-
}
|
|
791
|
+
for (const run of runs) if (isTextRun(run)) count += countCompressibleSpaces(run.text ?? "");
|
|
792
|
+
else if (isFieldRun(run)) count += countCompressibleSpaces(resolveFieldText(run, context));
|
|
797
793
|
return count;
|
|
798
794
|
}
|
|
799
795
|
/**
|
|
800
|
-
* Build
|
|
796
|
+
* Build the shared measurement style from a paintable text run.
|
|
801
797
|
*/
|
|
802
798
|
function runMeasureStyle(run) {
|
|
803
799
|
return {
|
|
@@ -805,7 +801,8 @@ function runMeasureStyle(run) {
|
|
|
805
801
|
...run.italic !== void 0 ? { italic: run.italic } : {},
|
|
806
802
|
...run.letterSpacing !== void 0 ? { letterSpacing: run.letterSpacing } : {},
|
|
807
803
|
...run.smallCaps !== void 0 ? { smallCaps: run.smallCaps } : {},
|
|
808
|
-
...run.eastAsiaFontFamily !== void 0 ? { eastAsiaFontFamily: run.eastAsiaFontFamily } : {}
|
|
804
|
+
...run.eastAsiaFontFamily !== void 0 ? { eastAsiaFontFamily: run.eastAsiaFontFamily } : {},
|
|
805
|
+
kerning: getRunFontKerningMode(run, 11) === FONT_KERNING_MODE.enabled
|
|
809
806
|
};
|
|
810
807
|
}
|
|
811
808
|
/**
|
|
@@ -910,6 +907,7 @@ function createTextMeasurer(doc) {
|
|
|
910
907
|
const ctx = doc.createElement("canvas").getContext("2d");
|
|
911
908
|
return (text, fontSize = 11, fontFamily = "Calibri", style = {}) => {
|
|
912
909
|
if (!ctx) return applyLetterSpacingToMeasuredWidth(text.length * 7, text, style.letterSpacing);
|
|
910
|
+
ctx.fontKerning = getFontKerningMode(style);
|
|
913
911
|
const cssFallback = resolveFontFamily(fontFamily).cssFallback;
|
|
914
912
|
const fontSizePx = fontSize * 96 / 72;
|
|
915
913
|
const fontPrefixParts = [];
|
|
@@ -996,7 +994,7 @@ function renderLine(block, line, alignment, doc, options) {
|
|
|
996
994
|
const firstLineHangingExpansionPx = hasVisibleListMarker ? Math.min(firstLineHangingPx, Math.max(0, options.leftIndentPx ?? 0)) : firstLineHangingPx;
|
|
997
995
|
const justifyCapacityPx = options.availableWidth + firstLineHangingExpansionPx;
|
|
998
996
|
const overfullPx = line.width - justifyCapacityPx;
|
|
999
|
-
const shrinkableSpaces = countShrinkableSpaces(runsForLine.filter((run) => !isTextRun(run) || !isCollapsedLineEdgeSpaceRun(run)));
|
|
997
|
+
const shrinkableSpaces = countShrinkableSpaces(runsForLine.filter((run) => !isTextRun(run) || !isCollapsedLineEdgeSpaceRun(run)), options.context);
|
|
1000
998
|
if (overfullPx > RIGHT_EDGE_EPSILON_PX && shrinkableSpaces > 0) {
|
|
1001
999
|
lineEl.style.textAlign = "left";
|
|
1002
1000
|
lineEl.style.textAlignLast = "auto";
|
|
@@ -1134,6 +1132,20 @@ function renderLine(block, line, alignment, doc, options) {
|
|
|
1134
1132
|
lineEl.append(runEl);
|
|
1135
1133
|
}
|
|
1136
1134
|
}
|
|
1135
|
+
if (line.discretionaryHyphen) {
|
|
1136
|
+
const sourceRun = block.runs[line.discretionaryHyphen.runIndex];
|
|
1137
|
+
if (sourceRun && isTextRun(sourceRun)) {
|
|
1138
|
+
const hyphenRun = {
|
|
1139
|
+
...sourceRun,
|
|
1140
|
+
text: "-"
|
|
1141
|
+
};
|
|
1142
|
+
Reflect.deleteProperty(hyphenRun, "pmStart");
|
|
1143
|
+
Reflect.deleteProperty(hyphenRun, "pmEnd");
|
|
1144
|
+
const hyphenEl = renderTextRun(hyphenRun, doc);
|
|
1145
|
+
hyphenEl.dataset["discretionaryHyphen"] = "true";
|
|
1146
|
+
lineEl.append(hyphenEl);
|
|
1147
|
+
}
|
|
1148
|
+
}
|
|
1137
1149
|
const inFlowRuns = runsForLine.filter((run) => !(isImageRun(run) && isFloatingImageRun(run)));
|
|
1138
1150
|
const blankBreakRun = inFlowRuns.length > 0 && inFlowRuns.every(isLineBreakRun) ? inFlowRuns.find(isLineBreakRun) : void 0;
|
|
1139
1151
|
if (blankBreakRun?.pmStart !== void 0) {
|
|
@@ -1239,7 +1251,10 @@ function renderParagraphFragment(fragment, block, measure, context, options = {}
|
|
|
1239
1251
|
}
|
|
1240
1252
|
};
|
|
1241
1253
|
fragmentEl.style.boxSizing = "border-box";
|
|
1242
|
-
const borderToCss = (
|
|
1254
|
+
const borderToCss = (border) => borderStrokeToCss({
|
|
1255
|
+
...border,
|
|
1256
|
+
style: borderStyleToCss(border.style)
|
|
1257
|
+
});
|
|
1243
1258
|
const groupedWithPrev = bordersFormGroup(options.prevBorders, borders);
|
|
1244
1259
|
const groupedWithNext = bordersFormGroup(borders, options.nextBorders);
|
|
1245
1260
|
const renderedTopBorder = groupedWithPrev ? borders.between : borders.top;
|
|
@@ -1249,8 +1264,9 @@ function renderParagraphFragment(fragment, block, measure, context, options = {}
|
|
|
1249
1264
|
borderBox.style.position = "absolute";
|
|
1250
1265
|
borderBox.style.pointerEvents = "none";
|
|
1251
1266
|
borderBox.style.boxSizing = "border-box";
|
|
1252
|
-
|
|
1253
|
-
borderBox.style.
|
|
1267
|
+
const horizontalOutsets = resolveParagraphBorderHorizontalOutsets(borders, renderedTopBorder !== void 0 || renderedBottomBorder !== void 0);
|
|
1268
|
+
borderBox.style.left = `${indentLeft - horizontalOutsets.left}px`;
|
|
1269
|
+
borderBox.style.right = `${indentRight - horizontalOutsets.right}px`;
|
|
1254
1270
|
borderBox.style.top = `${-(renderedTopBorder?.space ?? 0) - (renderedTopBorder?.width ?? 0)}px`;
|
|
1255
1271
|
borderBox.style.bottom = `${-(renderedBottomBorder?.space ?? 0) - (renderedBottomBorder?.width ?? 0)}px`;
|
|
1256
1272
|
if (renderedTopBorder) borderBox.style.borderTop = borderToCss(renderedTopBorder);
|
|
@@ -1342,7 +1358,7 @@ function renderParagraphFragment(fragment, block, measure, context, options = {}
|
|
|
1342
1358
|
lineEl.style.paddingLeft = `${Math.max(0, markerStart)}px`;
|
|
1343
1359
|
lineEl.style.textIndent = "0";
|
|
1344
1360
|
let firstTextRun;
|
|
1345
|
-
if (!block.attrs.listMarkerFontFamily || !block.attrs.listMarkerFontSize) for (let ri = line.fromRun; ri <= line.toRun; ri++) {
|
|
1361
|
+
if (!block.attrs.listMarkerFontFamily || !block.attrs.listMarkerFontSize || block.attrs.listMarkerBold === void 0) for (let ri = line.fromRun; ri <= line.toRun; ri++) {
|
|
1346
1362
|
const r = block.runs[ri];
|
|
1347
1363
|
if (r && r.kind === "text") {
|
|
1348
1364
|
firstTextRun = r;
|
|
@@ -1351,7 +1367,8 @@ function renderParagraphFragment(fragment, block, measure, context, options = {}
|
|
|
1351
1367
|
}
|
|
1352
1368
|
const markerFontFamily = block.attrs.listMarkerFontFamily ?? firstTextRun?.fontFamily ?? block.attrs.defaultFontFamily;
|
|
1353
1369
|
const markerFontSize = block.attrs.listMarkerFontSize ?? firstTextRun?.fontSize ?? block.attrs.defaultFontSize;
|
|
1354
|
-
const
|
|
1370
|
+
const markerBold = block.attrs.listMarkerBold ?? firstTextRun?.bold;
|
|
1371
|
+
const marker = renderListMarker(block.attrs.listMarker, getListMarkerInlineWidth(block), getListMarkerVisualOffset(block), doc, markerFontFamily, markerFontSize, markerBold, block.attrs.listMarkerRevision, block.attrs.listMarkerSecondSlotOffsetTwips);
|
|
1355
1372
|
const markerMarginLeft = markerStart - Math.min(indentLeft, 0);
|
|
1356
1373
|
if (markerMarginLeft < 0) marker.style.marginLeft = `${markerMarginLeft}px`;
|
|
1357
1374
|
lineEl.prepend(marker);
|
|
@@ -1362,22 +1379,24 @@ function renderParagraphFragment(fragment, block, measure, context, options = {}
|
|
|
1362
1379
|
}
|
|
1363
1380
|
/**
|
|
1364
1381
|
* Render a list marker element as an inline-block at the start of the first
|
|
1365
|
-
* body line. `
|
|
1382
|
+
* body line. `inlineWidth` (from `getListMarkerInlineWidth`) sizes the marker
|
|
1366
1383
|
* so the body text aligns at the next tab stop per ECMA-376 §17.9.25 —
|
|
1367
1384
|
* this honours `w:suff` (`tab` / `space` / `nothing`) and the document's
|
|
1368
1385
|
* tab grid. Long markers like "1.1.1." therefore grow to the next stop
|
|
1369
1386
|
* instead of butting against the body text.
|
|
1370
1387
|
*/
|
|
1371
|
-
function renderListMarker(marker,
|
|
1388
|
+
function renderListMarker(marker, inlineWidth, visualOffset, doc, fontFamily, fontSize, bold, revision, secondSlotOffsetTwips) {
|
|
1372
1389
|
const span = doc.createElement("span");
|
|
1373
1390
|
span.className = "layout-list-marker";
|
|
1374
1391
|
span.style.display = "inline-block";
|
|
1375
1392
|
if (fontFamily) span.style.fontFamily = resolveFontFamily(fontFamily).cssFallback;
|
|
1376
1393
|
if (fontSize) span.style.fontSize = `${fontSize * 96 / 72}px`;
|
|
1394
|
+
if (bold !== void 0) span.style.fontWeight = bold ? "800" : "normal";
|
|
1377
1395
|
span.style.textAlign = "left";
|
|
1378
1396
|
span.style.textAlignLast = "left";
|
|
1379
1397
|
span.style.boxSizing = "border-box";
|
|
1380
|
-
|
|
1398
|
+
span.style.width = `${inlineWidth}px`;
|
|
1399
|
+
if (visualOffset !== 0) span.style.transform = `translateX(${visualOffset}px)`;
|
|
1381
1400
|
if (revision) {
|
|
1382
1401
|
const authorIdx = getAuthorColorIdx(revision.author ?? "");
|
|
1383
1402
|
const authorColor = AUTHOR_COLORS[authorIdx];
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { TableBlock, TableFragment, TableMeasure } from "../layout-engine/types.js";
|
|
2
2
|
import { RenderContext } from "./renderUtils.js";
|
|
3
|
+
import { PageGeometry } from "./anchoredImagePosition.js";
|
|
3
4
|
|
|
4
5
|
//#region src/layout-painter/renderTable.d.ts
|
|
5
6
|
/**
|
|
@@ -20,6 +21,7 @@ declare const TABLE_CLASS_NAMES: {
|
|
|
20
21
|
*/
|
|
21
22
|
type RenderTableFragmentOptions = {
|
|
22
23
|
document?: Document;
|
|
24
|
+
pageGeometry?: PageGeometry;
|
|
23
25
|
};
|
|
24
26
|
/**
|
|
25
27
|
* Render a table fragment to DOM
|