reamkit 1.16.0 → 1.17.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 +473 -7
- 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 +181 -0
- package/dist/esm/core/ir/flow.d.ts +23 -1
- 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/vector.d.ts +17 -0
- package/dist/esm/excel/print-model.js +1 -1
- 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 +18 -4
- package/dist/esm/layout/page-doc.d.ts +62 -2
- package/dist/esm/layout/styled-layout.d.ts +44 -2
- package/dist/esm/layout/styled-layout.js +1870 -228
- package/dist/esm/pdf/shading.js +76 -6
- package/dist/esm/pdf/styled-page-emitter.js +217 -43
- package/dist/esm/pdf/vector-graphics.d.ts +28 -1
- package/dist/esm/pdf/vector-graphics.js +94 -11
- 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 +1 -1
- package/dist/esm/word/document-parser.d.ts +96 -7
- package/dist/esm/word/document-parser.js +537 -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 +53 -5
- package/dist/esm/word/drawing-parser.js +1540 -54
- 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";
|
|
@@ -97,17 +97,24 @@ function assembleStyledPdf(laid, options, doc) {
|
|
|
97
97
|
}
|
|
98
98
|
const alphaStateNames = /* @__PURE__ */ new Map();
|
|
99
99
|
const extGStateEntries = {};
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
if (alpha === void 0 || alpha >= 1) continue;
|
|
100
|
+
const wantAlpha = (alpha) => {
|
|
101
|
+
if (alpha === void 0 || alpha >= 1) return;
|
|
103
102
|
const key = Math.round(alpha * 1e3) / 1e3;
|
|
104
|
-
if (alphaStateNames.has(key))
|
|
103
|
+
if (alphaStateNames.has(key)) return;
|
|
105
104
|
const nm = `GSa${alphaStateNames.size}`;
|
|
106
105
|
alphaStateNames.set(key, nm);
|
|
107
106
|
extGStateEntries[nm] = dict({
|
|
108
107
|
ca: key,
|
|
109
108
|
CA: key
|
|
110
109
|
});
|
|
110
|
+
};
|
|
111
|
+
for (const page of renderedPages) {
|
|
112
|
+
const plan = paintPlan(page.commands);
|
|
113
|
+
for (const item of plan.shapes) {
|
|
114
|
+
const s = item.shape.shadow;
|
|
115
|
+
if (s) wantAlpha(shadowBlurLayers(s).alpha);
|
|
116
|
+
}
|
|
117
|
+
for (const line of plan.lines) for (const tok of line.line.tokens) if (tok.kind === "image" && tok.shadow) wantAlpha(shadowBlurLayers(tok.shadow).alpha);
|
|
111
118
|
}
|
|
112
119
|
const resourcesDict = dict({
|
|
113
120
|
Font: fontResourceDict,
|
|
@@ -378,7 +385,8 @@ function defaultPageCtx() {
|
|
|
378
385
|
even: { commands: [] }
|
|
379
386
|
},
|
|
380
387
|
titlePg: false,
|
|
381
|
-
evenAndOddHeaders: false
|
|
388
|
+
evenAndOddHeaders: false,
|
|
389
|
+
continuous: false
|
|
382
390
|
};
|
|
383
391
|
}
|
|
384
392
|
function embedImageResources(doc, laid) {
|
|
@@ -445,21 +453,37 @@ function emitTaggedRuns(out, cmds, tagging, body) {
|
|
|
445
453
|
close();
|
|
446
454
|
}
|
|
447
455
|
/**
|
|
448
|
-
* The
|
|
456
|
+
* The dash array for a §18.18.3 / §17.18.2 border style, in points.
|
|
449
457
|
*
|
|
450
|
-
*
|
|
451
|
-
*
|
|
452
|
-
*
|
|
458
|
+
* A pattern is a pattern, not a weight: Word and Excel both draw the same
|
|
459
|
+
* dashes on a hairline and on a 3pt rule, and both references agree on the
|
|
460
|
+
* lengths — a `dashed` page border strokes `8 2.5` whether its `w:sz` is 4 or
|
|
461
|
+
* 24. Scaling them by the width instead drew page-borders-export-case-2.docx's
|
|
462
|
+
* half-point frame as a row of specks a quarter the length it should be.
|
|
453
463
|
*/
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
464
|
+
var BORDER_DASHES = new Map([
|
|
465
|
+
["dashed", [8, 2.5]],
|
|
466
|
+
["dashSmallGap", [3, 1]],
|
|
467
|
+
["dotted", [.5, 1]],
|
|
468
|
+
["dashDot", [
|
|
469
|
+
8,
|
|
470
|
+
2.5,
|
|
471
|
+
2.5,
|
|
472
|
+
2.5
|
|
473
|
+
]],
|
|
474
|
+
["dashDotDot", [
|
|
475
|
+
8,
|
|
476
|
+
2.5,
|
|
477
|
+
2.5,
|
|
478
|
+
2.5,
|
|
479
|
+
2.5,
|
|
480
|
+
2.5
|
|
481
|
+
]]
|
|
482
|
+
]);
|
|
483
|
+
/** The PDF dash array for a border style; an empty string means a solid rule. */
|
|
484
|
+
function dashPatternFor(style) {
|
|
485
|
+
const pattern = style !== void 0 ? BORDER_DASHES.get(style) : void 0;
|
|
486
|
+
return pattern ? `[${pattern.map(formatNumber).join(" ")}]` : "";
|
|
463
487
|
}
|
|
464
488
|
function emitPageContent(page, tagging, gradientNames, alphaStateNames) {
|
|
465
489
|
const commands = page.commands;
|
|
@@ -491,7 +515,18 @@ function emitPageContent(page, tagging, gradientNames, alphaStateNames) {
|
|
|
491
515
|
}
|
|
492
516
|
emitTaggedRuns(out, plan.images.filter((c) => c.imageResourceName !== ""), tagging, (img) => {
|
|
493
517
|
out.push("q");
|
|
494
|
-
|
|
518
|
+
if (img.clip) {
|
|
519
|
+
const t = img.clip.transform;
|
|
520
|
+
out.push(...emitClipPath(img.clip.paths, [
|
|
521
|
+
t[0],
|
|
522
|
+
-t[1],
|
|
523
|
+
t[2],
|
|
524
|
+
-t[3],
|
|
525
|
+
t[4],
|
|
526
|
+
H - t[5]
|
|
527
|
+
]));
|
|
528
|
+
}
|
|
529
|
+
out.push(...placeImage(img.x, H - img.y - img.height, img.width, img.height, img.crop, img.rotationDeg, img.flipH, img.flipV));
|
|
495
530
|
out.push(`/${img.imageResourceName} Do`);
|
|
496
531
|
out.push("Q");
|
|
497
532
|
});
|
|
@@ -506,7 +541,7 @@ function emitPageContent(page, tagging, gradientNames, alphaStateNames) {
|
|
|
506
541
|
for (const b of borders) {
|
|
507
542
|
const width = b.borderSizePt;
|
|
508
543
|
const color = b.borderColorHex;
|
|
509
|
-
const dash = dashPatternFor(b.borderStyle
|
|
544
|
+
const dash = dashPatternFor(b.borderStyle);
|
|
510
545
|
if (dash !== lastDash) {
|
|
511
546
|
out.push(dash === "" ? "[] 0 d 2 J" : `${dash} 0 d 0 J`);
|
|
512
547
|
lastDash = dash;
|
|
@@ -578,14 +613,15 @@ function emitPageContent(page, tagging, gradientNames, alphaStateNames) {
|
|
|
578
613
|
H - t[5]
|
|
579
614
|
]
|
|
580
615
|
};
|
|
581
|
-
const
|
|
582
|
-
const alphaState =
|
|
616
|
+
const shadowLayer = sh.shape.shadow ? shadowBlurLayers(sh.shape.shadow) : void 0;
|
|
617
|
+
const alphaState = shadowLayer && shadowLayer.alpha < 1 ? alphaStateNames?.get(Math.round(shadowLayer.alpha * 1e3) / 1e3) : void 0;
|
|
583
618
|
for (const op of emitVectorShape(shape, gradientNames?.get(sh.shape), alphaState)) out.push(op);
|
|
584
619
|
});
|
|
585
620
|
const lines = plan.lines;
|
|
586
621
|
if (lines.length > 0) {
|
|
587
622
|
let inBT = false;
|
|
588
623
|
let lastFont = "";
|
|
624
|
+
let lastTc = 0;
|
|
589
625
|
let lastSize = -1;
|
|
590
626
|
let lastColor = "";
|
|
591
627
|
const switchFontIfNeeded = (tok) => {
|
|
@@ -595,6 +631,11 @@ function emitPageContent(page, tagging, gradientNames, alphaStateNames) {
|
|
|
595
631
|
lastFont = fontKey;
|
|
596
632
|
lastSize = tok.fontSizePt;
|
|
597
633
|
}
|
|
634
|
+
const tc = tok.resolvedRun.letterSpacingPt ?? 0;
|
|
635
|
+
if (tc !== lastTc) {
|
|
636
|
+
out.push(`${formatNumber(tc)} Tc`);
|
|
637
|
+
lastTc = tc;
|
|
638
|
+
}
|
|
598
639
|
if (tok.resolvedRun.colorHex !== lastColor) {
|
|
599
640
|
const [r, g, b] = hexToRgb01(tok.resolvedRun.colorHex);
|
|
600
641
|
out.push(`${formatNumber(r)} ${formatNumber(g)} ${formatNumber(b)} rg`);
|
|
@@ -607,13 +648,45 @@ function emitPageContent(page, tagging, gradientNames, alphaStateNames) {
|
|
|
607
648
|
out.push("ET");
|
|
608
649
|
inBT = false;
|
|
609
650
|
}
|
|
651
|
+
const shdw = tok.shadow;
|
|
652
|
+
if (shdw) {
|
|
653
|
+
const bx = tok.drawBox;
|
|
654
|
+
const [shr, shg, shb] = hexToRgb01(shdw.colorHex);
|
|
655
|
+
const layers = shadowBlurLayers(shdw);
|
|
656
|
+
const state = layers.alpha < 1 ? alphaStateNames?.get(Math.round(layers.alpha * 1e3) / 1e3) : void 0;
|
|
657
|
+
const sx = x + (bx?.dxPt ?? 0) + shdw.dxPt;
|
|
658
|
+
const sy = baselineY + (bx?.dyPt ?? 0) - shdw.dyPt;
|
|
659
|
+
const sw = bx?.widthPt ?? tok.widthPt;
|
|
660
|
+
const sh = bx?.heightPt ?? tok.heightPt;
|
|
661
|
+
out.push("q");
|
|
662
|
+
if (state) out.push(`/${state} gs`);
|
|
663
|
+
out.push(`${formatNumber(shr)} ${formatNumber(shg)} ${formatNumber(shb)} rg`);
|
|
664
|
+
for (let i = 0; i < layers.count; i++) {
|
|
665
|
+
const grow = layers.count > 1 ? -shdw.blurPt / 2 + (i + .5) * shdw.blurPt / layers.count : 0;
|
|
666
|
+
out.push(`${formatNumber(sx - grow)} ${formatNumber(sy - grow)} ${formatNumber(Math.max(sw + 2 * grow, 0))} ${formatNumber(Math.max(sh + 2 * grow, 0))} re`);
|
|
667
|
+
out.push("f");
|
|
668
|
+
}
|
|
669
|
+
out.push("Q");
|
|
670
|
+
}
|
|
610
671
|
out.push("q");
|
|
611
|
-
|
|
672
|
+
const b = tok.drawBox;
|
|
673
|
+
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
674
|
out.push(`/${tok.imageResourceName} Do`);
|
|
613
675
|
out.push("Q");
|
|
676
|
+
const frame = tok.outline;
|
|
677
|
+
if (frame) {
|
|
678
|
+
const [fr, fg, fb] = hexToRgb01(frame.colorHex);
|
|
679
|
+
out.push("q");
|
|
680
|
+
out.push(`${formatNumber(frame.widthPt)} w`);
|
|
681
|
+
out.push(`${formatNumber(fr)} ${formatNumber(fg)} ${formatNumber(fb)} RG`);
|
|
682
|
+
out.push(`${formatNumber(x + (b?.dxPt ?? 0))} ${formatNumber(baselineY + (b?.dyPt ?? 0))} ${formatNumber(b?.widthPt ?? tok.widthPt)} ${formatNumber(b?.heightPt ?? tok.heightPt)} re`);
|
|
683
|
+
out.push("S");
|
|
684
|
+
out.push("Q");
|
|
685
|
+
}
|
|
614
686
|
lastFont = "";
|
|
615
687
|
lastSize = -1;
|
|
616
688
|
lastColor = "";
|
|
689
|
+
lastTc = 0;
|
|
617
690
|
};
|
|
618
691
|
const emitMathToken = (tok, originX, baselineY) => {
|
|
619
692
|
for (const it of tok.items) if (it.kind === "glyph") {
|
|
@@ -631,7 +704,7 @@ function emitPageContent(page, tagging, gradientNames, alphaStateNames) {
|
|
|
631
704
|
lastColor = "000000";
|
|
632
705
|
}
|
|
633
706
|
out.push(`1 0 0 1 ${formatNumber(originX + it.x)} ${formatNumber(baselineY + it.y)} Tm`);
|
|
634
|
-
out.push(
|
|
707
|
+
out.push(it.font.measure.showText(it.text));
|
|
635
708
|
} else if (it.kind === "rule") {
|
|
636
709
|
if (inBT) {
|
|
637
710
|
out.push("ET");
|
|
@@ -687,6 +760,7 @@ function emitPageContent(page, tagging, gradientNames, alphaStateNames) {
|
|
|
687
760
|
lastFont = "";
|
|
688
761
|
lastSize = -1;
|
|
689
762
|
lastColor = "";
|
|
763
|
+
lastTc = 0;
|
|
690
764
|
out.push("BT");
|
|
691
765
|
inBT = true;
|
|
692
766
|
body(cmd);
|
|
@@ -698,6 +772,7 @@ function emitPageContent(page, tagging, gradientNames, alphaStateNames) {
|
|
|
698
772
|
lastFont = "";
|
|
699
773
|
lastSize = -1;
|
|
700
774
|
lastColor = "";
|
|
775
|
+
lastTc = 0;
|
|
701
776
|
};
|
|
702
777
|
const emitOneLine = (cmd) => {
|
|
703
778
|
const line = cmd.line;
|
|
@@ -749,12 +824,40 @@ function emitPageContent(page, tagging, gradientNames, alphaStateNames) {
|
|
|
749
824
|
const hasImageToken = line.tokens.some((t) => t.kind === "image");
|
|
750
825
|
const hasMathToken = line.tokens.some((t) => t.kind === "math");
|
|
751
826
|
const hasRtl = line.tokens.some((t) => t.bidiLevel % 2 === 1);
|
|
827
|
+
const hasTab = line.tokens.some((t) => t.kind === "text" && t.tab === true);
|
|
752
828
|
/** A text token carrying underline or strikethrough. */
|
|
753
829
|
const isDecorated = (tok) => tok.kind === "text" && (tok.resolvedRun.underline !== "none" || tok.resolvedRun.strike);
|
|
754
|
-
const
|
|
830
|
+
const showToken = (tok) => {
|
|
755
831
|
const text = tok.bidiLevel % 2 === 1 ? reverseByCodePoint(tok.text) : tok.text;
|
|
756
|
-
return tok.font.measure.
|
|
832
|
+
return tok.font.measure.showText(text);
|
|
757
833
|
};
|
|
834
|
+
if (line.tokens.some((t) => t.kind === "text" && t.resolvedRun.shadingColorHex)) {
|
|
835
|
+
if (inBT) {
|
|
836
|
+
out.push("ET");
|
|
837
|
+
inBT = false;
|
|
838
|
+
}
|
|
839
|
+
const shAscent = Math.max(lineFs, line.mathAscentPt ?? 0);
|
|
840
|
+
const shDescent = Math.max(lineFs * .2, line.mathDescentPt ?? 0);
|
|
841
|
+
out.push("q");
|
|
842
|
+
let sx = originX;
|
|
843
|
+
let painted = "";
|
|
844
|
+
for (const tok of line.tokens) {
|
|
845
|
+
const w = tok.widthPt + (tok.kind === "text" && tok.isSpace ? extraPerSpace : 0);
|
|
846
|
+
const hex = tok.kind === "text" ? tok.resolvedRun.shadingColorHex : void 0;
|
|
847
|
+
if (hex !== void 0) {
|
|
848
|
+
if (hex !== painted) {
|
|
849
|
+
if (painted !== "") out.push("f");
|
|
850
|
+
const [sr, sg, sb] = hexToRgb01(hex);
|
|
851
|
+
out.push(`${formatNumber(sr)} ${formatNumber(sg)} ${formatNumber(sb)} rg`);
|
|
852
|
+
painted = hex;
|
|
853
|
+
}
|
|
854
|
+
out.push(`${formatNumber(sx)} ${formatNumber(baselineY - shDescent)} ${formatNumber(w)} ${formatNumber(shAscent + shDescent)} re`);
|
|
855
|
+
}
|
|
856
|
+
sx += w;
|
|
857
|
+
}
|
|
858
|
+
if (painted !== "") out.push("f");
|
|
859
|
+
out.push("Q");
|
|
860
|
+
}
|
|
758
861
|
if (line.tokens.some((t) => t.kind === "text" && t.highlight === true)) {
|
|
759
862
|
if (inBT) {
|
|
760
863
|
out.push("ET");
|
|
@@ -784,12 +887,30 @@ function emitPageContent(page, tagging, gradientNames, alphaStateNames) {
|
|
|
784
887
|
for (const tok of line.tokens) {
|
|
785
888
|
const w = tok.widthPt + (tok.kind === "text" && tok.isSpace ? extraPerSpace : 0);
|
|
786
889
|
if (isDecorated(tok)) {
|
|
787
|
-
const [r, g, b] = hexToRgb01(tok.resolvedRun.colorHex);
|
|
788
890
|
const thickness = Math.max(.4, tok.fontSizePt * .06);
|
|
789
|
-
|
|
891
|
+
const paint = (hex) => {
|
|
892
|
+
const [r, g, b] = hexToRgb01(hex);
|
|
893
|
+
out.push(`${formatNumber(r)} ${formatNumber(g)} ${formatNumber(b)} rg`);
|
|
894
|
+
};
|
|
895
|
+
paint(tok.resolvedRun.colorHex);
|
|
790
896
|
if (tok.resolvedRun.underline !== "none") {
|
|
791
|
-
|
|
792
|
-
|
|
897
|
+
if (tok.resolvedRun.underlineColorHex) paint(tok.resolvedRun.underlineColorHex);
|
|
898
|
+
const style = tok.resolvedRun.underline;
|
|
899
|
+
const t = style === "thick" || style === "dottedHeavy" || style === "dashHeavy" ? thickness * 2 : thickness;
|
|
900
|
+
const y = baselineY - tok.fontSizePt * .12 - t;
|
|
901
|
+
const dashed = style === "dotted" || style === "dottedHeavy" ? [t, t * 2] : style === "dash" || style === "dashHeavy" ? [t * 4, t * 3] : void 0;
|
|
902
|
+
if (dashed) {
|
|
903
|
+
out.push(`${formatNumber(t)} w`);
|
|
904
|
+
out.push(`[${dashed.map((n) => formatNumber(n)).join(" ")}] 0 d`);
|
|
905
|
+
const [r, g, b] = hexToRgb01(tok.resolvedRun.underlineColorHex ?? tok.resolvedRun.colorHex);
|
|
906
|
+
out.push(`${formatNumber(r)} ${formatNumber(g)} ${formatNumber(b)} RG`);
|
|
907
|
+
out.push(`${formatNumber(dx)} ${formatNumber(y + t / 2)} m ${formatNumber(dx + w)} ${formatNumber(y + t / 2)} l S`);
|
|
908
|
+
out.push("[] 0 d");
|
|
909
|
+
} else {
|
|
910
|
+
out.push(`${formatNumber(dx)} ${formatNumber(y)} ${formatNumber(w)} ${formatNumber(t)} re f`);
|
|
911
|
+
if (style === "double") out.push(`${formatNumber(dx)} ${formatNumber(y - t * 2)} ${formatNumber(w)} ${formatNumber(t)} re f`);
|
|
912
|
+
}
|
|
913
|
+
if (tok.resolvedRun.underlineColorHex) paint(tok.resolvedRun.colorHex);
|
|
793
914
|
}
|
|
794
915
|
if (tok.resolvedRun.strike) {
|
|
795
916
|
const y = baselineY + tok.fontSizePt * .26;
|
|
@@ -813,9 +934,9 @@ function emitPageContent(page, tagging, gradientNames, alphaStateNames) {
|
|
|
813
934
|
for (const tok of line.tokens) {
|
|
814
935
|
if (tok.kind !== "text") continue;
|
|
815
936
|
switchFontIfNeeded(tok);
|
|
816
|
-
out.push(
|
|
937
|
+
out.push(tok.font.measure.showText(tok.text));
|
|
817
938
|
}
|
|
818
|
-
} else if (extraPerSpace
|
|
939
|
+
} else if (extraPerSpace !== 0 || hasImageToken || hasMathToken || hasRtl || hasRise || hasTab) {
|
|
819
940
|
const order = hasRtl ? lineVisualOrder(line) : line.tokens.map((_, i) => i);
|
|
820
941
|
let x = originX;
|
|
821
942
|
for (const ti of order) {
|
|
@@ -838,7 +959,7 @@ function emitPageContent(page, tagging, gradientNames, alphaStateNames) {
|
|
|
838
959
|
}
|
|
839
960
|
switchFontIfNeeded(tok);
|
|
840
961
|
out.push(`1 0 0 1 ${formatNumber(x)} ${formatNumber(baselineY + (tok.risePt ?? 0))} Tm`);
|
|
841
|
-
out.push(
|
|
962
|
+
out.push(showToken(tok));
|
|
842
963
|
const tokenX0 = x;
|
|
843
964
|
x += tok.widthPt;
|
|
844
965
|
if (tok.isSpace) x += extraPerSpace;
|
|
@@ -855,8 +976,7 @@ function emitPageContent(page, tagging, gradientNames, alphaStateNames) {
|
|
|
855
976
|
for (const tok of line.tokens) {
|
|
856
977
|
if (tok.kind !== "text") continue;
|
|
857
978
|
switchFontIfNeeded(tok);
|
|
858
|
-
|
|
859
|
-
out.push(`<${hex}> Tj`);
|
|
979
|
+
out.push(tok.font.measure.showText(tok.text));
|
|
860
980
|
trackLink(tok.href, tok.anchor, tok.text, x, x + tok.widthPt);
|
|
861
981
|
x += tok.widthPt;
|
|
862
982
|
}
|
|
@@ -898,15 +1018,69 @@ function emitPageContent(page, tagging, gradientNames, alphaStateNames) {
|
|
|
898
1018
|
function lineVisualOrder(line) {
|
|
899
1019
|
return reorderVisual(line.tokens.map((t) => t.bidiLevel));
|
|
900
1020
|
}
|
|
1021
|
+
/**
|
|
1022
|
+
* The operators that put an image XObject in a box — ISO 32000-1 §8.9.5.1: the
|
|
1023
|
+
* unit square scaled to the box and moved to `(x, y)`, its bottom-left corner.
|
|
1024
|
+
*
|
|
1025
|
+
* A `a:srcRect` crop (§20.1.8.55) cuts edges off the SOURCE and fits what is
|
|
1026
|
+
* left to the same box, so the picture is drawn larger than the box and the box
|
|
1027
|
+
* is made a clipping path. Ignored, ImageCrop.docx drew the whole mountain
|
|
1028
|
+
* where LibreOffice shows the peak.
|
|
1029
|
+
*
|
|
1030
|
+
* @returns The content-stream lines, to be pushed between `q` and the `Do`.
|
|
1031
|
+
*/
|
|
1032
|
+
function placeImage(x, y, width, height, crop, rotationDeg, flipH, flipV) {
|
|
1033
|
+
const cm = (w, h, ox, oy) => `${formatNumber(w)} 0 0 ${formatNumber(h)} ${formatNumber(ox)} ${formatNumber(oy)} cm`;
|
|
1034
|
+
const spin = rotationDeg ? [rotateAboutCm(x + width / 2, y + height / 2, -rotationDeg)] : [];
|
|
1035
|
+
if (flipH === true || flipV === true) {
|
|
1036
|
+
const sx = flipH === true ? -1 : 1;
|
|
1037
|
+
const sy = flipV === true ? -1 : 1;
|
|
1038
|
+
spin.push(`${formatNumber(sx)} 0 0 ${formatNumber(sy)} ${formatNumber(sx === -1 ? 2 * x + width : 0)} ${formatNumber(sy === -1 ? 2 * y + height : 0)} cm`);
|
|
1039
|
+
}
|
|
1040
|
+
if (!crop) return [...spin, cm(width, height, x, y)];
|
|
1041
|
+
const kept = {
|
|
1042
|
+
w: 1 - crop.left - crop.right,
|
|
1043
|
+
h: 1 - crop.top - crop.bottom
|
|
1044
|
+
};
|
|
1045
|
+
const full = {
|
|
1046
|
+
w: width / kept.w,
|
|
1047
|
+
h: height / kept.h
|
|
1048
|
+
};
|
|
1049
|
+
return [
|
|
1050
|
+
...spin,
|
|
1051
|
+
`${formatNumber(x)} ${formatNumber(y)} ${formatNumber(width)} ${formatNumber(height)} re`,
|
|
1052
|
+
"W",
|
|
1053
|
+
"n",
|
|
1054
|
+
cm(full.w, full.h, x - crop.left * full.w, y - crop.bottom * full.h)
|
|
1055
|
+
];
|
|
1056
|
+
}
|
|
1057
|
+
/** A `cm` that turns the frame `deg` counter-clockwise about `(cx, cy)`. */
|
|
1058
|
+
function rotateAboutCm(cx, cy, deg) {
|
|
1059
|
+
const rad = deg * Math.PI / 180;
|
|
1060
|
+
const c = Math.cos(rad);
|
|
1061
|
+
const s = Math.sin(rad);
|
|
1062
|
+
return `${formatNumber(c)} ${formatNumber(s)} ${formatNumber(-s)} ${formatNumber(c)} ${formatNumber(cx - cx * c + cy * s)} ${formatNumber(cy - cx * s - cy * c)} cm`;
|
|
1063
|
+
}
|
|
901
1064
|
function computeJustifyExtra(line) {
|
|
902
|
-
|
|
903
|
-
if (
|
|
1065
|
+
const alignment = line.resolved.alignment;
|
|
1066
|
+
if (alignment !== "both" && alignment !== "distribute") return 0;
|
|
1067
|
+
if (line.noJustify === true) return 0;
|
|
1068
|
+
if (line.isLastInParagraph && alignment !== "distribute") return 0;
|
|
1069
|
+
let last = line.tokens.length - 1;
|
|
1070
|
+
while (last >= 0 && line.tokens[last].kind === "text" && line.tokens[last].isSpace) last--;
|
|
904
1071
|
let spaces = 0;
|
|
905
|
-
|
|
1072
|
+
let narrowest = Infinity;
|
|
1073
|
+
let contentWidthPt = 0;
|
|
1074
|
+
for (let i = 0; i <= last; i++) {
|
|
1075
|
+
const tok = line.tokens[i];
|
|
1076
|
+
contentWidthPt += tok.widthPt;
|
|
1077
|
+
if (!tok.isSpace) continue;
|
|
1078
|
+
spaces++;
|
|
1079
|
+
if (tok.widthPt < narrowest) narrowest = tok.widthPt;
|
|
1080
|
+
}
|
|
906
1081
|
if (spaces === 0) return 0;
|
|
907
|
-
const extra = line.availableWidthPt -
|
|
908
|
-
|
|
909
|
-
return extra / spaces;
|
|
1082
|
+
const extra = (line.availableWidthPt - contentWidthPt) / spaces;
|
|
1083
|
+
return extra < 0 ? Math.max(extra, -narrowest * GLUE_SHRINK_RATIO) : extra;
|
|
910
1084
|
}
|
|
911
1085
|
function hexToRgb01(hex) {
|
|
912
1086
|
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
|
+
/**
|
|
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
|
+
};
|
|
18
34
|
export declare function emitVectorShape(shape: VectorShape, patternName?: string, alphaStateName?: 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>;
|