@stll/folio-core 0.32.2 → 0.33.1
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/ai-edits/__fixtures__/paragraphs.d.ts +17 -0
- package/dist/ai-edits/__fixtures__/paragraphs.js +33 -0
- package/dist/ai-edits/apply.d.ts +49 -3
- package/dist/ai-edits/apply.js +652 -86
- package/dist/ai-edits/blockRange.d.ts +7 -3
- package/dist/ai-edits/blockRange.js +14 -6
- package/dist/ai-edits/headless.d.ts +39 -5
- package/dist/ai-edits/headless.js +76 -17
- package/dist/ai-edits/index.d.ts +6 -6
- package/dist/ai-edits/index.js +3 -3
- package/dist/ai-edits/read.d.ts +4 -0
- package/dist/ai-edits/read.js +30 -6
- package/dist/ai-edits/scoped-reading.js +4 -3
- package/dist/ai-edits/snapshot.d.ts +42 -2
- package/dist/ai-edits/snapshot.js +127 -62
- package/dist/ai-edits/table-row-column-mutations.d.ts +24 -1
- package/dist/ai-edits/table-row-column-mutations.js +98 -6
- package/dist/ai-edits/table-targets.d.ts +12 -3
- package/dist/ai-edits/table-targets.js +15 -5
- package/dist/ai-edits/types.d.ts +219 -15
- package/dist/ai-edits/word-diff.d.ts +54 -11
- package/dist/ai-edits/word-diff.js +221 -61
- package/dist/compare/__fixtures__/body-sequence.d.ts +56 -0
- package/dist/compare/__fixtures__/body-sequence.js +136 -0
- package/dist/compare/__fixtures__/nested-table.d.ts +16 -0
- package/dist/compare/__fixtures__/nested-table.js +50 -0
- package/dist/compare/__fixtures__/numbered-list.d.ts +34 -0
- package/dist/compare/__fixtures__/numbered-list.js +85 -0
- package/dist/compare/column-alignment.d.ts +27 -0
- package/dist/compare/column-alignment.js +162 -0
- package/dist/compare/compare.d.ts +107 -0
- package/dist/compare/compare.js +412 -0
- package/dist/compare/formatting.d.ts +29 -0
- package/dist/compare/formatting.js +84 -0
- package/dist/compare/plan.d.ts +22 -0
- package/dist/compare/plan.js +1141 -0
- package/dist/compare/reproducible-package.d.ts +21 -0
- package/dist/compare/reproducible-package.js +47 -0
- package/dist/compare/scenario.d.ts +77 -0
- package/dist/compare/scenario.js +263 -0
- package/dist/compare/types.d.ts +297 -0
- package/dist/compare/types.js +42 -0
- package/dist/compare/verification.d.ts +83 -0
- package/dist/compare/verification.js +189 -0
- package/dist/compat/eigenpal.d.ts +9 -6
- package/dist/compat/eigenpal.js +6 -3
- package/dist/controller/layoutPipeline.d.ts +13 -0
- package/dist/controller/layoutPipeline.js +15 -1
- package/dist/display-list/build/buildContext.d.ts +39 -0
- package/dist/display-list/build/buildContext.js +31 -0
- package/dist/display-list/build/buildDisplayList.d.ts +79 -0
- package/dist/display-list/build/buildDisplayList.js +283 -0
- package/dist/display-list/build/colors.d.ts +20 -0
- package/dist/display-list/build/colors.js +136 -0
- package/dist/display-list/build/floatingImages.d.ts +24 -0
- package/dist/display-list/build/floatingImages.js +41 -0
- package/dist/display-list/build/fontTable.d.ts +31 -0
- package/dist/display-list/build/fontTable.js +97 -0
- package/dist/display-list/build/furniture.d.ts +86 -0
- package/dist/display-list/build/furniture.js +98 -0
- package/dist/display-list/build/glyphs.d.ts +55 -0
- package/dist/display-list/build/glyphs.js +103 -0
- package/dist/display-list/build/headerFooterPrimitives.d.ts +17 -0
- package/dist/display-list/build/headerFooterPrimitives.js +40 -0
- package/dist/display-list/build/imagePrimitives.d.ts +35 -0
- package/dist/display-list/build/imagePrimitives.js +264 -0
- package/dist/display-list/build/pageBorderPrimitives.d.ts +23 -0
- package/dist/display-list/build/pageBorderPrimitives.js +111 -0
- package/dist/display-list/build/pageFurniture.d.ts +35 -0
- package/dist/display-list/build/pageFurniture.js +141 -0
- package/dist/display-list/build/paragraphPrimitives.d.ts +19 -0
- package/dist/display-list/build/paragraphPrimitives.js +819 -0
- package/dist/display-list/build/regions.d.ts +46 -0
- package/dist/display-list/build/regions.js +56 -0
- package/dist/display-list/build/storyPrimitives.d.ts +29 -0
- package/dist/display-list/build/storyPrimitives.js +279 -0
- package/dist/display-list/build/strokes.d.ts +19 -0
- package/dist/display-list/build/strokes.js +78 -0
- package/dist/display-list/build/tablePrimitives.d.ts +38 -0
- package/dist/display-list/build/tablePrimitives.js +409 -0
- package/dist/display-list/build/textBoxPrimitives.d.ts +14 -0
- package/dist/display-list/build/textBoxPrimitives.js +129 -0
- package/dist/display-list/build/textDecorations.d.ts +29 -0
- package/dist/display-list/build/textDecorations.js +29 -0
- package/dist/display-list/build/unsupported.d.ts +57 -0
- package/dist/display-list/build/unsupported.js +0 -0
- package/dist/display-list/build/watermarkPrimitives.d.ts +15 -0
- package/dist/display-list/build/watermarkPrimitives.js +128 -0
- package/dist/display-list/dom/renderDisplayListToDom.d.ts +26 -0
- package/dist/display-list/dom/renderDisplayListToDom.js +620 -0
- package/dist/display-list/editor/displayListPagePainter.d.ts +22 -0
- package/dist/display-list/editor/displayListPagePainter.js +63 -0
- package/dist/display-list/editor/pageRenderer.d.ts +24 -0
- package/dist/display-list/editor/pageRenderer.js +36 -0
- package/dist/display-list/primitives.d.ts +95 -0
- package/dist/display-list/primitives.js +127 -0
- package/dist/display-list/types.d.ts +472 -0
- package/dist/display-list/types.js +0 -0
- package/dist/document-operations.d.ts +39 -8
- package/dist/document-operations.js +169 -16
- package/dist/document-stories.d.ts +16 -0
- package/dist/document-stories.js +48 -4
- package/dist/docx/hyperlinkParser.d.ts +9 -1
- package/dist/docx/hyperlinkParser.js +19 -13
- package/dist/docx/packageParts.d.ts +35 -0
- package/dist/docx/packageParts.js +120 -0
- package/dist/docx/paraIdRangeNormalization.d.ts +40 -0
- package/dist/docx/paraIdRangeNormalization.js +64 -0
- package/dist/docx/paragraphParser.js +101 -26
- package/dist/docx/paragraphTraversal.js +1 -1
- package/dist/docx/renderedPageBreakNormalization.js +3 -1
- package/dist/docx/revisionIdNormalization.d.ts +27 -0
- package/dist/docx/revisionIdNormalization.js +121 -0
- package/dist/docx/rezip.d.ts +6 -0
- package/dist/docx/rezip.js +51 -19
- package/dist/docx/selectiveSave.js +2 -2
- package/dist/docx/serializer/commentSerializer.js +6 -1
- package/dist/docx/serializer/paragraphSerializer.js +73 -20
- package/dist/docx/serializer/runSerializer.js +11 -10
- package/dist/docx/serializer/tableSerializer.js +26 -10
- package/dist/docx/server/applyDocxXmlPatchProposal.js +1 -1
- package/dist/docx/server/createBilingualDocx.js +3 -2
- package/dist/docx/streamingXmlParser.d.ts +13 -1
- package/dist/docx/streamingXmlParser.js +49 -11
- package/dist/docx/unzip.d.ts +1 -2
- package/dist/docx/unzip.js +25 -6
- package/dist/docx/xmlParser.d.ts +3 -1
- package/dist/docx/xmlParser.js +33 -27
- package/dist/export-pdf.d.ts +43 -0
- package/dist/export-pdf.js +98 -0
- package/dist/fonts/headlessMeasure.d.ts +76 -0
- package/dist/fonts/headlessMeasure.js +0 -0
- package/dist/fonts/sfnt/parse.d.ts +51 -0
- package/dist/fonts/sfnt/parse.js +525 -0
- package/dist/fonts/sfnt/subset.d.ts +20 -0
- package/dist/fonts/sfnt/subset.js +350 -0
- package/dist/fonts/sfnt/tables.d.ts +88 -0
- package/dist/fonts/sfnt/tables.js +131 -0
- package/dist/fonts/sfnt/woff.d.ts +14 -0
- package/dist/fonts/sfnt/woff.js +163 -0
- package/dist/generated/text_shaper.js +324 -0
- package/dist/generated/text_shaper_bg.wasm +0 -0
- package/dist/headless-layout.d.ts +75 -0
- package/dist/headless-layout.js +350 -0
- package/dist/index.d.ts +9 -6
- package/dist/index.js +6 -3
- package/dist/layout-bridge/convert/footnoteLayout.d.ts +1 -0
- package/dist/layout-bridge/convert/footnoteLayout.js +1 -0
- package/dist/layout-bridge/convert/headerFooterLayout.d.ts +1 -0
- package/dist/layout-bridge/convert/headerFooterLayout.js +2 -0
- package/dist/layout-bridge/convert/toFlowBlocks.d.ts +3 -1
- package/dist/layout-bridge/convert/toFlowBlocks.js +2 -0
- package/dist/layout-engine/measure/advanceComposition.d.ts +62 -0
- package/dist/layout-engine/measure/advanceComposition.js +148 -0
- package/dist/layout-engine/measure/cache.d.ts +2 -0
- package/dist/layout-engine/measure/measureContainer.js +13 -0
- package/dist/layout-engine/measure/measureParagraph.js +1 -2
- package/dist/layout-engine/measure/measureTypes.d.ts +12 -0
- package/dist/layout-engine/measure/tableInlinePlacement.d.ts +3 -2
- package/dist/layout-engine/measure/tableInlinePlacement.js +14 -1
- package/dist/layout-engine/tableIndentCompatibility.d.ts +5 -0
- package/dist/layout-engine/tableIndentCompatibility.js +15 -0
- package/dist/layout-engine/types.d.ts +8 -0
- package/dist/layout-painter/renderPage.d.ts +29 -1
- package/dist/layout-painter/renderPage.js +47 -19
- package/dist/layout-painter/renderParagraph.d.ts +21 -2
- package/dist/layout-painter/renderParagraph.js +1 -1
- package/dist/layout-painter/renderTable.js +37 -4
- package/dist/managers/editorShortcuts.d.ts +33 -1
- package/dist/managers/editorShortcuts.js +20 -1
- package/dist/markdown/renderRuns.js +7 -2
- package/dist/markdown/renderTable.js +5 -1
- package/dist/model.d.ts +3 -3
- package/dist/model.js +2 -2
- package/dist/pdf/contentStream.d.ts +73 -0
- package/dist/pdf/contentStream.js +143 -0
- package/dist/pdf/fonts.d.ts +96 -0
- package/dist/pdf/fonts.js +593 -0
- package/dist/pdf/images.d.ts +28 -0
- package/dist/pdf/images.js +439 -0
- package/dist/pdf/objects.d.ts +93 -0
- package/dist/pdf/objects.js +237 -0
- package/dist/pdf/pageSpace.d.ts +43 -0
- package/dist/pdf/pageSpace.js +57 -0
- package/dist/pdf/paint.d.ts +32 -0
- package/dist/pdf/paint.js +442 -0
- package/dist/pdf/writePdf.d.ts +56 -0
- package/dist/pdf/writePdf.js +345 -0
- package/dist/prosemirror/commands/comments.js +96 -6
- package/dist/prosemirror/conversion/fromProseDoc.js +41 -7
- package/dist/prosemirror/conversion/toProseDoc.js +41 -4
- package/dist/prosemirror/extensions/features/ParaIdAllocatorExtension.d.ts +15 -2
- package/dist/prosemirror/extensions/features/ParaIdAllocatorExtension.js +35 -6
- package/dist/prosemirror/extensions/features/ParagraphChangeTrackerExtension.js +2 -2
- package/dist/prosemirror/extensions/features/pasteCleanup.js +4 -3
- package/dist/prosemirror/extensions/nodes/BookmarkBoundaryExtension.d.ts +3 -1
- package/dist/prosemirror/extensions/nodes/BookmarkBoundaryExtension.js +5 -3
- package/dist/prosemirror/extensions/nodes/RenderedPageBreakExtension.d.ts +7 -1
- package/dist/prosemirror/extensions/nodes/RenderedPageBreakExtension.js +9 -3
- package/dist/prosemirror/extensions/nodes/TableExtension.js +56 -39
- package/dist/prosemirror/extensions/nodes/TextBoxAnchorExtension.d.ts +3 -1
- package/dist/prosemirror/extensions/nodes/TextBoxAnchorExtension.js +5 -3
- package/dist/prosemirror/plugins/suggestionMode.js +1 -0
- package/dist/prosemirror/revisionCarriers.js +19 -2
- package/dist/prosemirror/utils/extractTrackedChanges.d.ts +2 -2
- package/dist/prosemirror/utils/extractTrackedChanges.js +59 -23
- package/dist/prosemirror/validation.js +55 -27
- package/dist/prosemirror/zeroWidthAnchors.d.ts +10 -0
- package/dist/prosemirror/zeroWidthAnchors.js +23 -0
- package/dist/redline.js +12 -79
- package/dist/server.d.ts +3 -3
- package/dist/server.js +2 -2
- package/dist/shaping/placeRun.d.ts +51 -0
- package/dist/shaping/placeRun.js +84 -0
- package/dist/shaping/shaper.d.ts +92 -0
- package/dist/shaping/shaper.js +102 -0
- package/dist/types/block-id.d.ts +18 -2
- package/dist/types/block-id.js +49 -5
- package/dist/types/content.d.ts +2 -2
- package/dist/utils/clipboard.js +4 -3
- package/dist/utils/fontResolver.js +183 -0
- package/dist/utils/htmlComments.d.ts +17 -0
- package/dist/utils/htmlComments.js +22 -0
- package/dist/utils/scriptSegments.d.ts +12 -1
- package/dist/utils/scriptSegments.js +17 -1
- package/dist/version-comparison.d.ts +1 -1
- package/package.json +7 -3
|
@@ -0,0 +1,442 @@
|
|
|
1
|
+
import { STROKE_DASH_FACTORS, glyphCellOffsetsPx } from "../display-list/primitives.js";
|
|
2
|
+
import { needsShaping } from "../shaping/placeRun.js";
|
|
3
|
+
import { TEXT_RENDER_MODE, createContentStream } from "./contentStream.js";
|
|
4
|
+
import { basePageMatrix, rotationMatrix } from "./pageSpace.js";
|
|
5
|
+
import { Result, TaggedError, panic } from "better-result";
|
|
6
|
+
//#region src/pdf/paint.ts
|
|
7
|
+
/**
|
|
8
|
+
* Display list to content stream.
|
|
9
|
+
*
|
|
10
|
+
* Everything here is emitted in display-list coordinates: CSS px, origin at
|
|
11
|
+
* the page's top-left, y down. The page's base CTM (see `pageSpace.ts`)
|
|
12
|
+
* converts once, at the stream's first operator, which is why no primitive
|
|
13
|
+
* below does any arithmetic on the page height.
|
|
14
|
+
*/
|
|
15
|
+
var PaintError = class extends TaggedError("PaintError") {};
|
|
16
|
+
/** Thousandths of an em, the unit of a `TJ` adjustment. */
|
|
17
|
+
const TEXT_SPACE_UNITS_PER_EM = 1e3;
|
|
18
|
+
const OPAQUE = 1;
|
|
19
|
+
/** Points sampled along one period of a `wavy` stroke. */
|
|
20
|
+
const WAVY_SAMPLES_PER_PERIOD = 8;
|
|
21
|
+
/** A `double` stroke is drawn as two lines, one either side of the path. */
|
|
22
|
+
const DOUBLE_STROKE_LINES = 2;
|
|
23
|
+
const withAlpha = (context, alpha) => ({
|
|
24
|
+
...context,
|
|
25
|
+
alpha
|
|
26
|
+
});
|
|
27
|
+
/**
|
|
28
|
+
* Opens a graphics-state block for one primitive. Alpha is multiplied down
|
|
29
|
+
* from the enclosing groups rather than replaced, so a half-transparent run
|
|
30
|
+
* inside a half-transparent watermark paints at a quarter and not at a half.
|
|
31
|
+
* PDF's `/ca` is per-object, so a group's alpha reaches its children this way
|
|
32
|
+
* instead of compositing the group as a unit; overlapping children inside one
|
|
33
|
+
* opacity group therefore show their seams where a true transparency group
|
|
34
|
+
* would not.
|
|
35
|
+
*/
|
|
36
|
+
const beginPrimitive = (context, alpha) => {
|
|
37
|
+
context.stream.save();
|
|
38
|
+
if (alpha < OPAQUE) context.stream.setAlpha(alpha);
|
|
39
|
+
};
|
|
40
|
+
const applyStrokeState = (stream, stroke, widthPx) => {
|
|
41
|
+
stream.setStrokeColor(stroke.color);
|
|
42
|
+
stream.setLineWidth(widthPx);
|
|
43
|
+
const dash = STROKE_DASH_FACTORS[stroke.pattern];
|
|
44
|
+
if (dash !== null) stream.setDash([dash.dash * stroke.thicknessPx, dash.gap * stroke.thicknessPx], 0);
|
|
45
|
+
};
|
|
46
|
+
/** Unit normal of a segment, or null when the segment has no length. */
|
|
47
|
+
const segmentNormal = (segment) => {
|
|
48
|
+
const dx = segment.x2Px - segment.x1Px;
|
|
49
|
+
const dy = segment.y2Px - segment.y1Px;
|
|
50
|
+
const length = Math.hypot(dx, dy);
|
|
51
|
+
if (length === 0) return null;
|
|
52
|
+
return {
|
|
53
|
+
nx: -dy / length,
|
|
54
|
+
ny: dx / length
|
|
55
|
+
};
|
|
56
|
+
};
|
|
57
|
+
const strokeWavy = (stream, stroke, segment) => {
|
|
58
|
+
const normal = segmentNormal(segment);
|
|
59
|
+
if (normal === null) return;
|
|
60
|
+
const dx = segment.x2Px - segment.x1Px;
|
|
61
|
+
const dy = segment.y2Px - segment.y1Px;
|
|
62
|
+
const length = Math.hypot(dx, dy);
|
|
63
|
+
const period = 6 * stroke.thicknessPx;
|
|
64
|
+
const amplitude = 2 * stroke.thicknessPx / 2;
|
|
65
|
+
const steps = Math.max(WAVY_SAMPLES_PER_PERIOD, Math.ceil(length / period * WAVY_SAMPLES_PER_PERIOD));
|
|
66
|
+
for (let step = 0; step <= steps; step += 1) {
|
|
67
|
+
const along = length * step / steps;
|
|
68
|
+
const offset = amplitude * Math.sin(2 * Math.PI * along / period);
|
|
69
|
+
const x = segment.x1Px + dx * step / steps + normal.nx * offset;
|
|
70
|
+
const y = segment.y1Px + dy * step / steps + normal.ny * offset;
|
|
71
|
+
if (step === 0) stream.moveTo(x, y);
|
|
72
|
+
else stream.lineTo(x, y);
|
|
73
|
+
}
|
|
74
|
+
stream.stroke();
|
|
75
|
+
};
|
|
76
|
+
const strokeDouble = (stream, stroke, segment) => {
|
|
77
|
+
const normal = segmentNormal(segment);
|
|
78
|
+
if (normal === null) return;
|
|
79
|
+
const offset = 1 * stroke.thicknessPx * DOUBLE_STROKE_LINES / 2;
|
|
80
|
+
for (const side of [-1, 1]) {
|
|
81
|
+
stream.moveTo(segment.x1Px + normal.nx * offset * side, segment.y1Px + normal.ny * offset * side);
|
|
82
|
+
stream.lineTo(segment.x2Px + normal.nx * offset * side, segment.y2Px + normal.ny * offset * side);
|
|
83
|
+
stream.stroke();
|
|
84
|
+
}
|
|
85
|
+
};
|
|
86
|
+
const paintSegment = (context, stroke, segment) => {
|
|
87
|
+
const alpha = context.alpha * stroke.color.a;
|
|
88
|
+
beginPrimitive(context, alpha);
|
|
89
|
+
const { stream } = context;
|
|
90
|
+
switch (stroke.pattern) {
|
|
91
|
+
case "solid":
|
|
92
|
+
case "dashed":
|
|
93
|
+
case "dotted":
|
|
94
|
+
applyStrokeState(stream, stroke, stroke.thicknessPx);
|
|
95
|
+
stream.moveTo(segment.x1Px, segment.y1Px);
|
|
96
|
+
stream.lineTo(segment.x2Px, segment.y2Px);
|
|
97
|
+
stream.stroke();
|
|
98
|
+
break;
|
|
99
|
+
case "double":
|
|
100
|
+
applyStrokeState(stream, stroke, stroke.thicknessPx);
|
|
101
|
+
strokeDouble(stream, stroke, segment);
|
|
102
|
+
break;
|
|
103
|
+
case "wavy":
|
|
104
|
+
applyStrokeState(stream, stroke, stroke.thicknessPx);
|
|
105
|
+
strokeWavy(stream, stroke, segment);
|
|
106
|
+
break;
|
|
107
|
+
default: {
|
|
108
|
+
const unreachable = stroke.pattern;
|
|
109
|
+
panic(`unhandled stroke pattern: ${String(unreachable)}`);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
stream.restore();
|
|
113
|
+
};
|
|
114
|
+
const paintRect = (context, primitive) => {
|
|
115
|
+
const { rect, fill, stroke } = primitive;
|
|
116
|
+
if (fill !== void 0) {
|
|
117
|
+
beginPrimitive(context, context.alpha * fill.a);
|
|
118
|
+
context.stream.setFillColor(fill);
|
|
119
|
+
context.stream.appendRect(rect.xPx, rect.yPx, rect.widthPx, rect.heightPx);
|
|
120
|
+
context.stream.fill();
|
|
121
|
+
context.stream.restore();
|
|
122
|
+
}
|
|
123
|
+
if (stroke === void 0) return;
|
|
124
|
+
if (stroke.pattern === "solid") {
|
|
125
|
+
beginPrimitive(context, context.alpha * stroke.color.a);
|
|
126
|
+
applyStrokeState(context.stream, stroke, stroke.thicknessPx);
|
|
127
|
+
context.stream.appendRect(rect.xPx, rect.yPx, rect.widthPx, rect.heightPx);
|
|
128
|
+
context.stream.stroke();
|
|
129
|
+
context.stream.restore();
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
const { xPx, yPx, widthPx, heightPx } = rect;
|
|
133
|
+
const edges = [
|
|
134
|
+
{
|
|
135
|
+
x1Px: xPx,
|
|
136
|
+
y1Px: yPx,
|
|
137
|
+
x2Px: xPx + widthPx,
|
|
138
|
+
y2Px: yPx
|
|
139
|
+
},
|
|
140
|
+
{
|
|
141
|
+
x1Px: xPx + widthPx,
|
|
142
|
+
y1Px: yPx,
|
|
143
|
+
x2Px: xPx + widthPx,
|
|
144
|
+
y2Px: yPx + heightPx
|
|
145
|
+
},
|
|
146
|
+
{
|
|
147
|
+
x1Px: xPx + widthPx,
|
|
148
|
+
y1Px: yPx + heightPx,
|
|
149
|
+
x2Px: xPx,
|
|
150
|
+
y2Px: yPx + heightPx
|
|
151
|
+
},
|
|
152
|
+
{
|
|
153
|
+
x1Px: xPx,
|
|
154
|
+
y1Px: yPx + heightPx,
|
|
155
|
+
x2Px: xPx,
|
|
156
|
+
y2Px: yPx
|
|
157
|
+
}
|
|
158
|
+
];
|
|
159
|
+
for (const edge of edges) paintSegment(context, stroke, edge);
|
|
160
|
+
};
|
|
161
|
+
/**
|
|
162
|
+
* Left edge of every code point, from the ordering the display list pins
|
|
163
|
+
* down. The mirroring rule is shared with the DOM backend rather than stated
|
|
164
|
+
* twice: two backends agreeing by coincidence is the divergence the display
|
|
165
|
+
* list exists to prevent.
|
|
166
|
+
*/
|
|
167
|
+
const glyphOrigins = (run) => glyphCellOffsetsPx(run).map((offsetPx) => run.xPx + offsetPx);
|
|
168
|
+
/**
|
|
169
|
+
* Where each glyph of an embedded run goes.
|
|
170
|
+
*
|
|
171
|
+
* Cluster origins come from the display list's own advances whichever path
|
|
172
|
+
* produced the glyphs, so letter spacing and every other adjustment the
|
|
173
|
+
* measurer applied survive: the line was broken on those numbers. What shaping
|
|
174
|
+
* contributes is which glyphs there are and where they sit inside their
|
|
175
|
+
* cluster.
|
|
176
|
+
*/
|
|
177
|
+
const placeEmbeddedRun = (run, font, origins) => {
|
|
178
|
+
const shaped = font.placeShapedRun({
|
|
179
|
+
text: run.text,
|
|
180
|
+
direction: run.direction,
|
|
181
|
+
fontSizePx: run.fontSizePx
|
|
182
|
+
});
|
|
183
|
+
if (shaped === null) return [...run.text].map((character, index) => {
|
|
184
|
+
const { resourceIndex, glyphId, widthUnits } = font.glyphFor(character.codePointAt(0) ?? 0);
|
|
185
|
+
return {
|
|
186
|
+
resourceIndex,
|
|
187
|
+
glyphId,
|
|
188
|
+
widthUnits,
|
|
189
|
+
xPx: origins[index] ?? run.xPx,
|
|
190
|
+
yPx: run.baselineYPx,
|
|
191
|
+
advancePx: run.advancesPx[index] ?? 0,
|
|
192
|
+
displaced: false
|
|
193
|
+
};
|
|
194
|
+
});
|
|
195
|
+
const placed = [];
|
|
196
|
+
let penPx = run.xPx;
|
|
197
|
+
let openCluster = null;
|
|
198
|
+
for (const glyph of shaped) {
|
|
199
|
+
if (glyph.clusterIndex !== openCluster) {
|
|
200
|
+
penPx = origins[glyph.clusterIndex] ?? penPx;
|
|
201
|
+
openCluster = glyph.clusterIndex;
|
|
202
|
+
}
|
|
203
|
+
const displaced = glyph.xOffsetPx !== 0 || glyph.yOffsetPx !== 0;
|
|
204
|
+
placed.push({
|
|
205
|
+
resourceIndex: glyph.resourceIndex,
|
|
206
|
+
glyphId: glyph.glyphId,
|
|
207
|
+
widthUnits: glyph.widthUnits,
|
|
208
|
+
xPx: penPx + glyph.xOffsetPx,
|
|
209
|
+
yPx: run.baselineYPx - glyph.yOffsetPx,
|
|
210
|
+
advancePx: glyph.xAdvancePx,
|
|
211
|
+
displaced
|
|
212
|
+
});
|
|
213
|
+
penPx += glyph.xAdvancePx;
|
|
214
|
+
}
|
|
215
|
+
return placed;
|
|
216
|
+
};
|
|
217
|
+
/** Glyphs one text-showing operator can draw together, in painting order. */
|
|
218
|
+
const spanGlyphs = (placed, fontSizePx) => {
|
|
219
|
+
const spans = [];
|
|
220
|
+
for (const [index, glyph] of placed.entries()) {
|
|
221
|
+
const next = placed[index + 1];
|
|
222
|
+
const open = spans.at(-1);
|
|
223
|
+
const continues = open !== void 0 && !glyph.displaced && open.resourceIndex === glyph.resourceIndex && open.yPx === glyph.yPx;
|
|
224
|
+
const delta = next === void 0 || next.displaced || next.yPx !== glyph.yPx ? glyph.advancePx : next.xPx - glyph.xPx;
|
|
225
|
+
const positioned = {
|
|
226
|
+
glyphId: glyph.glyphId,
|
|
227
|
+
adjustment: glyph.widthUnits - delta * TEXT_SPACE_UNITS_PER_EM / fontSizePx
|
|
228
|
+
};
|
|
229
|
+
if (continues && open !== void 0) {
|
|
230
|
+
open.glyphs.push(positioned);
|
|
231
|
+
continue;
|
|
232
|
+
}
|
|
233
|
+
spans.push({
|
|
234
|
+
resourceIndex: glyph.resourceIndex,
|
|
235
|
+
xPx: glyph.xPx,
|
|
236
|
+
yPx: glyph.yPx,
|
|
237
|
+
glyphs: [positioned]
|
|
238
|
+
});
|
|
239
|
+
}
|
|
240
|
+
return spans;
|
|
241
|
+
};
|
|
242
|
+
const paintGlyphRun = (context, run) => {
|
|
243
|
+
const codePoints = [...run.text];
|
|
244
|
+
if (codePoints.length === 0 || run.fontSizePx <= 0) return;
|
|
245
|
+
const font = context.fonts.get(run.font) ?? panic(`glyph run refers to unprepared font ${String(run.font)}`);
|
|
246
|
+
const origins = glyphOrigins(run);
|
|
247
|
+
const { stream } = context;
|
|
248
|
+
beginPrimitive(context, context.alpha * run.color.a);
|
|
249
|
+
stream.setFillColor(run.color);
|
|
250
|
+
if (run.stroke !== void 0) {
|
|
251
|
+
stream.setStrokeColor(run.stroke.color);
|
|
252
|
+
stream.setLineWidth(run.stroke.thicknessPx);
|
|
253
|
+
}
|
|
254
|
+
stream.beginText();
|
|
255
|
+
switch (font.kind) {
|
|
256
|
+
case "embedded": {
|
|
257
|
+
const spans = spanGlyphs(placeEmbeddedRun(run, font, origins), run.fontSizePx);
|
|
258
|
+
for (const [spanIndex, span] of spans.entries()) {
|
|
259
|
+
stream.setFont(span.resourceIndex, run.fontSizePx);
|
|
260
|
+
if (spanIndex === 0 && run.stroke !== void 0) stream.setTextRenderMode(TEXT_RENDER_MODE.fillThenStroke);
|
|
261
|
+
stream.setTextMatrix([
|
|
262
|
+
1,
|
|
263
|
+
0,
|
|
264
|
+
0,
|
|
265
|
+
-1,
|
|
266
|
+
span.xPx,
|
|
267
|
+
span.yPx
|
|
268
|
+
]);
|
|
269
|
+
stream.showGlyphs(span.glyphs);
|
|
270
|
+
}
|
|
271
|
+
break;
|
|
272
|
+
}
|
|
273
|
+
case "standard":
|
|
274
|
+
stream.setFont(font.resourceIndex, run.fontSizePx);
|
|
275
|
+
if (run.stroke !== void 0) stream.setTextRenderMode(TEXT_RENDER_MODE.fillThenStroke);
|
|
276
|
+
for (const [index, codePoint] of codePoints.entries()) {
|
|
277
|
+
stream.setTextMatrix([
|
|
278
|
+
1,
|
|
279
|
+
0,
|
|
280
|
+
0,
|
|
281
|
+
-1,
|
|
282
|
+
origins[index] ?? run.xPx,
|
|
283
|
+
run.baselineYPx
|
|
284
|
+
]);
|
|
285
|
+
stream.showBytes([font.byteFor(codePoint.codePointAt(0) ?? 0)]);
|
|
286
|
+
}
|
|
287
|
+
break;
|
|
288
|
+
default: panic(`unhandled prepared font: ${JSON.stringify(font)}`);
|
|
289
|
+
}
|
|
290
|
+
stream.endText();
|
|
291
|
+
stream.restore();
|
|
292
|
+
};
|
|
293
|
+
const paintImage = (context, primitive) => {
|
|
294
|
+
const { rect, crop, opacity } = primitive;
|
|
295
|
+
const { stream } = context;
|
|
296
|
+
beginPrimitive(context, context.alpha * opacity);
|
|
297
|
+
if (crop === void 0) {
|
|
298
|
+
stream.concat([
|
|
299
|
+
rect.widthPx,
|
|
300
|
+
0,
|
|
301
|
+
0,
|
|
302
|
+
-rect.heightPx,
|
|
303
|
+
rect.xPx,
|
|
304
|
+
rect.yPx + rect.heightPx
|
|
305
|
+
]);
|
|
306
|
+
stream.drawImage(primitive.image);
|
|
307
|
+
stream.restore();
|
|
308
|
+
return;
|
|
309
|
+
}
|
|
310
|
+
const visibleWidth = 1 - crop.l - crop.r;
|
|
311
|
+
const visibleHeight = 1 - crop.t - crop.b;
|
|
312
|
+
if (visibleWidth <= 0 || visibleHeight <= 0) {
|
|
313
|
+
stream.restore();
|
|
314
|
+
return;
|
|
315
|
+
}
|
|
316
|
+
stream.appendRect(rect.xPx, rect.yPx, rect.widthPx, rect.heightPx);
|
|
317
|
+
stream.clipToCurrentPath();
|
|
318
|
+
const fullWidth = rect.widthPx / visibleWidth;
|
|
319
|
+
const fullHeight = rect.heightPx / visibleHeight;
|
|
320
|
+
const left = rect.xPx - crop.l * fullWidth;
|
|
321
|
+
const top = rect.yPx - crop.t * fullHeight;
|
|
322
|
+
stream.concat([
|
|
323
|
+
fullWidth,
|
|
324
|
+
0,
|
|
325
|
+
0,
|
|
326
|
+
-fullHeight,
|
|
327
|
+
left,
|
|
328
|
+
top + fullHeight
|
|
329
|
+
]);
|
|
330
|
+
stream.drawImage(primitive.image);
|
|
331
|
+
stream.restore();
|
|
332
|
+
};
|
|
333
|
+
const paintPrimitive = (context, primitive) => {
|
|
334
|
+
switch (primitive.kind) {
|
|
335
|
+
case "glyphRun":
|
|
336
|
+
paintGlyphRun(context, primitive);
|
|
337
|
+
return;
|
|
338
|
+
case "rect":
|
|
339
|
+
paintRect(context, primitive);
|
|
340
|
+
return;
|
|
341
|
+
case "line":
|
|
342
|
+
paintSegment(context, primitive.stroke, {
|
|
343
|
+
x1Px: primitive.x1Px,
|
|
344
|
+
y1Px: primitive.y1Px,
|
|
345
|
+
x2Px: primitive.x2Px,
|
|
346
|
+
y2Px: primitive.y2Px
|
|
347
|
+
});
|
|
348
|
+
return;
|
|
349
|
+
case "image":
|
|
350
|
+
paintImage(context, primitive);
|
|
351
|
+
return;
|
|
352
|
+
case "clipGroup":
|
|
353
|
+
context.stream.save();
|
|
354
|
+
context.stream.appendRect(primitive.rect.xPx, primitive.rect.yPx, primitive.rect.widthPx, primitive.rect.heightPx);
|
|
355
|
+
context.stream.clipToCurrentPath();
|
|
356
|
+
for (const child of primitive.children) paintPrimitive(context, child);
|
|
357
|
+
context.stream.restore();
|
|
358
|
+
return;
|
|
359
|
+
case "rotateGroup":
|
|
360
|
+
context.stream.save();
|
|
361
|
+
context.stream.concat(rotationMatrix(primitive.degrees, primitive.originXPx, primitive.originYPx));
|
|
362
|
+
for (const child of primitive.children) paintPrimitive(context, child);
|
|
363
|
+
context.stream.restore();
|
|
364
|
+
return;
|
|
365
|
+
case "opacityGroup": {
|
|
366
|
+
const alpha = context.alpha * primitive.opacity;
|
|
367
|
+
context.stream.save();
|
|
368
|
+
context.stream.setAlpha(alpha);
|
|
369
|
+
const inner = withAlpha(context, alpha);
|
|
370
|
+
for (const child of primitive.children) paintPrimitive(inner, child);
|
|
371
|
+
context.stream.restore();
|
|
372
|
+
return;
|
|
373
|
+
}
|
|
374
|
+
default: panic(`unhandled display primitive: ${JSON.stringify(primitive)}`);
|
|
375
|
+
}
|
|
376
|
+
};
|
|
377
|
+
const paintPage = ({ page, fonts }) => {
|
|
378
|
+
const stream = createContentStream();
|
|
379
|
+
stream.concat(basePageMatrix(page.heightPx));
|
|
380
|
+
const context = {
|
|
381
|
+
stream,
|
|
382
|
+
fonts,
|
|
383
|
+
alpha: OPAQUE
|
|
384
|
+
};
|
|
385
|
+
for (const primitive of page.primitives) paintPrimitive(context, primitive);
|
|
386
|
+
return stream.parts();
|
|
387
|
+
};
|
|
388
|
+
const collectPrimitive = (primitive, fontCount, imageCount, into) => {
|
|
389
|
+
switch (primitive.kind) {
|
|
390
|
+
case "glyphRun": {
|
|
391
|
+
if (primitive.font < 0 || primitive.font >= fontCount) return new PaintError({ message: `glyph run refers to font ${String(primitive.font)}, outside the font table` });
|
|
392
|
+
const codePoints = [...primitive.text];
|
|
393
|
+
if (codePoints.length !== primitive.advancesPx.length) return new PaintError({ message: `glyph run carries ${String(primitive.advancesPx.length)} advances for ${String(codePoints.length)} code points` });
|
|
394
|
+
const seen = into.codePointsByFont.get(primitive.font) ?? /* @__PURE__ */ new Set();
|
|
395
|
+
for (const codePoint of codePoints) seen.add(codePoint.codePointAt(0) ?? 0);
|
|
396
|
+
into.codePointsByFont.set(primitive.font, seen);
|
|
397
|
+
if (needsShaping(primitive.text)) {
|
|
398
|
+
const runs = into.shapedRunsByFont.get(primitive.font) ?? [];
|
|
399
|
+
runs.push({
|
|
400
|
+
text: primitive.text,
|
|
401
|
+
direction: primitive.direction,
|
|
402
|
+
fontSizePx: primitive.fontSizePx
|
|
403
|
+
});
|
|
404
|
+
into.shapedRunsByFont.set(primitive.font, runs);
|
|
405
|
+
}
|
|
406
|
+
return null;
|
|
407
|
+
}
|
|
408
|
+
case "rect":
|
|
409
|
+
case "line": return null;
|
|
410
|
+
case "image":
|
|
411
|
+
if (primitive.image < 0 || primitive.image >= imageCount) return new PaintError({ message: `image primitive refers to image ${String(primitive.image)}, outside the image table` });
|
|
412
|
+
into.imageIndices.add(primitive.image);
|
|
413
|
+
return null;
|
|
414
|
+
case "clipGroup":
|
|
415
|
+
case "rotateGroup":
|
|
416
|
+
case "opacityGroup":
|
|
417
|
+
for (const child of primitive.children) {
|
|
418
|
+
const error = collectPrimitive(child, fontCount, imageCount, into);
|
|
419
|
+
if (error !== null) return error;
|
|
420
|
+
}
|
|
421
|
+
return null;
|
|
422
|
+
default: return panic(`unhandled display primitive: ${JSON.stringify(primitive)}`);
|
|
423
|
+
}
|
|
424
|
+
};
|
|
425
|
+
/**
|
|
426
|
+
* Walks the pages once before anything is written, because subsetting needs
|
|
427
|
+
* every glyph the document uses before the first one is emitted.
|
|
428
|
+
*/
|
|
429
|
+
const collectUsage = (pages, fontCount, imageCount) => {
|
|
430
|
+
const into = {
|
|
431
|
+
codePointsByFont: /* @__PURE__ */ new Map(),
|
|
432
|
+
shapedRunsByFont: /* @__PURE__ */ new Map(),
|
|
433
|
+
imageIndices: /* @__PURE__ */ new Set()
|
|
434
|
+
};
|
|
435
|
+
for (const page of pages) for (const primitive of page.primitives) {
|
|
436
|
+
const error = collectPrimitive(primitive, fontCount, imageCount, into);
|
|
437
|
+
if (error !== null) return Result.err(error);
|
|
438
|
+
}
|
|
439
|
+
return Result.ok(into);
|
|
440
|
+
};
|
|
441
|
+
//#endregion
|
|
442
|
+
export { PaintError, collectUsage, paintPage };
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { DisplayList } from "../display-list/types.js";
|
|
2
|
+
import { PdfFontSource, PdfSubstitution } from "./fonts.js";
|
|
3
|
+
import { Result } from "better-result";
|
|
4
|
+
//#region src/pdf/writePdf.d.ts
|
|
5
|
+
type WritePdfOptions = {
|
|
6
|
+
readonly fonts: PdfFontSource;
|
|
7
|
+
/**
|
|
8
|
+
* Creation and modification instant stamped into the PDF, ISO-8601.
|
|
9
|
+
* Required rather than defaulted so a caller cannot get a
|
|
10
|
+
* nondeterministic document by omission.
|
|
11
|
+
*/
|
|
12
|
+
readonly timestamp: string;
|
|
13
|
+
readonly producer?: string;
|
|
14
|
+
/**
|
|
15
|
+
* Refuse to write a document containing a code point no supplied face can
|
|
16
|
+
* encode, instead of painting `.notdef` and reporting it. For a caller that
|
|
17
|
+
* would rather fail than hand a reader a page of empty boxes.
|
|
18
|
+
*/
|
|
19
|
+
readonly strictGlyphCoverage?: boolean;
|
|
20
|
+
};
|
|
21
|
+
/**
|
|
22
|
+
* A code point no binary of its face could encode. It is painted as `.notdef`
|
|
23
|
+
* and reported here, never dropped and never silently substituted: a reader
|
|
24
|
+
* cannot tell an empty box from a character the document never had, and every
|
|
25
|
+
* such code point collapses onto glyph 0, so even `/ToUnicode` cannot tell
|
|
26
|
+
* them apart afterwards.
|
|
27
|
+
*/
|
|
28
|
+
type PdfUnencodable = {
|
|
29
|
+
readonly codePoint: number;
|
|
30
|
+
readonly family: string;
|
|
31
|
+
readonly weight: number;
|
|
32
|
+
readonly italic: boolean;
|
|
33
|
+
};
|
|
34
|
+
type WritePdfResult = {
|
|
35
|
+
readonly bytes: Uint8Array;
|
|
36
|
+
/** Faces the font source could not supply, painted with a base-14 stand-in. */
|
|
37
|
+
readonly substitutions: readonly PdfSubstitution[];
|
|
38
|
+
/** Code points painted as `.notdef` because no supplied face covers them. */
|
|
39
|
+
readonly unencodable: readonly PdfUnencodable[];
|
|
40
|
+
};
|
|
41
|
+
declare const WritePdfError_base: import("better-result").TaggedErrorClass<"WritePdfError">;
|
|
42
|
+
declare class WritePdfError extends WritePdfError_base<{
|
|
43
|
+
message: string;
|
|
44
|
+
cause?: unknown;
|
|
45
|
+
}> {}
|
|
46
|
+
/**
|
|
47
|
+
* Writes the display list as a PDF.
|
|
48
|
+
*
|
|
49
|
+
* Asynchronous for one reason: a run in a script that shapes needs the shaper,
|
|
50
|
+
* and the shaper is fetched the first time a document actually contains one. A
|
|
51
|
+
* document in Latin, Cyrillic or Greek never awaits anything but the resolved
|
|
52
|
+
* promise this returns.
|
|
53
|
+
*/
|
|
54
|
+
declare const writePdf: (list: DisplayList, options: WritePdfOptions) => Promise<Result<WritePdfResult, WritePdfError>>;
|
|
55
|
+
//#endregion
|
|
56
|
+
export { type PdfFontSource, type PdfSubstitution, PdfUnencodable, WritePdfError, WritePdfOptions, WritePdfResult, writePdf };
|