reamkit 1.29.0 → 1.31.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +60 -24
- package/dist/esm/core/converter/project.js +3 -1
- package/dist/esm/core/crypto/offcrypto.js +1 -1
- package/dist/esm/core/document-model/index.d.ts +1 -1
- package/dist/esm/core/document-model/types.d.ts +65 -0
- package/dist/esm/core/drawingml/shape-render.js +13 -1
- package/dist/esm/core/font/index.d.ts +2 -0
- package/dist/esm/core/font/ttf-build.d.ts +113 -0
- package/dist/esm/core/font/ttf-build.js +1224 -0
- package/dist/esm/core/font/ttf-subset.d.ts +19 -0
- package/dist/esm/core/font/ttf-subset.js +14 -1
- package/dist/esm/core/fonts/index.d.ts +1 -1
- package/dist/esm/core/fonts/provider.d.ts +17 -0
- package/dist/esm/core/fonts/provider.js +27 -2
- package/dist/esm/core/fonts/remote-fonts.d.ts +8 -0
- package/dist/esm/core/fonts/remote-fonts.js +108 -18
- package/dist/esm/core/ir/flow.d.ts +88 -1
- package/dist/esm/core/numbering/index.d.ts +1 -1
- package/dist/esm/core/numbering/state.d.ts +11 -1
- package/dist/esm/core/numbering/state.js +10 -1
- package/dist/esm/core/style-cascade/resolver.js +35 -4
- package/dist/esm/core/style-cascade/types.d.ts +19 -1
- package/dist/esm/core/style-cascade/types.js +3 -0
- package/dist/esm/index.d.ts +2 -2
- package/dist/esm/layout/page-doc.d.ts +10 -3
- package/dist/esm/layout/styled-layout.d.ts +48 -7
- package/dist/esm/layout/styled-layout.js +881 -119
- package/dist/esm/layout/turned-section.d.ts +38 -0
- package/dist/esm/layout/turned-section.js +193 -0
- package/dist/esm/pdf/styled-page-emitter.js +78 -2
- package/dist/esm/pdf-reader/annot-draw.js +93 -1
- package/dist/esm/pdf-reader/annots.d.ts +18 -0
- package/dist/esm/pdf-reader/annots.js +86 -9
- package/dist/esm/pdf-reader/cff-outline.d.ts +27 -0
- package/dist/esm/pdf-reader/cff-outline.js +169 -21
- package/dist/esm/pdf-reader/cmap.js +5 -2
- package/dist/esm/pdf-reader/content.d.ts +70 -4
- package/dist/esm/pdf-reader/content.js +172 -12
- package/dist/esm/pdf-reader/display.d.ts +36 -0
- package/dist/esm/pdf-reader/display.js +82 -1
- package/dist/esm/pdf-reader/document.js +5 -1
- package/dist/esm/pdf-reader/embedded-fonts.d.ts +25 -0
- package/dist/esm/pdf-reader/embedded-fonts.js +78 -9
- package/dist/esm/pdf-reader/encodings.d.ts +8 -0
- package/dist/esm/pdf-reader/encodings.js +25 -3
- package/dist/esm/pdf-reader/face-outlines.d.ts +78 -0
- package/dist/esm/pdf-reader/face-outlines.js +362 -0
- package/dist/esm/pdf-reader/figures.d.ts +52 -0
- package/dist/esm/pdf-reader/figures.js +433 -0
- package/dist/esm/pdf-reader/flow-build.d.ts +224 -7
- package/dist/esm/pdf-reader/flow-build.js +545 -39
- package/dist/esm/pdf-reader/font.d.ts +27 -1
- package/dist/esm/pdf-reader/font.js +632 -63
- package/dist/esm/pdf-reader/glyf-outline.d.ts +33 -0
- package/dist/esm/pdf-reader/glyf-outline.js +148 -3
- package/dist/esm/pdf-reader/glyph-names.js +154 -1
- package/dist/esm/pdf-reader/glyph-shapes.d.ts +18 -0
- package/dist/esm/pdf-reader/glyph-shapes.js +57 -0
- package/dist/esm/pdf-reader/image-decode.js +70 -4
- package/dist/esm/pdf-reader/images.d.ts +5 -0
- package/dist/esm/pdf-reader/images.js +4 -2
- package/dist/esm/pdf-reader/jbig2.d.ts +40 -1
- package/dist/esm/pdf-reader/jbig2.js +78 -16
- package/dist/esm/pdf-reader/jpeg.d.ts +6 -3
- package/dist/esm/pdf-reader/jpeg.js +21 -1
- package/dist/esm/pdf-reader/layout.d.ts +119 -2
- package/dist/esm/pdf-reader/layout.js +2219 -184
- package/dist/esm/pdf-reader/lexer.d.ts +10 -0
- package/dist/esm/pdf-reader/lexer.js +17 -0
- package/dist/esm/pdf-reader/page-numbers.d.ts +53 -0
- package/dist/esm/pdf-reader/page-numbers.js +167 -0
- package/dist/esm/pdf-reader/pattern-tint.d.ts +11 -1
- package/dist/esm/pdf-reader/pattern-tint.js +21 -3
- package/dist/esm/pdf-reader/regions.d.ts +25 -0
- package/dist/esm/pdf-reader/regions.js +167 -0
- package/dist/esm/pdf-reader/shading.d.ts +58 -2
- package/dist/esm/pdf-reader/shading.js +181 -11
- package/dist/esm/pdf-reader/struct-tree.js +112 -8
- package/dist/esm/pdf-reader/tagged.js +207 -28
- package/dist/esm/pdf-reader/text-rules.js +1 -1
- package/dist/esm/pdf-reader/text.d.ts +6 -3
- package/dist/esm/pdf-reader/text.js +106 -22
- package/dist/esm/pdf-reader/type1-outline.d.ts +11 -0
- package/dist/esm/pdf-reader/type1-outline.js +63 -8
- package/dist/esm/pdf-reader/vector.d.ts +8 -2
- package/dist/esm/pdf-reader/vector.js +105 -6
- package/dist/esm/word/doc/doc-reader.js +6 -2
- package/dist/esm/word/doc/doc-text.d.ts +6 -0
- package/dist/esm/word/doc/doc-text.js +19 -1
- package/dist/esm/word/document-parser.d.ts +2 -2
- package/dist/esm/word/document-parser.js +11 -1
- package/dist/esm/word/docx-reader.js +5 -3
- package/dist/esm/word/docx-writer.js +341 -54
- package/dist/esm/word/drawing-parser.d.ts +5 -3
- package/dist/esm/word/drawing-parser.js +56 -10
- package/dist/esm/word/font-embed.d.ts +30 -0
- package/dist/esm/word/font-embed.js +173 -0
- package/dist/esm/word/font-table.d.ts +10 -0
- package/dist/esm/word/font-table.js +13 -1
- package/dist/esm/word/index.js +1 -1
- package/dist/esm/word/numbering-parser.d.ts +3 -1
- package/dist/esm/word/numbering-parser.js +2 -1
- package/dist/esm/word/paragraph-properties.d.ts +7 -6
- package/dist/esm/word/paragraph-properties.js +16 -2
- package/dist/esm/word/run-properties.js +26 -0
- package/package.json +12 -5
|
@@ -2,6 +2,7 @@ import { pt } from "../core/ir/units.js";
|
|
|
2
2
|
import { ResourceStore } from "../core/ir/resources.js";
|
|
3
3
|
import { EMPTY_STYLE_SHEET, resolveBodyStyles } from "../core/style-cascade/resolver.js";
|
|
4
4
|
import "../core/style-cascade/index.js";
|
|
5
|
+
import { gradientOverBox } from "./shading.js";
|
|
5
6
|
import { displayOf } from "./display.js";
|
|
6
7
|
//#region src/pdf-reader/flow-build.ts
|
|
7
8
|
/**
|
|
@@ -21,6 +22,35 @@ function paragraphBlock(text, outlineLevel) {
|
|
|
21
22
|
}
|
|
22
23
|
};
|
|
23
24
|
}
|
|
25
|
+
/** One piece of reconstructed text, carrying any hyperlink (E-PDF EP8). */
|
|
26
|
+
/**
|
|
27
|
+
* The space set between a span and what follows it, in the span's own size and
|
|
28
|
+
* face: a word space is as wide as the type it stands in.
|
|
29
|
+
*
|
|
30
|
+
* Written bare, the space took the document's default size, whatever the words
|
|
31
|
+
* around it were set at: issue10665_reduced.pdf sets "78" and "110" twenty
|
|
32
|
+
* points apart in 60-point type, and the eleven-point space between them closed
|
|
33
|
+
* them up to "78110"; in 7-point footnotes the same space is half as wide
|
|
34
|
+
* again as the page's, and pushes their lines over.
|
|
35
|
+
*
|
|
36
|
+
* A space between two words one text markup marks is marked with them: the
|
|
37
|
+
* band a highlighter lays runs across the gap. comments.pdf highlights two
|
|
38
|
+
* lines on its sixth page, and with its spaces left bare the band came back
|
|
39
|
+
* broken at every word.
|
|
40
|
+
*
|
|
41
|
+
* @param before The span the space follows, where there is one.
|
|
42
|
+
* @param after What follows the space, where anything does.
|
|
43
|
+
* @returns The space.
|
|
44
|
+
*/
|
|
45
|
+
function spaceAfter(before, after) {
|
|
46
|
+
const markup = before?.markup;
|
|
47
|
+
return {
|
|
48
|
+
text: " ",
|
|
49
|
+
...before?.sizePt !== void 0 ? { sizePt: before.sizePt } : {},
|
|
50
|
+
...before?.fontName !== void 0 ? { fontName: before.fontName } : {},
|
|
51
|
+
...markup !== void 0 && after?.markup !== void 0 && sameMarkup(markup, after.markup) ? { markup } : {}
|
|
52
|
+
};
|
|
53
|
+
}
|
|
24
54
|
/**
|
|
25
55
|
* Build a paragraph {@link BodyElement} from positioned {@link TextSpan}s,
|
|
26
56
|
* coalescing consecutive spans that share an `href` into one run (so a link
|
|
@@ -31,7 +61,7 @@ function paragraphFromRuns(spans, outlineLevel, placement) {
|
|
|
31
61
|
const merged = [];
|
|
32
62
|
for (const s of spans) {
|
|
33
63
|
const last = merged[merged.length - 1];
|
|
34
|
-
if (last && last.href === s.href && last.sizePt === s.sizePt && last.colorHex === s.colorHex && last.fontName === s.fontName && last.outline?.colorHex === s.outline?.colorHex && last.outline?.widthPt === s.outline?.widthPt && last.bold === s.bold && last.italic === s.italic && last.script === s.script && sameMarkup(last.markup, s.markup)) last.text += s.text;
|
|
64
|
+
if (last && last.href === s.href && last.sizePt === s.sizePt && last.colorHex === s.colorHex && last.fontName === s.fontName && last.outline?.colorHex === s.outline?.colorHex && last.outline?.widthPt === s.outline?.widthPt && last.bold === s.bold && last.italic === s.italic && last.script === s.script && last.widthScale === s.widthScale && sameMarkup(last.markup, s.markup)) last.text += s.text;
|
|
35
65
|
else merged.push({
|
|
36
66
|
text: s.text,
|
|
37
67
|
...s.href !== void 0 ? { href: s.href } : {},
|
|
@@ -42,7 +72,8 @@ function paragraphFromRuns(spans, outlineLevel, placement) {
|
|
|
42
72
|
...s.bold !== void 0 ? { bold: s.bold } : {},
|
|
43
73
|
...s.italic !== void 0 ? { italic: s.italic } : {},
|
|
44
74
|
...s.script !== void 0 ? { script: s.script } : {},
|
|
45
|
-
...s.markup !== void 0 ? { markup: s.markup } : {}
|
|
75
|
+
...s.markup !== void 0 ? { markup: s.markup } : {},
|
|
76
|
+
...s.widthScale !== void 0 ? { widthScale: s.widthScale } : {}
|
|
46
77
|
});
|
|
47
78
|
}
|
|
48
79
|
const runs = merged.map((m) => ({
|
|
@@ -65,7 +96,10 @@ function paragraphFromRuns(spans, outlineLevel, placement) {
|
|
|
65
96
|
properties: {
|
|
66
97
|
...r.sizePt !== void 0 ? { fontSizePt: pt(r.sizePt) } : {},
|
|
67
98
|
...r.colorHex !== void 0 ? { colorHex: r.colorHex } : {},
|
|
68
|
-
...r.fontName !== void 0 ? { fontFamily: {
|
|
99
|
+
...r.fontName !== void 0 ? { fontFamily: {
|
|
100
|
+
ascii: r.fontName,
|
|
101
|
+
hAnsi: r.fontName
|
|
102
|
+
} } : {},
|
|
69
103
|
...r.outline !== void 0 ? { textOutline: r.outline } : {},
|
|
70
104
|
...r.bold ? { bold: true } : {},
|
|
71
105
|
...r.italic ? { italic: true } : {},
|
|
@@ -73,7 +107,8 @@ function paragraphFromRuns(spans, outlineLevel, placement) {
|
|
|
73
107
|
...r.markup?.highlightHex !== void 0 ? { shadingColorHex: r.markup.highlightHex } : {},
|
|
74
108
|
...r.markup?.underline !== void 0 ? { underline: r.markup.underline } : {},
|
|
75
109
|
...r.markup?.underlineHex !== void 0 ? { underlineColorHex: r.markup.underlineHex } : {},
|
|
76
|
-
...r.markup?.strike === true ? { strike: true } : {}
|
|
110
|
+
...r.markup?.strike === true ? { strike: true } : {},
|
|
111
|
+
...r.widthScale !== void 0 ? { widthScale: r.widthScale } : {}
|
|
77
112
|
},
|
|
78
113
|
...r.href ? { href: r.href } : {}
|
|
79
114
|
}))
|
|
@@ -85,6 +120,23 @@ function sameMarkup(a, b) {
|
|
|
85
120
|
return a?.highlightHex === b?.highlightHex && a?.underline === b?.underline && a?.underlineHex === b?.underlineHex && a?.strike === b?.strike;
|
|
86
121
|
}
|
|
87
122
|
/**
|
|
123
|
+
* The height of a line that carries nothing to read: one twip, the least
|
|
124
|
+
* §17.3.1.33 can state. Zero is not a height a writer states at all — it is
|
|
125
|
+
* read as "no line spacing given", and the reader's single spacing comes back.
|
|
126
|
+
*/
|
|
127
|
+
var CARRIER_LINE_PT = pt(.05);
|
|
128
|
+
/**
|
|
129
|
+
* §17.3.1.33 — the paragraph a floating mark is anchored in, which takes no
|
|
130
|
+
* room: the mark stands where the page drew it and the line carrying it is on
|
|
131
|
+
* no page. Left at a reader's single spacing, every rule and fill an invoice
|
|
132
|
+
* draws was a blank line in the flow, and the text under it moved down a line
|
|
133
|
+
* for each.
|
|
134
|
+
*/
|
|
135
|
+
var FLOAT_CARRIER = {
|
|
136
|
+
spacingLine: CARRIER_LINE_PT,
|
|
137
|
+
spacingLineRule: "exact"
|
|
138
|
+
};
|
|
139
|
+
/**
|
|
88
140
|
* Store a {@link PdfImage}'s bytes (content-addressed dedup) and build the image
|
|
89
141
|
* {@link BodyElement} that references them, sized in points from the placement
|
|
90
142
|
* CTM. `alt` becomes the block's alt text when given.
|
|
@@ -112,9 +164,10 @@ function imageBlock(image, resources, alt, frame, zOrder, behind = false) {
|
|
|
112
164
|
width: pt(image.widthPt),
|
|
113
165
|
height: pt(image.heightPt),
|
|
114
166
|
...image.rotationDeg !== void 0 ? { rotation60k: Math.round(-image.rotationDeg * 6e4) } : {},
|
|
167
|
+
...image.flipV === true ? { flipV: true } : {},
|
|
115
168
|
...image.crop ? { crop: image.crop } : {},
|
|
116
169
|
...image.alpha !== void 0 ? { alpha: image.alpha } : {},
|
|
117
|
-
paragraphProperties: {},
|
|
170
|
+
paragraphProperties: float ? FLOAT_CARRIER : {},
|
|
118
171
|
...alt ? { altText: alt } : {}
|
|
119
172
|
}
|
|
120
173
|
};
|
|
@@ -138,7 +191,6 @@ function imageBlock(image, resources, alt, frame, zOrder, behind = false) {
|
|
|
138
191
|
* @returns A shape carrying the text, anchored where the glyphs were.
|
|
139
192
|
*/
|
|
140
193
|
function positionedText(spans, box, frame, zOrder, rotation60k) {
|
|
141
|
-
const paragraph = paragraphFromRuns(spans);
|
|
142
194
|
return {
|
|
143
195
|
kind: "shape",
|
|
144
196
|
shape: {
|
|
@@ -154,22 +206,134 @@ function positionedText(spans, box, frame, zOrder, rotation60k) {
|
|
|
154
206
|
offsetPt: pt(Math.max(0, frame.top - box.y - box.height))
|
|
155
207
|
}
|
|
156
208
|
},
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
209
|
+
...lineBox(spans, box, rotation60k),
|
|
210
|
+
paragraphProperties: FLOAT_CARRIER
|
|
211
|
+
}
|
|
212
|
+
};
|
|
213
|
+
}
|
|
214
|
+
/** A line of text in a box of its own, sized to the box, which nobody sees. */
|
|
215
|
+
function lineBox(spans, box, rotation60k, noWrap = false) {
|
|
216
|
+
const paragraph = paragraphFromRuns(spans, void 0, {
|
|
217
|
+
spacingLine: pt(Math.max(1, box.height)),
|
|
218
|
+
spacingLineRule: "exact"
|
|
219
|
+
});
|
|
220
|
+
return {
|
|
221
|
+
width: pt(Math.max(1, box.width)),
|
|
222
|
+
height: pt(Math.max(1, box.height)),
|
|
223
|
+
...rotation60k !== void 0 ? { transform: { rotation60k } } : {},
|
|
224
|
+
geometry: {
|
|
225
|
+
kind: "preset",
|
|
226
|
+
preset: "rect"
|
|
227
|
+
},
|
|
228
|
+
fill: { kind: "none" },
|
|
229
|
+
text: {
|
|
230
|
+
content: [paragraph],
|
|
231
|
+
insetLeft: pt(0),
|
|
232
|
+
insetTop: pt(0),
|
|
233
|
+
insetRight: pt(0),
|
|
234
|
+
insetBottom: pt(0),
|
|
235
|
+
...noWrap ? { noWrap: true } : {}
|
|
236
|
+
}
|
|
237
|
+
};
|
|
238
|
+
}
|
|
239
|
+
/** A path of a figure, drawn where the page drew it. */
|
|
240
|
+
function vectorMember(v) {
|
|
241
|
+
const el = shapeBlock(v);
|
|
242
|
+
if (el.kind !== "shape") throw new Error("a path is drawn as a shape");
|
|
243
|
+
return {
|
|
244
|
+
shape: el.shape,
|
|
245
|
+
left: v.minX,
|
|
246
|
+
top: v.maxY
|
|
247
|
+
};
|
|
248
|
+
}
|
|
249
|
+
/**
|
|
250
|
+
* A picture of a figure: a box filled with it (§20.1.8.14), which is what a
|
|
251
|
+
* `pic:pic` inside a group is — the shadows comments.pdf lays under each of
|
|
252
|
+
* its boxes.
|
|
253
|
+
*/
|
|
254
|
+
function imageMember(image, resources) {
|
|
255
|
+
const turned = image.rotationDeg !== void 0 || image.flipV === true;
|
|
256
|
+
return {
|
|
257
|
+
shape: {
|
|
258
|
+
width: pt(image.widthPt),
|
|
259
|
+
height: pt(image.heightPt),
|
|
260
|
+
...turned ? { transform: {
|
|
261
|
+
...image.rotationDeg !== void 0 ? { rotation60k: Math.round(-image.rotationDeg * 6e4) } : {},
|
|
262
|
+
...image.flipV === true ? { flipV: true } : {}
|
|
263
|
+
} } : {},
|
|
160
264
|
geometry: {
|
|
161
265
|
kind: "preset",
|
|
162
266
|
preset: "rect"
|
|
163
267
|
},
|
|
164
|
-
fill: {
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
insetRight: pt(0),
|
|
170
|
-
insetBottom: pt(0)
|
|
268
|
+
fill: {
|
|
269
|
+
kind: "picture",
|
|
270
|
+
imageResource: resources.put(image.bytes),
|
|
271
|
+
...image.crop ? { imageCrop: image.crop } : {},
|
|
272
|
+
...image.alpha !== void 0 ? { alpha: image.alpha } : {}
|
|
171
273
|
},
|
|
172
274
|
paragraphProperties: {}
|
|
275
|
+
},
|
|
276
|
+
left: image.x,
|
|
277
|
+
top: image.y + image.heightPt
|
|
278
|
+
};
|
|
279
|
+
}
|
|
280
|
+
/**
|
|
281
|
+
* A label of a figure: a line of its words, in a box as wide as they are.
|
|
282
|
+
* The box never wraps — set in a face a little wider than the page's, one
|
|
283
|
+
* word would break in two.
|
|
284
|
+
*/
|
|
285
|
+
function labelMember(spans, box, rotation60k) {
|
|
286
|
+
return {
|
|
287
|
+
shape: {
|
|
288
|
+
...lineBox(spans, box, rotation60k, true),
|
|
289
|
+
paragraphProperties: {}
|
|
290
|
+
},
|
|
291
|
+
left: box.x,
|
|
292
|
+
top: box.y + box.height
|
|
293
|
+
};
|
|
294
|
+
}
|
|
295
|
+
/** The box a figure's parts stand in, on the page (y-up). */
|
|
296
|
+
function membersBox(members) {
|
|
297
|
+
return {
|
|
298
|
+
left: Math.min(...members.map((m) => m.left)),
|
|
299
|
+
top: Math.max(...members.map((m) => m.top)),
|
|
300
|
+
right: Math.max(...members.map((m) => m.left + m.shape.width)),
|
|
301
|
+
bottom: Math.min(...members.map((m) => m.top - m.shape.height))
|
|
302
|
+
};
|
|
303
|
+
}
|
|
304
|
+
/**
|
|
305
|
+
* §20.5.2.17 — a figure as the one drawing it is: a group holding its paths,
|
|
306
|
+
* its pictures and its labels at their places in it, set in a paragraph of
|
|
307
|
+
* its own. An inline drawing takes the room it is tall (a line holding
|
|
308
|
+
* nothing but a picture is the picture's height in Word), so the text after
|
|
309
|
+
* it follows it, and the figure goes with its paragraph wherever the text
|
|
310
|
+
* puts that.
|
|
311
|
+
*
|
|
312
|
+
* @param members Its parts, in the order they are painted.
|
|
313
|
+
* @param properties The paragraph it stands in.
|
|
314
|
+
*/
|
|
315
|
+
function figureBlock(members, properties) {
|
|
316
|
+
const box = membersBox(members);
|
|
317
|
+
return {
|
|
318
|
+
kind: "shape",
|
|
319
|
+
shape: {
|
|
320
|
+
width: pt(box.right - box.left),
|
|
321
|
+
height: pt(box.top - box.bottom),
|
|
322
|
+
children: members.map((m) => ({
|
|
323
|
+
shape: m.shape,
|
|
324
|
+
xPt: pt(m.left - box.left),
|
|
325
|
+
yPt: pt(box.top - m.top)
|
|
326
|
+
})),
|
|
327
|
+
geometry: {
|
|
328
|
+
kind: "custom",
|
|
329
|
+
custom: {
|
|
330
|
+
pathWidth: 0,
|
|
331
|
+
pathHeight: 0,
|
|
332
|
+
commands: []
|
|
333
|
+
}
|
|
334
|
+
},
|
|
335
|
+
fill: { kind: "none" },
|
|
336
|
+
paragraphProperties: properties
|
|
173
337
|
}
|
|
174
338
|
};
|
|
175
339
|
}
|
|
@@ -226,17 +390,21 @@ function shapeBlock(v, frame, zOrder, behind = false) {
|
|
|
226
390
|
const alpha = v.alpha !== void 0 ? { alpha: v.alpha } : {};
|
|
227
391
|
const fill = v.gradient !== void 0 ? {
|
|
228
392
|
kind: "gradient",
|
|
229
|
-
gradient: v.gradient,
|
|
393
|
+
gradient: gradientOverBox(v.gradient, v),
|
|
230
394
|
...alpha
|
|
231
395
|
} : v.fillHex !== void 0 ? {
|
|
232
396
|
kind: "solid",
|
|
233
397
|
colorHex: v.fillHex,
|
|
234
398
|
...alpha
|
|
235
399
|
} : { kind: "none" };
|
|
400
|
+
const dash = v.strokeHex !== void 0 && v.dash !== void 0 ? penDash(v.dash, pen) : [];
|
|
236
401
|
const line = v.strokeHex !== void 0 ? {
|
|
237
402
|
width: pt(pen),
|
|
238
403
|
colorHex: v.strokeHex,
|
|
239
|
-
fill: "solid"
|
|
404
|
+
fill: "solid",
|
|
405
|
+
...dash.length > 0 ? { customDash: dash } : {},
|
|
406
|
+
cap: v.cap ?? "flat",
|
|
407
|
+
...v.strokeAlpha !== void 0 ? { alpha: v.strokeAlpha } : {}
|
|
240
408
|
} : void 0;
|
|
241
409
|
const float = frame !== void 0 ? {
|
|
242
410
|
wrap: "none",
|
|
@@ -267,7 +435,7 @@ function shapeBlock(v, frame, zOrder, behind = false) {
|
|
|
267
435
|
},
|
|
268
436
|
fill,
|
|
269
437
|
...line ? { line } : {},
|
|
270
|
-
paragraphProperties: {}
|
|
438
|
+
paragraphProperties: float ? FLOAT_CARRIER : {}
|
|
271
439
|
}
|
|
272
440
|
};
|
|
273
441
|
}
|
|
@@ -282,10 +450,10 @@ function shapeBlock(v, frame, zOrder, behind = false) {
|
|
|
282
450
|
* this single geometry — a known approximation, still far better than a fixed
|
|
283
451
|
* `A4`. Returns `undefined` when there is no usable first-page box.
|
|
284
452
|
*/
|
|
285
|
-
function sectionFromPdfPages(pages) {
|
|
453
|
+
function sectionFromPdfPages(pages, display) {
|
|
286
454
|
const first = pages[0];
|
|
287
455
|
if (!first) return void 0;
|
|
288
|
-
const shown = displayOf(first);
|
|
456
|
+
const shown = display ?? displayOf(first);
|
|
289
457
|
const width = shown.width;
|
|
290
458
|
const height = shown.height;
|
|
291
459
|
if (!(width > 0 && height > 0)) return void 0;
|
|
@@ -306,6 +474,227 @@ function sectionFromPdfPages(pages) {
|
|
|
306
474
|
};
|
|
307
475
|
}
|
|
308
476
|
/**
|
|
477
|
+
* §17.6.20 — a section read in a page's text frame (see `textFrameOf`), set
|
|
478
|
+
* back on the sheet the page is shown on, its lines running down it (`tbRl`).
|
|
479
|
+
*
|
|
480
|
+
* The frame is the sheet turned back a quarter, so its measurements carry
|
|
481
|
+
* over edge for edge: the frame's left margin is where each line starts, the
|
|
482
|
+
* sheet's top one; its top margin is where the first line stands, the
|
|
483
|
+
* sheet's right one.
|
|
484
|
+
*
|
|
485
|
+
* @param section The section as the frame measured it.
|
|
486
|
+
* @param display The geometry its pages were read in.
|
|
487
|
+
* @returns The section on the sheet, or the section unchanged for a page read
|
|
488
|
+
* on its sheet.
|
|
489
|
+
*/
|
|
490
|
+
function sectionOnSheet(section, display) {
|
|
491
|
+
const sheet = display?.sheet;
|
|
492
|
+
if (!section || !sheet) return section;
|
|
493
|
+
const m = section.margins;
|
|
494
|
+
return {
|
|
495
|
+
...section,
|
|
496
|
+
pageSize: {
|
|
497
|
+
width: pt(sheet.width),
|
|
498
|
+
height: pt(sheet.height),
|
|
499
|
+
orientation: sheet.width > sheet.height ? "landscape" : "portrait"
|
|
500
|
+
},
|
|
501
|
+
...m ? { margins: {
|
|
502
|
+
...m,
|
|
503
|
+
top: m.left,
|
|
504
|
+
right: m.top,
|
|
505
|
+
bottom: m.right,
|
|
506
|
+
left: m.bottom
|
|
507
|
+
} } : {},
|
|
508
|
+
textDirection: "tbRl"
|
|
509
|
+
};
|
|
510
|
+
}
|
|
511
|
+
/**
|
|
512
|
+
* A mark anchored to a page read in its text frame, set on the SHEET the page
|
|
513
|
+
* is shown on: where the page drew it, turned with the page.
|
|
514
|
+
*
|
|
515
|
+
* Word lays a `tbRl` section's text down the sheet but stands a drawing
|
|
516
|
+
* anchored to the page at its offsets on the sheet, upright (§17.6.20) — so a
|
|
517
|
+
* picture the frame placed is carried onto the sheet, and turned a quarter
|
|
518
|
+
* clockwise as the words around it are.
|
|
519
|
+
*
|
|
520
|
+
* @param el A body element, anchored or not.
|
|
521
|
+
* @param sheetWidth The sheet's width, which is the frame's height.
|
|
522
|
+
* @returns The element, anchored on the sheet.
|
|
523
|
+
*/
|
|
524
|
+
function floatOntoSheet(el, sheetWidth) {
|
|
525
|
+
const onto = (float, width, height) => {
|
|
526
|
+
if (float.posH?.relativeFrom !== "page" || float.posV?.relativeFrom !== "page") return void 0;
|
|
527
|
+
const cx = (float.posH.offsetPt ?? 0) + width / 2;
|
|
528
|
+
const cy = (float.posV.offsetPt ?? 0) + height / 2;
|
|
529
|
+
return {
|
|
530
|
+
...float,
|
|
531
|
+
posH: {
|
|
532
|
+
relativeFrom: "page",
|
|
533
|
+
offsetPt: pt(sheetWidth - cy - width / 2)
|
|
534
|
+
},
|
|
535
|
+
posV: {
|
|
536
|
+
relativeFrom: "page",
|
|
537
|
+
offsetPt: pt(cx - height / 2)
|
|
538
|
+
}
|
|
539
|
+
};
|
|
540
|
+
};
|
|
541
|
+
const quarter = (rotation60k) => ((rotation60k ?? 0) + 90 * 6e4) % (360 * 6e4);
|
|
542
|
+
if (el.kind === "image" && el.image.float) {
|
|
543
|
+
const float = onto(el.image.float, el.image.width, el.image.height);
|
|
544
|
+
if (!float) return el;
|
|
545
|
+
return {
|
|
546
|
+
...el,
|
|
547
|
+
image: {
|
|
548
|
+
...el.image,
|
|
549
|
+
float,
|
|
550
|
+
rotation60k: quarter(el.image.rotation60k)
|
|
551
|
+
}
|
|
552
|
+
};
|
|
553
|
+
}
|
|
554
|
+
if (el.kind === "shape" && el.shape.float) {
|
|
555
|
+
const float = onto(el.shape.float, el.shape.width, el.shape.height);
|
|
556
|
+
if (!float) return el;
|
|
557
|
+
const rotation60k = quarter(el.shape.transform?.rotation60k);
|
|
558
|
+
return {
|
|
559
|
+
...el,
|
|
560
|
+
shape: {
|
|
561
|
+
...el.shape,
|
|
562
|
+
float,
|
|
563
|
+
transform: {
|
|
564
|
+
...el.shape.transform,
|
|
565
|
+
rotation60k
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
};
|
|
569
|
+
}
|
|
570
|
+
return el;
|
|
571
|
+
}
|
|
572
|
+
/**
|
|
573
|
+
* §8.4.3.6 — a page's dash pattern as DrawingML states one (§20.1.8.21
|
|
574
|
+
* `a:custDash`): dash and gap in turn, as multiples of the pen's width.
|
|
575
|
+
*
|
|
576
|
+
* An odd count of lengths runs twice before it repeats, so the dashes and the
|
|
577
|
+
* gaps trade places the second time round; written out once more, the pairs
|
|
578
|
+
* come out even. A dash of no length is a DOT, drawn by the pen's cap alone —
|
|
579
|
+
* which no .docx line carries — so it is set a pen's width long and the gap
|
|
580
|
+
* after it gives that back, keeping the pattern's period.
|
|
581
|
+
*
|
|
582
|
+
* @param dash The lengths in page-space points.
|
|
583
|
+
* @param pen The pen's width in points.
|
|
584
|
+
* @returns The pattern in pen widths, or an empty array for a solid line.
|
|
585
|
+
*/
|
|
586
|
+
function penDash(dash, pen) {
|
|
587
|
+
if (dash.length === 0 || !(pen > 0)) return [];
|
|
588
|
+
const out = (dash.length % 2 === 1 ? [...dash, ...dash] : [...dash]).map((n) => n / pen);
|
|
589
|
+
for (let i = 0; i < out.length; i += 2) {
|
|
590
|
+
if (out[i] > 0) continue;
|
|
591
|
+
out[i] = 1;
|
|
592
|
+
out[i + 1] = Math.max(out[i + 1] - 1, MIN_DASH_GAP);
|
|
593
|
+
}
|
|
594
|
+
return out;
|
|
595
|
+
}
|
|
596
|
+
/** The least gap a dash pattern keeps, in pen widths, so a dotted line stays dotted. */
|
|
597
|
+
var MIN_DASH_GAP = .5;
|
|
598
|
+
/** What the measure gives back, so the widest line still fits when re-set. */
|
|
599
|
+
var SLACK = .01;
|
|
600
|
+
/** How much of the sheet a margin down the page may take. */
|
|
601
|
+
var DEEPEST_MARGIN = .5;
|
|
602
|
+
/**
|
|
603
|
+
* How far in, as a share of the sheet, a right margin the lines do not show
|
|
604
|
+
* may come: what a sheet of a line or two is re-set across is at least the
|
|
605
|
+
* rest of it.
|
|
606
|
+
*/
|
|
607
|
+
var GUESSED_MARGIN = 1 / 3;
|
|
608
|
+
/** The narrowest measure a sheet's own lines may leave, as a share of the sheet. */
|
|
609
|
+
var NARROWEST_MEASURE = .2;
|
|
610
|
+
/**
|
|
611
|
+
* The lines a sheet's runs stand on — baselines further apart than half the
|
|
612
|
+
* size of the type on them — and how many of them END at the sheet's right
|
|
613
|
+
* edge, within an em of the farthest: the lines a measure broke, or a margin
|
|
614
|
+
* justified, rather than the ones their author stopped.
|
|
615
|
+
*/
|
|
616
|
+
function linesOf(runs) {
|
|
617
|
+
const ink = runs.filter((r) => r.text.trim() !== "").map((r) => ({
|
|
618
|
+
y: r.y,
|
|
619
|
+
end: r.endX,
|
|
620
|
+
size: r.fontSizePt || 10
|
|
621
|
+
})).sort((a, b) => b.y - a.y);
|
|
622
|
+
const lines = [];
|
|
623
|
+
for (const at of ink) {
|
|
624
|
+
const last = lines[lines.length - 1];
|
|
625
|
+
if (last !== void 0 && last.y - at.y <= Math.max(last.size, at.size) / 2) {
|
|
626
|
+
last.end = Math.max(last.end, at.end);
|
|
627
|
+
last.size = Math.max(last.size, at.size);
|
|
628
|
+
} else lines.push({ ...at });
|
|
629
|
+
}
|
|
630
|
+
const edge = Math.max(...lines.map((l) => l.end));
|
|
631
|
+
return {
|
|
632
|
+
count: lines.length,
|
|
633
|
+
atEdge: lines.filter((l) => edge - l.end <= l.size).length
|
|
634
|
+
};
|
|
635
|
+
}
|
|
636
|
+
/**
|
|
637
|
+
* The white, in ems of the text, a running foot keeps above it — which is
|
|
638
|
+
* what the reader asked of it before it took the line for a foot at all.
|
|
639
|
+
*/
|
|
640
|
+
var FOOT_CLEAR_EM = 2;
|
|
641
|
+
/**
|
|
642
|
+
* §17.3.1.33 — where the baseline of an EXACT line stands in its box, from the
|
|
643
|
+
* top. LibreOffice puts it at four fifths of the height; Word at the height
|
|
644
|
+
* less the face's descent, which for the faces documents use is within a tenth
|
|
645
|
+
* of a line of the same place.
|
|
646
|
+
*/
|
|
647
|
+
var BASELINE_AT = .8;
|
|
648
|
+
/** A line's box where no pitch was measured: the ordinary single spacing, in ems. */
|
|
649
|
+
var NATURAL_LINE_EM = 1.2;
|
|
650
|
+
/**
|
|
651
|
+
* How far above its baseline the first line's BOX reaches, as a fraction of the
|
|
652
|
+
* size — which is what the top margin has to leave room for. The lines are set
|
|
653
|
+
* in exact boxes (see `groupIntoParagraphs`), so this is where the box's top
|
|
654
|
+
* stands, not where a face's ascender happens to.
|
|
655
|
+
*/
|
|
656
|
+
var ASCENDER = BASELINE_AT * NATURAL_LINE_EM;
|
|
657
|
+
/**
|
|
658
|
+
* And how far below its baseline the last line's box reaches. A box set half
|
|
659
|
+
* as deep again as its size reaches three tenths of it down; measured to a
|
|
660
|
+
* face's descender instead, bug1337429.pdf's last line did not fit the sheet
|
|
661
|
+
* it was drawn on and went to a second one.
|
|
662
|
+
*/
|
|
663
|
+
var DESCENDER = (1 - BASELINE_AT) * 1.5;
|
|
664
|
+
/**
|
|
665
|
+
* How small, against the document's own text, type may be set and still be
|
|
666
|
+
* read as text rather than as a mark the producer left on the sheet.
|
|
667
|
+
*/
|
|
668
|
+
var LEGIBLE_SHARE = .25;
|
|
669
|
+
/**
|
|
670
|
+
* The size a document's text is set in: the middle of every size its runs
|
|
671
|
+
* carry.
|
|
672
|
+
*
|
|
673
|
+
* @param pageRuns Each page's runs.
|
|
674
|
+
* @returns The size, in points; 0 where no run states one.
|
|
675
|
+
*/
|
|
676
|
+
function textSizeOf(pageRuns) {
|
|
677
|
+
const sizes = pageRuns.flat().map((r) => r.fontSizePt).filter((s) => s > 0).sort((a, b) => a - b);
|
|
678
|
+
return sizes[Math.floor(sizes.length / 2)] ?? 0;
|
|
679
|
+
}
|
|
680
|
+
/**
|
|
681
|
+
* Whether a run is set too small to be read, beside the text of its document —
|
|
682
|
+
* a mark the producer leaves on the sheet, not a line of the page.
|
|
683
|
+
*
|
|
684
|
+
* TCPDF signs the last page of everything it makes "Powered by TCPDF
|
|
685
|
+
* (www.tcpdf.org)" in type one point high, three points from the corner of the
|
|
686
|
+
* paper. Taken for text it was the leftmost and the lowest thing on the page:
|
|
687
|
+
* the margins came in at the edge of the sheet and every line of basicapi.pdf
|
|
688
|
+
* and alphatrans.pdf was set against it.
|
|
689
|
+
*
|
|
690
|
+
* @param run The run.
|
|
691
|
+
* @param textSize The size the document's text is set in (see {@link textSizeOf}).
|
|
692
|
+
* @returns `true` where the run is a mark rather than text.
|
|
693
|
+
*/
|
|
694
|
+
function tooSmallToRead(run, textSize) {
|
|
695
|
+
return run.fontSizePt > 0 && run.fontSizePt < textSize * LEGIBLE_SHARE;
|
|
696
|
+
}
|
|
697
|
+
/**
|
|
309
698
|
* The margins the SOURCE used, measured off where its words actually sit.
|
|
310
699
|
*
|
|
311
700
|
* A PDF states none — text is placed anywhere on the MediaBox — so the reader
|
|
@@ -322,18 +711,13 @@ function sectionFromPdfPages(pages) {
|
|
|
322
711
|
* @param section The section the page box gave, or `undefined`.
|
|
323
712
|
* @param shown Each page as it is shown, for its own width and height.
|
|
324
713
|
* @param pageRuns Each page's runs, already placed on the shown page.
|
|
714
|
+
* @param pageMarks Each page's pictures, which the measure has to hold.
|
|
715
|
+
* @param foot The running foot lifted off the pages, as the first page
|
|
716
|
+
* showed it — the band the text block stands above.
|
|
325
717
|
* @returns The section with measured margins, or `section` when nothing is
|
|
326
718
|
* measurable.
|
|
327
719
|
*/
|
|
328
|
-
|
|
329
|
-
var SLACK = .01;
|
|
330
|
-
/** How much of the sheet a margin down the page may take. */
|
|
331
|
-
var DEEPEST_MARGIN = .5;
|
|
332
|
-
/** How far a face's ascender stands above its baseline, as a fraction of the size. */
|
|
333
|
-
var ASCENDER = .8;
|
|
334
|
-
/** And its descender below — the two together are a little over one em. */
|
|
335
|
-
var DESCENDER = .22;
|
|
336
|
-
function withMeasuredMargins(section, shown, pageRuns, pageMarks = []) {
|
|
720
|
+
function withMeasuredMargins(section, shown, pageRuns, pageMarks = [], foot = []) {
|
|
337
721
|
if (!section?.pageSize) return section;
|
|
338
722
|
const width = section.pageSize.width;
|
|
339
723
|
const height = section.pageSize.height;
|
|
@@ -341,8 +725,10 @@ function withMeasuredMargins(section, shown, pageRuns, pageMarks = []) {
|
|
|
341
725
|
const rights = [];
|
|
342
726
|
const tops = [];
|
|
343
727
|
const bottoms = [];
|
|
344
|
-
|
|
728
|
+
const textSize = textSizeOf(pageRuns);
|
|
729
|
+
pageRuns.forEach((all, i) => {
|
|
345
730
|
const page = shown[i];
|
|
731
|
+
const runs = all.filter((r) => r.annotation !== true && !tooSmallToRead(r, textSize));
|
|
346
732
|
if (!page || runs.length === 0) return;
|
|
347
733
|
let minX = Infinity;
|
|
348
734
|
let maxX = -Infinity;
|
|
@@ -365,6 +751,7 @@ function withMeasuredMargins(section, shown, pageRuns, pageMarks = []) {
|
|
|
365
751
|
topSize = r.fontSizePt;
|
|
366
752
|
}
|
|
367
753
|
}
|
|
754
|
+
const textEnd = maxX;
|
|
368
755
|
for (const mark of pageMarks[i] ?? []) {
|
|
369
756
|
if (!Number.isFinite(mark.x) || !Number.isFinite(mark.y)) continue;
|
|
370
757
|
maxX = Math.max(maxX, mark.x + mark.widthPt);
|
|
@@ -372,7 +759,12 @@ function withMeasuredMargins(section, shown, pageRuns, pageMarks = []) {
|
|
|
372
759
|
}
|
|
373
760
|
if (!Number.isFinite(minX) || !Number.isFinite(minY)) return;
|
|
374
761
|
lefts.push(minX);
|
|
375
|
-
|
|
762
|
+
const lines = linesOf(runs);
|
|
763
|
+
rights.push({
|
|
764
|
+
value: page.width - maxX,
|
|
765
|
+
full: lines.count >= 3 || maxX > textEnd,
|
|
766
|
+
edged: lines.atEdge >= 2 && maxX <= textEnd
|
|
767
|
+
});
|
|
376
768
|
tops.push(page.height - maxY - topSize * ASCENDER);
|
|
377
769
|
bottoms.push(minY - bottomSize * DESCENDER);
|
|
378
770
|
});
|
|
@@ -382,34 +774,148 @@ function withMeasuredMargins(section, shown, pageRuns, pageMarks = []) {
|
|
|
382
774
|
return s[Math.floor(s.length / 2)] ?? 0;
|
|
383
775
|
};
|
|
384
776
|
const clamp = (v, span, most = 1 / 3) => pt(Math.max(0, Math.min(v, span * most)));
|
|
777
|
+
const inked = foot.filter((r) => r.text.trim() !== "");
|
|
778
|
+
const footTop = Math.max(...inked.map((r) => r.y + r.fontSizePt * ASCENDER));
|
|
779
|
+
const footBottom = Math.min(...inked.map((r) => r.y - r.fontSizePt * DESCENDER));
|
|
780
|
+
const floor = inked.length > 0 ? footTop + textSize * FOOT_CLEAR_EM : Infinity;
|
|
781
|
+
const left = clamp(Math.min(...lefts), width);
|
|
782
|
+
const voters = rights.filter((r) => r.full);
|
|
783
|
+
const farthest = voters.length > 0 && voters.every((r) => r.edged) ? Math.max(width * GUESSED_MARGIN, width * (1 - NARROWEST_MEASURE) - left) : width * GUESSED_MARGIN;
|
|
385
784
|
return {
|
|
386
785
|
...section,
|
|
387
786
|
margins: {
|
|
388
|
-
left
|
|
389
|
-
right:
|
|
787
|
+
left,
|
|
788
|
+
right: pt(Math.max(0, Math.min(median((voters.length > 0 ? voters : rights).map((r) => r.value)) - width * SLACK, farthest))),
|
|
390
789
|
top: clamp(Math.min(...tops), height, DEEPEST_MARGIN),
|
|
391
|
-
bottom: clamp(Math.min(...bottoms) - height * SLACK, height, DEEPEST_MARGIN)
|
|
790
|
+
bottom: clamp(Math.min(...bottoms, floor) - height * SLACK, height, DEEPEST_MARGIN),
|
|
791
|
+
...inked.length > 0 ? { footer: clamp(footBottom, height, DEEPEST_MARGIN) } : {}
|
|
392
792
|
}
|
|
393
793
|
};
|
|
394
794
|
}
|
|
395
795
|
/**
|
|
796
|
+
* The runs set in a face the page KERNED or LIGATED, marked so (see
|
|
797
|
+
* `pageSpacing`, `FaceOutlines.ligatures`): a word processor kerns nothing a
|
|
798
|
+
* run does not ask it to, and set unkerned, a line kerned on the page runs
|
|
799
|
+
* longer than it did there; a ligature the embedded face carries is formed
|
|
800
|
+
* where the run asks for its face's ligatures.
|
|
801
|
+
*
|
|
802
|
+
* @param blocks The blocks to mark, tables and text boxes within them too.
|
|
803
|
+
* @param typeset Run font name → what its runs ask for.
|
|
804
|
+
* @returns The blocks, their runs in those faces asking for it.
|
|
805
|
+
*/
|
|
806
|
+
function typesetRuns(blocks, typeset) {
|
|
807
|
+
if (typeset.size === 0) return [...blocks];
|
|
808
|
+
const shape = (s) => ({
|
|
809
|
+
...s,
|
|
810
|
+
...s.text ? { text: {
|
|
811
|
+
...s.text,
|
|
812
|
+
content: typesetRuns(s.text.content, typeset)
|
|
813
|
+
} } : {},
|
|
814
|
+
...s.children ? { children: s.children.map((child) => ({
|
|
815
|
+
...child,
|
|
816
|
+
shape: shape(child.shape)
|
|
817
|
+
})) } : {}
|
|
818
|
+
});
|
|
819
|
+
return blocks.map((el) => {
|
|
820
|
+
if (el.kind === "paragraph") {
|
|
821
|
+
const runs = el.paragraph.runs.map((run) => {
|
|
822
|
+
const face = run.properties.fontFamily?.ascii;
|
|
823
|
+
const asked = face !== void 0 ? typeset.get(face) : void 0;
|
|
824
|
+
return asked ? {
|
|
825
|
+
...run,
|
|
826
|
+
properties: {
|
|
827
|
+
...run.properties,
|
|
828
|
+
...asked
|
|
829
|
+
}
|
|
830
|
+
} : run;
|
|
831
|
+
});
|
|
832
|
+
return {
|
|
833
|
+
...el,
|
|
834
|
+
paragraph: {
|
|
835
|
+
...el.paragraph,
|
|
836
|
+
runs
|
|
837
|
+
}
|
|
838
|
+
};
|
|
839
|
+
}
|
|
840
|
+
if (el.kind === "table") {
|
|
841
|
+
const rows = el.table.rows.map((row) => ({
|
|
842
|
+
...row,
|
|
843
|
+
cells: row.cells.map((cell) => ({
|
|
844
|
+
...cell,
|
|
845
|
+
content: typesetRuns(cell.content, typeset)
|
|
846
|
+
}))
|
|
847
|
+
}));
|
|
848
|
+
return {
|
|
849
|
+
...el,
|
|
850
|
+
table: {
|
|
851
|
+
...el.table,
|
|
852
|
+
rows
|
|
853
|
+
}
|
|
854
|
+
};
|
|
855
|
+
}
|
|
856
|
+
if (el.kind === "shape") return {
|
|
857
|
+
...el,
|
|
858
|
+
shape: shape(el.shape)
|
|
859
|
+
};
|
|
860
|
+
return el;
|
|
861
|
+
});
|
|
862
|
+
}
|
|
863
|
+
/**
|
|
864
|
+
* What each face's runs ask for: kerning where the page kerned the face, and
|
|
865
|
+
* its standard ligatures where the page drew one of them (the ligatures the
|
|
866
|
+
* face's embedded font then forms are exactly the ones the page drew).
|
|
867
|
+
*
|
|
868
|
+
* @param kerned The run font names of the faces the page kerned.
|
|
869
|
+
* @param faceOutlines Run font name → the face's outlines, ligatures among them.
|
|
870
|
+
*/
|
|
871
|
+
function typesetting(kerned, faceOutlines) {
|
|
872
|
+
const out = /* @__PURE__ */ new Map();
|
|
873
|
+
for (const face of kerned) out.set(face, { kerningMinPt: KERN_FROM_PT });
|
|
874
|
+
for (const [face, outlines] of faceOutlines ?? []) if (outlines.ligatures && outlines.ligatures.size > 0) out.set(face, {
|
|
875
|
+
...out.get(face),
|
|
876
|
+
ligatures: "standard"
|
|
877
|
+
});
|
|
878
|
+
return out;
|
|
879
|
+
}
|
|
880
|
+
/** §17.3.2.19 — kerned from one point up: at every size a page is set in. */
|
|
881
|
+
var KERN_FROM_PT = pt(1);
|
|
882
|
+
/**
|
|
396
883
|
* Assemble the final {@link FlowDoc} for a reconstruction: the body elements
|
|
397
884
|
* with their styles resolved against the empty style sheet, the lifted-image
|
|
398
885
|
* resource store, and the optional page {@link SectionProperties}. Shared by
|
|
399
886
|
* both reconstruction paths (the tagged fast-path EP3 and the heuristic layout
|
|
400
887
|
* path EP4).
|
|
401
888
|
*/
|
|
402
|
-
function buildFlowDoc(body, resources = new ResourceStore(), section, embeddedFonts, sections = [], headersFooters) {
|
|
889
|
+
function buildFlowDoc(body, resources = new ResourceStore(), section, embeddedFonts, sections = [], headersFooters, faceFamilies, faceOutlines, kerned = /* @__PURE__ */ new Set()) {
|
|
890
|
+
const typeset = typesetting(kerned, faceOutlines);
|
|
891
|
+
const bands = headersFooters && typeset.size > 0 ? new Map([...headersFooters].map(([id, blocks]) => [id, typesetRuns(blocks, typeset)])) : headersFooters;
|
|
403
892
|
return {
|
|
404
893
|
kind: "flow",
|
|
405
|
-
body: resolveBodyStyles(
|
|
894
|
+
body: resolveBodyStyles(typesetRuns(body, typeset), RECONSTRUCTION_SHEET),
|
|
406
895
|
sections,
|
|
407
|
-
...
|
|
896
|
+
...bands && bands.size > 0 ? { headersFooters: bands } : {},
|
|
408
897
|
...section ? { section } : {},
|
|
409
898
|
...embeddedFonts && embeddedFonts.size > 0 ? { embeddedFonts } : {},
|
|
899
|
+
...faceFamilies && faceFamilies.size > 0 ? { faceFamilies } : {},
|
|
900
|
+
...faceOutlines && faceOutlines.size > 0 ? { faceOutlines } : {},
|
|
901
|
+
compatibilityMode: CURRENT_WORD_LAYOUT,
|
|
410
902
|
styles: EMPTY_STYLE_SHEET,
|
|
411
903
|
resources
|
|
412
904
|
};
|
|
413
905
|
}
|
|
906
|
+
/** [MS-DOCX] `compatibilityMode` 15 — Word 2013's layout, a current Word's. */
|
|
907
|
+
var CURRENT_WORD_LAYOUT = 15;
|
|
908
|
+
/**
|
|
909
|
+
* §17.3.1.44 — the sheet a reconstruction's paragraphs resolve against: the
|
|
910
|
+
* empty one, with widow control off. Every source page opens a page of its
|
|
911
|
+
* own, and the page already broke its paragraphs where it did; where a
|
|
912
|
+
* reconstructed column runs a line long, widow control — on wherever nothing
|
|
913
|
+
* says otherwise — carries a second line over with it, and the column after
|
|
914
|
+
* runs longer still.
|
|
915
|
+
*/
|
|
916
|
+
var RECONSTRUCTION_SHEET = {
|
|
917
|
+
...EMPTY_STYLE_SHEET,
|
|
918
|
+
defaultParagraphProperties: { widowControl: false }
|
|
919
|
+
};
|
|
414
920
|
//#endregion
|
|
415
|
-
export { buildFlowDoc, dedupeLosses, imageBlock, paragraphBlock, paragraphFromRuns, positionedText, sectionFromPdfPages, shapeBlock, withMeasuredMargins };
|
|
921
|
+
export { ASCENDER, BASELINE_AT, CARRIER_LINE_PT, GUESSED_MARGIN, NATURAL_LINE_EM, buildFlowDoc, dedupeLosses, figureBlock, floatOntoSheet, imageBlock, imageMember, labelMember, membersBox, paragraphBlock, paragraphFromRuns, positionedText, sectionFromPdfPages, sectionOnSheet, shapeBlock, spaceAfter, textSizeOf, tooSmallToRead, vectorMember, withMeasuredMargins };
|