@office-kit/pptx-preview 0.9.0 → 0.9.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -2
- package/dist/index.js +1 -1
- package/dist/node.js +1 -1
- package/dist/{src-DPHsJGe1.js → src-CXGhih2x.js} +316 -40
- package/dist/src-CXGhih2x.js.map +1 -0
- package/package.json +1 -1
- package/dist/src-DPHsJGe1.js.map +0 -1
package/README.md
CHANGED
|
@@ -125,8 +125,10 @@ placeholder layout/master cascade), strokes, rotation, effects (shadow, glow,
|
|
|
125
125
|
soft edge, reflection), images with adjustments, charts (column, bar, line,
|
|
126
126
|
area, pie, doughnut, scatter, radar, bubble), tables with per-run cell text,
|
|
127
127
|
vertical and multi-column text in both text-layout modes, picture bullets, and
|
|
128
|
-
template (layout/master) decoration all render.
|
|
129
|
-
|
|
128
|
+
template (layout/master) decoration all render. EMF pictures containing solid-filled
|
|
129
|
+
line/Bézier paths, including rectangular and path clipping with copy semantics,
|
|
130
|
+
render as transparent SVG images. Other EMF drawing commands, WMF, SmartArt,
|
|
131
|
+
animations, and 3D fall back to labelled placeholders carrying a machine-readable marker
|
|
130
132
|
(below). Per-slide closeness to a LibreOffice baseline is measured and gated
|
|
131
133
|
in CI by the fidelity harness in the monorepo (`site/fidelity`) — mean
|
|
132
134
|
fg-SSIM ≈ 0.78 across the corpus, with the residual gaps documented there.
|
package/dist/index.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { a as MONO, c as TIMES, d as substituteFamily, l as defaultMeasurer, n as renderSlideSvg, o as SANS, r as ARIAL, s as SERIF, t as auditTextLayout } from "./src-
|
|
1
|
+
import { a as MONO, c as TIMES, d as substituteFamily, l as defaultMeasurer, n as renderSlideSvg, o as SANS, r as ARIAL, s as SERIF, t as auditTextLayout } from "./src-CXGhih2x.js";
|
|
2
2
|
export { ARIAL, MONO, SANS, SERIF, TIMES, auditTextLayout, defaultMeasurer, renderSlideSvg as renderSlideToSvg, substituteFamily };
|
package/dist/node.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { a as MONO, c as TIMES, d as substituteFamily, i as AVG_GLYPH_W_RATIO, l as defaultMeasurer, n as renderSlideSvg, o as SANS, r as ARIAL, s as SERIF, t as auditTextLayout, u as isCjk } from "./src-
|
|
1
|
+
import { a as MONO, c as TIMES, d as substituteFamily, i as AVG_GLYPH_W_RATIO, l as defaultMeasurer, n as renderSlideSvg, o as SANS, r as ARIAL, s as SERIF, t as auditTextLayout, u as isCjk } from "./src-CXGhih2x.js";
|
|
2
2
|
import { getSlideSize } from "@office-kit/pptx";
|
|
3
3
|
import { Resvg } from "@resvg/resvg-js";
|
|
4
4
|
import * as fontkit from "fontkit";
|
|
@@ -1,4 +1,247 @@
|
|
|
1
1
|
import { getEffectiveColorMap, getGroupChildren, getGroupTransform, getParagraphAlignment, getParagraphBullet, getParagraphBulletImageBytes, getParagraphBulletStyle, getParagraphIndent, getParagraphLevel, getParagraphPropertiesEffective, getParagraphSpacing, getPresentationFonts, getPresentationTheme, getShapeAdjustValues, getShapeAltTitle, getShapeBodyPrEffective, getShapeBoundsResolved, getShapeChartSpec, getShapeClickAction, getShapeCustomGeometry, getShapeDescription, getShapeEffectsEffective, getShapeFillColorResolved, getShapeFillEffective, getShapeFlip, getShapeGradientFill, getShapeGradientFillEffective, getShapeHyperlink, getShapeHyperlinkTooltip, getShapeImageBiLevelThreshold, getShapeImageBrightness, getShapeImageBytes, getShapeImageContrast, getShapeImageCrop, getShapeImageDuotone, getShapeImageFillBytes, getShapeImageFormat, getShapeImageLinkUrl, getShapeImageOpacity, getShapeImagePartName, getShapeKind, getShapeName, getShapeParagraphCount, getShapeParagraphElements, getShapePatternFill, getShapePlaceholderType, getShapePreset, getShapeRotation, getShapeRunClickAction, getShapeRunFormat, getShapeRunFormatEffective, getShapeRunHyperlink, getShapeRunHyperlinkTooltip, getShapeStrokeArrow, getShapeStrokeCap, getShapeStrokeColorResolved, getShapeStrokeCompound, getShapeStrokeDash, getShapeStrokeEffective, getShapeStrokeJoin, getShapeTextAnchor, getShapeTextAutoFitParams, getShapeTextBodyRotationDeg, getShapeTextColumns, getShapeTextDirection, getShapeTextMargins, getShapeXmlString, getSlideBackground, getSlideBackgroundGradientFill, getSlideBackgroundImageBytes, getSlideBackgroundPatternFill, getSlideIndex, getSlideLayout, getSlideLayoutBackground, getSlideLayoutBackgroundGradientFill, getSlideLayoutBackgroundImageBytes, getSlideLayoutBackgroundPatternFill, getSlideLayoutShapes, getSlideMasterBackground, getSlideMasterBackgroundGradientFill, getSlideMasterBackgroundImageBytes, getSlideMasterBackgroundPatternFill, getSlideMasterShapes, getSlideShapes, getSlideSize, getSlides, getTableCellAnchor, getTableCellBorders, getTableCellFill, getTableCellMargins, getTableCellParagraphs, getTableCellSpan, getTableCells, getTableColumnWidths, getTableDimensions, getTableRowHeights, getTableStyleFlags, isChartShape, isParagraphBulletPicture, isShapeImageGrayscale, isShapePlaceholder, isShapeTextBox, isTableShape, resolveDeckBodyTextColor } from "@office-kit/pptx";
|
|
2
|
+
//#region src/emf.ts
|
|
3
|
+
const WORD_BYTES = 4;
|
|
4
|
+
const SHORT_BYTES = 2;
|
|
5
|
+
const RECORD_HEADER_BYTES = 8;
|
|
6
|
+
const HEADER_MIN_BYTES = 88;
|
|
7
|
+
const VALUE_RECORD_BYTES = 12;
|
|
8
|
+
const PAIR_RECORD_BYTES = 16;
|
|
9
|
+
const BOUNDS_RECORD_BYTES = 24;
|
|
10
|
+
const BRUSH_RECORD_BYTES = 24;
|
|
11
|
+
const EOF_MIN_BYTES = 20;
|
|
12
|
+
const HEADER_BOUNDS_OFFSET = 8;
|
|
13
|
+
const HEADER_FILE_SIZE_OFFSET = 48;
|
|
14
|
+
const HEADER_RECORD_COUNT_OFFSET = 52;
|
|
15
|
+
const VALUE_OFFSET = RECORD_HEADER_BYTES;
|
|
16
|
+
const SECOND_VALUE_OFFSET = 12;
|
|
17
|
+
const BRUSH_COLOR_OFFSET = 16;
|
|
18
|
+
const RGB_CHANNEL_COUNT = 3;
|
|
19
|
+
const HEX_RADIX = 16;
|
|
20
|
+
const EMFPLUS_COMMENT = 726027589;
|
|
21
|
+
const SIGNATURE_OFFSET = 40;
|
|
22
|
+
const SIGNATURE = 1179469088;
|
|
23
|
+
const POINT_COUNT_OFFSET = 24;
|
|
24
|
+
const POINTS_OFFSET = 28;
|
|
25
|
+
const BEZIER_POINT_COUNT = 3;
|
|
26
|
+
const STOCK_OBJECT_FLAG = 2147483648;
|
|
27
|
+
const REGION_COPY = 5;
|
|
28
|
+
const MAP_TEXT = 1;
|
|
29
|
+
const MAP_ANISOTROPIC = 8;
|
|
30
|
+
const BRUSH_SOLID = 0;
|
|
31
|
+
const BRUSH_NULL = 1;
|
|
32
|
+
const FILL_ALTERNATE = 1;
|
|
33
|
+
const FILL_WINDING = 2;
|
|
34
|
+
const STOCK_BRUSHES = [
|
|
35
|
+
"#ffffff",
|
|
36
|
+
"#c0c0c0",
|
|
37
|
+
"#808080",
|
|
38
|
+
"#404040",
|
|
39
|
+
"#000000",
|
|
40
|
+
"none"
|
|
41
|
+
];
|
|
42
|
+
const RECORD = {
|
|
43
|
+
HEADER: 1,
|
|
44
|
+
POLYBEZIERTO: 5,
|
|
45
|
+
POLYLINETO: 6,
|
|
46
|
+
SETWINDOWEXTEX: 9,
|
|
47
|
+
SETWINDOWORGEX: 10,
|
|
48
|
+
SETVIEWPORTEXTEX: 11,
|
|
49
|
+
SETVIEWPORTORGEX: 12,
|
|
50
|
+
SETBRUSHORGEX: 13,
|
|
51
|
+
EOF: 14,
|
|
52
|
+
SETMAPMODE: 17,
|
|
53
|
+
SETBKMODE: 18,
|
|
54
|
+
SETPOLYFILLMODE: 19,
|
|
55
|
+
SETSTRETCHBLTMODE: 21,
|
|
56
|
+
SETTEXTALIGN: 22,
|
|
57
|
+
MOVETOEX: 27,
|
|
58
|
+
SELECTOBJECT: 37,
|
|
59
|
+
CREATEBRUSHINDIRECT: 39,
|
|
60
|
+
DELETEOBJECT: 40,
|
|
61
|
+
BEGINPATH: 59,
|
|
62
|
+
ENDPATH: 60,
|
|
63
|
+
CLOSEFIGURE: 61,
|
|
64
|
+
FILLPATH: 62,
|
|
65
|
+
SELECTCLIPPATH: 67,
|
|
66
|
+
COMMENT: 70,
|
|
67
|
+
EXTSELECTCLIPRGN: 75,
|
|
68
|
+
POLYBEZIERTO16: 88,
|
|
69
|
+
POLYLINETO16: 89
|
|
70
|
+
};
|
|
71
|
+
const renderEmfToSvg = (bytes) => {
|
|
72
|
+
if (bytes.byteLength < HEADER_MIN_BYTES) return null;
|
|
73
|
+
const file = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
|
|
74
|
+
if (file.getUint32(0, true) !== RECORD.HEADER || file.getUint32(SIGNATURE_OFFSET, true) !== SIGNATURE) return null;
|
|
75
|
+
const left = file.getInt32(HEADER_BOUNDS_OFFSET, true);
|
|
76
|
+
const top = file.getInt32(12, true);
|
|
77
|
+
const width = file.getInt32(16, true) - left;
|
|
78
|
+
const height = file.getInt32(20, true) - top;
|
|
79
|
+
if (width <= 0 || height <= 0 || file.getUint32(HEADER_FILE_SIZE_OFFSET, true) !== bytes.byteLength) return null;
|
|
80
|
+
let windowX = 0;
|
|
81
|
+
let windowY = 0;
|
|
82
|
+
let windowWidth = 1;
|
|
83
|
+
let windowHeight = 1;
|
|
84
|
+
let viewportX = 0;
|
|
85
|
+
let viewportY = 0;
|
|
86
|
+
let viewportWidth = 1;
|
|
87
|
+
let viewportHeight = 1;
|
|
88
|
+
let mapMode = MAP_TEXT;
|
|
89
|
+
let fillRule = "evenodd";
|
|
90
|
+
let brush = "#ffffff";
|
|
91
|
+
const brushes = /* @__PURE__ */ new Map();
|
|
92
|
+
let path = [];
|
|
93
|
+
let recordingPath = false;
|
|
94
|
+
let clip = "";
|
|
95
|
+
let clipCount = 0;
|
|
96
|
+
const definitions = [];
|
|
97
|
+
const drawings = [];
|
|
98
|
+
let recordCount = 0;
|
|
99
|
+
const point = (x, y) => {
|
|
100
|
+
if (mapMode === MAP_TEXT) return `${x} ${y}`;
|
|
101
|
+
return `${(x - windowX) * viewportWidth / windowWidth + viewportX} ${(y - windowY) * viewportHeight / windowHeight + viewportY}`;
|
|
102
|
+
};
|
|
103
|
+
for (let offset = 0; offset < bytes.byteLength;) {
|
|
104
|
+
if (bytes.byteLength - offset < RECORD_HEADER_BYTES) return null;
|
|
105
|
+
const type = file.getUint32(offset, true);
|
|
106
|
+
const size = file.getUint32(offset + WORD_BYTES, true);
|
|
107
|
+
if (size < RECORD_HEADER_BYTES || size % WORD_BYTES !== 0 || size > bytes.byteLength - offset) return null;
|
|
108
|
+
const record = new DataView(bytes.buffer, bytes.byteOffset + offset, size);
|
|
109
|
+
const unsigned = (at) => record.getUint32(at, true);
|
|
110
|
+
const signed = (at) => record.getInt32(at, true);
|
|
111
|
+
recordCount++;
|
|
112
|
+
switch (type) {
|
|
113
|
+
case RECORD.HEADER:
|
|
114
|
+
if (offset !== 0 || size < HEADER_MIN_BYTES) return null;
|
|
115
|
+
break;
|
|
116
|
+
case RECORD.EOF:
|
|
117
|
+
if (size < EOF_MIN_BYTES || offset + size !== bytes.byteLength || recordingPath || recordCount !== file.getUint32(HEADER_RECORD_COUNT_OFFSET, true)) return null;
|
|
118
|
+
return `<svg xmlns="http://www.w3.org/2000/svg" viewBox="${left} ${top} ${width} ${height}" width="${width}" height="${height}"><defs>${definitions.join("")}</defs>${drawings.join("")}</svg>`;
|
|
119
|
+
case RECORD.SETMAPMODE:
|
|
120
|
+
if (size < VALUE_RECORD_BYTES) return null;
|
|
121
|
+
mapMode = unsigned(VALUE_OFFSET);
|
|
122
|
+
if (mapMode !== MAP_TEXT && mapMode !== MAP_ANISOTROPIC) return null;
|
|
123
|
+
break;
|
|
124
|
+
case RECORD.SETWINDOWORGEX:
|
|
125
|
+
case RECORD.SETVIEWPORTORGEX:
|
|
126
|
+
case RECORD.SETWINDOWEXTEX:
|
|
127
|
+
case RECORD.SETVIEWPORTEXTEX: {
|
|
128
|
+
if (size < PAIR_RECORD_BYTES) return null;
|
|
129
|
+
const x = signed(VALUE_OFFSET);
|
|
130
|
+
const y = signed(SECOND_VALUE_OFFSET);
|
|
131
|
+
if (type === RECORD.SETWINDOWORGEX) {
|
|
132
|
+
windowX = x;
|
|
133
|
+
windowY = y;
|
|
134
|
+
} else if (type === RECORD.SETVIEWPORTORGEX) {
|
|
135
|
+
viewportX = x;
|
|
136
|
+
viewportY = y;
|
|
137
|
+
} else {
|
|
138
|
+
if (x === 0 || y === 0) return null;
|
|
139
|
+
if (type === RECORD.SETWINDOWEXTEX) {
|
|
140
|
+
windowWidth = x;
|
|
141
|
+
windowHeight = y;
|
|
142
|
+
} else {
|
|
143
|
+
viewportWidth = x;
|
|
144
|
+
viewportHeight = y;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
break;
|
|
148
|
+
}
|
|
149
|
+
case RECORD.CREATEBRUSHINDIRECT: {
|
|
150
|
+
if (size < BRUSH_RECORD_BYTES) return null;
|
|
151
|
+
const style = unsigned(SECOND_VALUE_OFFSET);
|
|
152
|
+
if (style !== BRUSH_SOLID && style !== BRUSH_NULL) return null;
|
|
153
|
+
const color = [...bytes.subarray(offset + BRUSH_COLOR_OFFSET, offset + BRUSH_COLOR_OFFSET + RGB_CHANNEL_COUNT)].map((channel) => channel.toString(HEX_RADIX).padStart(2, "0")).join("");
|
|
154
|
+
brushes.set(unsigned(VALUE_OFFSET), style === BRUSH_NULL ? "none" : `#${color}`);
|
|
155
|
+
break;
|
|
156
|
+
}
|
|
157
|
+
case RECORD.SELECTOBJECT: {
|
|
158
|
+
if (size < VALUE_RECORD_BYTES) return null;
|
|
159
|
+
const handle = unsigned(VALUE_OFFSET);
|
|
160
|
+
const selected = handle >= STOCK_OBJECT_FLAG ? STOCK_BRUSHES[handle - STOCK_OBJECT_FLAG] : brushes.get(handle);
|
|
161
|
+
if (selected === void 0) return null;
|
|
162
|
+
brush = selected;
|
|
163
|
+
break;
|
|
164
|
+
}
|
|
165
|
+
case RECORD.DELETEOBJECT:
|
|
166
|
+
if (size < VALUE_RECORD_BYTES) return null;
|
|
167
|
+
brushes.delete(unsigned(VALUE_OFFSET));
|
|
168
|
+
break;
|
|
169
|
+
case RECORD.SETPOLYFILLMODE:
|
|
170
|
+
if (size < VALUE_RECORD_BYTES || unsigned(VALUE_OFFSET) !== FILL_ALTERNATE && unsigned(VALUE_OFFSET) !== FILL_WINDING) return null;
|
|
171
|
+
fillRule = unsigned(VALUE_OFFSET) === FILL_WINDING ? "nonzero" : "evenodd";
|
|
172
|
+
break;
|
|
173
|
+
case RECORD.BEGINPATH:
|
|
174
|
+
path = [];
|
|
175
|
+
recordingPath = true;
|
|
176
|
+
break;
|
|
177
|
+
case RECORD.MOVETOEX:
|
|
178
|
+
if (size < PAIR_RECORD_BYTES || !recordingPath) return null;
|
|
179
|
+
path.push(`M${point(signed(VALUE_OFFSET), signed(SECOND_VALUE_OFFSET))}`);
|
|
180
|
+
break;
|
|
181
|
+
case RECORD.POLYLINETO:
|
|
182
|
+
case RECORD.POLYLINETO16:
|
|
183
|
+
case RECORD.POLYBEZIERTO:
|
|
184
|
+
case RECORD.POLYBEZIERTO16: {
|
|
185
|
+
if (size < POINTS_OFFSET || !recordingPath || path.length === 0) return null;
|
|
186
|
+
const short = type === RECORD.POLYLINETO16 || type === RECORD.POLYBEZIERTO16;
|
|
187
|
+
const bezier = type === RECORD.POLYBEZIERTO || type === RECORD.POLYBEZIERTO16;
|
|
188
|
+
const coordinateBytes = short ? SHORT_BYTES : WORD_BYTES;
|
|
189
|
+
const pointBytes = coordinateBytes * 2;
|
|
190
|
+
const count = unsigned(POINT_COUNT_OFFSET);
|
|
191
|
+
if (count > Math.floor((size - POINTS_OFFSET) / pointBytes) || bezier && count % BEZIER_POINT_COUNT !== 0) return null;
|
|
192
|
+
for (let index = 0; index < count; index++) {
|
|
193
|
+
const at = POINTS_OFFSET + index * pointBytes;
|
|
194
|
+
const x = short ? record.getInt16(at, true) : signed(at);
|
|
195
|
+
const y = short ? record.getInt16(at + coordinateBytes, true) : signed(at + coordinateBytes);
|
|
196
|
+
const command = bezier ? index % BEZIER_POINT_COUNT === 0 ? "C" : " " : "L";
|
|
197
|
+
path.push(`${command}${point(x, y)}`);
|
|
198
|
+
}
|
|
199
|
+
break;
|
|
200
|
+
}
|
|
201
|
+
case RECORD.CLOSEFIGURE:
|
|
202
|
+
if (!recordingPath || path.length === 0) return null;
|
|
203
|
+
path.push("Z");
|
|
204
|
+
break;
|
|
205
|
+
case RECORD.ENDPATH:
|
|
206
|
+
if (!recordingPath) return null;
|
|
207
|
+
recordingPath = false;
|
|
208
|
+
break;
|
|
209
|
+
case RECORD.FILLPATH:
|
|
210
|
+
if (size < BOUNDS_RECORD_BYTES || recordingPath || path.length === 0) return null;
|
|
211
|
+
drawings.push(`<path d="${path.join("")}" fill="${brush}" fill-rule="${fillRule}"${clip}/>`);
|
|
212
|
+
path = [];
|
|
213
|
+
break;
|
|
214
|
+
case RECORD.SELECTCLIPPATH: {
|
|
215
|
+
if (size < VALUE_RECORD_BYTES || unsigned(VALUE_OFFSET) !== REGION_COPY || recordingPath || path.length === 0) return null;
|
|
216
|
+
const id = `emf-clip-${clipCount++}`;
|
|
217
|
+
definitions.push(`<clipPath id="${id}"><path d="${path.join("")}" clip-rule="${fillRule}"/></clipPath>`);
|
|
218
|
+
clip = ` clip-path="url(#${id})"`;
|
|
219
|
+
path = [];
|
|
220
|
+
break;
|
|
221
|
+
}
|
|
222
|
+
case RECORD.EXTSELECTCLIPRGN:
|
|
223
|
+
if (size < PAIR_RECORD_BYTES || unsigned(VALUE_OFFSET) !== 0 || unsigned(SECOND_VALUE_OFFSET) !== REGION_COPY) return null;
|
|
224
|
+
clip = "";
|
|
225
|
+
break;
|
|
226
|
+
case RECORD.COMMENT:
|
|
227
|
+
if (size < VALUE_RECORD_BYTES || unsigned(VALUE_OFFSET) > size - VALUE_RECORD_BYTES) return null;
|
|
228
|
+
if (unsigned(VALUE_OFFSET) >= WORD_BYTES && unsigned(SECOND_VALUE_OFFSET) === EMFPLUS_COMMENT) return null;
|
|
229
|
+
break;
|
|
230
|
+
case RECORD.SETBKMODE:
|
|
231
|
+
case RECORD.SETSTRETCHBLTMODE:
|
|
232
|
+
case RECORD.SETTEXTALIGN:
|
|
233
|
+
if (size < VALUE_RECORD_BYTES) return null;
|
|
234
|
+
break;
|
|
235
|
+
case RECORD.SETBRUSHORGEX:
|
|
236
|
+
if (size < PAIR_RECORD_BYTES) return null;
|
|
237
|
+
break;
|
|
238
|
+
default: return null;
|
|
239
|
+
}
|
|
240
|
+
offset += size;
|
|
241
|
+
}
|
|
242
|
+
return null;
|
|
243
|
+
};
|
|
244
|
+
//#endregion
|
|
2
245
|
//#region src/text-layout.ts
|
|
3
246
|
const SANS = "Carlito";
|
|
4
247
|
const SERIF = "Caladea";
|
|
@@ -32,6 +275,29 @@ const FALLBACK_DESCENT = .22;
|
|
|
32
275
|
const FALLBACK_LINEGAP = .08;
|
|
33
276
|
const BASELINE_LEADING_DROP = .036;
|
|
34
277
|
const GRID_NUDGE_X = -.75;
|
|
278
|
+
const EAST_ASIAN_CHAR = /[ᄀ-ᅟ⺀-〾ぁ-㏿㐀-䶿一-鿿ꀀ-가-힣豈-︰-﹏-⦆¢-₩]/;
|
|
279
|
+
const CLOSING_PUNCT = /^[、。,.・)」』】〉》”’!?:;ー〜…,.!?:;)\]}]+$/;
|
|
280
|
+
const OPENING_PUNCT = /^[(「『【〈《“‘([{]+$/;
|
|
281
|
+
const splitEastAsianBreakables = (word) => {
|
|
282
|
+
if (!EAST_ASIAN_CHAR.test(word)) return [word];
|
|
283
|
+
const parts = [];
|
|
284
|
+
let latin = "";
|
|
285
|
+
for (const ch of word) if (EAST_ASIAN_CHAR.test(ch)) {
|
|
286
|
+
if (latin !== "") {
|
|
287
|
+
parts.push(latin);
|
|
288
|
+
latin = "";
|
|
289
|
+
}
|
|
290
|
+
parts.push(ch);
|
|
291
|
+
} else latin += ch;
|
|
292
|
+
if (latin !== "") parts.push(latin);
|
|
293
|
+
const glued = [];
|
|
294
|
+
for (const part of parts) {
|
|
295
|
+
const prev = glued.at(-1);
|
|
296
|
+
if (prev !== void 0 && (CLOSING_PUNCT.test(part) || OPENING_PUNCT.test(prev))) glued[glued.length - 1] = prev + part;
|
|
297
|
+
else glued.push(part);
|
|
298
|
+
}
|
|
299
|
+
return glued;
|
|
300
|
+
};
|
|
35
301
|
const specOf = (piece) => ({
|
|
36
302
|
family: piece.family,
|
|
37
303
|
sizePx: piece.sizePx,
|
|
@@ -113,23 +379,25 @@ const layoutCore = (input, measure) => {
|
|
|
113
379
|
});
|
|
114
380
|
continue;
|
|
115
381
|
}
|
|
116
|
-
for (const
|
|
117
|
-
const isSpace = /^\s+$/.test(
|
|
118
|
-
const
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
382
|
+
for (const word of piece.text.match(/\s+|\S+/g) ?? []) {
|
|
383
|
+
const isSpace = /^\s+$/.test(word);
|
|
384
|
+
for (const seg of isSpace ? [word] : splitEastAsianBreakables(word)) {
|
|
385
|
+
const w = mWidth(seg, specOf(piece));
|
|
386
|
+
if (input.wrap && !isSpace && w > avail - bulletLead && [...seg].length > 1) for (const ch of seg) tokens.push({
|
|
387
|
+
text: ch,
|
|
388
|
+
piece,
|
|
389
|
+
isSpace: false,
|
|
390
|
+
isBreak: false,
|
|
391
|
+
width: mWidth(ch, specOf(piece))
|
|
392
|
+
});
|
|
393
|
+
else tokens.push({
|
|
394
|
+
text: seg,
|
|
395
|
+
piece,
|
|
396
|
+
isSpace,
|
|
397
|
+
isBreak: false,
|
|
398
|
+
width: w
|
|
399
|
+
});
|
|
400
|
+
}
|
|
133
401
|
}
|
|
134
402
|
}
|
|
135
403
|
const wrapped = wrapTokens(tokens, input.wrap, wrapRight - firstLeft - bulletLead, avail);
|
|
@@ -505,6 +773,13 @@ const mimeFromPartName = (name) => {
|
|
|
505
773
|
const renderPicture = (shape, pres, x, y, w, h, transform, textOverlay, bytes, format) => {
|
|
506
774
|
let mime = null;
|
|
507
775
|
if (bytes && format) mime = imageMime[format] ?? null;
|
|
776
|
+
if (bytes && !mime) {
|
|
777
|
+
const svg = renderEmfToSvg(bytes);
|
|
778
|
+
if (svg !== null) {
|
|
779
|
+
bytes = new TextEncoder().encode(svg);
|
|
780
|
+
mime = "image/svg+xml";
|
|
781
|
+
}
|
|
782
|
+
}
|
|
508
783
|
if (bytes && !mime) mime = mimeFromPartName(getShapeImagePartName(shape));
|
|
509
784
|
if (bytes && mime) {
|
|
510
785
|
const dataUrl = `data:${mime};base64,${u8ToBase64(bytes)}`;
|
|
@@ -644,7 +919,8 @@ let activeColorMap = null;
|
|
|
644
919
|
let activeDeckTextColor = "#000000";
|
|
645
920
|
const gradientDef = (grad, theme) => {
|
|
646
921
|
const id = mintId();
|
|
647
|
-
const
|
|
922
|
+
const orderedStops = [...grad.stops].sort((a, b) => a.offset - b.offset);
|
|
923
|
+
const stops = orderedStops.map((s) => `<stop offset="${s.offset.toFixed(4)}" stop-color="${resolveColor(s.color, theme, "#E5E7EB")}"/>`).join("");
|
|
648
924
|
if (grad.path === "circle" || grad.path === "rect" || grad.path === "shape") {
|
|
649
925
|
const focus = grad.focus ?? {
|
|
650
926
|
left: .5,
|
|
@@ -654,7 +930,7 @@ const gradientDef = (grad, theme) => {
|
|
|
654
930
|
};
|
|
655
931
|
const cx = (focus.left + focus.right) / 2;
|
|
656
932
|
const cy = (focus.top + focus.bottom) / 2;
|
|
657
|
-
const reversed =
|
|
933
|
+
const reversed = orderedStops.slice().reverse().map((s) => `<stop offset="${(1 - s.offset).toFixed(4)}" stop-color="${resolveColor(s.color, theme, "#E5E7EB")}"/>`).join("");
|
|
658
934
|
return {
|
|
659
935
|
defs: `<defs><radialGradient id="${id}" gradientUnits="objectBoundingBox" cx="${cx.toFixed(4)}" cy="${cy.toFixed(4)}" r="${Math.max(.5, Math.max(cx, cy, 1 - cx, 1 - cy)).toFixed(4)}">${reversed}</radialGradient></defs>`,
|
|
660
936
|
fillAttr: `url(#${id})`
|
|
@@ -2772,6 +3048,12 @@ const renderChartTitle = (f, title, style) => {
|
|
|
2772
3048
|
const fontStyleAttr = style?.italic ? " font-style=\"italic\"" : "";
|
|
2773
3049
|
return `<text x="${px(f.x + f.w / 2)}" y="${px(f.titleY)}" text-anchor="middle" dominant-baseline="middle" font-family="sans-serif" font-size="${chartFontPx(sz)}" fill="${fill}" font-weight="${weight}"${fontStyleAttr}>${escapeXml(title)}</text>`;
|
|
2774
3050
|
};
|
|
3051
|
+
const FULLWIDTH_CHAR_PATTERN = /[\u1100-\u115F\u2E80-\u303E\u3041-\u33FF\u3400-\u4DBF\u4E00-\u9FFF\uA000-\uA4CF\uAC00-\uD7A3\uF900-\uFAFF\uFE30-\uFE4F\uFF00-\uFF60\uFFE0-\uFFE6]/u;
|
|
3052
|
+
const approxLegendTextPx = (text, fontPx) => {
|
|
3053
|
+
let units = 0;
|
|
3054
|
+
for (const ch of text) units += FULLWIDTH_CHAR_PATTERN.test(ch) ? 1 : .55;
|
|
3055
|
+
return units * fontPx;
|
|
3056
|
+
};
|
|
2775
3057
|
const renderChartLegend = (f, names, colors, position = "b", textStyle, markerSymbols) => {
|
|
2776
3058
|
if (names.length === 0) return "";
|
|
2777
3059
|
const sz = textStyle?.sizePt ?? 11;
|
|
@@ -2789,33 +3071,27 @@ const renderChartLegend = (f, names, colors, position = "b", textStyle, markerSy
|
|
|
2789
3071
|
return `<rect x="${px(swatchX)}" y="${px(swatchY)}" width="9" height="9" fill="${color}"/>`;
|
|
2790
3072
|
};
|
|
2791
3073
|
const out = [];
|
|
2792
|
-
if (position === "b") {
|
|
2793
|
-
const
|
|
2794
|
-
const
|
|
2795
|
-
const
|
|
2796
|
-
|
|
2797
|
-
|
|
2798
|
-
|
|
2799
|
-
|
|
2800
|
-
|
|
2801
|
-
}
|
|
2802
|
-
return out.join("");
|
|
2803
|
-
}
|
|
2804
|
-
if (position === "t") {
|
|
2805
|
-
const itemPx = Math.min(140, f.w / names.length);
|
|
2806
|
-
const totalW = itemPx * names.length;
|
|
2807
|
-
const startX = f.x + (f.w - totalW) / 2;
|
|
2808
|
-
const yTop = f.y + 4;
|
|
3074
|
+
if (position === "b" || position === "t") {
|
|
3075
|
+
const swatchGapPx = 14;
|
|
3076
|
+
const itemGapPx = 12;
|
|
3077
|
+
const labelWidths = names.map((name, i) => approxLegendTextPx(name ?? `Series ${i + 1}`, sz * PX_PER_PT));
|
|
3078
|
+
const naturalTotal = labelWidths.reduce((sum, w) => sum + swatchGapPx + w, 0) + itemGapPx * (names.length - 1);
|
|
3079
|
+
const scale = Math.min(1, f.w / Math.max(1, naturalTotal));
|
|
3080
|
+
const effAttrs = scale < 1 ? `font-family="sans-serif" font-size="${chartFontPx(sz * scale)}" fill="${fill}"${weight}${italic}` : textAttrs;
|
|
3081
|
+
const rowY = position === "b" ? f.legendY : f.y + 12;
|
|
3082
|
+
let cursor = f.x + Math.max(0, (f.w - naturalTotal * scale) / 2);
|
|
2809
3083
|
for (let i = 0; i < names.length; i++) {
|
|
2810
|
-
|
|
2811
|
-
|
|
3084
|
+
out.push(swatch(i, cursor, rowY - 4), `<text x="${px(cursor + swatchGapPx * scale)}" y="${px(rowY)}" dominant-baseline="middle" ${effAttrs}>${escapeXml(names[i] ?? `Series ${i + 1}`)}</text>`);
|
|
3085
|
+
cursor += (swatchGapPx + (labelWidths[i] ?? 0) + itemGapPx) * scale;
|
|
2812
3086
|
}
|
|
2813
3087
|
return out.join("");
|
|
2814
3088
|
}
|
|
2815
3089
|
const lineH = 14;
|
|
2816
3090
|
const totalH = names.length * lineH;
|
|
2817
3091
|
const yStart = position === "tr" ? f.y + 12 : Math.max(f.y + 12, f.y + (f.h - totalH) / 2);
|
|
2818
|
-
const
|
|
3092
|
+
const maxLabelPx = Math.max(0, ...names.map((name, i) => approxLegendTextPx(name ?? `Series ${i + 1}`, sz * PX_PER_PT)));
|
|
3093
|
+
const rightColW = Math.min(f.w * .45, 14 + maxLabelPx + 4);
|
|
3094
|
+
const xCol = position === "l" ? f.x + 6 : f.x + f.w - rightColW;
|
|
2819
3095
|
for (let i = 0; i < names.length; i++) {
|
|
2820
3096
|
const yp = yStart + i * lineH;
|
|
2821
3097
|
out.push(swatch(i, xCol, yp - 4), `<text x="${px(xCol + 14)}" y="${px(yp + 4)}" dominant-baseline="middle" ${textAttrs}>${escapeXml(names[i] ?? `Series ${i + 1}`)}</text>`);
|
|
@@ -4660,4 +4936,4 @@ const auditTextLayout = (pres, options = {}) => {
|
|
|
4660
4936
|
//#endregion
|
|
4661
4937
|
export { MONO as a, TIMES as c, substituteFamily as d, AVG_GLYPH_W_RATIO as i, defaultMeasurer as l, renderSlideSvg as n, SANS as o, ARIAL as r, SERIF as s, auditTextLayout as t, isCjk as u };
|
|
4662
4938
|
|
|
4663
|
-
//# sourceMappingURL=src-
|
|
4939
|
+
//# sourceMappingURL=src-CXGhih2x.js.map
|