reamkit 1.16.0 → 1.18.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/esm/core/bidi/segments.js +1 -2
- package/dist/esm/core/converter/project.js +5 -1
- package/dist/esm/core/document-model/index.d.ts +1 -1
- package/dist/esm/core/document-model/types.d.ts +526 -8
- package/dist/esm/core/drawingml/chart-geometry.d.ts +8 -12
- package/dist/esm/core/drawingml/chart-geometry.js +58 -9
- package/dist/esm/core/drawingml/chart-parser.js +44 -5
- package/dist/esm/core/drawingml/colors.d.ts +1 -1
- package/dist/esm/core/drawingml/colors.js +27 -1
- package/dist/esm/core/drawingml/preset-geometry.js +233 -0
- package/dist/esm/core/drawingml/shape-render.d.ts +12 -0
- package/dist/esm/core/drawingml/shape-render.js +116 -3
- package/dist/esm/core/drawingml/theme-parser.d.ts +10 -0
- package/dist/esm/core/drawingml/theme-parser.js +19 -1
- package/dist/esm/core/font/measure.d.ts +5 -0
- package/dist/esm/core/font/measure.js +21 -1
- package/dist/esm/core/images.d.ts +2 -2
- package/dist/esm/core/images.js +219 -0
- package/dist/esm/core/ir/flow.d.ts +23 -1
- package/dist/esm/core/lzw.d.ts +17 -0
- package/dist/esm/core/lzw.js +83 -0
- package/dist/esm/core/metafile/emf.d.ts +16 -0
- package/dist/esm/core/metafile/emf.js +635 -0
- package/dist/esm/core/metafile/picture.d.ts +106 -0
- package/dist/esm/core/metafile/picture.js +55 -0
- package/dist/esm/core/metafile/wmf.d.ts +9 -0
- package/dist/esm/core/metafile/wmf.js +371 -0
- package/dist/esm/core/numbering/apply.d.ts +5 -3
- package/dist/esm/core/numbering/apply.js +42 -11
- package/dist/esm/core/numbering/index.js +2 -0
- package/dist/esm/core/numbering/state.d.ts +13 -1
- package/dist/esm/core/numbering/state.js +256 -15
- package/dist/esm/core/po-helpers.js +16 -1
- package/dist/esm/core/style-cascade/resolver.js +66 -11
- package/dist/esm/core/style-cascade/table.js +35 -11
- package/dist/esm/core/style-cascade/types.d.ts +42 -1
- package/dist/esm/core/style-cascade/types.js +4 -0
- package/dist/esm/core/tiff.d.ts +20 -0
- package/dist/esm/core/tiff.js +339 -0
- package/dist/esm/core/vector.d.ts +22 -0
- package/dist/esm/excel/print-model.js +4 -2
- package/dist/esm/excel/sheet-to-flow.js +10 -8
- package/dist/esm/excel/worksheet-parser.js +1 -1
- package/dist/esm/html/html-writer.js +20 -5
- package/dist/esm/layout/page-doc.d.ts +107 -4
- package/dist/esm/layout/styled-layout.d.ts +44 -2
- package/dist/esm/layout/styled-layout.js +2330 -283
- package/dist/esm/pdf/shading.js +76 -6
- package/dist/esm/pdf/styled-page-emitter.js +294 -45
- package/dist/esm/pdf/vector-graphics.d.ts +29 -2
- package/dist/esm/pdf/vector-graphics.js +96 -12
- package/dist/esm/pdf-reader/image-decode.js +7 -75
- package/dist/esm/pptx/pptx-reader.js +1 -0
- package/dist/esm/pptx/slide-parser.d.ts +21 -4
- package/dist/esm/pptx/slide-parser.js +86 -23
- package/dist/esm/svg/svg-writer.js +2 -2
- package/dist/esm/word/document-parser.d.ts +96 -7
- package/dist/esm/word/document-parser.js +540 -116
- package/dist/esm/word/docx-reader.js +135 -43
- package/dist/esm/word/docx-writer.js +6 -1
- package/dist/esm/word/drawing-parser.d.ts +58 -5
- package/dist/esm/word/drawing-parser.js +1632 -63
- package/dist/esm/word/index.d.ts +4 -4
- package/dist/esm/word/numbering-parser.d.ts +2 -1
- package/dist/esm/word/numbering-parser.js +107 -21
- package/dist/esm/word/paragraph-properties.d.ts +11 -1
- package/dist/esm/word/paragraph-properties.js +219 -10
- package/dist/esm/word/run-properties.js +45 -1
- package/dist/esm/word/settings-parser.d.ts +24 -0
- package/dist/esm/word/settings-parser.js +31 -5
- package/dist/esm/word/shading.d.ts +11 -0
- package/dist/esm/word/shading.js +64 -0
- package/dist/esm/word/styles-parser.js +8 -2
- package/dist/esm/word/table-parser.d.ts +9 -1
- package/dist/esm/word/table-parser.js +182 -30
- package/package.json +1 -1
package/dist/esm/pdf/shading.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { dict, name, ref } from "./objects.js";
|
|
1
|
+
import { dict, name, ref, stream } from "./objects.js";
|
|
2
2
|
//#region src/pdf/shading.ts
|
|
3
3
|
/**
|
|
4
4
|
* The local-space bounding box of a shape's paths (y-up).
|
|
@@ -41,8 +41,7 @@ function shapeBbox(shape) {
|
|
|
41
41
|
* @returns A reference to the `/Pattern` object, for a `/Pattern cs /Pn scn` fill.
|
|
42
42
|
*/
|
|
43
43
|
function buildGradientPattern(doc, gradient, bbox, ctm) {
|
|
44
|
-
const
|
|
45
|
-
const shading = gradient.kind === "radial" ? radialShading(bbox, fnRef) : axialShading(gradient.angle ?? 0, bbox, fnRef);
|
|
44
|
+
const shading = gradient.kind === "radial" && gradient.sweep === "rect" ? rectShading(doc, bbox, gradient.stops, gradient.center) : gradient.kind === "radial" ? radialShading(bbox, doc.add(buildRamp(gradient.stops)), gradient.center) : axialShading(gradient.angle ?? 0, bbox, doc.add(buildRamp(gradient.stops)));
|
|
46
45
|
return doc.add(dict({
|
|
47
46
|
Type: name("Pattern"),
|
|
48
47
|
PatternType: 2,
|
|
@@ -82,9 +81,9 @@ function axialShading(angleDeg, b, fnRef) {
|
|
|
82
81
|
Extend: [true, true]
|
|
83
82
|
});
|
|
84
83
|
}
|
|
85
|
-
function radialShading(b, fnRef) {
|
|
86
|
-
const cx = (b.minX + b.maxX) / 2;
|
|
87
|
-
const cy = (b.minY + b.maxY) / 2;
|
|
84
|
+
function radialShading(b, fnRef, center) {
|
|
85
|
+
const cx = center ? b.minX + center.x * (b.maxX - b.minX) : (b.minX + b.maxX) / 2;
|
|
86
|
+
const cy = center ? b.maxY - center.y * (b.maxY - b.minY) : (b.minY + b.maxY) / 2;
|
|
88
87
|
const r = Math.hypot(b.maxX - b.minX, b.maxY - b.minY) / 2;
|
|
89
88
|
return dict({
|
|
90
89
|
ShadingType: 3,
|
|
@@ -101,6 +100,77 @@ function radialShading(b, fnRef) {
|
|
|
101
100
|
Extend: [true, true]
|
|
102
101
|
});
|
|
103
102
|
}
|
|
103
|
+
function rectShading(doc, b, stopsIn, center) {
|
|
104
|
+
const cx = center ? b.minX + center.x * (b.maxX - b.minX) : (b.minX + b.maxX) / 2;
|
|
105
|
+
const cy = center ? b.maxY - center.y * (b.maxY - b.minY) : (b.minY + b.maxY) / 2;
|
|
106
|
+
const halfW = Math.max((b.maxX - b.minX) / 2, 1e-6);
|
|
107
|
+
const halfH = Math.max((b.maxY - b.minY) / 2, 1e-6);
|
|
108
|
+
const domain = [
|
|
109
|
+
b.minX,
|
|
110
|
+
b.maxX,
|
|
111
|
+
b.minY,
|
|
112
|
+
b.maxY
|
|
113
|
+
];
|
|
114
|
+
const fn = doc.add(stream({
|
|
115
|
+
FunctionType: 4,
|
|
116
|
+
Domain: domain,
|
|
117
|
+
Range: [
|
|
118
|
+
0,
|
|
119
|
+
1,
|
|
120
|
+
0,
|
|
121
|
+
1,
|
|
122
|
+
0,
|
|
123
|
+
1
|
|
124
|
+
]
|
|
125
|
+
}, new TextEncoder().encode(rectSweepProgram(cx, cy, 1 / halfW, 1 / halfH, stopsIn))));
|
|
126
|
+
return dict({
|
|
127
|
+
ShadingType: 1,
|
|
128
|
+
ColorSpace: name("DeviceRGB"),
|
|
129
|
+
Domain: domain,
|
|
130
|
+
Function: ref(fn.id)
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
function rectSweepProgram(cx, cy, sx, sy, stopsIn) {
|
|
134
|
+
const stops = normalizeStops(stopsIn);
|
|
135
|
+
return [
|
|
136
|
+
"{",
|
|
137
|
+
`${ps(cy)} sub abs ${ps(sy)} mul`,
|
|
138
|
+
`exch ${ps(cx)} sub abs ${ps(sx)} mul`,
|
|
139
|
+
"2 copy lt { exch } if pop",
|
|
140
|
+
"dup 1 gt { pop 1 } if",
|
|
141
|
+
rampProgram(stops, 0),
|
|
142
|
+
"}"
|
|
143
|
+
].join("\n");
|
|
144
|
+
}
|
|
145
|
+
function rampProgram(stops, i) {
|
|
146
|
+
const last = i >= stops.length - 2;
|
|
147
|
+
const a = stops[i];
|
|
148
|
+
const b = stops[i + 1];
|
|
149
|
+
const seg = segmentProgram(a, b);
|
|
150
|
+
if (last) return seg;
|
|
151
|
+
return `dup ${ps(b.offset)} le { ${seg} } { ${rampProgram(stops, i + 1)} } ifelse`;
|
|
152
|
+
}
|
|
153
|
+
function segmentProgram(a, b) {
|
|
154
|
+
const c0 = rgb01(a.colorHex);
|
|
155
|
+
const c1 = rgb01(b.colorHex);
|
|
156
|
+
const span = b.offset - a.offset;
|
|
157
|
+
if (span <= 1e-9) return `pop ${c1.map(ps).join(" ")}`;
|
|
158
|
+
return [
|
|
159
|
+
`${ps(a.offset)} sub ${ps(1 / span)} mul`,
|
|
160
|
+
"dup 0 lt { pop 0 } if dup 1 gt { pop 1 } if",
|
|
161
|
+
"dup dup",
|
|
162
|
+
`${ps(c1[0] - c0[0])} mul ${ps(c0[0])} add`,
|
|
163
|
+
"3 1 roll",
|
|
164
|
+
`${ps(c1[1] - c0[1])} mul ${ps(c0[1])} add`,
|
|
165
|
+
"exch",
|
|
166
|
+
`${ps(c1[2] - c0[2])} mul ${ps(c0[2])} add`
|
|
167
|
+
].join(" ");
|
|
168
|
+
}
|
|
169
|
+
function ps(n) {
|
|
170
|
+
if (!Number.isFinite(n)) return "0";
|
|
171
|
+
const s = n.toFixed(6).replace(/0+$/u, "").replace(/\.$/u, "");
|
|
172
|
+
return s === "" || s === "-0" ? "0" : s;
|
|
173
|
+
}
|
|
104
174
|
function buildRamp(stopsIn) {
|
|
105
175
|
const stops = normalizeStops(stopsIn);
|
|
106
176
|
if (stops.length <= 2) return dict({
|
|
@@ -3,9 +3,9 @@ import { embedTtfFont } from "./cid-font.js";
|
|
|
3
3
|
import { preparePdfEncryption } from "./encryption.js";
|
|
4
4
|
import { reorderVisual } from "../core/bidi/algorithm.js";
|
|
5
5
|
import { reverseByCodePoint } from "../core/bidi/index.js";
|
|
6
|
-
import "../layout/styled-layout.js";
|
|
6
|
+
import { GLUE_SHRINK_RATIO } from "../layout/styled-layout.js";
|
|
7
7
|
import { paintPlan } from "../layout/page-doc.js";
|
|
8
|
-
import { emitVectorShape } from "./vector-graphics.js";
|
|
8
|
+
import { emitClipPath, emitVectorShape, shadowBlurLayers } from "./vector-graphics.js";
|
|
9
9
|
import { buildGradientPattern, shapeBbox } from "./shading.js";
|
|
10
10
|
import { sanitizeHref } from "../core/links.js";
|
|
11
11
|
import { embedAssociatedFile } from "./embedded-file.js";
|
|
@@ -15,6 +15,33 @@ import { addSignaturePlaceholder } from "./signature.js";
|
|
|
15
15
|
import { buildXmpPacket } from "./xmp.js";
|
|
16
16
|
//#region src/pdf/styled-page-emitter.ts
|
|
17
17
|
var encoder = new TextEncoder();
|
|
18
|
+
function washVeil(wash) {
|
|
19
|
+
if (!wash) return void 0;
|
|
20
|
+
const alpha = 1 - wash.gain;
|
|
21
|
+
if (!(alpha > .004)) return void 0;
|
|
22
|
+
const offset = .5 * (1 - wash.gain) + wash.black;
|
|
23
|
+
return {
|
|
24
|
+
alpha: Math.min(1, alpha),
|
|
25
|
+
grey: Math.max(0, Math.min(1, offset / alpha))
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* The veil operators for a washed picture, to follow its own `/Im Do`: the
|
|
30
|
+
* placement matrix maps the unit square onto the picture as drawn, so the wash
|
|
31
|
+
* covers exactly it — cropped, rotated and flipped along with it.
|
|
32
|
+
*/
|
|
33
|
+
function washItems(wash, alphaStateNames) {
|
|
34
|
+
const veil = washVeil(wash);
|
|
35
|
+
if (!veil) return [];
|
|
36
|
+
const g = formatNumber(veil.grey);
|
|
37
|
+
const state = veil.alpha < 1 ? alphaStateNames?.get(Math.round(veil.alpha * 1e3) / 1e3) : void 0;
|
|
38
|
+
return [
|
|
39
|
+
...state === void 0 ? [] : [`/${state} gs`],
|
|
40
|
+
`${g} ${g} ${g} rg`,
|
|
41
|
+
"0 0 1 1 re",
|
|
42
|
+
"f"
|
|
43
|
+
];
|
|
44
|
+
}
|
|
18
45
|
/**
|
|
19
46
|
* The emit phase: turn a laid-out document into PDF objects (ISO 32000) and
|
|
20
47
|
* serialize it. The counterpart of `layoutStyledDocument` (the seam of
|
|
@@ -97,17 +124,29 @@ function assembleStyledPdf(laid, options, doc) {
|
|
|
97
124
|
}
|
|
98
125
|
const alphaStateNames = /* @__PURE__ */ new Map();
|
|
99
126
|
const extGStateEntries = {};
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
if (alpha === void 0 || alpha >= 1) continue;
|
|
127
|
+
const wantAlpha = (alpha) => {
|
|
128
|
+
if (alpha === void 0 || alpha >= 1) return;
|
|
103
129
|
const key = Math.round(alpha * 1e3) / 1e3;
|
|
104
|
-
if (alphaStateNames.has(key))
|
|
130
|
+
if (alphaStateNames.has(key)) return;
|
|
105
131
|
const nm = `GSa${alphaStateNames.size}`;
|
|
106
132
|
alphaStateNames.set(key, nm);
|
|
107
133
|
extGStateEntries[nm] = dict({
|
|
108
134
|
ca: key,
|
|
109
135
|
CA: key
|
|
110
136
|
});
|
|
137
|
+
};
|
|
138
|
+
for (const page of renderedPages) {
|
|
139
|
+
const plan = paintPlan(page.commands);
|
|
140
|
+
for (const item of plan.shapes) {
|
|
141
|
+
const s = item.shape.shadow;
|
|
142
|
+
if (s) wantAlpha(shadowBlurLayers(s).alpha);
|
|
143
|
+
wantAlpha(item.shape.fillAlpha);
|
|
144
|
+
}
|
|
145
|
+
for (const img of plan.images) wantAlpha(washVeil(img.wash)?.alpha);
|
|
146
|
+
for (const line of plan.lines) for (const tok of line.line.tokens) {
|
|
147
|
+
if (tok.kind === "image" && tok.shadow) wantAlpha(shadowBlurLayers(tok.shadow).alpha);
|
|
148
|
+
if (tok.kind === "image") wantAlpha(washVeil(tok.wash)?.alpha);
|
|
149
|
+
}
|
|
111
150
|
}
|
|
112
151
|
const resourcesDict = dict({
|
|
113
152
|
Font: fontResourceDict,
|
|
@@ -378,12 +417,16 @@ function defaultPageCtx() {
|
|
|
378
417
|
even: { commands: [] }
|
|
379
418
|
},
|
|
380
419
|
titlePg: false,
|
|
381
|
-
evenAndOddHeaders: false
|
|
420
|
+
evenAndOddHeaders: false,
|
|
421
|
+
continuous: false
|
|
382
422
|
};
|
|
383
423
|
}
|
|
384
424
|
function embedImageResources(doc, laid) {
|
|
385
425
|
const out = /* @__PURE__ */ new Map();
|
|
386
|
-
for (const [resourceId, res] of laid.imageResources)
|
|
426
|
+
for (const [resourceId, res] of laid.imageResources) {
|
|
427
|
+
if (!res.prepared) continue;
|
|
428
|
+
out.set(resourceId, addImage(doc, res.prepared).ref);
|
|
429
|
+
}
|
|
387
430
|
return out;
|
|
388
431
|
}
|
|
389
432
|
function embedFontResources(doc, laid, pdfaProfile) {
|
|
@@ -445,21 +488,37 @@ function emitTaggedRuns(out, cmds, tagging, body) {
|
|
|
445
488
|
close();
|
|
446
489
|
}
|
|
447
490
|
/**
|
|
448
|
-
* The
|
|
491
|
+
* The dash array for a §18.18.3 / §17.18.2 border style, in points.
|
|
449
492
|
*
|
|
450
|
-
*
|
|
451
|
-
*
|
|
452
|
-
*
|
|
493
|
+
* A pattern is a pattern, not a weight: Word and Excel both draw the same
|
|
494
|
+
* dashes on a hairline and on a 3pt rule, and both references agree on the
|
|
495
|
+
* lengths — a `dashed` page border strokes `8 2.5` whether its `w:sz` is 4 or
|
|
496
|
+
* 24. Scaling them by the width instead drew page-borders-export-case-2.docx's
|
|
497
|
+
* half-point frame as a row of specks a quarter the length it should be.
|
|
453
498
|
*/
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
499
|
+
var BORDER_DASHES = new Map([
|
|
500
|
+
["dashed", [8, 2.5]],
|
|
501
|
+
["dashSmallGap", [3, 1]],
|
|
502
|
+
["dotted", [.5, 1]],
|
|
503
|
+
["dashDot", [
|
|
504
|
+
8,
|
|
505
|
+
2.5,
|
|
506
|
+
2.5,
|
|
507
|
+
2.5
|
|
508
|
+
]],
|
|
509
|
+
["dashDotDot", [
|
|
510
|
+
8,
|
|
511
|
+
2.5,
|
|
512
|
+
2.5,
|
|
513
|
+
2.5,
|
|
514
|
+
2.5,
|
|
515
|
+
2.5
|
|
516
|
+
]]
|
|
517
|
+
]);
|
|
518
|
+
/** The PDF dash array for a border style; an empty string means a solid rule. */
|
|
519
|
+
function dashPatternFor(style) {
|
|
520
|
+
const pattern = style !== void 0 ? BORDER_DASHES.get(style) : void 0;
|
|
521
|
+
return pattern ? `[${pattern.map(formatNumber).join(" ")}]` : "";
|
|
463
522
|
}
|
|
464
523
|
function emitPageContent(page, tagging, gradientNames, alphaStateNames) {
|
|
465
524
|
const commands = page.commands;
|
|
@@ -491,8 +550,20 @@ function emitPageContent(page, tagging, gradientNames, alphaStateNames) {
|
|
|
491
550
|
}
|
|
492
551
|
emitTaggedRuns(out, plan.images.filter((c) => c.imageResourceName !== ""), tagging, (img) => {
|
|
493
552
|
out.push("q");
|
|
494
|
-
|
|
553
|
+
if (img.clip) {
|
|
554
|
+
const t = img.clip.transform;
|
|
555
|
+
out.push(...emitClipPath(img.clip.paths, [
|
|
556
|
+
t[0],
|
|
557
|
+
-t[1],
|
|
558
|
+
t[2],
|
|
559
|
+
-t[3],
|
|
560
|
+
t[4],
|
|
561
|
+
H - t[5]
|
|
562
|
+
]));
|
|
563
|
+
}
|
|
564
|
+
out.push(...placeImage(img.x, H - img.y - img.height, img.width, img.height, img.crop, img.rotationDeg, img.flipH, img.flipV));
|
|
495
565
|
out.push(`/${img.imageResourceName} Do`);
|
|
566
|
+
out.push(...washItems(img.wash, alphaStateNames));
|
|
496
567
|
out.push("Q");
|
|
497
568
|
});
|
|
498
569
|
const borders = plan.borders;
|
|
@@ -506,7 +577,7 @@ function emitPageContent(page, tagging, gradientNames, alphaStateNames) {
|
|
|
506
577
|
for (const b of borders) {
|
|
507
578
|
const width = b.borderSizePt;
|
|
508
579
|
const color = b.borderColorHex;
|
|
509
|
-
const dash = dashPatternFor(b.borderStyle
|
|
580
|
+
const dash = dashPatternFor(b.borderStyle);
|
|
510
581
|
if (dash !== lastDash) {
|
|
511
582
|
out.push(dash === "" ? "[] 0 d 2 J" : `${dash} 0 d 0 J`);
|
|
512
583
|
lastDash = dash;
|
|
@@ -578,14 +649,17 @@ function emitPageContent(page, tagging, gradientNames, alphaStateNames) {
|
|
|
578
649
|
H - t[5]
|
|
579
650
|
]
|
|
580
651
|
};
|
|
581
|
-
const
|
|
582
|
-
const alphaState =
|
|
583
|
-
|
|
652
|
+
const shadowLayer = sh.shape.shadow ? shadowBlurLayers(sh.shape.shadow) : void 0;
|
|
653
|
+
const alphaState = shadowLayer && shadowLayer.alpha < 1 ? alphaStateNames?.get(Math.round(shadowLayer.alpha * 1e3) / 1e3) : void 0;
|
|
654
|
+
const fillAlpha = sh.shape.fillAlpha;
|
|
655
|
+
const fillAlphaState = fillAlpha !== void 0 && fillAlpha < 1 ? alphaStateNames?.get(Math.round(fillAlpha * 1e3) / 1e3) : void 0;
|
|
656
|
+
for (const op of emitVectorShape(shape, gradientNames?.get(sh.shape), alphaState, fillAlphaState)) out.push(op);
|
|
584
657
|
});
|
|
585
658
|
const lines = plan.lines;
|
|
586
659
|
if (lines.length > 0) {
|
|
587
660
|
let inBT = false;
|
|
588
661
|
let lastFont = "";
|
|
662
|
+
let lastTc = 0;
|
|
589
663
|
let lastSize = -1;
|
|
590
664
|
let lastColor = "";
|
|
591
665
|
const switchFontIfNeeded = (tok) => {
|
|
@@ -595,6 +669,11 @@ function emitPageContent(page, tagging, gradientNames, alphaStateNames) {
|
|
|
595
669
|
lastFont = fontKey;
|
|
596
670
|
lastSize = tok.fontSizePt;
|
|
597
671
|
}
|
|
672
|
+
const tc = tok.resolvedRun.letterSpacingPt ?? 0;
|
|
673
|
+
if (tc !== lastTc) {
|
|
674
|
+
out.push(`${formatNumber(tc)} Tc`);
|
|
675
|
+
lastTc = tc;
|
|
676
|
+
}
|
|
598
677
|
if (tok.resolvedRun.colorHex !== lastColor) {
|
|
599
678
|
const [r, g, b] = hexToRgb01(tok.resolvedRun.colorHex);
|
|
600
679
|
out.push(`${formatNumber(r)} ${formatNumber(g)} ${formatNumber(b)} rg`);
|
|
@@ -602,18 +681,87 @@ function emitPageContent(page, tagging, gradientNames, alphaStateNames) {
|
|
|
602
681
|
}
|
|
603
682
|
};
|
|
604
683
|
const emitImageToken = (tok, x, baselineY) => {
|
|
684
|
+
const meta = tok.metafile;
|
|
685
|
+
if (meta) {
|
|
686
|
+
if (inBT) {
|
|
687
|
+
out.push("ET");
|
|
688
|
+
inBT = false;
|
|
689
|
+
}
|
|
690
|
+
const b = tok.drawBox;
|
|
691
|
+
const ox = x + (b?.dxPt ?? 0);
|
|
692
|
+
const oy = baselineY + (b?.dyPt ?? 0);
|
|
693
|
+
for (const sh of meta.shapes) for (const op of emitVectorShape({
|
|
694
|
+
...sh,
|
|
695
|
+
transform: [
|
|
696
|
+
1,
|
|
697
|
+
0,
|
|
698
|
+
0,
|
|
699
|
+
1,
|
|
700
|
+
ox,
|
|
701
|
+
oy
|
|
702
|
+
]
|
|
703
|
+
})) out.push(op);
|
|
704
|
+
for (const t of meta.texts) {
|
|
705
|
+
const first = t.line.tokens.find((k) => k.kind === "text");
|
|
706
|
+
if (!first) continue;
|
|
707
|
+
const [r, g, bl] = hexToRgb01(first.resolvedRun.colorHex);
|
|
708
|
+
out.push("BT");
|
|
709
|
+
out.push(`/${first.font.resourceName} ${formatNumber(first.fontSizePt)} Tf`);
|
|
710
|
+
out.push(`${formatNumber(r)} ${formatNumber(g)} ${formatNumber(bl)} rg`);
|
|
711
|
+
out.push(`1 0 0 1 ${formatNumber(ox + t.x)} ${formatNumber(oy + t.y)} Tm`);
|
|
712
|
+
out.push(first.font.measure.showText(first.text));
|
|
713
|
+
out.push("ET");
|
|
714
|
+
}
|
|
715
|
+
lastFont = "";
|
|
716
|
+
lastSize = -1;
|
|
717
|
+
lastColor = "";
|
|
718
|
+
return;
|
|
719
|
+
}
|
|
605
720
|
if (!tok.imageResourceName) return;
|
|
606
721
|
if (inBT) {
|
|
607
722
|
out.push("ET");
|
|
608
723
|
inBT = false;
|
|
609
724
|
}
|
|
725
|
+
const shdw = tok.shadow;
|
|
726
|
+
if (shdw) {
|
|
727
|
+
const bx = tok.drawBox;
|
|
728
|
+
const [shr, shg, shb] = hexToRgb01(shdw.colorHex);
|
|
729
|
+
const layers = shadowBlurLayers(shdw);
|
|
730
|
+
const state = layers.alpha < 1 ? alphaStateNames?.get(Math.round(layers.alpha * 1e3) / 1e3) : void 0;
|
|
731
|
+
const sx = x + (bx?.dxPt ?? 0) + shdw.dxPt;
|
|
732
|
+
const sy = baselineY + (bx?.dyPt ?? 0) - shdw.dyPt;
|
|
733
|
+
const sw = bx?.widthPt ?? tok.widthPt;
|
|
734
|
+
const sh = bx?.heightPt ?? tok.heightPt;
|
|
735
|
+
out.push("q");
|
|
736
|
+
if (state) out.push(`/${state} gs`);
|
|
737
|
+
out.push(`${formatNumber(shr)} ${formatNumber(shg)} ${formatNumber(shb)} rg`);
|
|
738
|
+
for (let i = 0; i < layers.count; i++) {
|
|
739
|
+
const grow = layers.count > 1 ? -shdw.blurPt / 2 + (i + .5) * shdw.blurPt / layers.count : 0;
|
|
740
|
+
out.push(`${formatNumber(sx - grow)} ${formatNumber(sy - grow)} ${formatNumber(Math.max(sw + 2 * grow, 0))} ${formatNumber(Math.max(sh + 2 * grow, 0))} re`);
|
|
741
|
+
out.push("f");
|
|
742
|
+
}
|
|
743
|
+
out.push("Q");
|
|
744
|
+
}
|
|
610
745
|
out.push("q");
|
|
611
|
-
|
|
746
|
+
const b = tok.drawBox;
|
|
747
|
+
out.push(...b ? placeImage(x + b.dxPt, baselineY + b.dyPt, b.widthPt, b.heightPt, tok.crop, tok.rotationDeg, tok.flipH, tok.flipV) : placeImage(x, baselineY, tok.widthPt, tok.heightPt, tok.crop, tok.rotationDeg, tok.flipH, tok.flipV));
|
|
612
748
|
out.push(`/${tok.imageResourceName} Do`);
|
|
749
|
+
out.push(...washItems(tok.wash, alphaStateNames));
|
|
613
750
|
out.push("Q");
|
|
751
|
+
const frame = tok.outline;
|
|
752
|
+
if (frame) {
|
|
753
|
+
const [fr, fg, fb] = hexToRgb01(frame.colorHex);
|
|
754
|
+
out.push("q");
|
|
755
|
+
out.push(`${formatNumber(frame.widthPt)} w`);
|
|
756
|
+
out.push(`${formatNumber(fr)} ${formatNumber(fg)} ${formatNumber(fb)} RG`);
|
|
757
|
+
out.push(`${formatNumber(x + (b?.dxPt ?? 0))} ${formatNumber(baselineY + (b?.dyPt ?? 0))} ${formatNumber(b?.widthPt ?? tok.widthPt)} ${formatNumber(b?.heightPt ?? tok.heightPt)} re`);
|
|
758
|
+
out.push("S");
|
|
759
|
+
out.push("Q");
|
|
760
|
+
}
|
|
614
761
|
lastFont = "";
|
|
615
762
|
lastSize = -1;
|
|
616
763
|
lastColor = "";
|
|
764
|
+
lastTc = 0;
|
|
617
765
|
};
|
|
618
766
|
const emitMathToken = (tok, originX, baselineY) => {
|
|
619
767
|
for (const it of tok.items) if (it.kind === "glyph") {
|
|
@@ -631,7 +779,7 @@ function emitPageContent(page, tagging, gradientNames, alphaStateNames) {
|
|
|
631
779
|
lastColor = "000000";
|
|
632
780
|
}
|
|
633
781
|
out.push(`1 0 0 1 ${formatNumber(originX + it.x)} ${formatNumber(baselineY + it.y)} Tm`);
|
|
634
|
-
out.push(
|
|
782
|
+
out.push(it.font.measure.showText(it.text));
|
|
635
783
|
} else if (it.kind === "rule") {
|
|
636
784
|
if (inBT) {
|
|
637
785
|
out.push("ET");
|
|
@@ -687,6 +835,7 @@ function emitPageContent(page, tagging, gradientNames, alphaStateNames) {
|
|
|
687
835
|
lastFont = "";
|
|
688
836
|
lastSize = -1;
|
|
689
837
|
lastColor = "";
|
|
838
|
+
lastTc = 0;
|
|
690
839
|
out.push("BT");
|
|
691
840
|
inBT = true;
|
|
692
841
|
body(cmd);
|
|
@@ -698,6 +847,7 @@ function emitPageContent(page, tagging, gradientNames, alphaStateNames) {
|
|
|
698
847
|
lastFont = "";
|
|
699
848
|
lastSize = -1;
|
|
700
849
|
lastColor = "";
|
|
850
|
+
lastTc = 0;
|
|
701
851
|
};
|
|
702
852
|
const emitOneLine = (cmd) => {
|
|
703
853
|
const line = cmd.line;
|
|
@@ -749,12 +899,40 @@ function emitPageContent(page, tagging, gradientNames, alphaStateNames) {
|
|
|
749
899
|
const hasImageToken = line.tokens.some((t) => t.kind === "image");
|
|
750
900
|
const hasMathToken = line.tokens.some((t) => t.kind === "math");
|
|
751
901
|
const hasRtl = line.tokens.some((t) => t.bidiLevel % 2 === 1);
|
|
902
|
+
const hasTab = line.tokens.some((t) => t.kind === "text" && t.tab === true);
|
|
752
903
|
/** A text token carrying underline or strikethrough. */
|
|
753
904
|
const isDecorated = (tok) => tok.kind === "text" && (tok.resolvedRun.underline !== "none" || tok.resolvedRun.strike);
|
|
754
|
-
const
|
|
905
|
+
const showToken = (tok) => {
|
|
755
906
|
const text = tok.bidiLevel % 2 === 1 ? reverseByCodePoint(tok.text) : tok.text;
|
|
756
|
-
return tok.font.measure.
|
|
907
|
+
return tok.font.measure.showText(text);
|
|
757
908
|
};
|
|
909
|
+
if (line.tokens.some((t) => t.kind === "text" && t.resolvedRun.shadingColorHex)) {
|
|
910
|
+
if (inBT) {
|
|
911
|
+
out.push("ET");
|
|
912
|
+
inBT = false;
|
|
913
|
+
}
|
|
914
|
+
const shAscent = Math.max(lineFs, line.mathAscentPt ?? 0);
|
|
915
|
+
const shDescent = Math.max(lineFs * .2, line.mathDescentPt ?? 0);
|
|
916
|
+
out.push("q");
|
|
917
|
+
let sx = originX;
|
|
918
|
+
let painted = "";
|
|
919
|
+
for (const tok of line.tokens) {
|
|
920
|
+
const w = tok.widthPt + (tok.kind === "text" && tok.isSpace ? extraPerSpace : 0);
|
|
921
|
+
const hex = tok.kind === "text" ? tok.resolvedRun.shadingColorHex : void 0;
|
|
922
|
+
if (hex !== void 0) {
|
|
923
|
+
if (hex !== painted) {
|
|
924
|
+
if (painted !== "") out.push("f");
|
|
925
|
+
const [sr, sg, sb] = hexToRgb01(hex);
|
|
926
|
+
out.push(`${formatNumber(sr)} ${formatNumber(sg)} ${formatNumber(sb)} rg`);
|
|
927
|
+
painted = hex;
|
|
928
|
+
}
|
|
929
|
+
out.push(`${formatNumber(sx)} ${formatNumber(baselineY - shDescent)} ${formatNumber(w)} ${formatNumber(shAscent + shDescent)} re`);
|
|
930
|
+
}
|
|
931
|
+
sx += w;
|
|
932
|
+
}
|
|
933
|
+
if (painted !== "") out.push("f");
|
|
934
|
+
out.push("Q");
|
|
935
|
+
}
|
|
758
936
|
if (line.tokens.some((t) => t.kind === "text" && t.highlight === true)) {
|
|
759
937
|
if (inBT) {
|
|
760
938
|
out.push("ET");
|
|
@@ -784,12 +962,30 @@ function emitPageContent(page, tagging, gradientNames, alphaStateNames) {
|
|
|
784
962
|
for (const tok of line.tokens) {
|
|
785
963
|
const w = tok.widthPt + (tok.kind === "text" && tok.isSpace ? extraPerSpace : 0);
|
|
786
964
|
if (isDecorated(tok)) {
|
|
787
|
-
const [r, g, b] = hexToRgb01(tok.resolvedRun.colorHex);
|
|
788
965
|
const thickness = Math.max(.4, tok.fontSizePt * .06);
|
|
789
|
-
|
|
966
|
+
const paint = (hex) => {
|
|
967
|
+
const [r, g, b] = hexToRgb01(hex);
|
|
968
|
+
out.push(`${formatNumber(r)} ${formatNumber(g)} ${formatNumber(b)} rg`);
|
|
969
|
+
};
|
|
970
|
+
paint(tok.resolvedRun.colorHex);
|
|
790
971
|
if (tok.resolvedRun.underline !== "none") {
|
|
791
|
-
|
|
792
|
-
|
|
972
|
+
if (tok.resolvedRun.underlineColorHex) paint(tok.resolvedRun.underlineColorHex);
|
|
973
|
+
const style = tok.resolvedRun.underline;
|
|
974
|
+
const t = style === "thick" || style === "dottedHeavy" || style === "dashHeavy" ? thickness * 2 : thickness;
|
|
975
|
+
const y = baselineY - tok.fontSizePt * .12 - t;
|
|
976
|
+
const dashed = style === "dotted" || style === "dottedHeavy" ? [t, t * 2] : style === "dash" || style === "dashHeavy" ? [t * 4, t * 3] : void 0;
|
|
977
|
+
if (dashed) {
|
|
978
|
+
out.push(`${formatNumber(t)} w`);
|
|
979
|
+
out.push(`[${dashed.map((n) => formatNumber(n)).join(" ")}] 0 d`);
|
|
980
|
+
const [r, g, b] = hexToRgb01(tok.resolvedRun.underlineColorHex ?? tok.resolvedRun.colorHex);
|
|
981
|
+
out.push(`${formatNumber(r)} ${formatNumber(g)} ${formatNumber(b)} RG`);
|
|
982
|
+
out.push(`${formatNumber(dx)} ${formatNumber(y + t / 2)} m ${formatNumber(dx + w)} ${formatNumber(y + t / 2)} l S`);
|
|
983
|
+
out.push("[] 0 d");
|
|
984
|
+
} else {
|
|
985
|
+
out.push(`${formatNumber(dx)} ${formatNumber(y)} ${formatNumber(w)} ${formatNumber(t)} re f`);
|
|
986
|
+
if (style === "double") out.push(`${formatNumber(dx)} ${formatNumber(y - t * 2)} ${formatNumber(w)} ${formatNumber(t)} re f`);
|
|
987
|
+
}
|
|
988
|
+
if (tok.resolvedRun.underlineColorHex) paint(tok.resolvedRun.colorHex);
|
|
793
989
|
}
|
|
794
990
|
if (tok.resolvedRun.strike) {
|
|
795
991
|
const y = baselineY + tok.fontSizePt * .26;
|
|
@@ -813,9 +1009,9 @@ function emitPageContent(page, tagging, gradientNames, alphaStateNames) {
|
|
|
813
1009
|
for (const tok of line.tokens) {
|
|
814
1010
|
if (tok.kind !== "text") continue;
|
|
815
1011
|
switchFontIfNeeded(tok);
|
|
816
|
-
out.push(
|
|
1012
|
+
out.push(tok.font.measure.showText(tok.text));
|
|
817
1013
|
}
|
|
818
|
-
} else if (extraPerSpace
|
|
1014
|
+
} else if (extraPerSpace !== 0 || hasImageToken || hasMathToken || hasRtl || hasRise || hasTab) {
|
|
819
1015
|
const order = hasRtl ? lineVisualOrder(line) : line.tokens.map((_, i) => i);
|
|
820
1016
|
let x = originX;
|
|
821
1017
|
for (const ti of order) {
|
|
@@ -838,7 +1034,7 @@ function emitPageContent(page, tagging, gradientNames, alphaStateNames) {
|
|
|
838
1034
|
}
|
|
839
1035
|
switchFontIfNeeded(tok);
|
|
840
1036
|
out.push(`1 0 0 1 ${formatNumber(x)} ${formatNumber(baselineY + (tok.risePt ?? 0))} Tm`);
|
|
841
|
-
out.push(
|
|
1037
|
+
out.push(showToken(tok));
|
|
842
1038
|
const tokenX0 = x;
|
|
843
1039
|
x += tok.widthPt;
|
|
844
1040
|
if (tok.isSpace) x += extraPerSpace;
|
|
@@ -855,8 +1051,7 @@ function emitPageContent(page, tagging, gradientNames, alphaStateNames) {
|
|
|
855
1051
|
for (const tok of line.tokens) {
|
|
856
1052
|
if (tok.kind !== "text") continue;
|
|
857
1053
|
switchFontIfNeeded(tok);
|
|
858
|
-
|
|
859
|
-
out.push(`<${hex}> Tj`);
|
|
1054
|
+
out.push(tok.font.measure.showText(tok.text));
|
|
860
1055
|
trackLink(tok.href, tok.anchor, tok.text, x, x + tok.widthPt);
|
|
861
1056
|
x += tok.widthPt;
|
|
862
1057
|
}
|
|
@@ -898,15 +1093,69 @@ function emitPageContent(page, tagging, gradientNames, alphaStateNames) {
|
|
|
898
1093
|
function lineVisualOrder(line) {
|
|
899
1094
|
return reorderVisual(line.tokens.map((t) => t.bidiLevel));
|
|
900
1095
|
}
|
|
1096
|
+
/**
|
|
1097
|
+
* The operators that put an image XObject in a box — ISO 32000-1 §8.9.5.1: the
|
|
1098
|
+
* unit square scaled to the box and moved to `(x, y)`, its bottom-left corner.
|
|
1099
|
+
*
|
|
1100
|
+
* A `a:srcRect` crop (§20.1.8.55) cuts edges off the SOURCE and fits what is
|
|
1101
|
+
* left to the same box, so the picture is drawn larger than the box and the box
|
|
1102
|
+
* is made a clipping path. Ignored, ImageCrop.docx drew the whole mountain
|
|
1103
|
+
* where LibreOffice shows the peak.
|
|
1104
|
+
*
|
|
1105
|
+
* @returns The content-stream lines, to be pushed between `q` and the `Do`.
|
|
1106
|
+
*/
|
|
1107
|
+
function placeImage(x, y, width, height, crop, rotationDeg, flipH, flipV) {
|
|
1108
|
+
const cm = (w, h, ox, oy) => `${formatNumber(w)} 0 0 ${formatNumber(h)} ${formatNumber(ox)} ${formatNumber(oy)} cm`;
|
|
1109
|
+
const spin = rotationDeg ? [rotateAboutCm(x + width / 2, y + height / 2, -rotationDeg)] : [];
|
|
1110
|
+
if (flipH === true || flipV === true) {
|
|
1111
|
+
const sx = flipH === true ? -1 : 1;
|
|
1112
|
+
const sy = flipV === true ? -1 : 1;
|
|
1113
|
+
spin.push(`${formatNumber(sx)} 0 0 ${formatNumber(sy)} ${formatNumber(sx === -1 ? 2 * x + width : 0)} ${formatNumber(sy === -1 ? 2 * y + height : 0)} cm`);
|
|
1114
|
+
}
|
|
1115
|
+
if (!crop) return [...spin, cm(width, height, x, y)];
|
|
1116
|
+
const kept = {
|
|
1117
|
+
w: 1 - crop.left - crop.right,
|
|
1118
|
+
h: 1 - crop.top - crop.bottom
|
|
1119
|
+
};
|
|
1120
|
+
const full = {
|
|
1121
|
+
w: width / kept.w,
|
|
1122
|
+
h: height / kept.h
|
|
1123
|
+
};
|
|
1124
|
+
return [
|
|
1125
|
+
...spin,
|
|
1126
|
+
`${formatNumber(x)} ${formatNumber(y)} ${formatNumber(width)} ${formatNumber(height)} re`,
|
|
1127
|
+
"W",
|
|
1128
|
+
"n",
|
|
1129
|
+
cm(full.w, full.h, x - crop.left * full.w, y - crop.bottom * full.h)
|
|
1130
|
+
];
|
|
1131
|
+
}
|
|
1132
|
+
/** A `cm` that turns the frame `deg` counter-clockwise about `(cx, cy)`. */
|
|
1133
|
+
function rotateAboutCm(cx, cy, deg) {
|
|
1134
|
+
const rad = deg * Math.PI / 180;
|
|
1135
|
+
const c = Math.cos(rad);
|
|
1136
|
+
const s = Math.sin(rad);
|
|
1137
|
+
return `${formatNumber(c)} ${formatNumber(s)} ${formatNumber(-s)} ${formatNumber(c)} ${formatNumber(cx - cx * c + cy * s)} ${formatNumber(cy - cx * s - cy * c)} cm`;
|
|
1138
|
+
}
|
|
901
1139
|
function computeJustifyExtra(line) {
|
|
902
|
-
|
|
903
|
-
if (
|
|
1140
|
+
const alignment = line.resolved.alignment;
|
|
1141
|
+
if (alignment !== "both" && alignment !== "distribute") return 0;
|
|
1142
|
+
if (line.noJustify === true) return 0;
|
|
1143
|
+
if (line.isLastInParagraph && alignment !== "distribute") return 0;
|
|
1144
|
+
let last = line.tokens.length - 1;
|
|
1145
|
+
while (last >= 0 && line.tokens[last].kind === "text" && line.tokens[last].isSpace) last--;
|
|
904
1146
|
let spaces = 0;
|
|
905
|
-
|
|
1147
|
+
let narrowest = Infinity;
|
|
1148
|
+
let contentWidthPt = 0;
|
|
1149
|
+
for (let i = 0; i <= last; i++) {
|
|
1150
|
+
const tok = line.tokens[i];
|
|
1151
|
+
contentWidthPt += tok.widthPt;
|
|
1152
|
+
if (!tok.isSpace) continue;
|
|
1153
|
+
spaces++;
|
|
1154
|
+
if (tok.widthPt < narrowest) narrowest = tok.widthPt;
|
|
1155
|
+
}
|
|
906
1156
|
if (spaces === 0) return 0;
|
|
907
|
-
const extra = line.availableWidthPt -
|
|
908
|
-
|
|
909
|
-
return extra / spaces;
|
|
1157
|
+
const extra = (line.availableWidthPt - contentWidthPt) / spaces;
|
|
1158
|
+
return extra < 0 ? Math.max(extra, -narrowest * GLUE_SHRINK_RATIO) : extra;
|
|
910
1159
|
}
|
|
911
1160
|
function hexToRgb01(hex) {
|
|
912
1161
|
return [
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { VectorShape } from '../core/vector.js';
|
|
1
|
+
import { VectorPath, VectorShape } from '../core/vector.js';
|
|
2
2
|
export type { PathSegment, StrokeStyle, VectorPath, VectorShape } from '../core/vector.js';
|
|
3
3
|
export { PathBuilder } from '../core/vector.js';
|
|
4
4
|
/**
|
|
@@ -15,4 +15,31 @@ export { PathBuilder } from '../core/vector.js';
|
|
|
15
15
|
* named `/ExtGState` (its constant alpha) from the page's resources.
|
|
16
16
|
* @returns The content-stream operator lines.
|
|
17
17
|
*/
|
|
18
|
-
|
|
18
|
+
/**
|
|
19
|
+
* §20.1.8.40 `blurRad` — how a blurred shadow is drawn: as `count` copies of
|
|
20
|
+
* the silhouette, each at `alpha` transparency, growing through the blur's
|
|
21
|
+
* width. Shared with the emitter that registers the /ExtGState for it, so the
|
|
22
|
+
* alpha it registers is the one the layers are drawn at.
|
|
23
|
+
*
|
|
24
|
+
* @param shadow The shadow (its `blurPt` and `alpha` are read).
|
|
25
|
+
* @returns The number of layers and the transparency of each.
|
|
26
|
+
*/
|
|
27
|
+
export declare function shadowBlurLayers(shadow: {
|
|
28
|
+
readonly blurPt: number;
|
|
29
|
+
readonly alpha: number;
|
|
30
|
+
}): {
|
|
31
|
+
count: number;
|
|
32
|
+
alpha: number;
|
|
33
|
+
};
|
|
34
|
+
export declare function emitVectorShape(shape: VectorShape, patternName?: string, alphaStateName?: string, fillAlphaStateName?: string): Array<string>;
|
|
35
|
+
/**
|
|
36
|
+
* The operators that make `paths` the current CLIP, under `transform` — the
|
|
37
|
+
* same local→y-up-page matrix {@link emitVectorShape} takes. Emitted inside the
|
|
38
|
+
* caller's own `q`…`Q`, and followed by the inverse matrix so what comes after
|
|
39
|
+
* is back in page coordinates.
|
|
40
|
+
*
|
|
41
|
+
* @param paths The clip outline, in the shape's local frame.
|
|
42
|
+
* @param transform The local→page matrix.
|
|
43
|
+
* @returns The operators: `cm`, the path, `W n`, and the inverse `cm`.
|
|
44
|
+
*/
|
|
45
|
+
export declare function emitClipPath(paths: ReadonlyArray<VectorPath>, transform: readonly [number, number, number, number, number, number]): Array<string>;
|