reamkit 1.15.3 → 1.16.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/converter/facade.d.ts +5 -0
- package/dist/esm/core/converter/ream.d.ts +6 -0
- package/dist/esm/core/converter/ream.js +34 -8
- package/dist/esm/core/document-model/index.d.ts +1 -1
- package/dist/esm/core/document-model/types.d.ts +187 -1
- package/dist/esm/core/drawingml/chart-geometry.d.ts +28 -0
- package/dist/esm/core/drawingml/chart-geometry.js +371 -88
- package/dist/esm/core/drawingml/chart-parser.js +262 -11
- package/dist/esm/core/drawingml/colors.d.ts +6 -6
- package/dist/esm/core/drawingml/colors.js +58 -18
- package/dist/esm/core/drawingml/preset-geometry.js +7 -6
- package/dist/esm/core/drawingml/sparkline-geometry.js +6 -4
- package/dist/esm/core/drawingml/theme-parser.d.ts +41 -0
- package/dist/esm/core/drawingml/theme-parser.js +74 -1
- package/dist/esm/core/font/ttf-subset.js +26 -1
- package/dist/esm/core/images.js +172 -43
- package/dist/esm/core/indexed-colors.d.ts +11 -0
- package/dist/esm/core/indexed-colors.js +81 -0
- package/dist/esm/core/ir/sheet.d.ts +67 -3
- package/dist/esm/{excel → core}/number-format.d.ts +37 -0
- package/dist/esm/core/number-format.js +800 -0
- package/dist/esm/core/opc/alternate-content.d.ts +13 -0
- package/dist/esm/core/opc/alternate-content.js +72 -0
- package/dist/esm/core/opc/core-properties.js +1 -0
- package/dist/esm/core/opc/package.d.ts +14 -1
- package/dist/esm/core/opc/package.js +43 -4
- package/dist/esm/core/opc/relationships.js +1 -0
- package/dist/esm/core/opc/xml-entities.d.ts +12 -0
- package/dist/esm/core/opc/xml-entities.js +91 -0
- package/dist/esm/core/spreadsheet-model/index.d.ts +1 -1
- package/dist/esm/core/spreadsheet-model/types.d.ts +137 -0
- package/dist/esm/core/vector.d.ts +13 -0
- package/dist/esm/excel/activex-parser.d.ts +9 -0
- package/dist/esm/excel/activex-parser.js +16 -1
- package/dist/esm/excel/column-bands.d.ts +26 -1
- package/dist/esm/excel/column-bands.js +156 -20
- package/dist/esm/excel/comments-parser.js +5 -3
- package/dist/esm/excel/conditional-format.d.ts +25 -2
- package/dist/esm/excel/conditional-format.js +63 -19
- package/dist/esm/excel/escaped-text.d.ts +9 -0
- package/dist/esm/excel/escaped-text.js +20 -0
- package/dist/esm/excel/form-control-parser.js +1 -0
- package/dist/esm/excel/formula/dates.js +1 -1
- package/dist/esm/excel/header-footer.d.ts +9 -1
- package/dist/esm/excel/header-footer.js +162 -14
- package/dist/esm/excel/index.d.ts +1 -1
- package/dist/esm/excel/index.js +2 -2
- package/dist/esm/excel/pivot-table-parser.js +1 -0
- package/dist/esm/excel/print-model.d.ts +75 -0
- package/dist/esm/excel/print-model.js +902 -77
- package/dist/esm/excel/printer-settings.d.ts +16 -0
- package/dist/esm/excel/printer-settings.js +36 -0
- package/dist/esm/excel/rich-value.d.ts +13 -0
- package/dist/esm/excel/rich-value.js +120 -0
- package/dist/esm/excel/shared-strings-parser.js +11 -7
- package/dist/esm/excel/sheet-drawing.d.ts +11 -0
- package/dist/esm/excel/sheet-drawing.js +26 -3
- package/dist/esm/excel/sheet-shape-parser.d.ts +53 -1
- package/dist/esm/excel/sheet-shape-parser.js +461 -32
- package/dist/esm/excel/sheet-to-flow.d.ts +23 -2
- package/dist/esm/excel/sheet-to-flow.js +551 -36
- package/dist/esm/excel/slicer-parser.js +1 -0
- package/dist/esm/excel/styles-parser.d.ts +30 -2
- package/dist/esm/excel/styles-parser.js +177 -40
- package/dist/esm/excel/table-parser.js +1 -0
- package/dist/esm/excel/vml-drawing.d.ts +68 -0
- package/dist/esm/excel/vml-drawing.js +158 -0
- package/dist/esm/excel/workbook-parser.d.ts +5 -0
- package/dist/esm/excel/workbook-parser.js +5 -1
- package/dist/esm/excel/worksheet-parser.d.ts +2 -1
- package/dist/esm/excel/worksheet-parser.js +149 -28
- package/dist/esm/excel/xlsx-reader.js +271 -33
- package/dist/esm/excel/xlsx-to-pdf.js +12 -1
- package/dist/esm/html/html-writer.js +11 -2
- package/dist/esm/layout/page-doc.d.ts +26 -0
- package/dist/esm/layout/styled-layout.d.ts +2 -0
- package/dist/esm/layout/styled-layout.js +493 -64
- package/dist/esm/pdf/cid-font.js +20 -9
- package/dist/esm/pdf/objects.d.ts +13 -0
- package/dist/esm/pdf/objects.js +18 -1
- package/dist/esm/pdf/styled-page-emitter.js +164 -32
- package/dist/esm/pdf/text-page-renderer.js +1 -0
- package/dist/esm/pdf/vector-graphics.d.ts +3 -1
- package/dist/esm/pdf/vector-graphics.js +19 -2
- package/dist/esm/pptx/pptx-reader.js +1 -0
- package/dist/esm/pptx/slide-parser.js +12 -1
- package/dist/esm/svg/svg-writer.js +23 -4
- package/dist/esm/word/document-parser.js +1 -0
- package/dist/esm/word/drawing-parser.d.ts +34 -1
- package/dist/esm/word/drawing-parser.js +52 -2
- package/dist/esm/word/numbering-parser.js +1 -0
- package/dist/esm/word/settings-parser.js +1 -0
- package/dist/esm/word/styles-parser.js +1 -0
- package/package.json +7 -3
- package/dist/esm/excel/number-format.js +0 -476
package/dist/esm/pdf/cid-font.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { glyphClosure, subsetTtf } from "../core/font/ttf-subset.js";
|
|
2
2
|
import { createFontMeasure } from "../core/font/measure.js";
|
|
3
|
-
import { dict, name, ref
|
|
3
|
+
import { deflatedStream, dict, name, ref } from "./objects.js";
|
|
4
4
|
//#region src/pdf/cid-font.ts
|
|
5
5
|
var encoder = new TextEncoder();
|
|
6
6
|
/**
|
|
@@ -17,9 +17,10 @@ function embedTtfFont(doc, parsed, options = {}) {
|
|
|
17
17
|
const scale = 1e3 / parsed.unitsPerEm;
|
|
18
18
|
const toPdfUnits = (v) => Math.round(v * scale);
|
|
19
19
|
const usedGidsArr = options.usedGids ? [...options.usedGids] : void 0;
|
|
20
|
+
const subsetGids = usedGidsArr ? [...glyphClosure(parsed, usedGidsArr)].sort((a, b) => a - b) : void 0;
|
|
20
21
|
const fontFileBytes = usedGidsArr ? subsetTtf(parsed, usedGidsArr) : parsed.raw;
|
|
21
|
-
const fontFileRef = doc.add(
|
|
22
|
-
const cidSetRef = usedGidsArr !== void 0 && options.cidSet ? doc.add(
|
|
22
|
+
const fontFileRef = doc.add(deflatedStream({ Length1: fontFileBytes.byteLength }, fontFileBytes));
|
|
23
|
+
const cidSetRef = usedGidsArr !== void 0 && options.cidSet ? doc.add(deflatedStream({}, buildCidSet(glyphClosure(parsed, usedGidsArr)))) : void 0;
|
|
23
24
|
const baseFontName = usedGidsArr ? `${subsetTag(parsed.postScriptName, usedGidsArr)}+${parsed.postScriptName}` : parsed.postScriptName;
|
|
24
25
|
const descriptorRef = doc.add(dict({
|
|
25
26
|
Type: name("FontDescriptor"),
|
|
@@ -40,8 +41,7 @@ function embedTtfFont(doc, parsed, options = {}) {
|
|
|
40
41
|
FontFile2: ref(fontFileRef.id),
|
|
41
42
|
...cidSetRef ? { CIDSet: ref(cidSetRef.id) } : {}
|
|
42
43
|
}));
|
|
43
|
-
const
|
|
44
|
-
for (let i = 0; i < parsed.numGlyphs; i++) widths[i] = toPdfUnits(parsed.advanceWidths[i] ?? 0);
|
|
44
|
+
const widthRuns = consecutiveRuns(subsetGids ?? Array.from({ length: parsed.numGlyphs }, (_, i) => i)).map((run) => [run[0], run.map((g) => toPdfUnits(parsed.advanceWidths[g] ?? 0))]);
|
|
45
45
|
const cidFontRef = doc.add(dict({
|
|
46
46
|
Type: name("Font"),
|
|
47
47
|
Subtype: name("CIDFontType2"),
|
|
@@ -53,10 +53,10 @@ function embedTtfFont(doc, parsed, options = {}) {
|
|
|
53
53
|
}),
|
|
54
54
|
FontDescriptor: ref(descriptorRef.id),
|
|
55
55
|
DW: 1e3,
|
|
56
|
-
W: [
|
|
56
|
+
W: widthRuns.flatMap(([first, ws]) => [first, ws]),
|
|
57
57
|
CIDToGIDMap: name("Identity")
|
|
58
58
|
}));
|
|
59
|
-
const toUnicodeRef = doc.add(
|
|
59
|
+
const toUnicodeRef = doc.add(deflatedStream({}, buildToUnicodeCMap(parsed, subsetGids)));
|
|
60
60
|
const type0Ref = doc.add(dict({
|
|
61
61
|
Type: name("Font"),
|
|
62
62
|
Subtype: name("Type0"),
|
|
@@ -99,7 +99,18 @@ function subsetTag(postScriptName, gids) {
|
|
|
99
99
|
}
|
|
100
100
|
return tag;
|
|
101
101
|
}
|
|
102
|
-
|
|
102
|
+
/** Split a sorted id list into runs of consecutive ids. */
|
|
103
|
+
function consecutiveRuns(ids) {
|
|
104
|
+
const runs = [];
|
|
105
|
+
for (const id of ids) {
|
|
106
|
+
const last = runs[runs.length - 1];
|
|
107
|
+
if (last && id === last[last.length - 1] + 1) last.push(id);
|
|
108
|
+
else runs.push([id]);
|
|
109
|
+
}
|
|
110
|
+
return runs;
|
|
111
|
+
}
|
|
112
|
+
function buildToUnicodeCMap(parsed, subsetGids) {
|
|
113
|
+
const inSubset = subsetGids ? new Set(subsetGids) : void 0;
|
|
103
114
|
const gidToCps = /* @__PURE__ */ new Map();
|
|
104
115
|
const order = [];
|
|
105
116
|
for (let cp = 9; cp <= 65535; cp++) {
|
|
@@ -131,7 +142,7 @@ function buildToUnicodeCMap(parsed) {
|
|
|
131
142
|
}
|
|
132
143
|
}
|
|
133
144
|
}
|
|
134
|
-
const pairs = order.map((g) => [g, gidToCps.get(g)]).sort((a, b) => a[0] - b[0]);
|
|
145
|
+
const pairs = order.filter((g) => inSubset === void 0 || inSubset.has(g)).map((g) => [g, gidToCps.get(g)]).sort((a, b) => a[0] - b[0]);
|
|
135
146
|
const utf16beHex = (cps) => {
|
|
136
147
|
let hex = "";
|
|
137
148
|
for (const cp of cps) if (cp <= 65535) hex += cp.toString(16).padStart(4, "0").toUpperCase();
|
|
@@ -62,6 +62,19 @@ export declare const ref: (id: number, gen?: number) => PdfRef;
|
|
|
62
62
|
export declare const dict: (entries: Record<string, PdfValue>) => PdfDict;
|
|
63
63
|
/** Construct a {@link PdfStream} from a dictionary literal plus its byte payload. */
|
|
64
64
|
export declare const stream: (entries: Record<string, PdfValue>, data: Uint8Array) => PdfStream;
|
|
65
|
+
/**
|
|
66
|
+
* A {@link PdfStream} whose payload is deflated (ISO 32000-1 §7.4.4).
|
|
67
|
+
*
|
|
68
|
+
* Nothing we wrote was compressed at all — `grep /Filter` over one of our own
|
|
69
|
+
* files returned the empty set — so a three-page workbook came out at 192 KB
|
|
70
|
+
* against LibreOffice's 21 KB, most of it an uncompressed font program. Any
|
|
71
|
+
* stream that is not already in a compressed image format belongs here.
|
|
72
|
+
*
|
|
73
|
+
* @param entries The stream's dictionary, minus `/Filter` and `/Length`.
|
|
74
|
+
* @param data The raw payload.
|
|
75
|
+
* @returns The stream, deflated, with `/Filter /FlateDecode`.
|
|
76
|
+
*/
|
|
77
|
+
export declare const deflatedStream: (entries: Record<string, PdfValue>, data: Uint8Array) => PdfStream;
|
|
65
78
|
/**
|
|
66
79
|
* Build a PDF Unicode string (UTF-16BE with BOM) suitable for `/Info` or
|
|
67
80
|
* document-level text metadata. Accepts arbitrary Unicode input.
|
package/dist/esm/pdf/objects.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { zlibSync } from "fflate";
|
|
1
2
|
//#region src/pdf/objects.ts
|
|
2
3
|
/** The PDF `null` object (§7.3.9), modelled as a unique symbol. */
|
|
3
4
|
var PDF_NULL = Symbol("PDF_NULL");
|
|
@@ -61,6 +62,22 @@ var dict = (entries) => new Map(Object.entries(entries));
|
|
|
61
62
|
/** Construct a {@link PdfStream} from a dictionary literal plus its byte payload. */
|
|
62
63
|
var stream = (entries, data) => new PdfStream(dict(entries), data);
|
|
63
64
|
/**
|
|
65
|
+
* A {@link PdfStream} whose payload is deflated (ISO 32000-1 §7.4.4).
|
|
66
|
+
*
|
|
67
|
+
* Nothing we wrote was compressed at all — `grep /Filter` over one of our own
|
|
68
|
+
* files returned the empty set — so a three-page workbook came out at 192 KB
|
|
69
|
+
* against LibreOffice's 21 KB, most of it an uncompressed font program. Any
|
|
70
|
+
* stream that is not already in a compressed image format belongs here.
|
|
71
|
+
*
|
|
72
|
+
* @param entries The stream's dictionary, minus `/Filter` and `/Length`.
|
|
73
|
+
* @param data The raw payload.
|
|
74
|
+
* @returns The stream, deflated, with `/Filter /FlateDecode`.
|
|
75
|
+
*/
|
|
76
|
+
var deflatedStream = (entries, data) => new PdfStream(dict({
|
|
77
|
+
...entries,
|
|
78
|
+
Filter: name("FlateDecode")
|
|
79
|
+
}), zlibSync(data));
|
|
80
|
+
/**
|
|
64
81
|
* Build a PDF Unicode string (UTF-16BE with BOM) suitable for `/Info` or
|
|
65
82
|
* document-level text metadata. Accepts arbitrary Unicode input.
|
|
66
83
|
*
|
|
@@ -87,4 +104,4 @@ var unicodeString = (s) => {
|
|
|
87
104
|
return new PdfHexString(out);
|
|
88
105
|
};
|
|
89
106
|
//#endregion
|
|
90
|
-
export { PDF_NULL, PdfHexString, PdfName, PdfRawToken, PdfRef, PdfStream, dict, name, ref, stream, unicodeString };
|
|
107
|
+
export { PDF_NULL, PdfHexString, PdfName, PdfRawToken, PdfRef, PdfStream, deflatedStream, dict, name, ref, stream, unicodeString };
|
|
@@ -95,10 +95,25 @@ function assembleStyledPdf(laid, options, doc) {
|
|
|
95
95
|
gradientNames.set(item.shape, nm);
|
|
96
96
|
}
|
|
97
97
|
}
|
|
98
|
+
const alphaStateNames = /* @__PURE__ */ new Map();
|
|
99
|
+
const extGStateEntries = {};
|
|
100
|
+
for (const page of renderedPages) for (const item of paintPlan(page.commands).shapes) {
|
|
101
|
+
const alpha = item.shape.shadow?.alpha;
|
|
102
|
+
if (alpha === void 0 || alpha >= 1) continue;
|
|
103
|
+
const key = Math.round(alpha * 1e3) / 1e3;
|
|
104
|
+
if (alphaStateNames.has(key)) continue;
|
|
105
|
+
const nm = `GSa${alphaStateNames.size}`;
|
|
106
|
+
alphaStateNames.set(key, nm);
|
|
107
|
+
extGStateEntries[nm] = dict({
|
|
108
|
+
ca: key,
|
|
109
|
+
CA: key
|
|
110
|
+
});
|
|
111
|
+
}
|
|
98
112
|
const resourcesDict = dict({
|
|
99
113
|
Font: fontResourceDict,
|
|
100
114
|
...xobjectResourceDict ? { XObject: xobjectResourceDict } : {},
|
|
101
|
-
...Object.keys(patternEntries).length > 0 ? { Pattern: dict(patternEntries) } : {}
|
|
115
|
+
...Object.keys(patternEntries).length > 0 ? { Pattern: dict(patternEntries) } : {},
|
|
116
|
+
...Object.keys(extGStateEntries).length > 0 ? { ExtGState: dict(extGStateEntries) } : {}
|
|
102
117
|
});
|
|
103
118
|
let pdfaIccRef;
|
|
104
119
|
let transparencyGroup;
|
|
@@ -125,7 +140,7 @@ function assembleStyledPdf(laid, options, doc) {
|
|
|
125
140
|
tagFor: (structId) => builder.node(structId).type
|
|
126
141
|
};
|
|
127
142
|
}
|
|
128
|
-
const { content: contentBytes, links } = emitPageContent(page, pageTagging, gradientNames);
|
|
143
|
+
const { content: contentBytes, links } = emitPageContent(page, pageTagging, gradientNames, alphaStateNames);
|
|
129
144
|
const contentsRef = doc.add(stream({}, contentBytes));
|
|
130
145
|
const pageEntries = {
|
|
131
146
|
Type: name("Page"),
|
|
@@ -374,10 +389,13 @@ function embedImageResources(doc, laid) {
|
|
|
374
389
|
function embedFontResources(doc, laid, pdfaProfile) {
|
|
375
390
|
const cidSet = pdfaProfile?.part === 1;
|
|
376
391
|
const out = /* @__PURE__ */ new Map();
|
|
377
|
-
for (const [variant, res] of laid.fontResources)
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
392
|
+
for (const [variant, res] of laid.fontResources) {
|
|
393
|
+
if (res.gids.size === 0) continue;
|
|
394
|
+
out.set(variant, embedTtfFont(doc, res.parsed, {
|
|
395
|
+
usedGids: res.gids,
|
|
396
|
+
cidSet
|
|
397
|
+
}));
|
|
398
|
+
}
|
|
381
399
|
return out;
|
|
382
400
|
}
|
|
383
401
|
function buildFontResourceDict(resources, embedded) {
|
|
@@ -426,7 +444,24 @@ function emitTaggedRuns(out, cmds, tagging, body) {
|
|
|
426
444
|
}
|
|
427
445
|
close();
|
|
428
446
|
}
|
|
429
|
-
|
|
447
|
+
/**
|
|
448
|
+
* The PDF dash array for a §18.18.3 border style, in points.
|
|
449
|
+
*
|
|
450
|
+
* Excel's dashes are proportional to the rule's weight; LibreOffice draws a
|
|
451
|
+
* `thin` dashed edge as 3pt on / 1pt off, which is 4× / 1.33× its 0.75pt width.
|
|
452
|
+
* An empty string means a solid rule.
|
|
453
|
+
*/
|
|
454
|
+
function dashPatternFor(style, widthPt) {
|
|
455
|
+
const u = Math.max(.25, widthPt);
|
|
456
|
+
switch (style) {
|
|
457
|
+
case "dashed": return `[${formatNumber(u * 4)} ${formatNumber(u * 1.33)}]`;
|
|
458
|
+
case "dashDot": return `[${formatNumber(u * 4)} ${formatNumber(u * 1.33)} ${formatNumber(u)} ${formatNumber(u * 1.33)}]`;
|
|
459
|
+
case "dashDotDot": return `[${formatNumber(u * 4)} ${formatNumber(u * 1.33)} ${formatNumber(u)} ${formatNumber(u * 1.33)} ${formatNumber(u)} ${formatNumber(u * 1.33)}]`;
|
|
460
|
+
case "dotted": return `[${formatNumber(u)} ${formatNumber(u * 1.33)}]`;
|
|
461
|
+
default: return "";
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
function emitPageContent(page, tagging, gradientNames, alphaStateNames) {
|
|
430
465
|
const commands = page.commands;
|
|
431
466
|
const links = [];
|
|
432
467
|
if (commands.length === 0) return {
|
|
@@ -464,14 +499,22 @@ function emitPageContent(page, tagging, gradientNames) {
|
|
|
464
499
|
if (borders.length > 0) {
|
|
465
500
|
if (tagging) out.push("/Artifact BMC");
|
|
466
501
|
out.push("q");
|
|
502
|
+
out.push("2 J");
|
|
467
503
|
let lastWidth = -1;
|
|
468
504
|
let lastColor = "";
|
|
505
|
+
let lastDash = "";
|
|
469
506
|
for (const b of borders) {
|
|
470
507
|
const width = b.borderSizePt;
|
|
471
508
|
const color = b.borderColorHex;
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
509
|
+
const dash = dashPatternFor(b.borderStyle, width);
|
|
510
|
+
if (dash !== lastDash) {
|
|
511
|
+
out.push(dash === "" ? "[] 0 d 2 J" : `${dash} 0 d 0 J`);
|
|
512
|
+
lastDash = dash;
|
|
513
|
+
}
|
|
514
|
+
const strokeW = b.borderStyle === "double" ? Math.max(.25, width / 3) : width;
|
|
515
|
+
if (strokeW !== lastWidth) {
|
|
516
|
+
out.push(`${formatNumber(strokeW)} w`);
|
|
517
|
+
lastWidth = strokeW;
|
|
475
518
|
}
|
|
476
519
|
if (color !== lastColor) {
|
|
477
520
|
const [r, g, bl] = hexToRgb01(color);
|
|
@@ -482,23 +525,39 @@ function emitPageContent(page, tagging, gradientNames) {
|
|
|
482
525
|
const y = H - b.y - b.height;
|
|
483
526
|
const w = b.width;
|
|
484
527
|
const h = b.height;
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
528
|
+
const [x1, y1, x2, y2, nx, ny] = b.side === "top" ? [
|
|
529
|
+
x,
|
|
530
|
+
y + h,
|
|
531
|
+
x + w,
|
|
532
|
+
y + h,
|
|
533
|
+
0,
|
|
534
|
+
1
|
|
535
|
+
] : b.side === "bottom" ? [
|
|
536
|
+
x,
|
|
537
|
+
y,
|
|
538
|
+
x + w,
|
|
539
|
+
y,
|
|
540
|
+
0,
|
|
541
|
+
1
|
|
542
|
+
] : b.side === "left" ? [
|
|
543
|
+
x,
|
|
544
|
+
y,
|
|
545
|
+
x,
|
|
546
|
+
y + h,
|
|
547
|
+
1,
|
|
548
|
+
0
|
|
549
|
+
] : [
|
|
550
|
+
x + w,
|
|
551
|
+
y,
|
|
552
|
+
x + w,
|
|
553
|
+
y + h,
|
|
554
|
+
1,
|
|
555
|
+
0
|
|
556
|
+
];
|
|
557
|
+
const offsets = b.borderStyle === "double" ? [-width / 3, width / 3] : [0];
|
|
558
|
+
for (const d of offsets) {
|
|
559
|
+
out.push(`${formatNumber(x1 + nx * d)} ${formatNumber(y1 + ny * d)} m`);
|
|
560
|
+
out.push(`${formatNumber(x2 + nx * d)} ${formatNumber(y2 + ny * d)} l`);
|
|
502
561
|
}
|
|
503
562
|
out.push("S");
|
|
504
563
|
}
|
|
@@ -519,7 +578,9 @@ function emitPageContent(page, tagging, gradientNames) {
|
|
|
519
578
|
H - t[5]
|
|
520
579
|
]
|
|
521
580
|
};
|
|
522
|
-
|
|
581
|
+
const alpha = sh.shape.shadow?.alpha;
|
|
582
|
+
const alphaState = alpha !== void 0 && alpha < 1 ? alphaStateNames?.get(Math.round(alpha * 1e3) / 1e3) : void 0;
|
|
583
|
+
for (const op of emitVectorShape(shape, gradientNames?.get(sh.shape), alphaState)) out.push(op);
|
|
523
584
|
});
|
|
524
585
|
const lines = plan.lines;
|
|
525
586
|
if (lines.length > 0) {
|
|
@@ -611,10 +672,38 @@ function emitPageContent(page, tagging, gradientNames) {
|
|
|
611
672
|
lastColor = "";
|
|
612
673
|
}
|
|
613
674
|
};
|
|
675
|
+
const emitClipped = (cmd, body) => {
|
|
676
|
+
const clip = cmd.clip;
|
|
677
|
+
if (!clip) {
|
|
678
|
+
body(cmd);
|
|
679
|
+
return;
|
|
680
|
+
}
|
|
681
|
+
if (inBT) {
|
|
682
|
+
out.push("ET");
|
|
683
|
+
inBT = false;
|
|
684
|
+
}
|
|
685
|
+
out.push("q");
|
|
686
|
+
out.push(`${formatNumber(clip.x)} ${formatNumber(H - clip.y - clip.height)} ${formatNumber(clip.width)} ${formatNumber(clip.height)} re W n`);
|
|
687
|
+
lastFont = "";
|
|
688
|
+
lastSize = -1;
|
|
689
|
+
lastColor = "";
|
|
690
|
+
out.push("BT");
|
|
691
|
+
inBT = true;
|
|
692
|
+
body(cmd);
|
|
693
|
+
if (inBT) {
|
|
694
|
+
out.push("ET");
|
|
695
|
+
inBT = false;
|
|
696
|
+
}
|
|
697
|
+
out.push("Q");
|
|
698
|
+
lastFont = "";
|
|
699
|
+
lastSize = -1;
|
|
700
|
+
lastColor = "";
|
|
701
|
+
};
|
|
614
702
|
const emitOneLine = (cmd) => {
|
|
615
703
|
const line = cmd.line;
|
|
616
704
|
const originX = cmd.originX;
|
|
617
705
|
const baselineY = H - cmd.baselineY;
|
|
706
|
+
const rotationDeg = cmd.rotationDeg ?? 0;
|
|
618
707
|
const lineFs = line.maxFontSizePt || 12;
|
|
619
708
|
const linkAscent = Math.max(lineFs, line.mathAscentPt ?? 0);
|
|
620
709
|
const linkDescent = Math.max(lineFs * .2, line.mathDescentPt ?? 0);
|
|
@@ -660,6 +749,8 @@ function emitPageContent(page, tagging, gradientNames) {
|
|
|
660
749
|
const hasImageToken = line.tokens.some((t) => t.kind === "image");
|
|
661
750
|
const hasMathToken = line.tokens.some((t) => t.kind === "math");
|
|
662
751
|
const hasRtl = line.tokens.some((t) => t.bidiLevel % 2 === 1);
|
|
752
|
+
/** A text token carrying underline or strikethrough. */
|
|
753
|
+
const isDecorated = (tok) => tok.kind === "text" && (tok.resolvedRun.underline !== "none" || tok.resolvedRun.strike);
|
|
663
754
|
const encodeToken = (tok) => {
|
|
664
755
|
const text = tok.bidiLevel % 2 === 1 ? reverseByCodePoint(tok.text) : tok.text;
|
|
665
756
|
return tok.font.measure.encodeTextAsCidHex(text);
|
|
@@ -683,7 +774,48 @@ function emitPageContent(page, tagging, gradientNames) {
|
|
|
683
774
|
out.push("f");
|
|
684
775
|
out.push("Q");
|
|
685
776
|
}
|
|
686
|
-
if (
|
|
777
|
+
if (line.tokens.some(isDecorated)) {
|
|
778
|
+
if (inBT) {
|
|
779
|
+
out.push("ET");
|
|
780
|
+
inBT = false;
|
|
781
|
+
}
|
|
782
|
+
out.push("q");
|
|
783
|
+
let dx = originX;
|
|
784
|
+
for (const tok of line.tokens) {
|
|
785
|
+
const w = tok.widthPt + (tok.kind === "text" && tok.isSpace ? extraPerSpace : 0);
|
|
786
|
+
if (isDecorated(tok)) {
|
|
787
|
+
const [r, g, b] = hexToRgb01(tok.resolvedRun.colorHex);
|
|
788
|
+
const thickness = Math.max(.4, tok.fontSizePt * .06);
|
|
789
|
+
out.push(`${formatNumber(r)} ${formatNumber(g)} ${formatNumber(b)} rg`);
|
|
790
|
+
if (tok.resolvedRun.underline !== "none") {
|
|
791
|
+
const y = baselineY - tok.fontSizePt * .12 - thickness;
|
|
792
|
+
out.push(`${formatNumber(dx)} ${formatNumber(y)} ${formatNumber(w)} ${formatNumber(thickness)} re f`);
|
|
793
|
+
}
|
|
794
|
+
if (tok.resolvedRun.strike) {
|
|
795
|
+
const y = baselineY + tok.fontSizePt * .26;
|
|
796
|
+
out.push(`${formatNumber(dx)} ${formatNumber(y)} ${formatNumber(w)} ${formatNumber(thickness)} re f`);
|
|
797
|
+
}
|
|
798
|
+
}
|
|
799
|
+
dx += w;
|
|
800
|
+
}
|
|
801
|
+
out.push("Q");
|
|
802
|
+
}
|
|
803
|
+
const hasRise = line.tokens.some((t) => t.kind === "text" && t.risePt !== void 0);
|
|
804
|
+
if (rotationDeg !== 0 && !hasImageToken && !hasMathToken) {
|
|
805
|
+
if (!inBT) {
|
|
806
|
+
out.push("BT");
|
|
807
|
+
inBT = true;
|
|
808
|
+
}
|
|
809
|
+
const rad = rotationDeg * Math.PI / 180;
|
|
810
|
+
const cos = formatNumber(Math.cos(rad));
|
|
811
|
+
const sin = formatNumber(Math.sin(rad));
|
|
812
|
+
out.push(`${cos} ${sin} ${formatNumber(-Math.sin(rad))} ${cos} ${formatNumber(originX)} ${formatNumber(baselineY)} Tm`);
|
|
813
|
+
for (const tok of line.tokens) {
|
|
814
|
+
if (tok.kind !== "text") continue;
|
|
815
|
+
switchFontIfNeeded(tok);
|
|
816
|
+
out.push(`<${tok.font.measure.encodeTextAsCidHex(tok.text)}> Tj`);
|
|
817
|
+
}
|
|
818
|
+
} else if (extraPerSpace > 0 || hasImageToken || hasMathToken || hasRtl || hasRise) {
|
|
687
819
|
const order = hasRtl ? lineVisualOrder(line) : line.tokens.map((_, i) => i);
|
|
688
820
|
let x = originX;
|
|
689
821
|
for (const ti of order) {
|
|
@@ -705,7 +837,7 @@ function emitPageContent(page, tagging, gradientNames) {
|
|
|
705
837
|
inBT = true;
|
|
706
838
|
}
|
|
707
839
|
switchFontIfNeeded(tok);
|
|
708
|
-
out.push(`1 0 0 1 ${formatNumber(x)} ${formatNumber(baselineY)} Tm`);
|
|
840
|
+
out.push(`1 0 0 1 ${formatNumber(x)} ${formatNumber(baselineY + (tok.risePt ?? 0))} Tm`);
|
|
709
841
|
out.push(`<${encodeToken(tok)}> Tj`);
|
|
710
842
|
const tokenX0 = x;
|
|
711
843
|
x += tok.widthPt;
|
|
@@ -744,7 +876,7 @@ function emitPageContent(page, tagging, gradientNames) {
|
|
|
744
876
|
out.push(`/${tagging.tagFor(sid)} <</MCID ${mcid}>> BDC`);
|
|
745
877
|
} else if (cmd.artifact === "pagination") out.push("/Artifact <</Type /Pagination>> BDC");
|
|
746
878
|
else out.push("/Artifact BMC");
|
|
747
|
-
|
|
879
|
+
emitClipped(cmd, emitOneLine);
|
|
748
880
|
if (inBT) {
|
|
749
881
|
out.push("ET");
|
|
750
882
|
inBT = false;
|
|
@@ -754,7 +886,7 @@ function emitPageContent(page, tagging, gradientNames) {
|
|
|
754
886
|
else {
|
|
755
887
|
out.push("BT");
|
|
756
888
|
inBT = true;
|
|
757
|
-
for (const cmd of lines)
|
|
889
|
+
for (const cmd of lines) emitClipped(cmd, emitOneLine);
|
|
758
890
|
if (inBT) out.push("ET");
|
|
759
891
|
}
|
|
760
892
|
}
|
|
@@ -11,6 +11,8 @@ export { PathBuilder } from '../core/vector.js';
|
|
|
11
11
|
* @param patternName When set, the fill is this named shading pattern from the
|
|
12
12
|
* page's `/Pattern` resources (a gradient, EP16b) rather than a solid colour;
|
|
13
13
|
* only used if the shape actually carries a `fillGradient`.
|
|
14
|
+
* @param alphaStateName When set, the shape's shadow is drawn first, under this
|
|
15
|
+
* named `/ExtGState` (its constant alpha) from the page's resources.
|
|
14
16
|
* @returns The content-stream operator lines.
|
|
15
17
|
*/
|
|
16
|
-
export declare function emitVectorShape(shape: VectorShape, patternName?: string): Array<string>;
|
|
18
|
+
export declare function emitVectorShape(shape: VectorShape, patternName?: string, alphaStateName?: string): Array<string>;
|
|
@@ -10,12 +10,29 @@ import "../core/vector.js";
|
|
|
10
10
|
* @param patternName When set, the fill is this named shading pattern from the
|
|
11
11
|
* page's `/Pattern` resources (a gradient, EP16b) rather than a solid colour;
|
|
12
12
|
* only used if the shape actually carries a `fillGradient`.
|
|
13
|
+
* @param alphaStateName When set, the shape's shadow is drawn first, under this
|
|
14
|
+
* named `/ExtGState` (its constant alpha) from the page's resources.
|
|
13
15
|
* @returns The content-stream operator lines.
|
|
14
16
|
*/
|
|
15
|
-
function emitVectorShape(shape, patternName) {
|
|
17
|
+
function emitVectorShape(shape, patternName, alphaStateName) {
|
|
16
18
|
const out = [];
|
|
17
|
-
out.push("q");
|
|
18
19
|
const [a, b, c, d, e, f] = shape.transform;
|
|
20
|
+
const shadow = shape.shadow;
|
|
21
|
+
if (shadow) {
|
|
22
|
+
out.push("q");
|
|
23
|
+
if (alphaStateName !== void 0) out.push(`/${alphaStateName} gs`);
|
|
24
|
+
out.push(`${num(a)} ${num(b)} ${num(c)} ${num(d)} ${num(e + shadow.dxPt)} ${num(f - shadow.dyPt)} cm`);
|
|
25
|
+
const [sr, sg, sb] = hexToRgb01(shadow.colorHex);
|
|
26
|
+
out.push(`${num(sr)} ${num(sg)} ${num(sb)} rg`);
|
|
27
|
+
let shadowEvenodd = false;
|
|
28
|
+
for (const path of shape.paths) {
|
|
29
|
+
if (path.fillRule === "evenodd") shadowEvenodd = true;
|
|
30
|
+
for (const seg of path.segments) out.push(emitSegment(seg));
|
|
31
|
+
}
|
|
32
|
+
out.push(shadowEvenodd ? "f*" : "f");
|
|
33
|
+
out.push("Q");
|
|
34
|
+
}
|
|
35
|
+
out.push("q");
|
|
19
36
|
out.push(`${num(a)} ${num(b)} ${num(c)} ${num(d)} ${num(e)} ${num(f)} cm`);
|
|
20
37
|
const stroke = shape.stroke;
|
|
21
38
|
if (stroke) {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { emuToPt } from "../core/ir/units.js";
|
|
1
|
+
import { emuToPt, pt } from "../core/ir/units.js";
|
|
2
2
|
import { FEATURES } from "../core/ir/features.js";
|
|
3
3
|
import { poAttr, poChildren, poFindDescendant, poIntAttr, poIs, poText } from "../core/po-helpers.js";
|
|
4
4
|
import { defaultColorResolver, resolveColorNode } from "../core/drawingml/colors.js";
|
|
@@ -400,6 +400,17 @@ function parseSlideParagraph(aP, ph, cascade, colors, resolveLink, counters) {
|
|
|
400
400
|
const run = parseSlideRun(child, defaults, colors, resolveLink);
|
|
401
401
|
if (run) runs.push(run);
|
|
402
402
|
}
|
|
403
|
+
if (runs.length === 0) {
|
|
404
|
+
const endRPr = poChildren(aP).find((c) => poIs(c, "a:endParaRPr"));
|
|
405
|
+
const sz = endRPr ? poIntAttr(endRPr, "sz") : void 0;
|
|
406
|
+
if (sz !== void 0 && sz > 0) runs.push({
|
|
407
|
+
text: "",
|
|
408
|
+
properties: {
|
|
409
|
+
...defaults,
|
|
410
|
+
fontSizePt: pt(sz / 100)
|
|
411
|
+
}
|
|
412
|
+
});
|
|
413
|
+
}
|
|
403
414
|
const marL = pPr ? poIntAttr(pPr, "marL") : void 0;
|
|
404
415
|
const indent = pPr ? poIntAttr(pPr, "indent") : void 0;
|
|
405
416
|
const indentLeft = marL !== void 0 ? emuToPt(marL) : level > 0 ? emuToPt(level * 457200) : void 0;
|
|
@@ -87,14 +87,20 @@ function emitPage(out, page, laid, losses, idc) {
|
|
|
87
87
|
x2,
|
|
88
88
|
yBottom
|
|
89
89
|
];
|
|
90
|
-
out.push(`<line x1="${fmt(ax)}" y1="${fmt(ay)}" x2="${fmt(bx)}" y2="${fmt(by)}" stroke="#${b.borderColorHex}" stroke-width="${fmt(b.borderSizePt)}"/>`);
|
|
90
|
+
out.push(`<line x1="${fmt(ax)}" y1="${fmt(ay)}" x2="${fmt(bx)}" y2="${fmt(by)}" stroke="#${b.borderColorHex}" stroke-width="${fmt(b.borderSizePt)}"${b.borderStyle === "dashed" ? ` stroke-dasharray="${fmt(b.borderSizePt * 4)} ${fmt(b.borderSizePt * 1.33)}"` : b.borderStyle === "dotted" ? ` stroke-dasharray="${fmt(b.borderSizePt)} ${fmt(b.borderSizePt * 1.33)}"` : ""}/>`);
|
|
91
91
|
}
|
|
92
92
|
for (const sh of plan.shapes) emitShape(out, sh.shape, idc);
|
|
93
|
-
for (const t of plan.lines) emitTextLine(out, t, losses);
|
|
93
|
+
for (const t of plan.lines) emitTextLine(out, t, losses, idc);
|
|
94
94
|
}
|
|
95
|
-
function emitTextLine(out, item, losses) {
|
|
95
|
+
function emitTextLine(out, item, losses, idc) {
|
|
96
|
+
const clip = item.clip;
|
|
97
|
+
if (clip) {
|
|
98
|
+
const id = `clip${idc.n++}`;
|
|
99
|
+
out.push(`<clipPath id="${id}"><rect x="${fmt(clip.x)}" y="${fmt(clip.y)}" width="${fmt(clip.width)}" height="${fmt(clip.height)}"/></clipPath>`, `<g clip-path="url(#${id})">`);
|
|
100
|
+
}
|
|
96
101
|
const y = item.baselineY;
|
|
97
102
|
let x = item.originX;
|
|
103
|
+
const rot = item.rotationDeg ? ` transform="rotate(${fmt(-item.rotationDeg)} ${fmt(item.originX)} ${fmt(item.baselineY)})"` : "";
|
|
98
104
|
for (const tok of item.line.tokens) {
|
|
99
105
|
if (tok.kind === "image") {
|
|
100
106
|
x += tok.widthPt;
|
|
@@ -109,9 +115,10 @@ function emitTextLine(out, item, losses) {
|
|
|
109
115
|
x += tok.widthPt;
|
|
110
116
|
continue;
|
|
111
117
|
}
|
|
112
|
-
if (tok.text.trim().length > 0) out.push(`<text x="${fmt(x)}" y="${fmt(y)}" font-family="sans-serif" font-size="${fmt(tok.fontSizePt)}" fill="#${tok.resolvedRun.colorHex}">${escapeXml(tok.text)}</text>`);
|
|
118
|
+
if (tok.text.trim().length > 0) out.push(`<text x="${fmt(x)}" y="${fmt(y)}"${rot} font-family="sans-serif" font-size="${fmt(tok.fontSizePt)}" fill="#${tok.resolvedRun.colorHex}">${escapeXml(tok.text)}</text>`);
|
|
113
119
|
x += tok.widthPt;
|
|
114
120
|
}
|
|
121
|
+
if (clip) out.push("</g>");
|
|
115
122
|
}
|
|
116
123
|
function emitShape(out, shape, idc) {
|
|
117
124
|
const [a, b, c, d, e, f] = shape.transform;
|
|
@@ -123,6 +130,18 @@ function emitShape(out, shape, idc) {
|
|
|
123
130
|
fill = `url(#${id})`;
|
|
124
131
|
} else fill = shape.fillColorHex ? `#${shape.fillColorHex}` : "none";
|
|
125
132
|
const stroke = shape.stroke ? ` stroke="#${shape.stroke.colorHex}" stroke-width="${fmt(shape.stroke.widthPt)}"` : "";
|
|
133
|
+
const shadow = shape.shadow;
|
|
134
|
+
if (shadow) {
|
|
135
|
+
const id = `shadow${idc.n++}`;
|
|
136
|
+
if (shadow.blurPt > 0) out.push(`<filter id="${id}" x="-50%" y="-50%" width="200%" height="200%"><feGaussianBlur stdDeviation="${fmt(shadow.blurPt / 2)}"/></filter>`);
|
|
137
|
+
const shTransform = `matrix(${fmt(a)} ${fmt(b)} ${fmt(c)} ${fmt(d)} ${fmt(e + shadow.dxPt)} ${fmt(f + shadow.dyPt)})`;
|
|
138
|
+
out.push(`<g opacity="${fmt(shadow.alpha)}"${shadow.blurPt > 0 ? ` filter="url(#${id})"` : ""}>`);
|
|
139
|
+
for (const path of shape.paths) {
|
|
140
|
+
const rule = path.fillRule === "evenodd" ? " fill-rule=\"evenodd\"" : "";
|
|
141
|
+
out.push(`<path d="${pathData(path.segments)}" fill="#${shadow.colorHex}"${rule} transform="${shTransform}"/>`);
|
|
142
|
+
}
|
|
143
|
+
out.push("</g>");
|
|
144
|
+
}
|
|
126
145
|
for (const path of shape.paths) {
|
|
127
146
|
const d2 = pathData(path.segments);
|
|
128
147
|
const rule = path.fillRule === "evenodd" ? " fill-rule=\"evenodd\"" : "";
|
|
@@ -12,6 +12,7 @@ import { XMLParser } from "fast-xml-parser";
|
|
|
12
12
|
//#region src/word/document-parser.ts
|
|
13
13
|
var decoder = new TextDecoder("utf-8");
|
|
14
14
|
var parser = new XMLParser({
|
|
15
|
+
htmlEntities: true,
|
|
15
16
|
ignoreAttributes: false,
|
|
16
17
|
attributeNamePrefix: "@_",
|
|
17
18
|
preserveOrder: true,
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { BodyElement, FloatAnchor, ShapeFill, ShapeGeometry, ShapeLine, ShapeTextBody, ShapeTransform } from '../core/document-model/index.js';
|
|
1
|
+
import { BodyElement, FloatAnchor, ShapeFill, ShapeGeometry, ShapeLine, ShapeShadow, ShapeTextBody, ShapeTransform } from '../core/document-model/index.js';
|
|
2
2
|
import { ColorResolver } from '../core/drawingml/colors.js';
|
|
3
3
|
import { PoNode } from '../core/po-helpers.js';
|
|
4
4
|
import { Pt } from '../core/ir/index.js';
|
|
@@ -104,6 +104,17 @@ export declare function parseDrawing(drawing: PoNode, resolveColor: ColorResolve
|
|
|
104
104
|
* @returns The picture, or `null` when there is no embedded `v:imagedata` or no usable size.
|
|
105
105
|
*/
|
|
106
106
|
export declare function parseVmlPicture(node: PoNode): DrawingContent | null;
|
|
107
|
+
/**
|
|
108
|
+
* §20.1.7.6 `a:xfrm` — how a shape sits in its box: rotated about its centre by
|
|
109
|
+
* `@rot` (sixtieth-thousandths of a degree) and mirrored by `@flipH`/`@flipV`.
|
|
110
|
+
* The box itself comes from elsewhere — the anchor on a sheet, the extent in a
|
|
111
|
+
* document — and holds the shape UNROTATED, which is what makes this a separate
|
|
112
|
+
* transform rather than a different rectangle.
|
|
113
|
+
*
|
|
114
|
+
* @param xfrm The `a:xfrm` element.
|
|
115
|
+
* @returns The transform; empty when it states neither rotation nor a flip.
|
|
116
|
+
*/
|
|
117
|
+
export declare function parseXfrm(xfrm: PoNode): ShapeTransform;
|
|
107
118
|
/**
|
|
108
119
|
* Parse an `a:prstGeom` (§20.1.9.18) into a preset {@link ShapeGeometry}: the
|
|
109
120
|
* `@prst` preset name plus the `a:avLst` adjust values (each `a:gd`'s `val …`
|
|
@@ -126,6 +137,28 @@ export declare function parseCustGeom(cust: PoNode): ShapeGeometry;
|
|
|
126
137
|
* @param resolveColor Resolver for theme/scheme colours.
|
|
127
138
|
*/
|
|
128
139
|
export declare function parseFill(spPr: PoNode, resolveColor: ColorResolver): ShapeFill;
|
|
140
|
+
/**
|
|
141
|
+
* Parse a shape's drop shadow from its `a:spPr` (§20.1.8.40 `a:effectLst` →
|
|
142
|
+
* `a:outerShdw`).
|
|
143
|
+
*
|
|
144
|
+
* The spec states the displacement in polar form: `dist` in EMU and `dir` in
|
|
145
|
+
* 60 000ths of a degree, measured clockwise from due east in a frame whose y
|
|
146
|
+
* grows DOWNWARD — so the standard 2 700 000 (45°) puts the shadow down and to
|
|
147
|
+
* the right, which is where every reader draws it.
|
|
148
|
+
*
|
|
149
|
+
* @param spPr The shape's `spPr` node.
|
|
150
|
+
* @param resolveColor Resolver for theme/scheme colours.
|
|
151
|
+
* @returns The shadow, or undefined when the shape declares none.
|
|
152
|
+
*/
|
|
153
|
+
export declare function parseShadow(spPr: PoNode, resolveColor: ColorResolver): ShapeShadow | undefined;
|
|
154
|
+
/**
|
|
155
|
+
* Build a {@link ShapeShadow} from an `a:outerShdw` node.
|
|
156
|
+
*
|
|
157
|
+
* @param shdw The `a:outerShdw` node.
|
|
158
|
+
* @param resolveColor Resolver for theme/scheme colours.
|
|
159
|
+
* @returns The shadow, or undefined when its colour will not resolve.
|
|
160
|
+
*/
|
|
161
|
+
export declare function shadowFromOuterShdw(shdw: PoNode, resolveColor: ColorResolver): ShapeShadow | undefined;
|
|
129
162
|
/**
|
|
130
163
|
* Parse a shape's outline (`a:ln`) from its `a:spPr` into a {@link ShapeLine}:
|
|
131
164
|
* width, cap, solid colour, dash pattern, and an explicit `a:noFill` (an unstroked
|