reamkit 1.17.0 → 1.19.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/document-model/types.d.ts +59 -1
- package/dist/esm/core/drawingml/colors.js +13 -6
- package/dist/esm/core/images.d.ts +2 -2
- package/dist/esm/core/images.js +38 -0
- 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/po-helpers.d.ts +9 -0
- package/dist/esm/core/po-helpers.js +9 -1
- package/dist/esm/core/tiff.d.ts +20 -0
- package/dist/esm/core/tiff.js +339 -0
- package/dist/esm/core/vector.d.ts +5 -0
- package/dist/esm/excel/print-model.js +3 -1
- package/dist/esm/html/html-writer.js +3 -2
- package/dist/esm/layout/page-doc.d.ts +46 -3
- package/dist/esm/layout/styled-layout.js +649 -108
- package/dist/esm/pdf/styled-page-emitter.js +78 -3
- package/dist/esm/pdf/vector-graphics.d.ts +1 -1
- package/dist/esm/pdf/vector-graphics.js +2 -1
- package/dist/esm/pdf-reader/image-decode.js +7 -75
- package/dist/esm/svg/svg-writer.js +1 -1
- package/dist/esm/word/document-parser.js +3 -0
- package/dist/esm/word/drawing-parser.d.ts +5 -0
- package/dist/esm/word/drawing-parser.js +100 -14
- package/dist/esm/word/paragraph-properties.js +2 -2
- package/dist/esm/word/run-properties.js +2 -2
- package/dist/esm/word/xml-helpers.d.ts +12 -0
- package/dist/esm/word/xml-helpers.js +23 -3
- package/package.json +1 -1
|
@@ -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
|
|
@@ -113,8 +140,13 @@ function assembleStyledPdf(laid, options, doc) {
|
|
|
113
140
|
for (const item of plan.shapes) {
|
|
114
141
|
const s = item.shape.shadow;
|
|
115
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);
|
|
116
149
|
}
|
|
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);
|
|
118
150
|
}
|
|
119
151
|
const resourcesDict = dict({
|
|
120
152
|
Font: fontResourceDict,
|
|
@@ -391,7 +423,10 @@ function defaultPageCtx() {
|
|
|
391
423
|
}
|
|
392
424
|
function embedImageResources(doc, laid) {
|
|
393
425
|
const out = /* @__PURE__ */ new Map();
|
|
394
|
-
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
|
+
}
|
|
395
430
|
return out;
|
|
396
431
|
}
|
|
397
432
|
function embedFontResources(doc, laid, pdfaProfile) {
|
|
@@ -528,6 +563,7 @@ function emitPageContent(page, tagging, gradientNames, alphaStateNames) {
|
|
|
528
563
|
}
|
|
529
564
|
out.push(...placeImage(img.x, H - img.y - img.height, img.width, img.height, img.crop, img.rotationDeg, img.flipH, img.flipV));
|
|
530
565
|
out.push(`/${img.imageResourceName} Do`);
|
|
566
|
+
out.push(...washItems(img.wash, alphaStateNames));
|
|
531
567
|
out.push("Q");
|
|
532
568
|
});
|
|
533
569
|
const borders = plan.borders;
|
|
@@ -615,7 +651,9 @@ function emitPageContent(page, tagging, gradientNames, alphaStateNames) {
|
|
|
615
651
|
};
|
|
616
652
|
const shadowLayer = sh.shape.shadow ? shadowBlurLayers(sh.shape.shadow) : void 0;
|
|
617
653
|
const alphaState = shadowLayer && shadowLayer.alpha < 1 ? alphaStateNames?.get(Math.round(shadowLayer.alpha * 1e3) / 1e3) : void 0;
|
|
618
|
-
|
|
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);
|
|
619
657
|
});
|
|
620
658
|
const lines = plan.lines;
|
|
621
659
|
if (lines.length > 0) {
|
|
@@ -643,6 +681,42 @@ function emitPageContent(page, tagging, gradientNames, alphaStateNames) {
|
|
|
643
681
|
}
|
|
644
682
|
};
|
|
645
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
|
+
}
|
|
646
720
|
if (!tok.imageResourceName) return;
|
|
647
721
|
if (inBT) {
|
|
648
722
|
out.push("ET");
|
|
@@ -672,6 +746,7 @@ function emitPageContent(page, tagging, gradientNames, alphaStateNames) {
|
|
|
672
746
|
const b = tok.drawBox;
|
|
673
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));
|
|
674
748
|
out.push(`/${tok.imageResourceName} Do`);
|
|
749
|
+
out.push(...washItems(tok.wash, alphaStateNames));
|
|
675
750
|
out.push("Q");
|
|
676
751
|
const frame = tok.outline;
|
|
677
752
|
if (frame) {
|
|
@@ -31,7 +31,7 @@ export declare function shadowBlurLayers(shadow: {
|
|
|
31
31
|
count: number;
|
|
32
32
|
alpha: number;
|
|
33
33
|
};
|
|
34
|
-
export declare function emitVectorShape(shape: VectorShape, patternName?: string, alphaStateName?: string): Array<string>;
|
|
34
|
+
export declare function emitVectorShape(shape: VectorShape, patternName?: string, alphaStateName?: string, fillAlphaStateName?: string): Array<string>;
|
|
35
35
|
/**
|
|
36
36
|
* The operators that make `paths` the current CLIP, under `transform` — the
|
|
37
37
|
* same local→y-up-page matrix {@link emitVectorShape} takes. Emitted inside the
|
|
@@ -53,7 +53,7 @@ function shadowBbox(shape) {
|
|
|
53
53
|
maxY
|
|
54
54
|
} : void 0;
|
|
55
55
|
}
|
|
56
|
-
function emitVectorShape(shape, patternName, alphaStateName) {
|
|
56
|
+
function emitVectorShape(shape, patternName, alphaStateName, fillAlphaStateName) {
|
|
57
57
|
const out = [];
|
|
58
58
|
const [a, b, c, d, e, f] = shape.transform;
|
|
59
59
|
const shadow = shape.shadow;
|
|
@@ -88,6 +88,7 @@ function emitVectorShape(shape, patternName, alphaStateName) {
|
|
|
88
88
|
}
|
|
89
89
|
}
|
|
90
90
|
out.push("q");
|
|
91
|
+
if (fillAlphaStateName !== void 0) out.push(`/${fillAlphaStateName} gs`);
|
|
91
92
|
out.push(`${num(a)} ${num(b)} ${num(c)} ${num(d)} ${num(e)} ${num(f)} cm`);
|
|
92
93
|
const stroke = shape.stroke;
|
|
93
94
|
if (stroke) {
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { PDF_NULL, PdfHexString, PdfName, PdfStream } from "../pdf/objects.js";
|
|
2
|
+
import { lzwDecodeMsb } from "../core/lzw.js";
|
|
2
3
|
import { reversePredictor } from "./predictor.js";
|
|
3
4
|
import { decodeCcitt } from "./ccitt.js";
|
|
4
5
|
import { encodePng } from "./png-encode.js";
|
|
@@ -342,9 +343,7 @@ function decodeChain(file, stream, filters) {
|
|
|
342
343
|
return;
|
|
343
344
|
}
|
|
344
345
|
else if (f === "LZWDecode" || f === "LZW") {
|
|
345
|
-
|
|
346
|
-
if (!dec) return void 0;
|
|
347
|
-
data = dec;
|
|
346
|
+
data = lzwDecode(data, lzwEarlyChange(file, stream.dict));
|
|
348
347
|
mayPredict = true;
|
|
349
348
|
} else if (f === "RunLengthDecode" || f === "RL") data = runLengthDecode(data);
|
|
350
349
|
else if (f === "ASCII85Decode" || f === "A85") data = ascii85Decode(data);
|
|
@@ -359,7 +358,7 @@ function applyChainExceptLast(filters, raw) {
|
|
|
359
358
|
if (f === "FlateDecode" || f === "Fl") try {
|
|
360
359
|
data = unzlibSync(data);
|
|
361
360
|
} catch {}
|
|
362
|
-
else if (f === "LZWDecode" || f === "LZW") data = lzwDecode(data, 1)
|
|
361
|
+
else if (f === "LZWDecode" || f === "LZW") data = lzwDecode(data, 1);
|
|
363
362
|
else if (f === "RunLengthDecode" || f === "RL") data = runLengthDecode(data);
|
|
364
363
|
else if (f === "ASCII85Decode" || f === "A85") data = ascii85Decode(data);
|
|
365
364
|
else if (f === "ASCIIHexDecode" || f === "AHx") data = asciiHexDecode(data);
|
|
@@ -380,77 +379,10 @@ function applyPredictor(file, d, data) {
|
|
|
380
379
|
}
|
|
381
380
|
var MAX_LZW_OUT = MAX_PIXELS * 4;
|
|
382
381
|
function lzwDecode(data, earlyChange) {
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
if (outLen + e.length > out.length) {
|
|
388
|
-
let cap = out.length * 2;
|
|
389
|
-
while (cap < outLen + e.length) cap *= 2;
|
|
390
|
-
const grown = new Uint8Array(cap);
|
|
391
|
-
grown.set(out.subarray(0, outLen));
|
|
392
|
-
out = grown;
|
|
393
|
-
}
|
|
394
|
-
out.set(e, outLen);
|
|
395
|
-
outLen += e.length;
|
|
396
|
-
return true;
|
|
397
|
-
};
|
|
398
|
-
let bitBuffer = 0;
|
|
399
|
-
let bitCount = 0;
|
|
400
|
-
let pos = 0;
|
|
401
|
-
let codeLength = 9;
|
|
402
|
-
const readCode = () => {
|
|
403
|
-
while (bitCount < codeLength) {
|
|
404
|
-
if (pos >= data.length) return -1;
|
|
405
|
-
bitBuffer = (bitBuffer << 8 | data[pos++]) >>> 0;
|
|
406
|
-
bitCount += 8;
|
|
407
|
-
}
|
|
408
|
-
bitCount -= codeLength;
|
|
409
|
-
return bitBuffer >>> bitCount & (1 << codeLength) - 1;
|
|
410
|
-
};
|
|
411
|
-
const dict = new Array(4096);
|
|
412
|
-
let nextCode = 258;
|
|
413
|
-
let prev = -1;
|
|
414
|
-
const reset = () => {
|
|
415
|
-
for (let i = 0; i < 256; i++) dict[i] = Uint8Array.of(i);
|
|
416
|
-
nextCode = 258;
|
|
417
|
-
codeLength = 9;
|
|
418
|
-
prev = -1;
|
|
419
|
-
};
|
|
420
|
-
reset();
|
|
421
|
-
for (;;) {
|
|
422
|
-
const code = readCode();
|
|
423
|
-
if (code < 0 || code === 257) break;
|
|
424
|
-
if (code === 256) {
|
|
425
|
-
reset();
|
|
426
|
-
continue;
|
|
427
|
-
}
|
|
428
|
-
if (prev < 0) {
|
|
429
|
-
const first = dict[code];
|
|
430
|
-
if (!first || !emit(first)) break;
|
|
431
|
-
prev = code;
|
|
432
|
-
continue;
|
|
433
|
-
}
|
|
434
|
-
const prevEntry = dict[prev];
|
|
435
|
-
let entry = code < nextCode ? dict[code] : void 0;
|
|
436
|
-
if (!entry) {
|
|
437
|
-
entry = new Uint8Array(prevEntry.length + 1);
|
|
438
|
-
entry.set(prevEntry);
|
|
439
|
-
entry[prevEntry.length] = prevEntry[0];
|
|
440
|
-
}
|
|
441
|
-
if (!emit(entry)) break;
|
|
442
|
-
if (nextCode < 4096) {
|
|
443
|
-
const added = new Uint8Array(prevEntry.length + 1);
|
|
444
|
-
added.set(prevEntry);
|
|
445
|
-
added[prevEntry.length] = entry[0];
|
|
446
|
-
dict[nextCode++] = added;
|
|
447
|
-
if (nextCode + earlyChange === 512) codeLength = 10;
|
|
448
|
-
else if (nextCode + earlyChange === 1024) codeLength = 11;
|
|
449
|
-
else if (nextCode + earlyChange === 2048) codeLength = 12;
|
|
450
|
-
}
|
|
451
|
-
prev = code;
|
|
452
|
-
}
|
|
453
|
-
return out.subarray(0, outLen);
|
|
382
|
+
return lzwDecodeMsb(data, {
|
|
383
|
+
earlyChange,
|
|
384
|
+
limit: MAX_LZW_OUT
|
|
385
|
+
});
|
|
454
386
|
}
|
|
455
387
|
function lzwEarlyChange(file, d) {
|
|
456
388
|
const ec = decodeParmsOf(file, d)?.get("EarlyChange");
|
|
@@ -156,7 +156,7 @@ function imageHref(resourceName, laid) {
|
|
|
156
156
|
for (const [resourceId, res] of laid.imageResources) {
|
|
157
157
|
if (res.resourceName !== resourceName) continue;
|
|
158
158
|
const bytes = laid.resources.get(resourceId);
|
|
159
|
-
if (!bytes) return void 0;
|
|
159
|
+
if (!bytes || !res.prepared) return void 0;
|
|
160
160
|
return `data:${res.prepared.mimeType};base64,${toBase64(bytes)}`;
|
|
161
161
|
}
|
|
162
162
|
}
|
|
@@ -457,6 +457,7 @@ function blocksForDrawing(content, paragraphProperties, ctx) {
|
|
|
457
457
|
...resource ? { resource } : {},
|
|
458
458
|
...content.outline ? { outline: content.outline } : {},
|
|
459
459
|
...content.shadow ? { shadow: content.shadow } : {},
|
|
460
|
+
...content.wash ? { wash: content.wash } : {},
|
|
460
461
|
width: content.width,
|
|
461
462
|
height: content.height,
|
|
462
463
|
...content.crop ? { crop: content.crop } : {},
|
|
@@ -935,6 +936,7 @@ function parseRun(r, ctx, anchored) {
|
|
|
935
936
|
...resource ? { resource } : {},
|
|
936
937
|
...content.outline ? { outline: content.outline } : {},
|
|
937
938
|
...content.shadow ? { shadow: content.shadow } : {},
|
|
939
|
+
...content.wash ? { wash: content.wash } : {},
|
|
938
940
|
width: content.width,
|
|
939
941
|
height: content.height,
|
|
940
942
|
...content.crop ? { crop: content.crop } : {},
|
|
@@ -953,6 +955,7 @@ function parseRun(r, ctx, anchored) {
|
|
|
953
955
|
resource,
|
|
954
956
|
...content.outline ? { outline: content.outline } : {},
|
|
955
957
|
...content.shadow ? { shadow: content.shadow } : {},
|
|
958
|
+
...content.wash ? { wash: content.wash } : {},
|
|
956
959
|
width: content.width,
|
|
957
960
|
height: content.height
|
|
958
961
|
};
|
|
@@ -57,6 +57,11 @@ export type DrawingContent = {
|
|
|
57
57
|
readonly outline?: PictureOutline;
|
|
58
58
|
/** §20.1.8.40 `a:outerShdw` on the same `pic:spPr` — the picture's shadow. */
|
|
59
59
|
readonly shadow?: ShapeShadow;
|
|
60
|
+
/** §14.1.2.10 `@gain`/`@blacklevel` — the wash the picture is drawn through. */
|
|
61
|
+
readonly wash?: {
|
|
62
|
+
readonly gain: number;
|
|
63
|
+
readonly black: number;
|
|
64
|
+
};
|
|
60
65
|
/** §20.1.8.55 `a:srcRect` — the part of the source the frame shows. */
|
|
61
66
|
readonly crop?: ImageCrop;
|
|
62
67
|
/** §20.1.7.6 `a:xfrm @rot` — the picture's rotation (1/60000°, clockwise). */
|
|
@@ -25,10 +25,16 @@ function parseFloatAnchor(anchor) {
|
|
|
25
25
|
const behindRaw = poAttr(anchor, "behindDoc");
|
|
26
26
|
const behind = behindRaw === "1" || behindRaw === "true";
|
|
27
27
|
let wrap = "none";
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
28
|
+
let wrapSide;
|
|
29
|
+
for (const child of poChildren(anchor)) {
|
|
30
|
+
if (poIs(child, "wp:wrapSquare")) wrap = "square";
|
|
31
|
+
else if (poIs(child, "wp:wrapTight")) wrap = "tight";
|
|
32
|
+
else if (poIs(child, "wp:wrapThrough")) wrap = "through";
|
|
33
|
+
else if (poIs(child, "wp:wrapTopAndBottom")) wrap = "topAndBottom";
|
|
34
|
+
else continue;
|
|
35
|
+
const side = poAttr(child, "wrapText");
|
|
36
|
+
wrapSide = side === "left" || side === "right" || side === "largest" ? side : "bothSides";
|
|
37
|
+
}
|
|
32
38
|
const zOrder = poIntAttr(anchor, "relativeHeight");
|
|
33
39
|
const posH = parseAnchorPos(anchor, "wp:positionH", [
|
|
34
40
|
"margin",
|
|
@@ -53,8 +59,12 @@ function parseFloatAnchor(anchor) {
|
|
|
53
59
|
rightPt: dist("distR")
|
|
54
60
|
};
|
|
55
61
|
const anyDist = Object.values(wrapDist).some((v) => v > 0);
|
|
62
|
+
const inCellRaw = poAttr(anchor, "layoutInCell");
|
|
63
|
+
const inCell = !(inCellRaw === "0" || inCellRaw === "false");
|
|
56
64
|
return {
|
|
57
65
|
wrap,
|
|
66
|
+
...wrapSide ? { wrapSide } : {},
|
|
67
|
+
...inCell ? {} : { inCell: false },
|
|
58
68
|
...behind ? { behind: true } : {},
|
|
59
69
|
...zOrder !== void 0 ? { zOrder } : {},
|
|
60
70
|
...anyDist ? { wrapDist } : {},
|
|
@@ -355,12 +365,14 @@ function parseVmlPicture(node, parseBody) {
|
|
|
355
365
|
const shapeType = typeRef !== void 0 ? vmlShapeTypes(node).get(typeRef) : void 0;
|
|
356
366
|
const stroked = shape ? poAttr(shape, "stroked") ?? (shapeType ? poAttr(shapeType, "stroked") : void 0) : void 0;
|
|
357
367
|
const outline = shape && (stroked === "t" || stroked === "true") ? pictureOutline(vmlLine(shape, shapeType)) : void 0;
|
|
368
|
+
const wash = vmlWash(imagedata);
|
|
358
369
|
return {
|
|
359
370
|
kind: "image",
|
|
360
371
|
imageId,
|
|
361
372
|
width,
|
|
362
373
|
height,
|
|
363
374
|
...outline ? { outline } : {},
|
|
375
|
+
...wash ? { wash } : {},
|
|
364
376
|
...float ? { float } : {},
|
|
365
377
|
...altText ? { altText } : {}
|
|
366
378
|
};
|
|
@@ -404,13 +416,29 @@ function parseVmlShape(node, parseBody) {
|
|
|
404
416
|
const data = vmlShapeData(shape, tag, pt(Math.max(1, boxW)), pt(Math.max(1, boxH)), parseBody, vmlShapeTypes(node));
|
|
405
417
|
if (!data) return null;
|
|
406
418
|
const float = vmlFloat(style, poIntAttr(shape, "z-index"), shape);
|
|
419
|
+
const placed = float && from && to ? vmlLinePlaced(float, from, to) : float;
|
|
407
420
|
return {
|
|
408
421
|
kind: "shape",
|
|
409
422
|
data,
|
|
410
|
-
...
|
|
423
|
+
...placed ? { float: placed } : {},
|
|
411
424
|
...poAttr(shape, "alt")?.trim() ? { altText: poAttr(shape, "alt").trim() } : {}
|
|
412
425
|
};
|
|
413
426
|
}
|
|
427
|
+
function vmlLinePlaced(float, from, to) {
|
|
428
|
+
const x = Math.min(from.x, to.x);
|
|
429
|
+
const y = Math.min(from.y, to.y);
|
|
430
|
+
return {
|
|
431
|
+
...float,
|
|
432
|
+
...float.posH?.align === void 0 ? { posH: {
|
|
433
|
+
...float.posH ?? { relativeFrom: "column" },
|
|
434
|
+
offsetPt: pt(x)
|
|
435
|
+
} } : {},
|
|
436
|
+
...float.posV?.align === void 0 ? { posV: {
|
|
437
|
+
...float.posV ?? { relativeFrom: "paragraph" },
|
|
438
|
+
offsetPt: pt(y)
|
|
439
|
+
} } : {}
|
|
440
|
+
};
|
|
441
|
+
}
|
|
414
442
|
/**
|
|
415
443
|
* One VML primitive as {@link ShapeData}: its preset geometry, fill, outline
|
|
416
444
|
* and the words in its text box.
|
|
@@ -810,20 +838,24 @@ function vmlFill(shape, shapeType, resolveImage) {
|
|
|
810
838
|
const fillEl = poChildren(shape).find((c) => poIs(c, "v:fill"));
|
|
811
839
|
if (fillEl && (poAttr(fillEl, "type") === "none" || poAttr(fillEl, "on") === "f")) return { kind: "none" };
|
|
812
840
|
const base = vmlColor(poAttr(shape, "fillcolor")) ?? (fillEl ? vmlColor(poAttr(fillEl, "color")) : void 0) ?? vmlColor(shapeType ? poAttr(shapeType, "fillcolor") : void 0) ?? "FFFFFF";
|
|
813
|
-
const
|
|
841
|
+
const fillType = fillEl ? poAttr(fillEl, "type") : void 0;
|
|
842
|
+
const fillRelId = fillType === "frame" || fillType === "tile" ? poAttr(fillEl, "r:id") : void 0;
|
|
814
843
|
const picture = fillRelId !== void 0 ? resolveImage?.(fillRelId) : void 0;
|
|
815
844
|
if (picture !== void 0) return {
|
|
816
845
|
kind: "picture",
|
|
817
|
-
imageResource: picture
|
|
846
|
+
imageResource: picture,
|
|
847
|
+
...fillType === "tile" ? { tiled: true } : {}
|
|
818
848
|
};
|
|
819
849
|
const gradient = fillEl ? vmlGradient(fillEl, base) : void 0;
|
|
820
850
|
if (gradient) return {
|
|
821
851
|
kind: "gradient",
|
|
822
852
|
gradient
|
|
823
853
|
};
|
|
854
|
+
const opacity = vmlOpacity(poAttr(shape, "opacity") ?? (fillEl ? poAttr(fillEl, "opacity") : void 0));
|
|
824
855
|
return {
|
|
825
856
|
kind: "solid",
|
|
826
|
-
colorHex: base
|
|
857
|
+
colorHex: base,
|
|
858
|
+
...opacity !== void 0 ? { alpha: opacity } : {}
|
|
827
859
|
};
|
|
828
860
|
}
|
|
829
861
|
function vmlGradient(fillEl, base) {
|
|
@@ -878,6 +910,33 @@ function vmlFocusStops(raw, base, color2) {
|
|
|
878
910
|
}
|
|
879
911
|
];
|
|
880
912
|
}
|
|
913
|
+
function vmlWash(imagedata) {
|
|
914
|
+
const gain = vmlFraction(poAttr(imagedata, "gain"));
|
|
915
|
+
const black = vmlFraction(poAttr(imagedata, "blacklevel"));
|
|
916
|
+
if (gain === void 0 && black === void 0) return void 0;
|
|
917
|
+
const g = Math.max(0, gain ?? 1);
|
|
918
|
+
const b = black ?? 0;
|
|
919
|
+
return Math.abs(g - 1) < .01 && Math.abs(b) < .01 ? void 0 : {
|
|
920
|
+
gain: g,
|
|
921
|
+
black: b
|
|
922
|
+
};
|
|
923
|
+
}
|
|
924
|
+
function vmlFraction(raw) {
|
|
925
|
+
if (raw === void 0 || raw.trim() === "") return void 0;
|
|
926
|
+
const t = raw.trim();
|
|
927
|
+
const pct = t.endsWith("%");
|
|
928
|
+
const n = Number.parseFloat(pct ? t.slice(0, -1) : t.replace(/f$/iu, ""));
|
|
929
|
+
if (!Number.isFinite(n)) return void 0;
|
|
930
|
+
return pct ? n / 100 : Math.abs(n) > 1 ? n / 65536 : n;
|
|
931
|
+
}
|
|
932
|
+
function vmlOpacity(raw) {
|
|
933
|
+
if (raw === void 0 || raw.trim() === "") return void 0;
|
|
934
|
+
const pct = raw.trim().endsWith("%");
|
|
935
|
+
const n = Number.parseFloat(pct ? raw.trim().slice(0, -1) : raw.replace(/f$/iu, ""));
|
|
936
|
+
if (!Number.isFinite(n)) return void 0;
|
|
937
|
+
const v = pct ? n / 100 : n > 1 ? n / 65536 : n;
|
|
938
|
+
return v >= 1 ? void 0 : Math.max(0, v);
|
|
939
|
+
}
|
|
881
940
|
function vmlStops(raw) {
|
|
882
941
|
if (raw === void 0 || raw.trim() === "") return void 0;
|
|
883
942
|
const stops = [];
|
|
@@ -1020,6 +1079,7 @@ function vmlFloat(style, zIndexAttr, shape) {
|
|
|
1020
1079
|
const zIndex = prop("z-index") ?? zIndexAttr;
|
|
1021
1080
|
return {
|
|
1022
1081
|
wrap: vmlWrap(shape),
|
|
1082
|
+
...poAttrLocal(shape, "allowincell") === "f" ? { inCell: false } : {},
|
|
1023
1083
|
...zIndex !== void 0 && zIndex < 0 ? { behind: true } : {},
|
|
1024
1084
|
...zIndex !== void 0 ? { zOrder: Math.abs(zIndex) } : {},
|
|
1025
1085
|
posH: {
|
|
@@ -1530,11 +1590,17 @@ function withStyleFontColor(text, style, resolveColor) {
|
|
|
1530
1590
|
}
|
|
1531
1591
|
function parseTextBox(wsp, parseBody) {
|
|
1532
1592
|
const txbx = poChildren(wsp).find((c) => poIs(c, "wps:txbx"));
|
|
1533
|
-
|
|
1534
|
-
const
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1593
|
+
const linked = poChildren(wsp).find((c) => poIs(c, "wps:linkedTxbx"));
|
|
1594
|
+
const chainId = poAttr(txbx, "id") ?? poAttr(linked, "id");
|
|
1595
|
+
const chain = chainId === void 0 ? void 0 : {
|
|
1596
|
+
id: chainId,
|
|
1597
|
+
seq: linked ? poIntAttr(linked, "seq") ?? 1 : 0
|
|
1598
|
+
};
|
|
1599
|
+
if (!txbx && !linked) return void 0;
|
|
1600
|
+
const txContent = txbx ? poChildren(txbx).find((c) => poIs(c, "w:txbxContent")) : void 0;
|
|
1601
|
+
if (txbx && !txContent) return void 0;
|
|
1602
|
+
const content = txContent ? parseBody(poChildren(txContent)) : [];
|
|
1603
|
+
if (content.length === 0 && !linked) return void 0;
|
|
1538
1604
|
const bodyPr = poChildren(wsp).find((c) => poIs(c, "wps:bodyPr"));
|
|
1539
1605
|
const lIns = bodyPr ? poIntAttr(bodyPr, "lIns") : void 0;
|
|
1540
1606
|
const tIns = bodyPr ? poIntAttr(bodyPr, "tIns") : void 0;
|
|
@@ -1547,6 +1613,7 @@ function parseTextBox(wsp, parseBody) {
|
|
|
1547
1613
|
const autoFit = bodyPr !== void 0 && poChildren(bodyPr).some((c) => poIs(c, "a:spAutoFit"));
|
|
1548
1614
|
return {
|
|
1549
1615
|
content,
|
|
1616
|
+
...chain ? { chain } : {},
|
|
1550
1617
|
...vertical ? { vertical } : {},
|
|
1551
1618
|
...autoFit ? { autoFit: true } : {},
|
|
1552
1619
|
...lIns !== void 0 ? { insetLeft: emuToPt(lIns) } : {},
|
|
@@ -1737,14 +1804,17 @@ function fillFromNode(child, resolveColor, resolveImage) {
|
|
|
1737
1804
|
return {
|
|
1738
1805
|
kind: "picture",
|
|
1739
1806
|
imageResource: resource,
|
|
1807
|
+
...poChildren(child).some((c) => poIs(c, "a:tile")) ? { tiled: true } : {},
|
|
1740
1808
|
...crop ? { imageCrop: crop } : {}
|
|
1741
1809
|
};
|
|
1742
1810
|
}
|
|
1743
1811
|
if (poIs(child, "a:solidFill")) {
|
|
1744
1812
|
const hex = colorFromContainer(child, resolveColor);
|
|
1813
|
+
const alpha = containerAlpha(child);
|
|
1745
1814
|
return hex ? {
|
|
1746
1815
|
kind: "solid",
|
|
1747
|
-
colorHex: hex
|
|
1816
|
+
colorHex: alpha === void 0 ? hex : unblendWhite(hex, alpha),
|
|
1817
|
+
...alpha !== void 0 ? { alpha } : {}
|
|
1748
1818
|
} : { kind: "none" };
|
|
1749
1819
|
}
|
|
1750
1820
|
if (poIs(child, "a:pattFill")) {
|
|
@@ -1997,6 +2067,22 @@ function normalizeDash(v) {
|
|
|
1997
2067
|
if (v === "lgDashDotDot") return "lgDashDot";
|
|
1998
2068
|
if (v === "sysDashDot" || v === "sysDashDotDot") return "sysDash";
|
|
1999
2069
|
}
|
|
2070
|
+
function unblendWhite(hex, alpha) {
|
|
2071
|
+
if (alpha <= .004) return hex;
|
|
2072
|
+
const n = parseInt(hex, 16);
|
|
2073
|
+
if (Number.isNaN(n)) return hex;
|
|
2074
|
+
const ch = (shift) => {
|
|
2075
|
+
const raw = ((n >> shift & 255) / 255 - (1 - alpha)) / alpha;
|
|
2076
|
+
return Math.round(Math.max(0, Math.min(1, raw)) * 255).toString(16).padStart(2, "0");
|
|
2077
|
+
};
|
|
2078
|
+
return (ch(16) + ch(8) + ch(0)).toUpperCase();
|
|
2079
|
+
}
|
|
2080
|
+
function containerAlpha(parent) {
|
|
2081
|
+
for (const c of poChildren(parent)) {
|
|
2082
|
+
const mod = readColorMods(c).find((m) => m.kind === "alpha");
|
|
2083
|
+
if (mod) return Math.max(0, Math.min(1, mod.val));
|
|
2084
|
+
}
|
|
2085
|
+
}
|
|
2000
2086
|
function colorFromContainer(parent, resolveColor) {
|
|
2001
2087
|
for (const c of poChildren(parent)) {
|
|
2002
2088
|
const hex = resolveColorNode(c, resolveColor);
|
|
@@ -131,8 +131,8 @@ function parseParagraphProperties(pPr, autoSpacingPt = 0) {
|
|
|
131
131
|
}
|
|
132
132
|
if ("w:ind" in el) {
|
|
133
133
|
const node = el["w:ind"];
|
|
134
|
-
const left = parseIntAttr(node, "left");
|
|
135
|
-
const right = parseIntAttr(node, "right");
|
|
134
|
+
const left = parseIntAttr(node, "left") ?? parseIntAttr(node, "start");
|
|
135
|
+
const right = parseIntAttr(node, "right") ?? parseIntAttr(node, "end");
|
|
136
136
|
const firstLine = parseIntAttr(node, "firstLine");
|
|
137
137
|
const hanging = parseIntAttr(node, "hanging");
|
|
138
138
|
if (left !== void 0) out.indentLeft = twipsToPt(left);
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { halfPtToPt, twipsToPt } from "../core/ir/units.js";
|
|
2
|
-
import { asElement, getAttr, getVal,
|
|
2
|
+
import { asElement, getAttr, getVal, parseHalfPointAttr, parseToggle } from "./xml-helpers.js";
|
|
3
3
|
import { shadingFillHex } from "./shading.js";
|
|
4
4
|
//#region src/word/run-properties.ts
|
|
5
5
|
var UNDERLINE_STYLES = new Set([
|
|
@@ -81,7 +81,7 @@ function parseRunProperties(rPr) {
|
|
|
81
81
|
if (c && /^[0-9A-Fa-f]{6}$/u.test(c)) out.underlineColorHex = c.toUpperCase();
|
|
82
82
|
}
|
|
83
83
|
if ("w:sz" in el) {
|
|
84
|
-
const v =
|
|
84
|
+
const v = parseHalfPointAttr(el["w:sz"], "val");
|
|
85
85
|
if (v !== void 0) out.fontSizePt = halfPtToPt(v);
|
|
86
86
|
}
|
|
87
87
|
if ("w:color" in el) {
|
|
@@ -36,6 +36,18 @@ export declare function getVal(node: unknown): string | undefined;
|
|
|
36
36
|
* @param name The local attribute name.
|
|
37
37
|
* @returns The parsed number, or `undefined` when absent or not finite.
|
|
38
38
|
*/
|
|
39
|
+
/**
|
|
40
|
+
* §17.3.2.38 ST_HpsMeasure — a size in HALF-POINTS, or the same universal
|
|
41
|
+
* measure with a unit on it ("20pt"). The two units differ by a factor of ten
|
|
42
|
+
* from the twips a length is written in, so a size is read here rather than
|
|
43
|
+
* through {@link parseIntAttr}: tdf108408.docx writes `w:sz w:val="20pt"` and
|
|
44
|
+
* read as twips it set its sample text 200 points tall.
|
|
45
|
+
*
|
|
46
|
+
* @param node The element carrying the attribute.
|
|
47
|
+
* @param name The attribute name.
|
|
48
|
+
* @returns The size in half-points, or undefined.
|
|
49
|
+
*/
|
|
50
|
+
export declare function parseHalfPointAttr(node: unknown, name: string): number | undefined;
|
|
39
51
|
export declare function parseIntAttr(node: unknown, name: string): number | undefined;
|
|
40
52
|
/**
|
|
41
53
|
* Parse a toggle (boolean) property per ECMA-376 Part 1 §17.17.4:
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { universalMeasureTwips } from "../core/po-helpers.js";
|
|
1
2
|
//#region src/word/xml-helpers.ts
|
|
2
3
|
/**
|
|
3
4
|
* Narrow an arbitrary parsed value to an {@link XmlElement}.
|
|
@@ -47,12 +48,31 @@ function getVal(node) {
|
|
|
47
48
|
* @param name The local attribute name.
|
|
48
49
|
* @returns The parsed number, or `undefined` when absent or not finite.
|
|
49
50
|
*/
|
|
51
|
+
/**
|
|
52
|
+
* §17.3.2.38 ST_HpsMeasure — a size in HALF-POINTS, or the same universal
|
|
53
|
+
* measure with a unit on it ("20pt"). The two units differ by a factor of ten
|
|
54
|
+
* from the twips a length is written in, so a size is read here rather than
|
|
55
|
+
* through {@link parseIntAttr}: tdf108408.docx writes `w:sz w:val="20pt"` and
|
|
56
|
+
* read as twips it set its sample text 200 points tall.
|
|
57
|
+
*
|
|
58
|
+
* @param node The element carrying the attribute.
|
|
59
|
+
* @param name The attribute name.
|
|
60
|
+
* @returns The size in half-points, or undefined.
|
|
61
|
+
*/
|
|
62
|
+
function parseHalfPointAttr(node, name) {
|
|
63
|
+
const v = getAttr(node, name);
|
|
64
|
+
if (v === void 0) return void 0;
|
|
65
|
+
const n = Number(v);
|
|
66
|
+
if (Number.isFinite(n)) return n;
|
|
67
|
+
const twips = universalMeasureTwips(v);
|
|
68
|
+
return twips === void 0 ? void 0 : twips / 10;
|
|
69
|
+
}
|
|
50
70
|
function parseIntAttr(node, name) {
|
|
51
71
|
const v = getAttr(node, name);
|
|
52
72
|
if (v === void 0) return void 0;
|
|
53
73
|
const n = Number(v);
|
|
54
|
-
if (
|
|
55
|
-
return
|
|
74
|
+
if (Number.isFinite(n)) return n;
|
|
75
|
+
return universalMeasureTwips(v);
|
|
56
76
|
}
|
|
57
77
|
/**
|
|
58
78
|
* Parse a toggle (boolean) property per ECMA-376 Part 1 §17.17.4:
|
|
@@ -71,4 +91,4 @@ function parseToggle(node) {
|
|
|
71
91
|
return !(v === "false" || v === "0" || v === "off");
|
|
72
92
|
}
|
|
73
93
|
//#endregion
|
|
74
|
-
export { asArray, asElement, getAttr, getVal, parseIntAttr, parseToggle };
|
|
94
|
+
export { asArray, asElement, getAttr, getVal, parseHalfPointAttr, parseIntAttr, parseToggle };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "reamkit",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.19.0",
|
|
4
4
|
"description": "Ream — convert DOCX, XLSX, PPTX and PDF to PDF, SVG, HTML, DOCX and XLSX, built from scratch on the ECMA-376 and ISO 32000 specifications. Parse once, convert anywhere.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Alex Krassavin <info@reamkit.dev>",
|