@office-kit/pptx-preview 0.11.0 → 0.13.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/README.md +42 -0
- package/dist/index-DwLYANVE.d.ts +249 -0
- package/dist/index.d.ts +2 -2
- package/dist/index.js +2 -2
- package/dist/node.d.ts +2 -2
- package/dist/node.js +23 -3
- package/dist/node.js.map +1 -1
- package/dist/{src-DC6HxO-i.js → src-DYfIZIh8.js} +1979 -608
- package/dist/src-DYfIZIh8.js.map +1 -0
- package/package.json +3 -3
- package/dist/index-B6ISDjnu.d.ts +0 -93
- package/dist/src-DC6HxO-i.js.map +0 -1
|
@@ -1,4 +1,255 @@
|
|
|
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, getShapeFillOpacity, getShapeFlip, getShapeGradientFill, getShapeGradientFillEffective,
|
|
1
|
+
import { getEffectiveColorMap, getGroupChildren, getGroupTransform, getParagraphAlignment, getParagraphBullet, getParagraphBulletImageBytes, getParagraphBulletStyle, getParagraphIndent, getParagraphLevel, getParagraphPropertiesEffective, getParagraphSpacing, getPresentationFirstSlideNumber, getPresentationFonts, getPresentationTheme, getShapeAdjustValues, getShapeAltTitle, getShapeBodyPrEffective, getShapeBounds, getShapeBoundsResolved, getShapeChartSpec, getShapeClickAction, getShapeCustomGeometry, getShapeDescription, getShapeEffectsEffective, getShapeFillColorResolved, getShapeFillEffective, getShapeFillOpacity, getShapeFlip, getShapeGradientFill, getShapeGradientFillEffective, getShapeHyperlinkTooltip, getShapeId, getShapeImageBiLevelThreshold, getShapeImageBrightness, getShapeImageBytes, getShapeImageContrast, getShapeImageCrop, getShapeImageDuotone, getShapeImageFillBytes, getShapeImageFillLayout, getShapeImageFormat, getShapeImageIntrinsicSize, getShapeImageLinkUrl, getShapeImageOpacity, getShapeImagePartName, getShapeKind, getShapeName, getShapeParagraphCount, getShapeParagraphElements, getShapePatternFill, getShapePlaceholderType, getShapePreset, getShapeRotation, getShapeRunClickAction, getShapeRunFormat, getShapeRunFormatEffective, getShapeRunHyperlink, getShapeRunHyperlinkTooltip, getShapeStrokeArrow, getShapeStrokeCap, getShapeStrokeColorResolved, getShapeStrokeCompound, getShapeStrokeDash, getShapeStrokeEffective, getShapeStrokeJoin, getShapeStrokeOpacity, getShapeTextAnchor, getShapeTextAutoFit, getShapeTextAutoFitParams, getShapeTextBodyRotationDeg, getShapeTextColumns, getShapeTextDirection, getShapeTextMargins, getShapeXmlString, getSlideBackground, getSlideBackgroundGradientFill, getSlideBackgroundImageBytes, getSlideBackgroundImageCrop, getSlideBackgroundImageFillLayout, getSlideBackgroundImageIntrinsicSize, getSlideBackgroundImageOpacity, getSlideBackgroundPatternFill, getSlideIndex, getSlideLayout, getSlideLayoutBackground, getSlideLayoutBackgroundGradientFill, getSlideLayoutBackgroundImageBytes, getSlideLayoutBackgroundPatternFill, getSlideLayoutShapes, getSlideMasterBackground, getSlideMasterBackgroundGradientFill, getSlideMasterBackgroundImageBytes, getSlideMasterBackgroundPatternFill, getSlideMasterShapes, getSlideShapes, getSlideSize, getSlides, getTableCellAnchor, getTableCellAppearanceEffective, getTableCellMargins, getTableCellParagraphs, getTableCellRunFormatEffective, getTableCellSpan, getTableCellTextDirection, getTableCells, getTableColumnWidths, getTableDimensions, getTableRowHeights, isChartShape, isParagraphBulletPicture, isShapeHidden, isShapeImageGrayscale, isShapePlaceholder, isShapeTextBox, isSlideBackgroundGraphicsHidden, isSlideLayoutBackgroundGraphicsHidden, isTableShape, resolveDeckBodyTextColor } from "@office-kit/pptx";
|
|
2
|
+
//#region src/text-body-rect.ts
|
|
3
|
+
const presetTextRect = (preset) => {
|
|
4
|
+
switch (preset) {
|
|
5
|
+
case "triangle": return {
|
|
6
|
+
l: .25,
|
|
7
|
+
t: .5,
|
|
8
|
+
r: .75,
|
|
9
|
+
b: 1
|
|
10
|
+
};
|
|
11
|
+
case "diamond": return {
|
|
12
|
+
l: .25,
|
|
13
|
+
t: .25,
|
|
14
|
+
r: .75,
|
|
15
|
+
b: .75
|
|
16
|
+
};
|
|
17
|
+
case "pentagon": return {
|
|
18
|
+
l: .191,
|
|
19
|
+
t: .236,
|
|
20
|
+
r: .809,
|
|
21
|
+
b: 1
|
|
22
|
+
};
|
|
23
|
+
case "star5": return {
|
|
24
|
+
l: .309,
|
|
25
|
+
t: .382,
|
|
26
|
+
r: .691,
|
|
27
|
+
b: .764
|
|
28
|
+
};
|
|
29
|
+
case "leftRightArrow": return {
|
|
30
|
+
l: .18,
|
|
31
|
+
t: .35,
|
|
32
|
+
r: .82,
|
|
33
|
+
b: .65
|
|
34
|
+
};
|
|
35
|
+
default: return null;
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
/**
|
|
39
|
+
* The text rectangle a custom-geometry shape states for itself, as fractions
|
|
40
|
+
* of its own extent, or `null` for a preset shape and for a custom one that
|
|
41
|
+
* states none. `<a:rect>` is written in EMU against the shape's extents, so
|
|
42
|
+
* dividing by them here is what lets the result travel with a shape that a
|
|
43
|
+
* group has scaled.
|
|
44
|
+
*/
|
|
45
|
+
function shapeCustomTextRect(custom, extent) {
|
|
46
|
+
const rect = custom?.textRect;
|
|
47
|
+
if (!rect || extent === null || extent.w <= 0 || extent.h <= 0) return null;
|
|
48
|
+
return {
|
|
49
|
+
l: rect.l / extent.w,
|
|
50
|
+
t: rect.t / extent.h,
|
|
51
|
+
r: rect.r / extent.w,
|
|
52
|
+
b: rect.b / extent.h
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Text layout rectangle used by the preview, including preset geometry and body
|
|
57
|
+
* insets. All coordinates and margins must use the same unit (normally EMU).
|
|
58
|
+
* Preset regions follow the preview's current geometry approximations. When
|
|
59
|
+
* margins collapse a preset region, retain that region without margins.
|
|
60
|
+
*
|
|
61
|
+
* A custom-geometry shape states its own rectangle in `<a:custGeom><a:rect>`.
|
|
62
|
+
* Pass it as `custom`, as fractions of the shape's extent so that it scales
|
|
63
|
+
* with `bounds` the way a preset region does, and it wins: unlike the table
|
|
64
|
+
* above it is not an approximation of anything — it is what the file says.
|
|
65
|
+
*/
|
|
66
|
+
function resolveTextBodyRect(preset, bounds, margins, custom) {
|
|
67
|
+
const region = custom ?? presetTextRect(preset);
|
|
68
|
+
const x = bounds.x + (region?.l ?? 0) * bounds.w;
|
|
69
|
+
const y = bounds.y + (region?.t ?? 0) * bounds.h;
|
|
70
|
+
const w = ((region?.r ?? 1) - (region?.l ?? 0)) * bounds.w;
|
|
71
|
+
const h = ((region?.b ?? 1) - (region?.t ?? 0)) * bounds.h;
|
|
72
|
+
const innerW = w - margins.left - margins.right;
|
|
73
|
+
const innerH = h - margins.top - margins.bottom;
|
|
74
|
+
if (region && (innerW <= 0 || innerH <= 0)) return {
|
|
75
|
+
x,
|
|
76
|
+
y,
|
|
77
|
+
w,
|
|
78
|
+
h
|
|
79
|
+
};
|
|
80
|
+
return {
|
|
81
|
+
x: x + margins.left,
|
|
82
|
+
y: y + margins.top,
|
|
83
|
+
w: innerW,
|
|
84
|
+
h: innerH
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
//#endregion
|
|
88
|
+
//#region src/text-body-style.ts
|
|
89
|
+
/** Mac PowerPoint uses zero spacing when `spcCol` is absent. */
|
|
90
|
+
const DEFAULT_COLUMN_GAP_PX = 0;
|
|
91
|
+
const EMU_PER_PX$2 = 9525;
|
|
92
|
+
const NONE = {
|
|
93
|
+
declarations: "",
|
|
94
|
+
transform: ""
|
|
95
|
+
};
|
|
96
|
+
/**
|
|
97
|
+
* `<a:bodyPr vert="…"/>` as CSS. `writing-mode` is the right primitive for
|
|
98
|
+
* the East-Asian and Mongolian cases, and `text-orientation` for the WordArt
|
|
99
|
+
* ones, which stack upright glyphs. `vert270` reads bottom-to-top, which no
|
|
100
|
+
* writing-mode expresses on its own — hence the half turn. Its columns still
|
|
101
|
+
* follow the OOXML vertical-rl progression before that half turn; using
|
|
102
|
+
* vertical-lr here reverses the column order relative to the SVG renderer.
|
|
103
|
+
*/
|
|
104
|
+
function verticalTextStyle(vert) {
|
|
105
|
+
switch (vert) {
|
|
106
|
+
case "vert":
|
|
107
|
+
case "eaVert": return {
|
|
108
|
+
declarations: "writing-mode:vertical-rl",
|
|
109
|
+
transform: ""
|
|
110
|
+
};
|
|
111
|
+
case "mongolianVert": return {
|
|
112
|
+
declarations: "writing-mode:vertical-lr",
|
|
113
|
+
transform: ""
|
|
114
|
+
};
|
|
115
|
+
case "vert270": return {
|
|
116
|
+
declarations: "writing-mode:vertical-rl",
|
|
117
|
+
transform: "rotate(180deg)"
|
|
118
|
+
};
|
|
119
|
+
case "wordArtVert": return {
|
|
120
|
+
declarations: "writing-mode:vertical-rl;text-orientation:upright",
|
|
121
|
+
transform: ""
|
|
122
|
+
};
|
|
123
|
+
case "wordArtVertRtl": return {
|
|
124
|
+
declarations: "writing-mode:vertical-rl;text-orientation:upright;direction:rtl",
|
|
125
|
+
transform: ""
|
|
126
|
+
};
|
|
127
|
+
case null: return NONE;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* `<a:bodyPr numCol="N" spcCol="EMU"/>` as CSS declarations, without a
|
|
132
|
+
* trailing `;`. Empty below two columns, which is the same as not splitting
|
|
133
|
+
* the body.
|
|
134
|
+
*/
|
|
135
|
+
function textColumnsStyle(cols) {
|
|
136
|
+
if (!cols || cols.count < 2) return "";
|
|
137
|
+
const gapPx = cols.gapEmu !== void 0 ? (cols.gapEmu / EMU_PER_PX$2).toFixed(2) : DEFAULT_COLUMN_GAP_PX;
|
|
138
|
+
return `column-count:${cols.count};column-gap:${gapPx}px`;
|
|
139
|
+
}
|
|
140
|
+
//#endregion
|
|
141
|
+
//#region src/text-underline-style.ts
|
|
142
|
+
const escapeXml$2 = (value) => value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll("\"", """).replaceAll("'", "'");
|
|
143
|
+
/**
|
|
144
|
+
* DrawingML underline as CSS for an inline decoration box. Pass a resolved
|
|
145
|
+
* CSS color for image-based patterns. For `words`, apply only to non-space
|
|
146
|
+
* text segments; keep strikethrough on a separate outer box.
|
|
147
|
+
*/
|
|
148
|
+
function textUnderlineStyle(underline, color, includeDecorationColor = true) {
|
|
149
|
+
if (underline === void 0 || underline === false || underline === "none") return "";
|
|
150
|
+
const heavy = underline === "heavy" || String(underline).endsWith("Heavy");
|
|
151
|
+
const thickness = heavy ? ";text-decoration-thickness:0.1em" : "";
|
|
152
|
+
const decorationColor = includeDecorationColor ? `;text-decoration-color:${escapeXml$2(color)}` : "";
|
|
153
|
+
if (underline === "dbl") return `text-decoration:underline;text-decoration-style:double${decorationColor}`;
|
|
154
|
+
if (underline === "wavy" || underline === "wavyHeavy") return `text-decoration:underline;text-decoration-style:wavy${decorationColor}${thickness}`;
|
|
155
|
+
if (!(typeof underline === "string" && (underline.startsWith("dash") || underline.startsWith("dotted") || underline.startsWith("dotDash") || underline.startsWith("dotDotDash") || underline === "wavyDbl"))) return `text-decoration:underline${decorationColor}${thickness}`;
|
|
156
|
+
const stroke = heavy ? 2 : 1;
|
|
157
|
+
const dash = underline.startsWith("dotted") ? "1 2" : underline === "dash" || underline === "dashHeavy" ? "4 2" : underline.startsWith("dashLong") ? "8 3" : underline.startsWith("dotDotDash") ? "5 2 1 2 1 2" : "5 2 1 2";
|
|
158
|
+
const width = underline === "wavyDbl" ? 8 : underline.startsWith("dotted") ? 3 : underline === "dash" || underline === "dashHeavy" ? 6 : underline.startsWith("dashLong") ? 11 : underline.startsWith("dotDotDash") ? 13 : 10;
|
|
159
|
+
const path = underline === "wavyDbl" ? "<path d=\"M0 1 Q2 -1 4 1 T8 1 M0 4 Q2 2 4 4 T8 4\"/>" : `<path d="M0 3 H${width}" stroke-dasharray="${dash}"/>`;
|
|
160
|
+
const svg = `<svg xmlns="http://www.w3.org/2000/svg" width="${width}" height="6" viewBox="0 0 ${width} 6"><g fill="none" stroke="${escapeXml$2(color)}" stroke-width="${stroke}">${path}</g></svg>`;
|
|
161
|
+
return `text-decoration:none;background-image:url('data:image/svg+xml,${encodeURIComponent(svg).replaceAll("'", "%27")}');background-repeat:repeat-x;background-size:${width / 20}em 0.3em;background-position:0 100%;box-decoration-break:clone;-webkit-box-decoration-break:clone`;
|
|
162
|
+
}
|
|
163
|
+
//#endregion
|
|
164
|
+
//#region src/paragraph-number-labels.ts
|
|
165
|
+
const bulletAutoNumType = (style) => {
|
|
166
|
+
if (style === "number") return "arabicPeriod";
|
|
167
|
+
if (style !== null && typeof style === "object" && "autoNum" in style) return style.autoNum ?? null;
|
|
168
|
+
return null;
|
|
169
|
+
};
|
|
170
|
+
const toRoman = (n) => {
|
|
171
|
+
if (n <= 0) return String(n);
|
|
172
|
+
const map = [
|
|
173
|
+
[1e3, "M"],
|
|
174
|
+
[900, "CM"],
|
|
175
|
+
[500, "D"],
|
|
176
|
+
[400, "CD"],
|
|
177
|
+
[100, "C"],
|
|
178
|
+
[90, "XC"],
|
|
179
|
+
[50, "L"],
|
|
180
|
+
[40, "XL"],
|
|
181
|
+
[10, "X"],
|
|
182
|
+
[9, "IX"],
|
|
183
|
+
[5, "V"],
|
|
184
|
+
[4, "IV"],
|
|
185
|
+
[1, "I"]
|
|
186
|
+
];
|
|
187
|
+
let out = "";
|
|
188
|
+
let r = n;
|
|
189
|
+
for (const [v, s] of map) while (r >= v) {
|
|
190
|
+
out += s;
|
|
191
|
+
r -= v;
|
|
192
|
+
}
|
|
193
|
+
return out;
|
|
194
|
+
};
|
|
195
|
+
const toAlpha = (n) => {
|
|
196
|
+
if (n <= 0) return String(n);
|
|
197
|
+
let r = n;
|
|
198
|
+
let out = "";
|
|
199
|
+
while (r > 0) {
|
|
200
|
+
r -= 1;
|
|
201
|
+
out = String.fromCharCode(65 + r % 26) + out;
|
|
202
|
+
r = Math.floor(r / 26);
|
|
203
|
+
}
|
|
204
|
+
return out;
|
|
205
|
+
};
|
|
206
|
+
const formatAutoNum = (token, n) => {
|
|
207
|
+
const arabic = String(n);
|
|
208
|
+
switch (token) {
|
|
209
|
+
case "arabicPlain": return arabic;
|
|
210
|
+
case "arabicPeriod": return `${arabic}.`;
|
|
211
|
+
case "arabicParenR": return `${arabic})`;
|
|
212
|
+
case "arabicParenBoth": return `(${arabic})`;
|
|
213
|
+
case "romanUcPeriod": return `${toRoman(n)}.`;
|
|
214
|
+
case "romanLcPeriod": return `${toRoman(n).toLowerCase()}.`;
|
|
215
|
+
case "romanUcParenR": return `${toRoman(n)})`;
|
|
216
|
+
case "romanLcParenR": return `${toRoman(n).toLowerCase()})`;
|
|
217
|
+
case "romanUcParenBoth": return `(${toRoman(n)})`;
|
|
218
|
+
case "romanLcParenBoth": return `(${toRoman(n).toLowerCase()})`;
|
|
219
|
+
case "alphaUcPeriod": return `${toAlpha(n)}.`;
|
|
220
|
+
case "alphaLcPeriod": return `${toAlpha(n).toLowerCase()}.`;
|
|
221
|
+
case "alphaUcParenR": return `${toAlpha(n)})`;
|
|
222
|
+
case "alphaLcParenR": return `${toAlpha(n).toLowerCase()})`;
|
|
223
|
+
case "alphaUcParenBoth": return `(${toAlpha(n)})`;
|
|
224
|
+
case "alphaLcParenBoth": return `(${toAlpha(n).toLowerCase()})`;
|
|
225
|
+
default: return `${arabic}.`;
|
|
226
|
+
}
|
|
227
|
+
};
|
|
228
|
+
/** Resolve list numbering consistently for rendered slides and editing surfaces. */
|
|
229
|
+
const paragraphNumberLabels = (paraData) => {
|
|
230
|
+
const numberLabels = Array.from({ length: paraData.length }, () => null);
|
|
231
|
+
{
|
|
232
|
+
const counters = [];
|
|
233
|
+
const types = [];
|
|
234
|
+
for (let i = 0; i < paraData.length; i++) {
|
|
235
|
+
const para = paraData[i];
|
|
236
|
+
const num = bulletAutoNumType(para.bulletStyle);
|
|
237
|
+
const level = Math.max(0, para.level);
|
|
238
|
+
for (let l = num === null ? level : level + 1; l < counters.length; l++) {
|
|
239
|
+
counters[l] = 0;
|
|
240
|
+
types[l] = null;
|
|
241
|
+
}
|
|
242
|
+
if (num === null) continue;
|
|
243
|
+
if (types[level] !== num) {
|
|
244
|
+
counters[level] = 1;
|
|
245
|
+
types[level] = num;
|
|
246
|
+
} else counters[level] = (counters[level] ?? 0) + 1;
|
|
247
|
+
numberLabels[i] = formatAutoNum(num, counters[level]);
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
return numberLabels;
|
|
251
|
+
};
|
|
252
|
+
//#endregion
|
|
2
253
|
//#region src/emf.ts
|
|
3
254
|
const WORD_BYTES = 4;
|
|
4
255
|
const SHORT_BYTES = 2;
|
|
@@ -275,6 +526,8 @@ const FALLBACK_DESCENT = .22;
|
|
|
275
526
|
const FALLBACK_LINEGAP = .08;
|
|
276
527
|
const BASELINE_LEADING_DROP = .036;
|
|
277
528
|
const GRID_NUDGE_X = -.75;
|
|
529
|
+
const EMU_PER_PX$1 = 9525;
|
|
530
|
+
let reflectionNamespace = 0;
|
|
278
531
|
const EAST_ASIAN_CHAR = /[ᄀ-ᅟ⺀-〾ぁ-㏿㐀-䶿一-鿿ꀀ-가-힣豈-︰-﹏-⦆¢-₩]/;
|
|
279
532
|
const CLOSING_PUNCT = /^[、。,.・)」』】〉》”’!?:;ー〜…,.!?:;)\]}]+$/;
|
|
280
533
|
const OPENING_PUNCT = /^[(「『【〈《“‘([{]+$/;
|
|
@@ -303,14 +556,16 @@ const specOf = (piece) => ({
|
|
|
303
556
|
sizePx: piece.sizePx,
|
|
304
557
|
bold: piece.bold,
|
|
305
558
|
italic: piece.italic,
|
|
306
|
-
letterSpacingPx: piece.letterSpacingPx
|
|
559
|
+
letterSpacingPx: piece.letterSpacingPx,
|
|
560
|
+
kerning: piece.kerning ?? true
|
|
307
561
|
});
|
|
308
562
|
const bulletSpec = (b) => ({
|
|
309
563
|
family: b.family,
|
|
310
564
|
sizePx: b.sizePx,
|
|
311
565
|
bold: false,
|
|
312
566
|
italic: false,
|
|
313
|
-
letterSpacingPx: 0
|
|
567
|
+
letterSpacingPx: 0,
|
|
568
|
+
kerning: true
|
|
314
569
|
});
|
|
315
570
|
const escapeXml$1 = (s) => s.replace(/[&<>"]/g, (c) => ({
|
|
316
571
|
"&": "&",
|
|
@@ -325,7 +580,7 @@ const fmt = (n) => {
|
|
|
325
580
|
const layoutCore = (input, measure) => {
|
|
326
581
|
const widthCache = /* @__PURE__ */ new Map();
|
|
327
582
|
const metricCache = /* @__PURE__ */ new Map();
|
|
328
|
-
const key = (text, s) => `${s.family}|${s.sizePx}|${s.bold}|${s.italic}|${s.letterSpacingPx}|${text}`;
|
|
583
|
+
const key = (text, s) => `${s.family}|${s.sizePx}|${s.bold}|${s.italic}|${s.letterSpacingPx}|${s.kerning ?? true}|${text}`;
|
|
329
584
|
const mWidth = (text, s) => {
|
|
330
585
|
const k = key(text, s);
|
|
331
586
|
let w = widthCache.get(k);
|
|
@@ -368,8 +623,49 @@ const layoutCore = (input, measure) => {
|
|
|
368
623
|
const bulletLead = bullet ? bullet.imageHref ? bullet.sizePx + mWidth(" ", bulletSpec(bullet)) : mWidth(`${bullet.text} `, bulletSpec(bullet)) : 0;
|
|
369
624
|
const avail = Math.max(1, wrapRight - wrapLeft);
|
|
370
625
|
const tokens = [];
|
|
626
|
+
let wordParts = [];
|
|
627
|
+
const makeToken = (text, piece, continuesSegment = false) => ({
|
|
628
|
+
text,
|
|
629
|
+
piece,
|
|
630
|
+
isSpace: false,
|
|
631
|
+
isBreak: false,
|
|
632
|
+
continuesSegment,
|
|
633
|
+
width: mWidth(text, {
|
|
634
|
+
...specOf(piece),
|
|
635
|
+
sizePx: renderedSizePxOf(piece)
|
|
636
|
+
})
|
|
637
|
+
});
|
|
638
|
+
const flushWord = () => {
|
|
639
|
+
if (wordParts.length === 0) return;
|
|
640
|
+
const word = wordParts.map((part) => part.text).join("");
|
|
641
|
+
let partIndex = 0;
|
|
642
|
+
let partOffset = 0;
|
|
643
|
+
for (const segment of splitEastAsianBreakables(word)) {
|
|
644
|
+
const fragments = [];
|
|
645
|
+
let remaining = segment.length;
|
|
646
|
+
let width = 0;
|
|
647
|
+
while (remaining > 0) {
|
|
648
|
+
const part = wordParts[partIndex];
|
|
649
|
+
const length = Math.min(remaining, part.text.length - partOffset);
|
|
650
|
+
const token = makeToken(part.text.slice(partOffset, partOffset + length), part.piece, fragments.length > 0);
|
|
651
|
+
fragments.push(token);
|
|
652
|
+
width += token.width;
|
|
653
|
+
remaining -= length;
|
|
654
|
+
partOffset += length;
|
|
655
|
+
if (partOffset === part.text.length) {
|
|
656
|
+
partIndex++;
|
|
657
|
+
partOffset = 0;
|
|
658
|
+
}
|
|
659
|
+
}
|
|
660
|
+
const canBreak = EAST_ASIAN_CHAR.test(segment) ? width > avail - bulletLead : para.latinLineBreak === true || width > avail - bulletLead;
|
|
661
|
+
if (input.wrap && canBreak && [...segment].length > 1) for (const fragment of fragments) for (const ch of fragment.text) tokens.push(makeToken(ch, fragment.piece));
|
|
662
|
+
else for (const fragment of fragments) tokens.push(fragment);
|
|
663
|
+
}
|
|
664
|
+
wordParts = [];
|
|
665
|
+
};
|
|
371
666
|
for (const piece of para.pieces) {
|
|
372
667
|
if (piece.isBreak) {
|
|
668
|
+
flushWord();
|
|
373
669
|
tokens.push({
|
|
374
670
|
text: "",
|
|
375
671
|
piece,
|
|
@@ -379,28 +675,51 @@ const layoutCore = (input, measure) => {
|
|
|
379
675
|
});
|
|
380
676
|
continue;
|
|
381
677
|
}
|
|
382
|
-
for (const word of piece.text.match(/\
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
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
|
|
678
|
+
for (const word of piece.text.match(/\t|[^\S\t]+|\S+/g) ?? []) {
|
|
679
|
+
if (!/^\s+$/.test(word)) {
|
|
680
|
+
wordParts.push({
|
|
681
|
+
text: word,
|
|
682
|
+
piece
|
|
399
683
|
});
|
|
684
|
+
continue;
|
|
685
|
+
}
|
|
686
|
+
flushWord();
|
|
687
|
+
if (word === " ") tokens.push({
|
|
688
|
+
text: "",
|
|
689
|
+
piece,
|
|
690
|
+
isSpace: true,
|
|
691
|
+
isBreak: false,
|
|
692
|
+
isTab: true,
|
|
693
|
+
width: 0
|
|
694
|
+
});
|
|
695
|
+
else tokens.push({
|
|
696
|
+
...makeToken(word, piece),
|
|
697
|
+
isSpace: true
|
|
698
|
+
});
|
|
699
|
+
}
|
|
700
|
+
}
|
|
701
|
+
flushWord();
|
|
702
|
+
let fieldWidth = 0;
|
|
703
|
+
let decimalWidth = 0;
|
|
704
|
+
for (let ti = tokens.length - 1; ti >= 0; ti--) {
|
|
705
|
+
const token = tokens[ti];
|
|
706
|
+
if (token.isTab || token.isBreak) {
|
|
707
|
+
if (token.isTab) {
|
|
708
|
+
token.tabFieldWidth = fieldWidth;
|
|
709
|
+
token.tabDecimalWidth = decimalWidth;
|
|
400
710
|
}
|
|
711
|
+
fieldWidth = decimalWidth = 0;
|
|
712
|
+
} else {
|
|
713
|
+
fieldWidth += token.width;
|
|
714
|
+
const decimal = token.text.indexOf(".");
|
|
715
|
+
decimalWidth = decimal < 0 ? decimalWidth + token.width : mWidth(token.text.slice(0, decimal), {
|
|
716
|
+
...specOf(token.piece),
|
|
717
|
+
sizePx: renderedSizePxOf(token.piece)
|
|
718
|
+
});
|
|
401
719
|
}
|
|
402
720
|
}
|
|
403
|
-
const
|
|
721
|
+
for (const token of tokens) if (token.piece.highlightHex) token.highlightMetrics = mMetrics(token.piece);
|
|
722
|
+
const wrapped = wrapTokens(tokens, input.wrap, wrapRight - firstLeft - bulletLead, avail, para, firstLeft + bulletLead - wrapLeft);
|
|
404
723
|
const paraLines = wrapped.length > 0 ? wrapped : [[]];
|
|
405
724
|
for (let li = 0; li < paraLines.length; li++) {
|
|
406
725
|
const toks = paraLines[li];
|
|
@@ -414,6 +733,13 @@ const layoutCore = (input, measure) => {
|
|
|
414
733
|
if (m.d > descent) descent = m.d;
|
|
415
734
|
if (m.g > lineGap) lineGap = m.g;
|
|
416
735
|
}
|
|
736
|
+
const emptyLineBreak = toks.find((token) => token.isBreak && token.piece.sizePx > 0);
|
|
737
|
+
if (ascent === 0 && emptyLineBreak) {
|
|
738
|
+
const metrics = mMetrics(emptyLineBreak.piece);
|
|
739
|
+
ascent = metrics.a;
|
|
740
|
+
descent = metrics.d;
|
|
741
|
+
lineGap = metrics.g;
|
|
742
|
+
}
|
|
417
743
|
if (ascent === 0) {
|
|
418
744
|
ascent = para.fallbackSizePx * FALLBACK_ASCENT;
|
|
419
745
|
descent = para.fallbackSizePx * FALLBACK_DESCENT;
|
|
@@ -482,20 +808,130 @@ const layoutCore = (input, measure) => {
|
|
|
482
808
|
};
|
|
483
809
|
const columns = vert === "none" ? input.columns ?? null : null;
|
|
484
810
|
const { placements, requiredH } = columns && columns.count >= 2 ? placeColumns(frame, columns, input.anchor, buildLines) : placeSingle(frame, input.anchor, buildLines);
|
|
811
|
+
let anchorShift = 0;
|
|
812
|
+
if (input.anchorCentered && placements.length) {
|
|
813
|
+
let left = Infinity;
|
|
814
|
+
let right = -Infinity;
|
|
815
|
+
for (const { line, dx } of placements) {
|
|
816
|
+
let end = line.tokens.length;
|
|
817
|
+
while (end > 0 && (line.tokens[end - 1].isSpace || line.tokens[end - 1].isBreak)) end--;
|
|
818
|
+
let width = 0;
|
|
819
|
+
for (let i = 0; i < end; i++) if (!line.tokens[i].isBreak) width += line.tokens[i].width;
|
|
820
|
+
if (end) {
|
|
821
|
+
const x = line.anchorX + dx - (line.textAnchor === "end" ? width : line.textAnchor === "middle" ? width / 2 : 0);
|
|
822
|
+
left = Math.min(left, x);
|
|
823
|
+
right = Math.max(right, x + width);
|
|
824
|
+
}
|
|
825
|
+
if (line.bullet) {
|
|
826
|
+
const { x, b } = line.bullet;
|
|
827
|
+
left = Math.min(left, x + dx);
|
|
828
|
+
right = Math.max(right, x + dx + (b.imageHref ? b.sizePx : mWidth(b.text, bulletSpec(b))));
|
|
829
|
+
}
|
|
830
|
+
}
|
|
831
|
+
if (left !== Infinity) {
|
|
832
|
+
const shift = frame.x + frame.w / 2 - (left + right) / 2;
|
|
833
|
+
anchorShift = shift;
|
|
834
|
+
for (let i = 0; i < placements.length; i++) {
|
|
835
|
+
const placement = placements[i];
|
|
836
|
+
placements[i] = {
|
|
837
|
+
...placement,
|
|
838
|
+
dx: placement.dx + shift
|
|
839
|
+
};
|
|
840
|
+
}
|
|
841
|
+
}
|
|
842
|
+
}
|
|
485
843
|
return {
|
|
486
844
|
placements,
|
|
487
845
|
requiredH,
|
|
488
846
|
vert,
|
|
489
847
|
cx,
|
|
490
|
-
cy
|
|
848
|
+
cy,
|
|
849
|
+
anchorShift
|
|
491
850
|
};
|
|
492
851
|
};
|
|
493
852
|
const layoutTextSvg = (input, measure) => {
|
|
494
|
-
const
|
|
495
|
-
const
|
|
853
|
+
const core = layoutCore(input, measure);
|
|
854
|
+
const { vert, cx, cy } = core;
|
|
855
|
+
const { defs, placements } = resolveFillPaints(core.placements);
|
|
856
|
+
const body = defs + emitPlacements(placements, input.effectsOnly === true, input.effectKind ?? "all");
|
|
496
857
|
if (vert === "none" || vert === "upright") return body;
|
|
497
858
|
return `<g transform="rotate(${vert === "cw90" ? 90 : 270} ${fmt(cx)} ${fmt(cy)})">${body}</g>`;
|
|
498
859
|
};
|
|
860
|
+
const lineContentExtent = (line, dx) => {
|
|
861
|
+
const toks = [...line.tokens];
|
|
862
|
+
while (toks.length > 0 && (toks[toks.length - 1].isSpace || toks[toks.length - 1].isBreak)) toks.pop();
|
|
863
|
+
const content = toks.filter((t) => !t.isBreak);
|
|
864
|
+
if (content.length === 0) return null;
|
|
865
|
+
const width = content.reduce((sum, t) => sum + t.width, 0);
|
|
866
|
+
return {
|
|
867
|
+
left: line.anchorX + dx + GRID_NUDGE_X - (line.textAnchor === "middle" ? width / 2 : line.textAnchor === "end" ? width : 0),
|
|
868
|
+
width
|
|
869
|
+
};
|
|
870
|
+
};
|
|
871
|
+
const resolveFillPaints = (placements) => {
|
|
872
|
+
if (!placements.some(({ line }) => line.tokens.some((t) => t.piece.fillPaint))) return {
|
|
873
|
+
defs: "",
|
|
874
|
+
placements
|
|
875
|
+
};
|
|
876
|
+
let left = Number.POSITIVE_INFINITY;
|
|
877
|
+
let top = Number.POSITIVE_INFINITY;
|
|
878
|
+
let right = Number.NEGATIVE_INFINITY;
|
|
879
|
+
let bottom = Number.NEGATIVE_INFINITY;
|
|
880
|
+
for (const { line, baselineY, dx } of placements) {
|
|
881
|
+
const extent = lineContentExtent(line, dx);
|
|
882
|
+
if (extent === null) continue;
|
|
883
|
+
left = Math.min(left, extent.left);
|
|
884
|
+
right = Math.max(right, extent.left + extent.width);
|
|
885
|
+
top = Math.min(top, baselineY - line.ascent);
|
|
886
|
+
bottom = Math.max(bottom, baselineY + line.descent);
|
|
887
|
+
}
|
|
888
|
+
if (!(right > left && bottom > top)) return {
|
|
889
|
+
defs: "",
|
|
890
|
+
placements
|
|
891
|
+
};
|
|
892
|
+
const bounds = {
|
|
893
|
+
x: left,
|
|
894
|
+
y: top,
|
|
895
|
+
w: right - left,
|
|
896
|
+
h: bottom - top
|
|
897
|
+
};
|
|
898
|
+
const painted = /* @__PURE__ */ new Map();
|
|
899
|
+
let defs = "";
|
|
900
|
+
const pieces = /* @__PURE__ */ new Map();
|
|
901
|
+
const repaint = (piece) => {
|
|
902
|
+
const paint = piece.fillPaint;
|
|
903
|
+
if (paint === void 0) return piece;
|
|
904
|
+
let next = pieces.get(piece);
|
|
905
|
+
if (next) return next;
|
|
906
|
+
let fill = painted.get(paint);
|
|
907
|
+
if (fill === void 0) {
|
|
908
|
+
const built = paint(bounds);
|
|
909
|
+
defs += built.defs;
|
|
910
|
+
fill = built.fill;
|
|
911
|
+
painted.set(paint, fill);
|
|
912
|
+
}
|
|
913
|
+
next = {
|
|
914
|
+
...piece,
|
|
915
|
+
fillHex: fill
|
|
916
|
+
};
|
|
917
|
+
pieces.set(piece, next);
|
|
918
|
+
return next;
|
|
919
|
+
};
|
|
920
|
+
const repainted = placements.map((placement) => ({
|
|
921
|
+
...placement,
|
|
922
|
+
line: {
|
|
923
|
+
...placement.line,
|
|
924
|
+
tokens: placement.line.tokens.map((token) => token.piece.fillPaint ? {
|
|
925
|
+
...token,
|
|
926
|
+
piece: repaint(token.piece)
|
|
927
|
+
} : token)
|
|
928
|
+
}
|
|
929
|
+
}));
|
|
930
|
+
return {
|
|
931
|
+
defs,
|
|
932
|
+
placements: repainted
|
|
933
|
+
};
|
|
934
|
+
};
|
|
499
935
|
/** Content height (px) the body would occupy at the given input's font sizes —
|
|
500
936
|
* for a single column the block height, for multi-column the tallest filled
|
|
501
937
|
* column. The SVG normAutofit path uses this to pick a shrink scale. */
|
|
@@ -558,17 +994,40 @@ const placeColumns = (frame, columns, anchor, buildLines) => {
|
|
|
558
994
|
requiredH: tallest
|
|
559
995
|
};
|
|
560
996
|
};
|
|
561
|
-
|
|
562
|
-
|
|
997
|
+
/**
|
|
998
|
+
* Draws the placed lines, with each paragraph's own lines — its bullet
|
|
999
|
+
* included — inside a `<g data-pptx-paragraph>`.
|
|
1000
|
+
*
|
|
1001
|
+
* The index is the one `<p:bldP build="p">` counts in, so a player revealing a
|
|
1002
|
+
* text body one paragraph at a time has a single element to show or hide. A
|
|
1003
|
+
* paragraph that wraps into the next column appears as a second group under
|
|
1004
|
+
* the same index, because its lines are drawn where that column is.
|
|
1005
|
+
*/
|
|
1006
|
+
const emitPlacements = (placements, effectsOnly = false, effectKind = "all") => {
|
|
1007
|
+
const paragraphs = [];
|
|
1008
|
+
let parts = [];
|
|
1009
|
+
let paraIndex = null;
|
|
1010
|
+
const close = () => {
|
|
1011
|
+
if (paraIndex !== null && parts.length > 0) paragraphs.push(`<g data-pptx-paragraph="${paraIndex}">${parts.join("")}</g>`);
|
|
1012
|
+
parts = [];
|
|
1013
|
+
};
|
|
1014
|
+
let effectIndex = 0;
|
|
1015
|
+
const namespace = reflectionNamespace++;
|
|
1016
|
+
const nextEffectId = (kind) => `text-${kind}-${namespace}-${effectIndex++}`;
|
|
563
1017
|
for (const { line, baselineY, dx } of placements) {
|
|
564
|
-
if (line.
|
|
1018
|
+
if (line.paraIndex !== paraIndex) {
|
|
1019
|
+
close();
|
|
1020
|
+
paraIndex = line.paraIndex;
|
|
1021
|
+
}
|
|
1022
|
+
if (line.bullet && !effectsOnly) {
|
|
565
1023
|
const b = line.bullet.b;
|
|
566
1024
|
if (b.imageHref) parts.push(`<image x="${fmt(line.bullet.x + dx + GRID_NUDGE_X)}" y="${fmt(baselineY - b.sizePx)}" width="${fmt(b.sizePx)}" height="${fmt(b.sizePx)}" href="${b.imageHref}" xlink:href="${b.imageHref}" preserveAspectRatio="xMidYMid meet"/>`);
|
|
567
1025
|
else parts.push(`<text x="${fmt(line.bullet.x + dx + GRID_NUDGE_X)}" y="${fmt(baselineY)}" font-family="${escapeXml$1(b.family)}" font-size="${fmt(b.sizePx)}" fill="${b.fillHex}" xml:space="preserve">${escapeXml$1(b.text)}</text>`);
|
|
568
1026
|
}
|
|
569
|
-
parts.push(emitLine(line, baselineY, dx));
|
|
1027
|
+
parts.push(emitLine(line, baselineY, dx, line.descent, (kind) => nextEffectId(kind), effectsOnly, effectKind));
|
|
570
1028
|
}
|
|
571
|
-
|
|
1029
|
+
close();
|
|
1030
|
+
return paragraphs.join("");
|
|
572
1031
|
};
|
|
573
1032
|
const topPad = (line) => {
|
|
574
1033
|
const lead = line.advance - (line.ascent + line.descent);
|
|
@@ -582,64 +1041,296 @@ const lineAdvance = (line, para) => {
|
|
|
582
1041
|
else adv = natural;
|
|
583
1042
|
return adv * para.lineAdvanceScale;
|
|
584
1043
|
};
|
|
585
|
-
const emitLine = (line, baselineY, dx) => {
|
|
1044
|
+
const emitLine = (line, baselineY, dx, descent, nextEffectId, effectsOnly, effectKind) => {
|
|
586
1045
|
const toks = [...line.tokens];
|
|
587
1046
|
while (toks.length > 0 && (toks[toks.length - 1].isSpace || toks[toks.length - 1].isBreak)) toks.pop();
|
|
588
1047
|
const content = toks.filter((t) => !t.isBreak);
|
|
589
1048
|
if (content.length === 0) return "";
|
|
590
1049
|
const groups = groupTokens(content);
|
|
591
|
-
const tspans = groups.map((g) =>
|
|
1050
|
+
const tspans = groups.map((g) => {
|
|
1051
|
+
const span = tspan(g);
|
|
1052
|
+
if (!g.piece.href) return span;
|
|
1053
|
+
const target = g.piece.href.startsWith("#") ? "" : " target=\"_blank\" rel=\"noopener noreferrer\"";
|
|
1054
|
+
const title = g.piece.hrefTip ? `<title>${escapeXml$1(g.piece.hrefTip)}</title>` : "";
|
|
1055
|
+
return `<a href="${escapeXml$1(g.piece.href)}"${target}>${title}${span}</a>`;
|
|
1056
|
+
}).join("");
|
|
592
1057
|
if (tspans === "") return "";
|
|
593
1058
|
const x0 = line.anchorX + dx + GRID_NUDGE_X;
|
|
594
|
-
|
|
1059
|
+
const text = `<text x="${fmt(x0)}" y="${fmt(baselineY)}" text-anchor="${line.textAnchor}" xml:space="preserve">${tspans}</text>`;
|
|
1060
|
+
if (effectsOnly && effectKind === "fill") {
|
|
1061
|
+
const painted = groups.map((g) => g.piece.fillPaint ? g : {
|
|
1062
|
+
...g,
|
|
1063
|
+
piece: unpaintedPiece(g.piece)
|
|
1064
|
+
});
|
|
1065
|
+
if (!painted.some((g) => g.piece.fillPaint)) return "";
|
|
1066
|
+
const glyphs = `<text x="${fmt(x0)}" y="${fmt(baselineY)}" text-anchor="${line.textAnchor}" xml:space="preserve" aria-hidden="true" pointer-events="none">${painted.map(tspan).join("")}</text>`;
|
|
1067
|
+
return emitTextOuterEffects(painted, line.textAnchor, x0, baselineY, nextEffectId) + glyphs + emitTextDecorations(painted, line.textAnchor, x0, baselineY);
|
|
1068
|
+
}
|
|
1069
|
+
const reflections = effectKind === "reflection" || effectKind === "all" ? emitTextReflections(groups, line.textAnchor, x0, baselineY, descent, nextEffectId) : "";
|
|
1070
|
+
const outerEffects = effectKind === "outer" || effectKind === "all" ? emitTextOuterEffects(groups, line.textAnchor, x0, baselineY, nextEffectId) : "";
|
|
1071
|
+
const innerShadows = effectKind === "innerShadow" || effectKind === "all" ? emitTextInnerShadows(groups, line.textAnchor, x0, baselineY, nextEffectId) : "";
|
|
1072
|
+
if (effectsOnly) return outerEffects + reflections + innerShadows;
|
|
1073
|
+
return emitHighlights(groups, line.textAnchor, x0, baselineY) + outerEffects + reflections + text + innerShadows + emitTextDecorations(groups, line.textAnchor, x0, baselineY);
|
|
1074
|
+
};
|
|
1075
|
+
const unpaintedPiece = (piece) => {
|
|
1076
|
+
const { outlineHex: _outline, shadow: _shadow, glow: _glow, ...rest } = piece;
|
|
1077
|
+
return {
|
|
1078
|
+
...rest,
|
|
1079
|
+
fillHex: "none",
|
|
1080
|
+
underline: "none",
|
|
1081
|
+
strike: false
|
|
1082
|
+
};
|
|
595
1083
|
};
|
|
596
1084
|
const groupTokens = (toks) => {
|
|
597
1085
|
const groups = [];
|
|
598
1086
|
for (const t of toks) {
|
|
599
1087
|
if (t.isBreak) continue;
|
|
600
1088
|
const last = groups[groups.length - 1];
|
|
601
|
-
if (last && samePiece(last.piece, t.piece)) {
|
|
1089
|
+
if (last && !last.isTab && !t.isTab && samePiece(last.piece, t.piece)) {
|
|
602
1090
|
last.text += t.text;
|
|
603
1091
|
last.width += t.width;
|
|
1092
|
+
last.parts.push({
|
|
1093
|
+
text: t.text,
|
|
1094
|
+
width: t.width
|
|
1095
|
+
});
|
|
604
1096
|
} else groups.push({
|
|
605
1097
|
text: t.text,
|
|
1098
|
+
parts: [{
|
|
1099
|
+
text: t.text,
|
|
1100
|
+
width: t.width
|
|
1101
|
+
}],
|
|
1102
|
+
isTab: t.isTab === true,
|
|
606
1103
|
piece: t.piece,
|
|
607
|
-
width: t.width
|
|
1104
|
+
width: t.width,
|
|
1105
|
+
...t.highlightMetrics ? { highlightMetrics: t.highlightMetrics } : {}
|
|
608
1106
|
});
|
|
609
1107
|
}
|
|
610
1108
|
return groups;
|
|
611
1109
|
};
|
|
612
|
-
const
|
|
613
|
-
const
|
|
614
|
-
|
|
1110
|
+
const emitTextReflections = (groups, textAnchor, x0, baselineY, descent, nextEffectId) => {
|
|
1111
|
+
const totalWidth = groups.reduce((sum, group) => sum + group.width, 0);
|
|
1112
|
+
let cursor = x0 - (textAnchor === "middle" ? totalWidth / 2 : textAnchor === "end" ? totalWidth : 0);
|
|
1113
|
+
const parts = [];
|
|
1114
|
+
for (const group of groups) {
|
|
1115
|
+
const reflection = group.piece.reflection;
|
|
1116
|
+
if (!reflection || group.width <= 0 || group.isTab) {
|
|
1117
|
+
cursor += group.width;
|
|
1118
|
+
continue;
|
|
1119
|
+
}
|
|
1120
|
+
const scaleY = reflection.scaleY ?? 1;
|
|
1121
|
+
const distPx = (reflection.offsetEmu ?? 0) / EMU_PER_PX$1;
|
|
1122
|
+
const contactY = baselineY + descent;
|
|
1123
|
+
const angleRad = (reflection.angleDeg ?? 0) * Math.PI / 180;
|
|
1124
|
+
const offsetX = distPx * Math.cos(angleRad);
|
|
1125
|
+
const offsetY = distPx * Math.sin(angleRad);
|
|
1126
|
+
const startA = Math.max(0, Math.min(1, reflection.startOpacity ?? 1));
|
|
1127
|
+
const endA = Math.max(0, Math.min(1, reflection.opacity ?? 0));
|
|
1128
|
+
const endPosition = Math.max(0, Math.min(1, reflection.endPosition ?? 1));
|
|
1129
|
+
const startPosition = Math.max(0, Math.min(endPosition, reflection.startPosition ?? 0));
|
|
1130
|
+
const id = nextEffectId("reflection");
|
|
1131
|
+
const gradientId = `${id}-gradient`;
|
|
1132
|
+
const maskId = `${id}-mask`;
|
|
1133
|
+
const filterId = `${id}-blur`;
|
|
1134
|
+
const blurPx = Math.max(0, (reflection.blurEmu ?? 0) / EMU_PER_PX$1 / 2);
|
|
1135
|
+
const fadeDirection = reflection.fadeDirection;
|
|
1136
|
+
const defs = `<defs><linearGradient id="${gradientId}" ${fadeDirection === void 0 || fadeDirection === 90 ? "x1=\"0\" y1=\"1\" x2=\"0\" y2=\"0\"" : (() => {
|
|
1137
|
+
const radians = fadeDirection * Math.PI / 180;
|
|
1138
|
+
const dx = Math.cos(radians) / 2;
|
|
1139
|
+
const dy = Math.sin(radians) / 2;
|
|
1140
|
+
return `x1="${(.5 + dx).toFixed(3)}" y1="${(.5 + dy).toFixed(3)}" x2="${(.5 - dx).toFixed(3)}" y2="${(.5 - dy).toFixed(3)}"`;
|
|
1141
|
+
})()}><stop offset="0" stop-color="#fff" stop-opacity="${startA.toFixed(3)}"/><stop offset="${startPosition.toFixed(3)}" stop-color="#fff" stop-opacity="${startA.toFixed(3)}"/><stop offset="${endPosition.toFixed(3)}" stop-color="#fff" stop-opacity="${endA.toFixed(3)}"/><stop offset="1" stop-color="#fff" stop-opacity="${endA.toFixed(3)}"/></linearGradient><mask id="${maskId}" maskContentUnits="objectBoundingBox"><rect width="1" height="1" fill="url(#${gradientId})"/></mask>` + (blurPx > 0 ? `<filter id="${filterId}" x="-25%" y="-25%" width="150%" height="150%"><feGaussianBlur stdDeviation="${fmt(blurPx)}"/></filter>` : "") + `</defs>`;
|
|
1142
|
+
const filter = blurPx > 0 ? ` filter="url(#${filterId})"` : "";
|
|
1143
|
+
const scaleX = reflection.scaleX ?? 1;
|
|
1144
|
+
const alignment = reflection.alignment ?? "b";
|
|
1145
|
+
const anchorX = alignment === "l" || alignment === "tl" || alignment === "bl" ? cursor : alignment === "r" || alignment === "tr" || alignment === "br" ? cursor + group.width : cursor + group.width / 2;
|
|
1146
|
+
const anchorY = alignment === "tl" || alignment === "t" || alignment === "tr" ? baselineY - group.piece.sizePx : alignment === "l" || alignment === "ctr" || alignment === "r" ? baselineY - group.piece.sizePx / 2 : contactY;
|
|
1147
|
+
const skew = (reflection.skewX ?? 0) !== 0 || (reflection.skewY ?? 0) !== 0 ? ` skewX(${fmt(reflection.skewX ?? 0)}) skewY(${fmt(reflection.skewY ?? 0)})` : "";
|
|
1148
|
+
const transform = `translate(${fmt(anchorX + offsetX)} ${fmt(anchorY + offsetY)})${skew} scale(${fmt(scaleX)} ${fmt(scaleY)}) translate(${fmt(-anchorX)} ${fmt(-anchorY)})`;
|
|
1149
|
+
const glyph = `<text x="${fmt(cursor)}" y="${fmt(baselineY)}" text-anchor="start" xml:space="preserve">${tspan(group)}</text>`;
|
|
1150
|
+
parts.push(`${defs}<g transform="${transform}" mask="url(#${maskId})"${filter} data-pptx-reflection="text" aria-hidden="true" pointer-events="none">${glyph}</g>`);
|
|
1151
|
+
cursor += group.width;
|
|
1152
|
+
}
|
|
1153
|
+
return parts.join("");
|
|
1154
|
+
};
|
|
1155
|
+
const emitTextOuterEffects = (groups, textAnchor, x0, baselineY, nextId) => {
|
|
1156
|
+
const totalWidth = groups.reduce((sum, group) => sum + group.width, 0);
|
|
1157
|
+
let cursor = x0 - (textAnchor === "middle" ? totalWidth / 2 : textAnchor === "end" ? totalWidth : 0);
|
|
1158
|
+
const parts = [];
|
|
1159
|
+
for (const group of groups) {
|
|
1160
|
+
const shadow = group.piece.shadow;
|
|
1161
|
+
const glow = group.piece.glow;
|
|
1162
|
+
if (group.width <= 0 || group.isTab || !shadow && !glow) {
|
|
1163
|
+
cursor += group.width;
|
|
1164
|
+
continue;
|
|
1165
|
+
}
|
|
1166
|
+
const glyph = `<text x="${fmt(cursor)}" y="${fmt(baselineY)}" text-anchor="start" xml:space="preserve">${tspan(group)}</text>`;
|
|
1167
|
+
if (shadow) {
|
|
1168
|
+
const id = nextId("outer-shadow");
|
|
1169
|
+
const angle = (shadow.angleDeg ?? 45) * Math.PI / 180;
|
|
1170
|
+
const distance = (shadow.offsetEmu ?? 38100) / EMU_PER_PX$1;
|
|
1171
|
+
const blur = Math.max(0, (shadow.blurEmu ?? 50800) / EMU_PER_PX$1 / 2);
|
|
1172
|
+
const glyphSize = renderedSizePxOf(group.piece);
|
|
1173
|
+
const pad = Math.max(1, distance + blur * 3);
|
|
1174
|
+
const color = shadow.color ?? "#000000";
|
|
1175
|
+
const opacity = Math.max(0, Math.min(1, shadow.opacity ?? 1));
|
|
1176
|
+
const defs = `<defs><filter id="${id}" filterUnits="userSpaceOnUse" x="${fmt(cursor - pad)}" y="${fmt(baselineY - glyphSize * 1.5 - pad)}" width="${fmt(group.width + pad * 2)}" height="${fmt(glyphSize * 2 + pad * 2)}"><feGaussianBlur in="SourceAlpha" stdDeviation="${fmt(blur)}" result="shadowBlur"/><feOffset in="shadowBlur" dx="${fmt(Math.cos(angle) * distance)}" dy="${fmt(Math.sin(angle) * distance)}" result="shadowOffset"/><feFlood flood-color="${escapeXml$1(color)}" flood-opacity="${opacity.toFixed(3)}" result="shadowColor"/><feComposite in="shadowColor" in2="shadowOffset" operator="in"/></filter></defs>`;
|
|
1177
|
+
parts.push(`${defs}${glyph.replace("<text ", `<text filter="url(#${id})" aria-hidden="true" pointer-events="none" `)}`);
|
|
1178
|
+
}
|
|
1179
|
+
if (glow) {
|
|
1180
|
+
const id = nextId("glow");
|
|
1181
|
+
const radius = Math.max(0, (glow.radiusEmu ?? 63500) / EMU_PER_PX$1);
|
|
1182
|
+
const glyphSize = renderedSizePxOf(group.piece);
|
|
1183
|
+
const pad = Math.max(1, radius * 2);
|
|
1184
|
+
const color = glow.color ?? "#FFFF00";
|
|
1185
|
+
const opacity = Math.max(0, Math.min(1, glow.opacity ?? 1));
|
|
1186
|
+
const defs = `<defs><filter id="${id}" filterUnits="userSpaceOnUse" x="${fmt(cursor - pad)}" y="${fmt(baselineY - glyphSize * 1.5 - pad)}" width="${fmt(group.width + pad * 2)}" height="${fmt(glyphSize * 2 + pad * 2)}"><feMorphology in="SourceAlpha" operator="dilate" radius="${fmt(radius / 2)}" result="glowExpanded"/><feGaussianBlur in="glowExpanded" stdDeviation="${fmt(radius / 2)}" result="glowBlur"/><feFlood flood-color="${escapeXml$1(color)}" flood-opacity="${opacity.toFixed(3)}" result="glowColor"/><feComposite in="glowColor" in2="glowBlur" operator="in"/></filter></defs>`;
|
|
1187
|
+
parts.push(`${defs}${glyph.replace("<text ", `<text filter="url(#${id})" aria-hidden="true" pointer-events="none" `)}`);
|
|
1188
|
+
}
|
|
1189
|
+
cursor += group.width;
|
|
1190
|
+
}
|
|
1191
|
+
return parts.join("");
|
|
1192
|
+
};
|
|
1193
|
+
const emitTextInnerShadows = (groups, textAnchor, x0, baselineY, nextId) => {
|
|
1194
|
+
const totalWidth = groups.reduce((sum, group) => sum + group.width, 0);
|
|
1195
|
+
let cursor = x0 - (textAnchor === "middle" ? totalWidth / 2 : textAnchor === "end" ? totalWidth : 0);
|
|
1196
|
+
const parts = [];
|
|
1197
|
+
for (const group of groups) {
|
|
1198
|
+
const shadow = group.piece.innerShadow;
|
|
1199
|
+
if (!shadow || group.width <= 0 || group.isTab) {
|
|
1200
|
+
cursor += group.width;
|
|
1201
|
+
continue;
|
|
1202
|
+
}
|
|
1203
|
+
const id = nextId("inner-shadow");
|
|
1204
|
+
const angle = (shadow.angleDeg ?? 45) * Math.PI / 180;
|
|
1205
|
+
const dx = (shadow.offsetEmu ?? 38100) / EMU_PER_PX$1 * Math.cos(angle);
|
|
1206
|
+
const dy = (shadow.offsetEmu ?? 38100) / EMU_PER_PX$1 * Math.sin(angle);
|
|
1207
|
+
const blurPx = Math.max(0, (shadow.blurEmu ?? 50800) / EMU_PER_PX$1 / 2);
|
|
1208
|
+
const color = shadow.color ?? "#000000";
|
|
1209
|
+
const opacity = Math.max(0, Math.min(1, shadow.opacity ?? 1));
|
|
1210
|
+
const defs = `<defs><filter id="${id}" x="-25%" y="-25%" width="150%" height="150%"><feGaussianBlur in="SourceAlpha" stdDeviation="${fmt(blurPx)}" result="innerBlur"/><feOffset in="innerBlur" dx="${fmt(dx)}" dy="${fmt(dy)}" result="innerOff"/><feComposite in="innerOff" in2="SourceAlpha" operator="arithmetic" k2="-1" k3="1" result="innerMask"/><feFlood flood-color="${escapeXml$1(color)}" flood-opacity="${opacity.toFixed(3)}" result="innerColor"/><feComposite in="innerColor" in2="innerMask" operator="in" result="innerOut"/><feComposite in="innerOut" in2="SourceAlpha" operator="in"/></filter></defs>`;
|
|
1211
|
+
const glyph = `<text x="${fmt(cursor)}" y="${fmt(baselineY)}" text-anchor="start" xml:space="preserve" filter="url(#${id})" aria-hidden="true" pointer-events="none">${tspan(group)}</text>`;
|
|
1212
|
+
parts.push(defs + glyph);
|
|
1213
|
+
cursor += group.width;
|
|
1214
|
+
}
|
|
1215
|
+
return parts.join("");
|
|
1216
|
+
};
|
|
1217
|
+
const baselineShiftPxOf = (p) => p.sizePx * p.baseline;
|
|
615
1218
|
const SUPER_SUB_SIZE_RATIO = .65;
|
|
616
|
-
const
|
|
617
|
-
const
|
|
618
|
-
|
|
1219
|
+
const SMALL_CAPS_LOWERCASE_RATIO = .8;
|
|
1220
|
+
const renderedSizePxOf = (p) => p.sizePx * (p.baseline !== 0 ? SUPER_SUB_SIZE_RATIO : 1) * (p.smallCaps === true ? SMALL_CAPS_LOWERCASE_RATIO : 1);
|
|
1221
|
+
const underlineStrokeWidth = (piece) => {
|
|
1222
|
+
const size = renderedSizePxOf(piece);
|
|
1223
|
+
const heavy = piece.underline === "heavy" || piece.underline.endsWith("Heavy");
|
|
1224
|
+
return Math.max(heavy ? 1 : .6, size * (heavy ? .09 : .06));
|
|
1225
|
+
};
|
|
1226
|
+
const underlineDashArray = (piece) => {
|
|
1227
|
+
const underline = piece.underline;
|
|
1228
|
+
const scale = renderedSizePxOf(piece) / 10;
|
|
1229
|
+
const pattern = (values) => values.map((value) => fmt(value * scale)).join(" ");
|
|
1230
|
+
switch (underline) {
|
|
1231
|
+
case "dotted":
|
|
1232
|
+
case "dottedHeavy": return pattern([1.5, 3]);
|
|
1233
|
+
case "dash":
|
|
1234
|
+
case "dashHeavy": return pattern([5, 3]);
|
|
1235
|
+
case "dashLong":
|
|
1236
|
+
case "dashLongHeavy": return pattern([9, 3]);
|
|
1237
|
+
case "dotDash":
|
|
1238
|
+
case "dotDashHeavy": return pattern([
|
|
1239
|
+
1.5,
|
|
1240
|
+
3,
|
|
1241
|
+
6,
|
|
1242
|
+
3
|
|
1243
|
+
]);
|
|
1244
|
+
case "dotDotDash":
|
|
1245
|
+
case "dotDotDashHeavy": return pattern([
|
|
1246
|
+
1.5,
|
|
1247
|
+
3,
|
|
1248
|
+
1.5,
|
|
1249
|
+
3,
|
|
1250
|
+
6,
|
|
1251
|
+
3
|
|
1252
|
+
]);
|
|
1253
|
+
default: return null;
|
|
1254
|
+
}
|
|
1255
|
+
};
|
|
1256
|
+
const underlineSegments = (group, x) => {
|
|
1257
|
+
if (group.piece.underline !== "words" || !/\s/.test(group.text)) return [{
|
|
1258
|
+
x,
|
|
1259
|
+
width: group.width
|
|
1260
|
+
}];
|
|
1261
|
+
const segments = [];
|
|
1262
|
+
let offset = 0;
|
|
1263
|
+
for (const part of group.parts) {
|
|
1264
|
+
if (!/^\s+$/u.test(part.text) && part.text.length > 0) segments.push({
|
|
1265
|
+
x: x + offset,
|
|
1266
|
+
width: part.width
|
|
1267
|
+
});
|
|
1268
|
+
offset += part.width;
|
|
1269
|
+
}
|
|
1270
|
+
return segments;
|
|
1271
|
+
};
|
|
1272
|
+
const STRIKE_CENTER_EM = .3;
|
|
1273
|
+
const STRIKE_THICKNESS_EM = .05;
|
|
1274
|
+
const MIN_STRIKE_THICKNESS_PX = .6;
|
|
1275
|
+
const emitTextDecorations = (groups, textAnchor, x0, baselineY) => {
|
|
619
1276
|
const totalWidth = groups.reduce((sum, g) => sum + g.width, 0);
|
|
620
1277
|
let cursor = textAnchor === "middle" ? x0 - totalWidth / 2 : textAnchor === "end" ? x0 - totalWidth : x0;
|
|
621
1278
|
const parts = [];
|
|
622
1279
|
for (const g of groups) {
|
|
623
|
-
if (g.piece.
|
|
624
|
-
const
|
|
625
|
-
|
|
1280
|
+
if (g.piece.strike === "double" && !g.isTab && g.width > 0) {
|
|
1281
|
+
const size = renderedSizePxOf(g.piece);
|
|
1282
|
+
const center = baselineY - baselineShiftPxOf(g.piece) - size * STRIKE_CENTER_EM;
|
|
1283
|
+
const stroke = Math.max(MIN_STRIKE_THICKNESS_PX, size * STRIKE_THICKNESS_EM);
|
|
1284
|
+
for (const offset of [-stroke, stroke]) parts.push(`<line x1="${fmt(cursor)}" x2="${fmt(cursor + g.width)}" y1="${fmt(center + offset)}" y2="${fmt(center + offset)}" stroke="${g.piece.fillHex}" stroke-width="${fmt(stroke)}"/>`);
|
|
1285
|
+
}
|
|
1286
|
+
if (g.piece.underline !== "none" && g.width > 0 && (g.piece.underline !== "sng" || g.piece.underlineHex !== void 0)) {
|
|
1287
|
+
const size = renderedSizePxOf(g.piece);
|
|
1288
|
+
const baseline = baselineY - baselineShiftPxOf(g.piece);
|
|
1289
|
+
const y = baseline + size * WAVY_BASELINE_OFFSET_RATIO;
|
|
1290
|
+
const dash = underlineDashArray(g.piece);
|
|
1291
|
+
for (const segment of underlineSegments(g, cursor)) if (g.piece.underline.startsWith("wavy")) if (g.piece.underline === "wavyDbl") {
|
|
1292
|
+
const offset = size * .08;
|
|
1293
|
+
parts.push(wavyPath(segment.x, segment.x + segment.width, baseline, g.piece, -offset));
|
|
1294
|
+
parts.push(wavyPath(segment.x, segment.x + segment.width, baseline, g.piece, offset));
|
|
1295
|
+
} else parts.push(wavyPath(segment.x, segment.x + segment.width, baseline, g.piece));
|
|
1296
|
+
else {
|
|
1297
|
+
const stroke = underlineStrokeWidth(g.piece);
|
|
1298
|
+
const offsets = g.piece.underline === "dbl" ? [-size * .08, size * .08] : [0];
|
|
1299
|
+
for (const offset of offsets) {
|
|
1300
|
+
const dashAttr = dash === null ? "" : ` stroke-dasharray="${dash}"`;
|
|
1301
|
+
parts.push(`<line x1="${fmt(segment.x)}" x2="${fmt(segment.x + segment.width)}" y1="${fmt(y + offset)}" y2="${fmt(y + offset)}" stroke="${g.piece.underlineHex ?? g.piece.fillHex}" stroke-width="${fmt(stroke)}"${dashAttr}/>`);
|
|
1302
|
+
}
|
|
1303
|
+
}
|
|
626
1304
|
}
|
|
627
1305
|
cursor += g.width;
|
|
628
1306
|
}
|
|
629
1307
|
return parts.join("");
|
|
630
1308
|
};
|
|
1309
|
+
const emitHighlights = (groups, anchor, x0, baselineY) => {
|
|
1310
|
+
const width = groups.reduce((sum, group) => sum + group.width, 0);
|
|
1311
|
+
let x = x0 - (anchor === "middle" ? width / 2 : anchor === "end" ? width : 0);
|
|
1312
|
+
const backgrounds = [];
|
|
1313
|
+
for (const group of groups) {
|
|
1314
|
+
const metrics = group.highlightMetrics;
|
|
1315
|
+
if (group.piece.highlightHex && metrics && group.width > 0) {
|
|
1316
|
+
const scale = (group.piece.baseline === 0 ? 1 : SUPER_SUB_SIZE_RATIO) * (group.piece.smallCaps === true ? SMALL_CAPS_LOWERCASE_RATIO : 1);
|
|
1317
|
+
const y = baselineY - baselineShiftPxOf(group.piece) - metrics.a * scale;
|
|
1318
|
+
backgrounds.push(`<rect x="${fmt(x)}" y="${fmt(y)}" width="${fmt(group.width)}" height="${fmt((metrics.a + metrics.d) * scale)}" fill="${escapeXml$1(group.piece.highlightHex)}"/>`);
|
|
1319
|
+
}
|
|
1320
|
+
x += group.width;
|
|
1321
|
+
}
|
|
1322
|
+
return backgrounds.join("");
|
|
1323
|
+
};
|
|
631
1324
|
const WAVY_AMPLITUDE_RATIO = .045;
|
|
632
1325
|
const WAVY_AMPLITUDE_MIN_PX = .6;
|
|
633
1326
|
const WAVY_PERIOD_RATIO = .18;
|
|
634
1327
|
const WAVY_PERIOD_MIN_PX = 2;
|
|
635
1328
|
const WAVY_BASELINE_OFFSET_RATIO = .12;
|
|
636
|
-
const
|
|
637
|
-
const WAVY_STROKE_WIDTH_MIN_PX = .6;
|
|
638
|
-
const wavyPath = (x1, x2, baselineY, piece) => {
|
|
1329
|
+
const wavyPath = (x1, x2, baselineY, piece, yOffset = 0) => {
|
|
639
1330
|
const size = renderedSizePxOf(piece);
|
|
640
1331
|
const amp = Math.max(WAVY_AMPLITUDE_MIN_PX, size * WAVY_AMPLITUDE_RATIO);
|
|
641
1332
|
const period = Math.max(WAVY_PERIOD_MIN_PX, size * WAVY_PERIOD_RATIO);
|
|
642
|
-
const y = baselineY + size * WAVY_BASELINE_OFFSET_RATIO;
|
|
1333
|
+
const y = baselineY + size * WAVY_BASELINE_OFFSET_RATIO + yOffset;
|
|
643
1334
|
let d = `M${fmt(x1)} ${fmt(y)}`;
|
|
644
1335
|
let cx = x1;
|
|
645
1336
|
let up = true;
|
|
@@ -650,11 +1341,25 @@ const wavyPath = (x1, x2, baselineY, piece) => {
|
|
|
650
1341
|
cx = midX;
|
|
651
1342
|
up = !up;
|
|
652
1343
|
}
|
|
653
|
-
const strokeWidth =
|
|
654
|
-
return `<path d="${d}" stroke="${piece.fillHex}" stroke-width="${fmt(strokeWidth)}" fill="none"/>`;
|
|
1344
|
+
const strokeWidth = underlineStrokeWidth(piece);
|
|
1345
|
+
return `<path d="${d}" stroke="${piece.underlineHex ?? piece.fillHex}" stroke-width="${fmt(strokeWidth)}" fill="none"/>`;
|
|
1346
|
+
};
|
|
1347
|
+
const sameReflection = (a, b) => a === void 0 === (b === void 0) && a?.blurEmu === b?.blurEmu && a?.offsetEmu === b?.offsetEmu && a?.angleDeg === b?.angleDeg && a?.opacity === b?.opacity && a?.startOpacity === b?.startOpacity && a?.startPosition === b?.startPosition && a?.endPosition === b?.endPosition && a?.fadeDirection === b?.fadeDirection && a?.scaleX === b?.scaleX && a?.scaleY === b?.scaleY && a?.skewX === b?.skewX && a?.skewY === b?.skewY && a?.alignment === b?.alignment && a?.rotateWithShape === b?.rotateWithShape;
|
|
1348
|
+
const sameInnerShadow = (a, b) => {
|
|
1349
|
+
if (a === void 0 || b === void 0) return a === b;
|
|
1350
|
+
return a.color === b.color && a.blurEmu === b.blurEmu && a.offsetEmu === b.offsetEmu && a.angleDeg === b.angleDeg && a.opacity === b.opacity;
|
|
655
1351
|
};
|
|
656
|
-
const
|
|
1352
|
+
const sameShadow = (a, b) => {
|
|
1353
|
+
if (a === void 0 || b === void 0) return a === b;
|
|
1354
|
+
return a.color === b.color && a.blurEmu === b.blurEmu && a.offsetEmu === b.offsetEmu && a.angleDeg === b.angleDeg && a.opacity === b.opacity;
|
|
1355
|
+
};
|
|
1356
|
+
const sameGlow = (a, b) => {
|
|
1357
|
+
if (a === void 0 || b === void 0) return a === b;
|
|
1358
|
+
return a.color === b.color && a.radiusEmu === b.radiusEmu && a.opacity === b.opacity;
|
|
1359
|
+
};
|
|
1360
|
+
const samePiece = (a, b) => a.family === b.family && a.sizePx === b.sizePx && a.bold === b.bold && a.italic === b.italic && a.letterSpacingPx === b.letterSpacingPx && a.kerning === b.kerning && a.fillHex === b.fillHex && a.outlineHex === b.outlineHex && a.outlineWidthPx === b.outlineWidthPx && a.underlineHex === b.underlineHex && a.highlightHex === b.highlightHex && a.underline === b.underline && a.strike === b.strike && a.baseline === b.baseline && a.smallCaps === b.smallCaps && sameShadow(a.shadow, b.shadow) && sameGlow(a.glow, b.glow) && sameInnerShadow(a.innerShadow, b.innerShadow) && sameReflection(a.reflection, b.reflection) && a.href === b.href && a.hrefTip === b.hrefTip;
|
|
657
1361
|
const tspan = (g) => {
|
|
1362
|
+
if (g.isTab) return `<tspan dx="${fmt(g.width)}">​</tspan>`;
|
|
658
1363
|
const p = g.piece;
|
|
659
1364
|
const sizePx = renderedSizePxOf(p);
|
|
660
1365
|
const attrs = [
|
|
@@ -664,15 +1369,21 @@ const tspan = (g) => {
|
|
|
664
1369
|
];
|
|
665
1370
|
if (p.bold) attrs.push("font-weight=\"700\"");
|
|
666
1371
|
if (p.italic) attrs.push("font-style=\"italic\"");
|
|
667
|
-
|
|
668
|
-
if (p.
|
|
669
|
-
|
|
670
|
-
|
|
1372
|
+
if (p.kerning !== void 0) attrs.push(`font-kerning="${p.kerning ? "normal" : "none"}"`);
|
|
1373
|
+
if (p.outlineHex !== void 0 && (p.outlineWidthPx ?? 0) > 0) {
|
|
1374
|
+
attrs.push(`stroke="${p.outlineHex}"`);
|
|
1375
|
+
attrs.push(`stroke-width="${fmt(p.outlineWidthPx)}"`);
|
|
1376
|
+
attrs.push("paint-order=\"stroke fill\"");
|
|
1377
|
+
}
|
|
1378
|
+
const decorations = [];
|
|
1379
|
+
if (p.underline === "sng" && p.underlineHex === void 0) decorations.push("underline");
|
|
1380
|
+
if (p.strike === true) decorations.push("line-through");
|
|
1381
|
+
if (decorations.length) attrs.push(`text-decoration="${decorations.join(" ")}"`);
|
|
671
1382
|
if (p.letterSpacingPx !== 0) attrs.push(`letter-spacing="${fmt(p.letterSpacingPx)}"`);
|
|
672
|
-
if (p.
|
|
1383
|
+
if (p.baseline !== 0) attrs.push(`baseline-shift="${fmt(baselineShiftPxOf(p))}"`);
|
|
673
1384
|
return `<tspan ${attrs.join(" ")}>${escapeXml$1(g.text)}</tspan>`;
|
|
674
1385
|
};
|
|
675
|
-
const wrapTokens = (tokens, wrap, firstAvail, avail) => {
|
|
1386
|
+
const wrapTokens = (tokens, wrap, firstAvail, avail, para, firstOffset) => {
|
|
676
1387
|
const lines = [];
|
|
677
1388
|
let cur = [];
|
|
678
1389
|
let lineW = 0;
|
|
@@ -682,19 +1393,41 @@ const wrapTokens = (tokens, wrap, firstAvail, avail) => {
|
|
|
682
1393
|
while (cur.length > 0 && (cur[cur.length - 1].isSpace || cur[cur.length - 1].isBreak)) cur.pop();
|
|
683
1394
|
};
|
|
684
1395
|
const close = () => {
|
|
1396
|
+
const endingBreak = cur[cur.length - 1];
|
|
685
1397
|
trimTrailing();
|
|
1398
|
+
if (cur.length === 0 && endingBreak?.isBreak) cur.push(endingBreak);
|
|
686
1399
|
lines.push(cur);
|
|
687
1400
|
cur = [];
|
|
688
1401
|
lineW = 0;
|
|
689
1402
|
trailingSpaceW = 0;
|
|
690
1403
|
first = false;
|
|
691
1404
|
};
|
|
692
|
-
|
|
1405
|
+
const wordWidths = tokens.map((token) => token.width);
|
|
1406
|
+
for (let index = tokens.length - 2; index >= 0; index--) if (tokens[index + 1].continuesSegment) wordWidths[index] += wordWidths[index + 1];
|
|
1407
|
+
for (let index = 0; index < tokens.length; index++) {
|
|
1408
|
+
const tok = tokens[index];
|
|
693
1409
|
if (tok.isBreak) {
|
|
694
1410
|
cur.push(tok);
|
|
695
1411
|
close();
|
|
696
1412
|
continue;
|
|
697
1413
|
}
|
|
1414
|
+
if (tok.isTab) {
|
|
1415
|
+
const position = lineW + (first ? firstOffset : 0);
|
|
1416
|
+
const stops = para.tabStops ?? [];
|
|
1417
|
+
let lo = 0;
|
|
1418
|
+
let hi = stops.length;
|
|
1419
|
+
while (lo < hi) {
|
|
1420
|
+
const mid = lo + hi >>> 1;
|
|
1421
|
+
if (stops[mid].positionPx <= position + .01) lo = mid + 1;
|
|
1422
|
+
else hi = mid;
|
|
1423
|
+
}
|
|
1424
|
+
const stop = stops[lo];
|
|
1425
|
+
const interval = para.defaultTabSizePx ?? 96;
|
|
1426
|
+
const next = stop?.positionPx ?? (interval > 0 ? (Math.floor(position / interval) + 1) * interval : position);
|
|
1427
|
+
const alignment = stop?.alignment ?? "left";
|
|
1428
|
+
const offset = alignment === "center" ? (tok.tabFieldWidth ?? 0) / 2 : alignment === "right" ? tok.tabFieldWidth ?? 0 : alignment === "decimal" ? tok.tabDecimalWidth ?? 0 : 0;
|
|
1429
|
+
tok.width = Math.max(0, next - offset - position);
|
|
1430
|
+
}
|
|
698
1431
|
if (tok.isSpace) {
|
|
699
1432
|
cur.push(tok);
|
|
700
1433
|
lineW += tok.width;
|
|
@@ -703,7 +1436,7 @@ const wrapTokens = (tokens, wrap, firstAvail, avail) => {
|
|
|
703
1436
|
}
|
|
704
1437
|
const limit = first ? firstAvail : avail;
|
|
705
1438
|
const hasContent = lineW - trailingSpaceW > 0;
|
|
706
|
-
if (wrap && hasContent && lineW +
|
|
1439
|
+
if (wrap && hasContent && !tok.continuesSegment && lineW + wordWidths[index] > limit + .5) {
|
|
707
1440
|
close();
|
|
708
1441
|
cur.push(tok);
|
|
709
1442
|
lineW = tok.width;
|
|
@@ -718,7 +1451,46 @@ const wrapTokens = (tokens, wrap, firstAvail, avail) => {
|
|
|
718
1451
|
return lines;
|
|
719
1452
|
};
|
|
720
1453
|
//#endregion
|
|
1454
|
+
//#region src/browser-measure.ts
|
|
1455
|
+
/** Custom tab alignment needs the same font advances as the browser paints. */
|
|
1456
|
+
function browserTextMeasurer() {
|
|
1457
|
+
if (typeof document === "undefined") return void 0;
|
|
1458
|
+
const context = document.createElement("canvas").getContext("2d");
|
|
1459
|
+
if (!context) return void 0;
|
|
1460
|
+
return (text, spec) => {
|
|
1461
|
+
context.font = `${spec.italic ? "italic" : "normal"} ${spec.bold ? "bold" : "normal"} ${spec.sizePx}px ${spec.family}`;
|
|
1462
|
+
context.fontKerning = spec.kerning === false ? "none" : "normal";
|
|
1463
|
+
context.letterSpacing = `${spec.letterSpacingPx}px`;
|
|
1464
|
+
const metrics = context.measureText(text);
|
|
1465
|
+
return {
|
|
1466
|
+
widthPx: metrics.width,
|
|
1467
|
+
ascentPx: metrics.fontBoundingBoxAscent,
|
|
1468
|
+
descentPx: metrics.fontBoundingBoxDescent,
|
|
1469
|
+
lineGapPx: 0,
|
|
1470
|
+
inkAscentPx: metrics.actualBoundingBoxAscent,
|
|
1471
|
+
inkDescentPx: metrics.actualBoundingBoxDescent
|
|
1472
|
+
};
|
|
1473
|
+
};
|
|
1474
|
+
}
|
|
1475
|
+
//#endregion
|
|
721
1476
|
//#region src/render-slide.ts
|
|
1477
|
+
const clickActionHref = (pres, action) => {
|
|
1478
|
+
if (!action) return void 0;
|
|
1479
|
+
if (action.kind === "url") return action.url;
|
|
1480
|
+
if (action.kind === "customShow") return `#pptx-custom-show?id=${action.id}&return=${action.returnToShow}`;
|
|
1481
|
+
if (action.kind === "endShow") return "#pptx-end-show";
|
|
1482
|
+
if (action.kind === "lastSlideViewed") return "#pptx-last-slide-viewed";
|
|
1483
|
+
if (action.kind === "slide") {
|
|
1484
|
+
const index = getSlideIndex(pres, action.slide);
|
|
1485
|
+
return index >= 0 ? `#slide-${index + 1}` : void 0;
|
|
1486
|
+
}
|
|
1487
|
+
return {
|
|
1488
|
+
nextSlide: "#pptx-next-slide",
|
|
1489
|
+
prevSlide: "#pptx-prev-slide",
|
|
1490
|
+
firstSlide: "#pptx-first-slide",
|
|
1491
|
+
lastSlide: "#pptx-last-slide"
|
|
1492
|
+
}[action.kind];
|
|
1493
|
+
};
|
|
722
1494
|
const DEFAULT_SIZE = {
|
|
723
1495
|
width: 12192e3,
|
|
724
1496
|
height: 6858e3
|
|
@@ -728,6 +1500,7 @@ const PX_PER_PT = 96 / 72;
|
|
|
728
1500
|
const DEFAULT_BODY_PT = 18;
|
|
729
1501
|
const DEFAULT_TITLE_PT = 44;
|
|
730
1502
|
const DEFAULT_FONT = "Calibri, 'Helvetica Neue', Arial, sans-serif";
|
|
1503
|
+
const browserFontFamily = (family) => family ? `${JSON.stringify(family)}, ${DEFAULT_FONT}` : DEFAULT_FONT;
|
|
731
1504
|
const DEFAULT_BULLET_FONT = "Arial";
|
|
732
1505
|
const DEFAULT_INSET_X = 91440;
|
|
733
1506
|
const DEFAULT_INSET_Y = 45720;
|
|
@@ -770,7 +1543,7 @@ const mimeFromPartName = (name) => {
|
|
|
770
1543
|
if (dot < 0) return null;
|
|
771
1544
|
return EXT_TO_MIME[name.slice(dot + 1).toLowerCase()] ?? null;
|
|
772
1545
|
};
|
|
773
|
-
const renderPicture = (shape, pres, x, y, w, h, transform, textOverlay, bytes, format) => {
|
|
1546
|
+
const renderPicture = (shape, pres, x, y, w, h, transform, textOverlay, bytes, format, outline) => {
|
|
774
1547
|
let mime = null;
|
|
775
1548
|
if (bytes && format) mime = imageMime[format] ?? null;
|
|
776
1549
|
if (bytes && !mime) {
|
|
@@ -784,69 +1557,137 @@ const renderPicture = (shape, pres, x, y, w, h, transform, textOverlay, bytes, f
|
|
|
784
1557
|
if (bytes && mime) {
|
|
785
1558
|
const dataUrl = `data:${mime};base64,${u8ToBase64(bytes)}`;
|
|
786
1559
|
const crop = getShapeImageCrop(shape);
|
|
787
|
-
|
|
1560
|
+
const layout = getShapeImageFillLayout(shape);
|
|
1561
|
+
const stretch = layout?.mode === "stretch" ? layout : null;
|
|
1562
|
+
const fillL = stretch?.left ?? 0;
|
|
1563
|
+
const fillT = stretch?.top ?? 0;
|
|
1564
|
+
const fillR = stretch?.right ?? 0;
|
|
1565
|
+
const fillB = stretch?.bottom ?? 0;
|
|
1566
|
+
const hasFillOffsets = fillL !== 0 || fillT !== 0 || fillR !== 0 || fillB !== 0;
|
|
1567
|
+
let imgX = x + w * fillL, imgY = y + h * fillT, imgW = Math.max(0, w * (1 - fillL - fillR)), imgH = Math.max(0, h * (1 - fillT - fillB));
|
|
788
1568
|
let clipDef = "";
|
|
789
1569
|
let clipAttr = "";
|
|
790
1570
|
const cropL = crop?.left ?? 0;
|
|
791
1571
|
const cropT = crop?.top ?? 0;
|
|
792
1572
|
const cropR = crop?.right ?? 0;
|
|
793
1573
|
const cropB = crop?.bottom ?? 0;
|
|
794
|
-
if (cropL
|
|
1574
|
+
if (cropL !== 0 || cropT !== 0 || cropR !== 0 || cropB !== 0) {
|
|
795
1575
|
const scaleX = 1 / Math.max(.001, 1 - cropL - cropR);
|
|
796
1576
|
const scaleY = 1 / Math.max(.001, 1 - cropT - cropB);
|
|
797
|
-
imgW
|
|
798
|
-
imgH
|
|
799
|
-
imgX
|
|
800
|
-
imgY
|
|
1577
|
+
imgW *= scaleX;
|
|
1578
|
+
imgH *= scaleY;
|
|
1579
|
+
imgX -= imgW * cropL;
|
|
1580
|
+
imgY -= imgH * cropT;
|
|
1581
|
+
}
|
|
1582
|
+
const preset = getShapePreset(shape) ?? "rect";
|
|
1583
|
+
if (crop || hasFillOffsets || preset !== "rect") {
|
|
801
1584
|
const clipId = mintId();
|
|
802
|
-
clipDef = `<defs><clipPath id="${clipId}"
|
|
1585
|
+
clipDef = `<defs><clipPath id="${clipId}">${pictureClipGeometry(shape, preset, x, y, w, h)}</clipPath></defs>`;
|
|
803
1586
|
clipAttr = ` clip-path="url(#${clipId})"`;
|
|
804
1587
|
}
|
|
805
1588
|
const brightness = getShapeImageBrightness(shape) ?? 0;
|
|
806
|
-
const contrast = getShapeImageContrast(shape) ??
|
|
1589
|
+
const contrast = getShapeImageContrast(shape) ?? 0;
|
|
807
1590
|
const opacity = getShapeImageOpacity(shape) ?? 1;
|
|
808
1591
|
const grayscale = isShapeImageGrayscale(shape);
|
|
809
1592
|
const biLevel = getShapeImageBiLevelThreshold(shape);
|
|
810
1593
|
const duotone = getShapeImageDuotone(pres, shape);
|
|
811
1594
|
let filterAttr = "";
|
|
812
|
-
if (brightness !== 0 || contrast !==
|
|
1595
|
+
if (brightness !== 0 || contrast !== 0 || grayscale || biLevel !== null || duotone && (duotone.firstColor || duotone.secondColor)) {
|
|
813
1596
|
const fid = mintId();
|
|
814
1597
|
const prims = [];
|
|
815
|
-
if (brightness !== 0 || contrast !==
|
|
1598
|
+
if (brightness !== 0 || contrast !== 0) {
|
|
1599
|
+
const midpoint = 128 / 255;
|
|
1600
|
+
const msoBrightness = Math.max(-1, Math.min(1, brightness));
|
|
1601
|
+
const slope = contrast >= 0 ? 128 / (128 - 127 * Math.min(1, contrast)) : (128 + 127 * Math.max(-1, contrast)) / 128;
|
|
1602
|
+
const intercept = (msoBrightness / 2 - midpoint) * slope + midpoint + msoBrightness / 2;
|
|
1603
|
+
prims.push(`<feComponentTransfer color-interpolation-filters="sRGB"><feFuncR type="linear" slope="${slope}" intercept="${intercept}"/><feFuncG type="linear" slope="${slope}" intercept="${intercept}"/><feFuncB type="linear" slope="${slope}" intercept="${intercept}"/></feComponentTransfer>`);
|
|
1604
|
+
}
|
|
816
1605
|
if (grayscale) prims.push(`<feColorMatrix type="matrix" values="0.2126 0.7152 0.0722 0 0 0.2126 0.7152 0.0722 0 0 0.2126 0.7152 0.0722 0 0 0 0 0 1 0"/>`);
|
|
817
1606
|
if (duotone && duotone.firstColor && duotone.secondColor) {
|
|
818
1607
|
const [r1, g1, b1] = hexChannels(duotone.firstColor);
|
|
819
1608
|
const [r2, g2, b2] = hexChannels(duotone.secondColor);
|
|
820
|
-
|
|
821
|
-
const tR = [];
|
|
822
|
-
const tG = [];
|
|
823
|
-
const tB = [];
|
|
824
|
-
for (let i = 0; i < steps; i++) {
|
|
825
|
-
const t = i / (steps - 1);
|
|
826
|
-
tR.push(((r1 + (r2 - r1) * t) / 255).toFixed(4));
|
|
827
|
-
tG.push(((g1 + (g2 - g1) * t) / 255).toFixed(4));
|
|
828
|
-
tB.push(((b1 + (b2 - b1) * t) / 255).toFixed(4));
|
|
829
|
-
}
|
|
830
|
-
prims.push(`<feColorMatrix type="matrix" values="0.2126 0.7152 0.0722 0 0 0.2126 0.7152 0.0722 0 0 0.2126 0.7152 0.0722 0 0 0 0 0 1 0"/>`, `<feComponentTransfer><feFuncR type="table" tableValues="${tR.join(" ")}"/><feFuncG type="table" tableValues="${tG.join(" ")}"/><feFuncB type="table" tableValues="${tB.join(" ")}"/></feComponentTransfer>`);
|
|
1609
|
+
prims.push(`<feColorMatrix color-interpolation-filters="sRGB" type="matrix" values="0.2126 0.7152 0.0722 0 0 0.2126 0.7152 0.0722 0 0 0.2126 0.7152 0.0722 0 0 0 0 0 1 0"/>`, `<feComponentTransfer color-interpolation-filters="sRGB"><feFuncR type="table" tableValues="${r1 / 255} ${r2 / 255}"/><feFuncG type="table" tableValues="${g1 / 255} ${g2 / 255}"/><feFuncB type="table" tableValues="${b1 / 255} ${b2 / 255}"/></feComponentTransfer>`);
|
|
831
1610
|
}
|
|
832
1611
|
if (biLevel !== null) {
|
|
833
1612
|
const t = biLevel / 100;
|
|
834
|
-
|
|
835
|
-
const
|
|
836
|
-
|
|
837
|
-
const tableStr = vals.join(" ");
|
|
838
|
-
prims.push(`<feComponentTransfer><feFuncR type="discrete" tableValues="${tableStr}"/><feFuncG type="discrete" tableValues="${tableStr}"/><feFuncB type="discrete" tableValues="${tableStr}"/></feComponentTransfer>`);
|
|
1613
|
+
prims.push(`<feColorMatrix type="matrix" values="0.2126 0.7152 0.0722 0 0 0.2126 0.7152 0.0722 0 0 0.2126 0.7152 0.0722 0 0 0 0 0 1 0"/>`);
|
|
1614
|
+
const shift = .5 - t;
|
|
1615
|
+
prims.push(`<feComponentTransfer><feFuncR type="linear" slope="1" intercept="${shift}"/><feFuncG type="linear" slope="1" intercept="${shift}"/><feFuncB type="linear" slope="1" intercept="${shift}"/></feComponentTransfer>`, "<feComponentTransfer><feFuncR type=\"discrete\" tableValues=\"0 1\"/><feFuncG type=\"discrete\" tableValues=\"0 1\"/><feFuncB type=\"discrete\" tableValues=\"0 1\"/></feComponentTransfer>");
|
|
839
1616
|
}
|
|
840
1617
|
clipDef += `<defs><filter id="${fid}">${prims.join("")}</filter></defs>`;
|
|
841
1618
|
filterAttr = ` filter="url(#${fid})"`;
|
|
842
1619
|
}
|
|
843
1620
|
const opacityAttr = opacity !== 1 ? ` opacity="${opacity.toFixed(3)}"` : "";
|
|
844
|
-
|
|
1621
|
+
const border = outline.stroke !== "none" && outline.strokeWidth > 0 ? `<g${transform} fill="none" stroke="${outline.stroke}" stroke-width="${E(outline.strokeWidth)}"${outline.strokeAttrs ? ` ${outline.strokeAttrs}` : ""}>${pictureClipGeometry(shape, preset, x, y, w, h)}</g>` : "";
|
|
1622
|
+
if (layout?.mode === "tile") {
|
|
1623
|
+
const intrinsic = getShapeImageIntrinsicSize(shape);
|
|
1624
|
+
if (intrinsic) {
|
|
1625
|
+
const pattern = imageTilePattern(dataUrl, layout, intrinsic, x, y, w, h, cropL, cropT, cropR, cropB);
|
|
1626
|
+
const geometry = pictureClipGeometry(shape, preset, x, y, w, h);
|
|
1627
|
+
return `${clipDef}${pattern.defs}<g${transform} fill="${pattern.fill}"${filterAttr}${opacityAttr}>${geometry}</g>${border}<g${transform}>${textOverlay}</g>`;
|
|
1628
|
+
}
|
|
1629
|
+
}
|
|
1630
|
+
return `${clipDef}<g${transform}${clipAttr}><image x="${E(imgX)}" y="${E(imgY)}" width="${E(imgW)}" height="${E(imgH)}" href="${dataUrl}" xlink:href="${dataUrl}" preserveAspectRatio="none"${filterAttr}${opacityAttr}/></g>${border}<g${transform}>${textOverlay}</g>`;
|
|
845
1631
|
}
|
|
846
1632
|
const linkUrl = getShapeImageLinkUrl(shape);
|
|
847
1633
|
const label = !bytes ? linkUrl ? `picture (link: ${linkUrl.length > 48 ? linkUrl.slice(0, 45) + "…" : linkUrl})` : "picture (no bytes)" : `picture (${format ?? "unknown"}${bytes ? `, ${bytes.byteLength} B` : ""})`;
|
|
848
1634
|
return `<g data-pptx-fallback="image"${transform}><rect x="${E(x)}" y="${E(y)}" width="${E(w)}" height="${E(h)}" fill="#F3F4F6" stroke="#9CA3AF" stroke-width="${E(9525)}" stroke-dasharray="${E(5e4)},${E(3e4)}"/>${renderPicturePlaceholderLabel(x, y, w, h, label)}${textOverlay}</g>`;
|
|
849
1635
|
};
|
|
1636
|
+
const imageTilePattern = (dataUrl, layout, intrinsic, x, y, w, h, cropL = 0, cropT = 0, cropR = 0, cropB = 0) => {
|
|
1637
|
+
const sourceW = intrinsic.width * (layout.scaleX ?? 1);
|
|
1638
|
+
const sourceH = intrinsic.height * (layout.scaleY ?? 1);
|
|
1639
|
+
const tileW = sourceW * (1 - cropL - cropR);
|
|
1640
|
+
const tileH = sourceH * (1 - cropT - cropB);
|
|
1641
|
+
if (tileW <= 0 || tileH <= 0) return {
|
|
1642
|
+
defs: "",
|
|
1643
|
+
fill: "none"
|
|
1644
|
+
};
|
|
1645
|
+
const alignment = layout.alignment ?? "tl";
|
|
1646
|
+
const horizontal = [
|
|
1647
|
+
"t",
|
|
1648
|
+
"ctr",
|
|
1649
|
+
"b"
|
|
1650
|
+
].includes(alignment) ? .5 : [
|
|
1651
|
+
"tr",
|
|
1652
|
+
"r",
|
|
1653
|
+
"br"
|
|
1654
|
+
].includes(alignment) ? 1 : 0;
|
|
1655
|
+
const vertical = [
|
|
1656
|
+
"l",
|
|
1657
|
+
"ctr",
|
|
1658
|
+
"r"
|
|
1659
|
+
].includes(alignment) ? .5 : [
|
|
1660
|
+
"bl",
|
|
1661
|
+
"b",
|
|
1662
|
+
"br"
|
|
1663
|
+
].includes(alignment) ? 1 : 0;
|
|
1664
|
+
const tileX = x + (w - tileW) * horizontal + (layout.offsetX ?? 0);
|
|
1665
|
+
const tileY = y + (h - tileH) * vertical + (layout.offsetY ?? 0);
|
|
1666
|
+
const mirrorX = layout.flip === "x" || layout.flip === "xy";
|
|
1667
|
+
const mirrorY = layout.flip === "y" || layout.flip === "xy";
|
|
1668
|
+
const patternId = mintId();
|
|
1669
|
+
const images = [];
|
|
1670
|
+
for (let row = 0; row < (mirrorY ? 2 : 1); row++) for (let col = 0; col < (mirrorX ? 2 : 1); col++) {
|
|
1671
|
+
const reflection = col || row ? ` transform="translate(${E(col * 2 * tileW)} ${E(row * 2 * tileH)}) scale(${col ? -1 : 1} ${row ? -1 : 1})"` : "";
|
|
1672
|
+
images.push(`<g${reflection}><svg width="${E(tileW)}" height="${E(tileH)}" overflow="hidden"><image x="${E(-sourceW * cropL)}" y="${E(-sourceH * cropT)}" width="${E(sourceW)}" height="${E(sourceH)}" href="${dataUrl}" xlink:href="${dataUrl}" preserveAspectRatio="none"/></svg></g>`);
|
|
1673
|
+
}
|
|
1674
|
+
return {
|
|
1675
|
+
defs: `<defs><pattern id="${patternId}" patternUnits="userSpaceOnUse" x="${E(tileX)}" y="${E(tileY)}" width="${E(tileW * (mirrorX ? 2 : 1))}" height="${E(tileH * (mirrorY ? 2 : 1))}">${images.join("")}</pattern></defs>`,
|
|
1676
|
+
fill: `url(#${patternId})`
|
|
1677
|
+
};
|
|
1678
|
+
};
|
|
1679
|
+
const pictureClipGeometry = (shape, preset, x, y, w, h) => {
|
|
1680
|
+
if (preset === "ellipse") return `<ellipse cx="${E(x + w / 2)}" cy="${E(y + h / 2)}" rx="${E(w / 2)}" ry="${E(h / 2)}"/>`;
|
|
1681
|
+
if (preset === "roundRect") {
|
|
1682
|
+
const radius = E(Math.min(w, h) * Math.max(0, Math.min(.5, (getShapeAdjustValues(shape).adj ?? 16667) / 1e5)));
|
|
1683
|
+
return `<rect x="${E(x)}" y="${E(y)}" width="${E(w)}" height="${E(h)}" rx="${radius}" ry="${radius}"/>`;
|
|
1684
|
+
}
|
|
1685
|
+
const path = PRESET_PATHS[preset];
|
|
1686
|
+
if (path) return `<path d="${path(x / EMU_PER_PX, y / EMU_PER_PX, w / EMU_PER_PX, h / EMU_PER_PX)}" clip-rule="evenodd"/>`;
|
|
1687
|
+
const points = PRESET_POINTS[preset];
|
|
1688
|
+
if (points) return `<polygon points="${points(w / EMU_PER_PX, h / EMU_PER_PX).map(([nx, ny]) => `${E(x + nx * w)},${E(y + ny * h)}`).join(" ")}"/>`;
|
|
1689
|
+
return `<rect x="${E(x)}" y="${E(y)}" width="${E(w)}" height="${E(h)}"/>`;
|
|
1690
|
+
};
|
|
850
1691
|
const renderPicturePlaceholderLabel = (x, y, w, h, text) => {
|
|
851
1692
|
const cx = x + w / 2;
|
|
852
1693
|
const cy = y + h / 2;
|
|
@@ -917,10 +1758,88 @@ let nextDefId = 0;
|
|
|
917
1758
|
const mintId = () => `pkdef-${(nextDefId++).toString(36)}`;
|
|
918
1759
|
let activeColorMap = null;
|
|
919
1760
|
let activeDeckTextColor = "#000000";
|
|
920
|
-
|
|
1761
|
+
let activeSlideNumber = "1";
|
|
1762
|
+
const gradientDef = (grad, theme, transform = "", box) => {
|
|
921
1763
|
const id = mintId();
|
|
1764
|
+
const units = box ? "userSpaceOnUse" : "objectBoundingBox";
|
|
1765
|
+
if (box) transform = ` gradientTransform="matrix(${[
|
|
1766
|
+
box.w,
|
|
1767
|
+
0,
|
|
1768
|
+
0,
|
|
1769
|
+
box.h,
|
|
1770
|
+
box.x,
|
|
1771
|
+
box.y
|
|
1772
|
+
].map((value) => value.toFixed(4)).join(" ")})"`;
|
|
922
1773
|
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("");
|
|
1774
|
+
const stops = orderedStops.map((s) => `<stop offset="${s.offset.toFixed(4)}" stop-color="${s.resolvedColor ?? resolveColor(s.color, theme, "#E5E7EB")}"${s.opacity !== void 0 ? ` stop-opacity="${s.opacity}"` : ""}/>`).join("");
|
|
1775
|
+
if (grad.path === "rect") {
|
|
1776
|
+
const focus = grad.focus ?? {
|
|
1777
|
+
left: .5,
|
|
1778
|
+
top: .5,
|
|
1779
|
+
right: .5,
|
|
1780
|
+
bottom: .5
|
|
1781
|
+
};
|
|
1782
|
+
const tile = grad.tileRect ?? {
|
|
1783
|
+
left: 0,
|
|
1784
|
+
top: 0,
|
|
1785
|
+
right: 0,
|
|
1786
|
+
bottom: 0
|
|
1787
|
+
};
|
|
1788
|
+
const l = tile.left;
|
|
1789
|
+
const t = tile.top;
|
|
1790
|
+
const r = 1 - tile.right;
|
|
1791
|
+
const b = 1 - tile.bottom;
|
|
1792
|
+
const fl = focus.left;
|
|
1793
|
+
const ft = focus.top;
|
|
1794
|
+
const fr = 1 - focus.right;
|
|
1795
|
+
const fb = 1 - focus.bottom;
|
|
1796
|
+
if (l <= fl && fl <= fr && fr <= r && t <= ft && ft <= fb && fb <= b && l < r && t < b) {
|
|
1797
|
+
const sides = [
|
|
1798
|
+
{
|
|
1799
|
+
points: `${l},${t} ${r},${t} ${fr},${ft} ${fl},${ft}`,
|
|
1800
|
+
x1: l,
|
|
1801
|
+
y1: t,
|
|
1802
|
+
x2: l,
|
|
1803
|
+
y2: ft
|
|
1804
|
+
},
|
|
1805
|
+
{
|
|
1806
|
+
points: `${r},${t} ${r},${b} ${fr},${fb} ${fr},${ft}`,
|
|
1807
|
+
x1: r,
|
|
1808
|
+
y1: t,
|
|
1809
|
+
x2: fr,
|
|
1810
|
+
y2: t
|
|
1811
|
+
},
|
|
1812
|
+
{
|
|
1813
|
+
points: `${r},${b} ${l},${b} ${fl},${fb} ${fr},${fb}`,
|
|
1814
|
+
x1: l,
|
|
1815
|
+
y1: b,
|
|
1816
|
+
x2: l,
|
|
1817
|
+
y2: fb
|
|
1818
|
+
},
|
|
1819
|
+
{
|
|
1820
|
+
points: `${l},${b} ${l},${t} ${fl},${ft} ${fl},${fb}`,
|
|
1821
|
+
x1: l,
|
|
1822
|
+
y1: t,
|
|
1823
|
+
x2: fl,
|
|
1824
|
+
y2: t
|
|
1825
|
+
}
|
|
1826
|
+
];
|
|
1827
|
+
let gradients = "";
|
|
1828
|
+
let polygons = "";
|
|
1829
|
+
for (const side of sides) {
|
|
1830
|
+
if (side.x1 === side.x2 && side.y1 === side.y2) continue;
|
|
1831
|
+
const sideId = mintId();
|
|
1832
|
+
gradients += `<linearGradient id="${sideId}" gradientUnits="userSpaceOnUse" x1="${side.x2}" y1="${side.y2}" x2="${side.x1}" y2="${side.y1}">${stops}</linearGradient>`;
|
|
1833
|
+
polygons += `<polygon points="${side.points}" fill="url(#${sideId})"/>`;
|
|
1834
|
+
}
|
|
1835
|
+
const first = orderedStops[0];
|
|
1836
|
+
const center = first && fr > fl && fb > ft ? `<rect x="${fl}" y="${ft}" width="${fr - fl}" height="${fb - ft}" fill="${first.resolvedColor ?? resolveColor(first.color, theme, "#E5E7EB")}"${first.opacity !== void 0 ? ` fill-opacity="${first.opacity}"` : ""}/>` : "";
|
|
1837
|
+
return {
|
|
1838
|
+
defs: `<defs>${gradients}<pattern id="${id}" patternUnits="${units}" patternContentUnits="${units}" x="${l}" y="${t}" width="${r - l}" height="${b - t}"${transform.replace("gradientTransform", "patternTransform")}><g shape-rendering="crispEdges" transform="translate(${-l} ${-t})">${polygons}${center}</g></pattern></defs>`,
|
|
1839
|
+
fillAttr: `url(#${id})`
|
|
1840
|
+
};
|
|
1841
|
+
}
|
|
1842
|
+
}
|
|
924
1843
|
if (grad.path === "circle" || grad.path === "rect" || grad.path === "shape") {
|
|
925
1844
|
const focus = grad.focus ?? {
|
|
926
1845
|
left: .5,
|
|
@@ -928,11 +1847,10 @@ const gradientDef = (grad, theme) => {
|
|
|
928
1847
|
right: .5,
|
|
929
1848
|
bottom: .5
|
|
930
1849
|
};
|
|
931
|
-
const cx = (focus.left + focus.right) / 2;
|
|
932
|
-
const cy = (focus.top + focus.bottom) / 2;
|
|
933
|
-
const reversed = orderedStops.slice().reverse().map((s) => `<stop offset="${(1 - s.offset).toFixed(4)}" stop-color="${resolveColor(s.color, theme, "#E5E7EB")}"/>`).join("");
|
|
1850
|
+
const cx = (focus.left + 1 - focus.right) / 2;
|
|
1851
|
+
const cy = (focus.top + 1 - focus.bottom) / 2;
|
|
934
1852
|
return {
|
|
935
|
-
defs: `<defs><radialGradient id="${id}" gradientUnits="
|
|
1853
|
+
defs: `<defs><radialGradient id="${id}" gradientUnits="${units}"${box ? transform : ""} cx="${cx.toFixed(4)}" cy="${cy.toFixed(4)}" r="${Math.max(.5, Math.max(cx, cy, 1 - cx, 1 - cy)).toFixed(4)}">${stops}</radialGradient></defs>`,
|
|
936
1854
|
fillAttr: `url(#${id})`
|
|
937
1855
|
};
|
|
938
1856
|
}
|
|
@@ -944,7 +1862,7 @@ const gradientDef = (grad, theme) => {
|
|
|
944
1862
|
const x2 = .5 + dx;
|
|
945
1863
|
const y2 = .5 + dy;
|
|
946
1864
|
return {
|
|
947
|
-
defs: `<defs><linearGradient id="${id}" gradientUnits="
|
|
1865
|
+
defs: `<defs><linearGradient id="${id}" gradientUnits="${units}"${transform} x1="${x1.toFixed(4)}" y1="${y1.toFixed(4)}" x2="${x2.toFixed(4)}" y2="${y2.toFixed(4)}">${stops}</linearGradient></defs>`,
|
|
948
1866
|
fillAttr: `url(#${id})`
|
|
949
1867
|
};
|
|
950
1868
|
};
|
|
@@ -1020,6 +1938,24 @@ const patternDef = (pat) => {
|
|
|
1020
1938
|
fillAttr: `url(#${id})`
|
|
1021
1939
|
};
|
|
1022
1940
|
};
|
|
1941
|
+
const textFillPaint = (fill, theme) => (box) => {
|
|
1942
|
+
if (fill.kind === "pattern") {
|
|
1943
|
+
const built = patternDef({
|
|
1944
|
+
preset: fill.preset,
|
|
1945
|
+
foreground: resolveColor(fill.foreground, theme, "#000000"),
|
|
1946
|
+
background: resolveColor(fill.background, theme, "#FFFFFF")
|
|
1947
|
+
});
|
|
1948
|
+
return {
|
|
1949
|
+
defs: built.defs,
|
|
1950
|
+
fill: built.fillAttr
|
|
1951
|
+
};
|
|
1952
|
+
}
|
|
1953
|
+
const built = gradientDef(fill, theme, "", box);
|
|
1954
|
+
return {
|
|
1955
|
+
defs: built.defs,
|
|
1956
|
+
fill: built.fillAttr
|
|
1957
|
+
};
|
|
1958
|
+
};
|
|
1023
1959
|
const DASH_PATTERNS = {
|
|
1024
1960
|
solid: "",
|
|
1025
1961
|
dot: "1 3",
|
|
@@ -1068,13 +2004,39 @@ const paint = (shape, fill, stroke, theme, isPlaceholder, pres) => {
|
|
|
1068
2004
|
let resolved = null;
|
|
1069
2005
|
if (shape && pres) resolved = getShapeFillColorResolved(pres, shape);
|
|
1070
2006
|
fillColor = resolved ?? resolveColor(fill.color, theme, "#E5E7EB");
|
|
1071
|
-
const opacity = shape ? getShapeFillOpacity(shape) : null;
|
|
2007
|
+
const opacity = shape ? getShapeFillOpacity(shape, pres) : null;
|
|
1072
2008
|
if (opacity !== null && opacity < 1) fillAttrs = ` fill-opacity="${opacity.toFixed(3)}"`;
|
|
1073
2009
|
} else if (fill.kind === "none") fillColor = "none";
|
|
1074
2010
|
else if (fill.kind === "gradient") {
|
|
1075
2011
|
const grad = shape ? pres ? getShapeGradientFillEffective(pres, shape) : getShapeGradientFill(shape) : null;
|
|
1076
2012
|
if (grad) {
|
|
1077
|
-
|
|
2013
|
+
let transform = "";
|
|
2014
|
+
const bounds = shape && (pres ? getShapeBoundsResolved(pres, shape) : getShapeBounds(shape));
|
|
2015
|
+
if (grad.rotateWithShape === false && shape && bounds && bounds.w > 0 && bounds.h > 0) {
|
|
2016
|
+
const angle = getShapeRotation(shape) * Math.PI / 180;
|
|
2017
|
+
const cos = Math.cos(angle);
|
|
2018
|
+
const sin = Math.sin(angle);
|
|
2019
|
+
const w = bounds.w;
|
|
2020
|
+
const h = bounds.h;
|
|
2021
|
+
const rotatedW = Math.abs(cos) * w + Math.abs(sin) * h;
|
|
2022
|
+
const rotatedH = Math.abs(sin) * w + Math.abs(cos) * h;
|
|
2023
|
+
const flip = getShapeFlip(shape);
|
|
2024
|
+
const sx = flip?.horizontal ? -1 : 1;
|
|
2025
|
+
const sy = flip?.vertical ? -1 : 1;
|
|
2026
|
+
const a = sx * cos * rotatedW / w;
|
|
2027
|
+
const b = -sy * sin * rotatedW / h;
|
|
2028
|
+
const c = sx * sin * rotatedH / w;
|
|
2029
|
+
const d = sy * cos * rotatedH / h;
|
|
2030
|
+
transform = ` gradientTransform="matrix(${[
|
|
2031
|
+
a,
|
|
2032
|
+
b,
|
|
2033
|
+
c,
|
|
2034
|
+
d,
|
|
2035
|
+
(1 - a - c) / 2,
|
|
2036
|
+
(1 - b - d) / 2
|
|
2037
|
+
].map((value) => value.toFixed(6)).join(" ")})"`;
|
|
2038
|
+
}
|
|
2039
|
+
const built = gradientDef(grad, theme, transform);
|
|
1078
2040
|
defs = built.defs;
|
|
1079
2041
|
fillColor = built.fillAttr;
|
|
1080
2042
|
} else fillColor = (shape && pres ? getShapeFillColorResolved(pres, shape) : null) ?? "none";
|
|
@@ -1097,9 +2059,9 @@ const paint = (shape, fill, stroke, theme, isPlaceholder, pres) => {
|
|
|
1097
2059
|
strokeColor = resolved ?? resolveColor(stroke.color, theme, "#9CA3AF");
|
|
1098
2060
|
strokeWidth = stroke.widthEmu ?? 9525;
|
|
1099
2061
|
if (shape) {
|
|
1100
|
-
const opacity = getShapeStrokeOpacity(shape);
|
|
2062
|
+
const opacity = getShapeStrokeOpacity(shape, pres);
|
|
1101
2063
|
if (opacity !== null && opacity < 1) strokeAttrParts.push(`stroke-opacity="${opacity.toFixed(3)}"`);
|
|
1102
|
-
const dash = getShapeStrokeDash(shape);
|
|
2064
|
+
const dash = getShapeStrokeDash(shape, pres);
|
|
1103
2065
|
if (dash && dash !== "solid") {
|
|
1104
2066
|
const pattern = DASH_PATTERNS[dash];
|
|
1105
2067
|
if (pattern) {
|
|
@@ -1108,17 +2070,17 @@ const paint = (shape, fill, stroke, theme, isPlaceholder, pres) => {
|
|
|
1108
2070
|
strokeAttrParts.push(`stroke-dasharray="${arr}"`);
|
|
1109
2071
|
}
|
|
1110
2072
|
}
|
|
1111
|
-
const cap = getShapeStrokeCap(shape);
|
|
2073
|
+
const cap = getShapeStrokeCap(shape, pres);
|
|
1112
2074
|
if (cap === "rnd") strokeAttrParts.push("stroke-linecap=\"round\"");
|
|
1113
2075
|
else if (cap === "sq") strokeAttrParts.push("stroke-linecap=\"square\"");
|
|
1114
2076
|
else if (cap === "flat") strokeAttrParts.push("stroke-linecap=\"butt\"");
|
|
1115
|
-
const join = getShapeStrokeJoin(shape);
|
|
2077
|
+
const join = getShapeStrokeJoin(shape, pres);
|
|
1116
2078
|
if (join === "round") strokeAttrParts.push("stroke-linejoin=\"round\"");
|
|
1117
2079
|
else if (join === "bevel") strokeAttrParts.push("stroke-linejoin=\"bevel\"");
|
|
1118
2080
|
else if (join === "miter") strokeAttrParts.push("stroke-linejoin=\"miter\"");
|
|
1119
|
-
if (getShapeStrokeCompound(shape) === "dbl") strokeWidth = Math.max(strokeWidth, 19050);
|
|
1120
|
-
const head = getShapeStrokeArrow(shape, "head");
|
|
1121
|
-
const tail = getShapeStrokeArrow(shape, "tail");
|
|
2081
|
+
if (getShapeStrokeCompound(shape, pres) === "dbl") strokeWidth = Math.max(strokeWidth, 19050);
|
|
2082
|
+
const head = getShapeStrokeArrow(shape, "head", pres);
|
|
2083
|
+
const tail = getShapeStrokeArrow(shape, "tail", pres);
|
|
1122
2084
|
if (head && head.type !== "none") {
|
|
1123
2085
|
const m = buildArrowMarker(head.type, head.width, head.length, strokeColor, "auto-start-reverse");
|
|
1124
2086
|
defs += m.def;
|
|
@@ -2045,116 +3007,86 @@ const ALIGNMENT_TO_CSS = {
|
|
|
2045
3007
|
right: "right",
|
|
2046
3008
|
justify: "justify"
|
|
2047
3009
|
};
|
|
2048
|
-
const ANCHOR_TO_CSS = {
|
|
2049
|
-
top: "flex-start",
|
|
2050
|
-
center: "center",
|
|
2051
|
-
bottom: "flex-end"
|
|
2052
|
-
};
|
|
2053
|
-
const placeholderDefaultPt = (phType) => {
|
|
2054
|
-
if (phType === "title" || phType === "ctrTitle") return DEFAULT_TITLE_PT;
|
|
2055
|
-
if (phType === "subTitle") return 32;
|
|
2056
|
-
if (phType === "ftr" || phType === "dt" || phType === "sldNum") return 12;
|
|
2057
|
-
return DEFAULT_BODY_PT;
|
|
2058
|
-
};
|
|
2059
|
-
const bulletChar = (level) => level <= 0 ? "•" : level === 1 ? "◦" : "▪";
|
|
2060
|
-
const
|
|
2061
|
-
if (
|
|
2062
|
-
|
|
2063
|
-
return
|
|
2064
|
-
|
|
2065
|
-
|
|
2066
|
-
|
|
2067
|
-
|
|
2068
|
-
|
|
2069
|
-
|
|
2070
|
-
[500, "D"],
|
|
2071
|
-
[400, "CD"],
|
|
2072
|
-
[100, "C"],
|
|
2073
|
-
[90, "XC"],
|
|
2074
|
-
[50, "L"],
|
|
2075
|
-
[40, "XL"],
|
|
2076
|
-
[10, "X"],
|
|
2077
|
-
[9, "IX"],
|
|
2078
|
-
[5, "V"],
|
|
2079
|
-
[4, "IV"],
|
|
2080
|
-
[1, "I"]
|
|
2081
|
-
];
|
|
2082
|
-
let out = "";
|
|
2083
|
-
let r = n;
|
|
2084
|
-
for (const [v, s] of map) while (r >= v) {
|
|
2085
|
-
out += s;
|
|
2086
|
-
r -= v;
|
|
2087
|
-
}
|
|
2088
|
-
return out;
|
|
2089
|
-
};
|
|
2090
|
-
const toAlpha = (n) => {
|
|
2091
|
-
if (n <= 0) return String(n);
|
|
2092
|
-
let r = n;
|
|
2093
|
-
let out = "";
|
|
2094
|
-
while (r > 0) {
|
|
2095
|
-
r -= 1;
|
|
2096
|
-
out = String.fromCharCode(65 + r % 26) + out;
|
|
2097
|
-
r = Math.floor(r / 26);
|
|
2098
|
-
}
|
|
2099
|
-
return out;
|
|
2100
|
-
};
|
|
2101
|
-
const formatAutoNum = (token, n) => {
|
|
2102
|
-
const arabic = String(n);
|
|
2103
|
-
switch (token) {
|
|
2104
|
-
case "arabicPlain": return arabic;
|
|
2105
|
-
case "arabicPeriod": return `${arabic}.`;
|
|
2106
|
-
case "arabicParenR": return `${arabic})`;
|
|
2107
|
-
case "arabicParenBoth": return `(${arabic})`;
|
|
2108
|
-
case "romanUcPeriod": return `${toRoman(n)}.`;
|
|
2109
|
-
case "romanLcPeriod": return `${toRoman(n).toLowerCase()}.`;
|
|
2110
|
-
case "romanUcParenR": return `${toRoman(n)})`;
|
|
2111
|
-
case "romanLcParenR": return `${toRoman(n).toLowerCase()})`;
|
|
2112
|
-
case "romanUcParenBoth": return `(${toRoman(n)})`;
|
|
2113
|
-
case "romanLcParenBoth": return `(${toRoman(n).toLowerCase()})`;
|
|
2114
|
-
case "alphaUcPeriod": return `${toAlpha(n)}.`;
|
|
2115
|
-
case "alphaLcPeriod": return `${toAlpha(n).toLowerCase()}.`;
|
|
2116
|
-
case "alphaUcParenR": return `${toAlpha(n)})`;
|
|
2117
|
-
case "alphaLcParenR": return `${toAlpha(n).toLowerCase()})`;
|
|
2118
|
-
case "alphaUcParenBoth": return `(${toAlpha(n)})`;
|
|
2119
|
-
case "alphaLcParenBoth": return `(${toAlpha(n).toLowerCase()})`;
|
|
2120
|
-
default: return `${arabic}.`;
|
|
2121
|
-
}
|
|
3010
|
+
const ANCHOR_TO_CSS = {
|
|
3011
|
+
top: "flex-start",
|
|
3012
|
+
center: "center",
|
|
3013
|
+
bottom: "flex-end"
|
|
3014
|
+
};
|
|
3015
|
+
const placeholderDefaultPt = (phType) => {
|
|
3016
|
+
if (phType === "title" || phType === "ctrTitle") return DEFAULT_TITLE_PT;
|
|
3017
|
+
if (phType === "subTitle") return 32;
|
|
3018
|
+
if (phType === "ftr" || phType === "dt" || phType === "sldNum") return 12;
|
|
3019
|
+
return DEFAULT_BODY_PT;
|
|
3020
|
+
};
|
|
3021
|
+
const bulletChar = (level) => level <= 0 ? "•" : level === 1 ? "◦" : "▪";
|
|
3022
|
+
const cssColorWithOpacity = (hex, opacity) => {
|
|
3023
|
+
if (opacity === void 0 || opacity >= 1) return hex;
|
|
3024
|
+
const match = /^#([0-9a-f]{2})([0-9a-f]{2})([0-9a-f]{2})$/i.exec(hex);
|
|
3025
|
+
if (!match) return hex;
|
|
3026
|
+
const [r, g, b] = [
|
|
3027
|
+
match[1],
|
|
3028
|
+
match[2],
|
|
3029
|
+
match[3]
|
|
3030
|
+
].map((part) => Number.parseInt(part, 16));
|
|
3031
|
+
return `rgba(${r},${g},${b},${Math.max(0, opacity).toFixed(3)})`;
|
|
2122
3032
|
};
|
|
2123
|
-
const renderRun = (text, format, theme, effectivePt, _wasDefault = false) => {
|
|
3033
|
+
const renderRun = (text, format, theme, effectivePt, defaultColor, _wasDefault = false, suppressLineHeight = false, lineHeightOverride = void 0) => {
|
|
2124
3034
|
if (text === "") return "";
|
|
3035
|
+
if (format?.textFill) {
|
|
3036
|
+
const { textFill: _fill, outline: _outline, shadow: _shadow, glow: _glow, underline: _underline, strike: _strike, ...layout } = format;
|
|
3037
|
+
return renderRun(text, {
|
|
3038
|
+
...layout,
|
|
3039
|
+
color: "transparent"
|
|
3040
|
+
}, theme, effectivePt, defaultColor, _wasDefault, suppressLineHeight, lineHeightOverride);
|
|
3041
|
+
}
|
|
2125
3042
|
const styles = [];
|
|
2126
3043
|
styles.push(`font-size:${(effectivePt * PX_PER_PT).toFixed(2)}px`);
|
|
2127
|
-
styles.push(`line-height:1.05
|
|
3044
|
+
styles.push(suppressLineHeight ? "line-height:0;vertical-align:top" : lineHeightOverride !== void 0 ? `line-height:${lineHeightOverride}` : "line-height:1.05");
|
|
2128
3045
|
if (format?.font) styles.push(`font-family:${escapeXml(format.font)}, ${DEFAULT_FONT}`);
|
|
2129
3046
|
if (format?.bold) styles.push("font-weight:700");
|
|
2130
3047
|
if (format?.italic) styles.push("font-style:italic");
|
|
2131
3048
|
const underline = format?.underline;
|
|
2132
3049
|
const strike = format?.strike;
|
|
2133
3050
|
const hasUnderline = underline !== void 0 && underline !== false && underline !== "none";
|
|
2134
|
-
|
|
2135
|
-
const
|
|
2136
|
-
const nestedWavyUnderline = hasUnderline && hasStrike && isWavyUnderline;
|
|
2137
|
-
if (nestedWavyUnderline) styles.push("text-decoration:line-through");
|
|
2138
|
-
else if (hasUnderline && hasStrike) styles.push("text-decoration:underline line-through");
|
|
2139
|
-
else if (hasUnderline) {
|
|
2140
|
-
styles.push("text-decoration:underline");
|
|
2141
|
-
if (isWavyUnderline) styles.push("text-decoration-style:wavy");
|
|
2142
|
-
} else if (hasStrike) styles.push("text-decoration:line-through");
|
|
3051
|
+
if (strike !== void 0 && strike !== false && strike !== "noStrike") styles.push(`text-decoration:line-through${strike === "dblStrike" ? " double" : ""}`);
|
|
3052
|
+
const underlineCss = hasUnderline ? textUnderlineStyle(underline, resolveColor(format?.underlineColor ?? format?.color, theme, defaultColor), format?.underlineColor !== void 0 && format.underlineColor !== null) : "";
|
|
2143
3053
|
if (format?.color !== void 0 && format.color !== null) styles.push(`color:${resolveColor(format.color, theme, "#000000")}`);
|
|
2144
3054
|
if (format?.spc !== void 0 && format.spc !== 0) {
|
|
2145
3055
|
const trackingPx = format.spc / 100 * PX_PER_PT;
|
|
2146
3056
|
styles.push(`letter-spacing:${trackingPx.toFixed(3)}px`);
|
|
2147
3057
|
}
|
|
2148
3058
|
if (format?.baseline !== void 0 && format.baseline !== 0) {
|
|
2149
|
-
|
|
2150
|
-
styles.push(`
|
|
2151
|
-
styles.push("font-size:0.65em");
|
|
3059
|
+
styles.push(`vertical-align:${(format.baseline * effectivePt * PX_PER_PT).toFixed(2)}px`);
|
|
3060
|
+
styles.push(`font-size:${(effectivePt * PX_PER_PT * .65).toFixed(2)}px`);
|
|
2152
3061
|
}
|
|
2153
3062
|
if (format?.cap === "all") styles.push("text-transform:uppercase");
|
|
2154
3063
|
else if (format?.cap === "small") styles.push("font-variant:small-caps");
|
|
3064
|
+
if (format?.kern !== void 0) styles.push(`font-kerning:${format.kern > 0 && effectivePt >= format.kern / 100 ? "normal" : "none"}`);
|
|
2155
3065
|
if (format?.highlight !== void 0 && format.highlight !== null) styles.push(`background-color:${resolveColor(format.highlight, theme, "#FFFF00")}`);
|
|
3066
|
+
const outline = format?.outline;
|
|
3067
|
+
if (outline && outline.color) {
|
|
3068
|
+
const widthPx = (outline.widthEmu ?? 9525) / EMU_PER_PX;
|
|
3069
|
+
styles.push(`-webkit-text-stroke:${widthPx.toFixed(2)}px ${resolveColor(outline.color, theme, "#000000")}`);
|
|
3070
|
+
styles.push("paint-order:stroke fill");
|
|
3071
|
+
}
|
|
3072
|
+
const textShadows = [];
|
|
3073
|
+
const glow = format?.glow;
|
|
3074
|
+
if (glow?.color) {
|
|
3075
|
+
const radiusPx = (glow.radiusEmu ?? 63500) / EMU_PER_PX;
|
|
3076
|
+
const color = cssColorWithOpacity(resolveColor(glow.color, theme, "#FFFF00"), glow.opacity);
|
|
3077
|
+
textShadows.push(`0 0 ${radiusPx.toFixed(2)}px ${color}`);
|
|
3078
|
+
}
|
|
3079
|
+
const shadow = format?.shadow;
|
|
3080
|
+
if (shadow) {
|
|
3081
|
+
const distancePx = (shadow.offsetEmu ?? 38100) / EMU_PER_PX;
|
|
3082
|
+
const radians = (shadow.angleDeg ?? 45) * Math.PI / 180;
|
|
3083
|
+
const blurPx = (shadow.blurEmu ?? 50800) / EMU_PER_PX;
|
|
3084
|
+
const color = cssColorWithOpacity(resolveColor(shadow.color ?? "#000000", theme, "#000000"), shadow.opacity);
|
|
3085
|
+
textShadows.push(`${(Math.cos(radians) * distancePx).toFixed(2)}px ${(Math.sin(radians) * distancePx).toFixed(2)}px ${blurPx.toFixed(2)}px ${color}`);
|
|
3086
|
+
}
|
|
3087
|
+
if (textShadows.length) styles.push(`text-shadow:${textShadows.join(",")}`);
|
|
2156
3088
|
const html = text.split("\n").map((part) => escapeXml(part)).join("<br/>");
|
|
2157
|
-
const content =
|
|
3089
|
+
const content = !hasUnderline ? html : underline === "words" ? text.split(/(\s+)/u).map((part) => /^\s+$/u.test(part) ? escapeXml(part).replaceAll("\n", "<br/>") : part === "" ? "" : `<span style="${underlineCss}">${escapeXml(part)}</span>`).join("") : `<span style="${underlineCss}">${html}</span>`;
|
|
2158
3090
|
return `<span style="${styles.join(";")}">${content}</span>`;
|
|
2159
3091
|
};
|
|
2160
3092
|
const LINE_HEIGHT = 1.05;
|
|
@@ -2163,13 +3095,33 @@ const AUTOFIT_STEP = .05;
|
|
|
2163
3095
|
const underlineStyleOf = (fmt) => {
|
|
2164
3096
|
const u = fmt?.underline;
|
|
2165
3097
|
if (u === void 0 || u === false || u === "none") return "none";
|
|
2166
|
-
|
|
2167
|
-
|
|
3098
|
+
switch (u) {
|
|
3099
|
+
case "words":
|
|
3100
|
+
case "sng":
|
|
3101
|
+
case "dbl":
|
|
3102
|
+
case "heavy":
|
|
3103
|
+
case "dotted":
|
|
3104
|
+
case "dottedHeavy":
|
|
3105
|
+
case "dash":
|
|
3106
|
+
case "dashHeavy":
|
|
3107
|
+
case "dashLong":
|
|
3108
|
+
case "dashLongHeavy":
|
|
3109
|
+
case "dotDash":
|
|
3110
|
+
case "dotDashHeavy":
|
|
3111
|
+
case "dotDotDash":
|
|
3112
|
+
case "dotDotDashHeavy":
|
|
3113
|
+
case "wavy":
|
|
3114
|
+
case "wavyHeavy":
|
|
3115
|
+
case "wavyDbl": return u;
|
|
3116
|
+
default: return "sng";
|
|
3117
|
+
}
|
|
2168
3118
|
};
|
|
2169
|
-
const
|
|
3119
|
+
const strikeStyleOf = (fmt) => {
|
|
2170
3120
|
const s = fmt?.strike;
|
|
3121
|
+
if (s === "dblStrike") return "double";
|
|
2171
3122
|
return s !== void 0 && s !== false && s !== "noStrike";
|
|
2172
3123
|
};
|
|
3124
|
+
const hasPaintedTextFill = (run) => !!run.fmt?.textFill && !run.href;
|
|
2173
3125
|
const alignOf = (a) => a === "center" || a === "right" || a === "justify" ? a : "left";
|
|
2174
3126
|
const verticalLayoutOf = (vert) => {
|
|
2175
3127
|
switch (vert) {
|
|
@@ -2182,13 +3134,44 @@ const verticalLayoutOf = (vert) => {
|
|
|
2182
3134
|
case null: return "none";
|
|
2183
3135
|
}
|
|
2184
3136
|
};
|
|
3137
|
+
const graphemeSegmenter = new Intl.Segmenter(void 0, { granularity: "grapheme" });
|
|
3138
|
+
const graphemesOf = (text) => Array.from(graphemeSegmenter.segment(text), ({ segment }) => segment);
|
|
3139
|
+
const isSmallCapsLowercase = (grapheme) => grapheme.toLowerCase() === grapheme && grapheme.toUpperCase() !== grapheme;
|
|
3140
|
+
const smallCapsParts = (text, base) => {
|
|
3141
|
+
const parts = [];
|
|
3142
|
+
let current = "";
|
|
3143
|
+
let currentSmall = false;
|
|
3144
|
+
const flush = () => {
|
|
3145
|
+
if (current === "") return;
|
|
3146
|
+
parts.push({
|
|
3147
|
+
...base,
|
|
3148
|
+
text: current,
|
|
3149
|
+
...currentSmall ? { smallCaps: true } : {}
|
|
3150
|
+
});
|
|
3151
|
+
current = "";
|
|
3152
|
+
};
|
|
3153
|
+
for (const grapheme of graphemesOf(text)) {
|
|
3154
|
+
const small = isSmallCapsLowercase(grapheme);
|
|
3155
|
+
if (current !== "" && small !== currentSmall) flush();
|
|
3156
|
+
currentSmall = small;
|
|
3157
|
+
current += grapheme.toUpperCase();
|
|
3158
|
+
}
|
|
3159
|
+
flush();
|
|
3160
|
+
return parts;
|
|
3161
|
+
};
|
|
2185
3162
|
const buildSvgTextInput = (a) => {
|
|
2186
3163
|
const scale = a.autoFitScale;
|
|
2187
3164
|
const paragraphs = a.paraData.map((para, pi) => {
|
|
2188
3165
|
const pieces = [];
|
|
2189
3166
|
for (const run of para.runs) {
|
|
2190
|
-
if (run.text === "\n"
|
|
2191
|
-
pieces.push(
|
|
3167
|
+
if (run.text === "\n") {
|
|
3168
|
+
pieces.push({
|
|
3169
|
+
...breakPiece(),
|
|
3170
|
+
family: (a.resolveFamily ?? substituteFamily)(run.fmt?.font ?? a.themeFace),
|
|
3171
|
+
sizePx: run.sizePt * scale * PX_PER_PT,
|
|
3172
|
+
bold: run.fmt?.bold ?? false,
|
|
3173
|
+
italic: run.fmt?.italic ?? false
|
|
3174
|
+
});
|
|
2192
3175
|
continue;
|
|
2193
3176
|
}
|
|
2194
3177
|
let fmt = run.fmt;
|
|
@@ -2203,36 +3186,63 @@ const buildSvgTextInput = (a) => {
|
|
|
2203
3186
|
const family = (a.resolveFamily ?? substituteFamily)(fmt?.font ?? a.themeFace);
|
|
2204
3187
|
const sizePx = run.sizePt * scale * PX_PER_PT;
|
|
2205
3188
|
const fillHex = fmt?.color !== void 0 && fmt.color !== null ? resolveColor(fmt.color, a.theme, "#000000") : a.defaultColor;
|
|
2206
|
-
const
|
|
3189
|
+
const baseline = fmt?.baseline ?? 0;
|
|
2207
3190
|
const letterSpacingPx = fmt?.spc !== void 0 && fmt.spc !== 0 ? fmt.spc / 100 * PX_PER_PT : 0;
|
|
2208
|
-
const
|
|
3191
|
+
const kerning = fmt?.kern === void 0 ? true : fmt.kern > 0 && sizePx / PX_PER_PT >= fmt.kern / 100;
|
|
3192
|
+
const cap = fmt?.cap;
|
|
3193
|
+
const caps = cap === "all" || cap === "small";
|
|
2209
3194
|
const base = {
|
|
2210
3195
|
family,
|
|
2211
3196
|
sizePx,
|
|
2212
3197
|
bold: fmt?.bold ?? false,
|
|
2213
3198
|
italic: fmt?.italic ?? false,
|
|
2214
3199
|
letterSpacingPx,
|
|
3200
|
+
kerning,
|
|
2215
3201
|
fillHex,
|
|
3202
|
+
...fmt?.textFill && !run.href ? { fillPaint: textFillPaint(fmt.textFill, a.theme) } : {},
|
|
3203
|
+
...fmt?.underlineColor !== void 0 && fmt.underlineColor !== null ? { underlineHex: resolveColor(fmt.underlineColor, a.theme, fillHex) } : {},
|
|
2216
3204
|
underline: underlineStyleOf(fmt),
|
|
2217
|
-
strike:
|
|
2218
|
-
|
|
2219
|
-
|
|
3205
|
+
strike: strikeStyleOf(fmt),
|
|
3206
|
+
...fmt?.highlight ? { highlightHex: resolveColor(fmt.highlight, a.theme, "#FFFF00") } : {},
|
|
3207
|
+
...fmt?.outline?.color ? {
|
|
3208
|
+
outlineHex: resolveColor(fmt.outline.color, a.theme, "#000000"),
|
|
3209
|
+
outlineWidthPx: (fmt.outline.widthEmu ?? 9525) / EMU_PER_PX
|
|
3210
|
+
} : {},
|
|
3211
|
+
...fmt?.shadow ? { shadow: {
|
|
3212
|
+
...fmt.shadow,
|
|
3213
|
+
...fmt.shadow.color ? { color: resolveColor(fmt.shadow.color, a.theme, "#000000") } : {}
|
|
3214
|
+
} } : {},
|
|
3215
|
+
...fmt?.glow ? { glow: {
|
|
3216
|
+
...fmt.glow,
|
|
3217
|
+
color: resolveColor(fmt.glow.color, a.theme, "#FFFF00")
|
|
3218
|
+
} } : {},
|
|
3219
|
+
baseline,
|
|
3220
|
+
...fmt?.reflection ? { reflection: fmt.reflection } : {},
|
|
3221
|
+
...fmt?.innerShadow ? { innerShadow: {
|
|
3222
|
+
...fmt.innerShadow,
|
|
3223
|
+
...fmt.innerShadow.color ? { color: resolveColor(fmt.innerShadow.color, a.theme, "#000000") } : {}
|
|
3224
|
+
} } : {},
|
|
3225
|
+
href: run.href ?? null,
|
|
3226
|
+
...run.hrefTip !== void 0 ? { hrefTip: run.hrefTip } : {}
|
|
2220
3227
|
};
|
|
2221
3228
|
const segs = run.text.split("\n");
|
|
2222
3229
|
segs.forEach((seg, i) => {
|
|
2223
3230
|
const segText = caps ? seg.toUpperCase() : seg;
|
|
2224
|
-
|
|
3231
|
+
const segPieces = cap === "small" ? smallCapsParts(seg, base) : [{
|
|
3232
|
+
...base,
|
|
3233
|
+
text: segText
|
|
3234
|
+
}];
|
|
3235
|
+
if (a.vert === "upright") for (const segPiece of segPieces) for (const g of graphemesOf(segPiece.text)) {
|
|
2225
3236
|
const last = pieces[pieces.length - 1];
|
|
2226
3237
|
if (last !== void 0 && !last.isBreak) pieces.push(breakPiece());
|
|
2227
3238
|
pieces.push({
|
|
2228
|
-
...
|
|
3239
|
+
...segPiece,
|
|
2229
3240
|
text: g,
|
|
2230
3241
|
isBreak: false
|
|
2231
3242
|
});
|
|
2232
3243
|
}
|
|
2233
|
-
else pieces.push({
|
|
2234
|
-
...
|
|
2235
|
-
text: segText,
|
|
3244
|
+
else for (const segPiece of segPieces) pieces.push({
|
|
3245
|
+
...segPiece,
|
|
2236
3246
|
isBreak: false
|
|
2237
3247
|
});
|
|
2238
3248
|
if (i < segs.length - 1) pieces.push(breakPiece());
|
|
@@ -2251,7 +3261,13 @@ const buildSvgTextInput = (a) => {
|
|
|
2251
3261
|
px: para.lineSpacing.value * PX_PER_PT * scale
|
|
2252
3262
|
} : null;
|
|
2253
3263
|
return {
|
|
3264
|
+
latinLineBreak: para.latinLineBreak,
|
|
2254
3265
|
align: alignOf(para.align),
|
|
3266
|
+
tabStops: (para.tabStops ?? []).map((stop) => ({
|
|
3267
|
+
positionPx: stop.positionEmu / EMU_PER_PX * scale,
|
|
3268
|
+
alignment: stop.alignment
|
|
3269
|
+
})),
|
|
3270
|
+
defaultTabSizePx: (para.defaultTabSizeEmu ?? 914400) / EMU_PER_PX * scale,
|
|
2255
3271
|
marLpx,
|
|
2256
3272
|
marRpx,
|
|
2257
3273
|
firstIndentPx,
|
|
@@ -2261,7 +3277,7 @@ const buildSvgTextInput = (a) => {
|
|
|
2261
3277
|
lineAdvanceScale: a.lineHeightScale,
|
|
2262
3278
|
bullet: buildBullet(a, para, pi),
|
|
2263
3279
|
pieces,
|
|
2264
|
-
fallbackSizePx: a.defaultPt * scale * PX_PER_PT
|
|
3280
|
+
fallbackSizePx: (para.emptySizePt ?? a.defaultPt) * scale * PX_PER_PT
|
|
2265
3281
|
};
|
|
2266
3282
|
});
|
|
2267
3283
|
return {
|
|
@@ -2270,10 +3286,13 @@ const buildSvgTextInput = (a) => {
|
|
|
2270
3286
|
boxWpx: a.innerW / EMU_PER_PX,
|
|
2271
3287
|
boxHpx: a.innerH / EMU_PER_PX,
|
|
2272
3288
|
anchor: a.anchor,
|
|
3289
|
+
anchorCentered: a.anchorCentered ?? false,
|
|
2273
3290
|
wrap: a.wrap,
|
|
2274
3291
|
paragraphs,
|
|
2275
3292
|
vert: a.vert,
|
|
2276
|
-
columns: a.columns
|
|
3293
|
+
columns: a.columns,
|
|
3294
|
+
...a.effectsOnly ? { effectsOnly: true } : {},
|
|
3295
|
+
...a.effectKind ? { effectKind: a.effectKind } : {}
|
|
2277
3296
|
};
|
|
2278
3297
|
};
|
|
2279
3298
|
const buildAndLayoutSvgText = (a) => layoutTextSvg(buildSvgTextInput(a), a.measure);
|
|
@@ -2287,7 +3306,7 @@ const breakPiece = () => ({
|
|
|
2287
3306
|
fillHex: "#000000",
|
|
2288
3307
|
underline: "none",
|
|
2289
3308
|
strike: false,
|
|
2290
|
-
|
|
3309
|
+
baseline: 0,
|
|
2291
3310
|
href: null,
|
|
2292
3311
|
isBreak: true
|
|
2293
3312
|
});
|
|
@@ -2299,9 +3318,10 @@ const buildBullet = (a, para, pi) => {
|
|
|
2299
3318
|
const baseSizePx = (para.runs.find((r) => r.text !== "\n" && r.text !== "")?.sizePt ?? a.defaultPt) * PX_PER_PT * a.autoFitScale;
|
|
2300
3319
|
const sizePx = para.bulletDetail.sizePct !== null ? baseSizePx * para.bulletDetail.sizePct : para.bulletDetail.sizePts !== null ? para.bulletDetail.sizePts * PX_PER_PT * a.autoFitScale : baseSizePx;
|
|
2301
3320
|
const fillHex = bulletFillOf(para, a.theme, a.defaultColor);
|
|
3321
|
+
const bulletFont = para.bulletDetail.fontFollowText ? para.runs.find((r) => r.text !== "\n" && r.text !== "")?.fmt?.font ?? DEFAULT_BULLET_FONT : para.bulletDetail.font ?? DEFAULT_BULLET_FONT;
|
|
2302
3322
|
return {
|
|
2303
3323
|
text: char,
|
|
2304
|
-
family: (a.resolveFamily ?? substituteFamily)(
|
|
3324
|
+
family: (a.resolveFamily ?? substituteFamily)(bulletFont),
|
|
2305
3325
|
sizePx,
|
|
2306
3326
|
fillHex,
|
|
2307
3327
|
...para.bulletImageHref ? { imageHref: para.bulletImageHref } : {}
|
|
@@ -2312,42 +3332,7 @@ const bulletFillOf = (para, theme, defaultColor) => {
|
|
|
2312
3332
|
const runColor = para.runs.find((r) => r.text !== "\n" && r.text !== "")?.fmt?.color;
|
|
2313
3333
|
return runColor === void 0 || runColor === null ? defaultColor : resolveColor(runColor, theme, defaultColor);
|
|
2314
3334
|
};
|
|
2315
|
-
const
|
|
2316
|
-
switch (preset) {
|
|
2317
|
-
case "triangle": return {
|
|
2318
|
-
l: .25,
|
|
2319
|
-
t: .5,
|
|
2320
|
-
r: .75,
|
|
2321
|
-
b: 1
|
|
2322
|
-
};
|
|
2323
|
-
case "diamond": return {
|
|
2324
|
-
l: .25,
|
|
2325
|
-
t: .25,
|
|
2326
|
-
r: .75,
|
|
2327
|
-
b: .75
|
|
2328
|
-
};
|
|
2329
|
-
case "pentagon": return {
|
|
2330
|
-
l: .191,
|
|
2331
|
-
t: .236,
|
|
2332
|
-
r: .809,
|
|
2333
|
-
b: 1
|
|
2334
|
-
};
|
|
2335
|
-
case "star5": return {
|
|
2336
|
-
l: .309,
|
|
2337
|
-
t: .382,
|
|
2338
|
-
r: .691,
|
|
2339
|
-
b: .764
|
|
2340
|
-
};
|
|
2341
|
-
case "leftRightArrow": return {
|
|
2342
|
-
l: .18,
|
|
2343
|
-
t: .35,
|
|
2344
|
-
r: .82,
|
|
2345
|
-
b: .65
|
|
2346
|
-
};
|
|
2347
|
-
default: return null;
|
|
2348
|
-
}
|
|
2349
|
-
};
|
|
2350
|
-
const resolveTextBodyModel = (pres, shape, bounds, theme, phType, measure, defaultColor) => {
|
|
3335
|
+
const resolveTextBodyModel = (pres, shape, bounds, theme, phType, measure, defaultColor, useSvgTextRect = false) => {
|
|
2351
3336
|
let paragraphCount;
|
|
2352
3337
|
try {
|
|
2353
3338
|
paragraphCount = getShapeParagraphCount(shape);
|
|
@@ -2365,6 +3350,10 @@ const resolveTextBodyModel = (pres, shape, bounds, theme, phType, measure, defau
|
|
|
2365
3350
|
} catch {
|
|
2366
3351
|
effectiveBody = {
|
|
2367
3352
|
anchor: getShapeTextAnchor(shape),
|
|
3353
|
+
anchorCentered: null,
|
|
3354
|
+
autoFit: getShapeTextAutoFit(shape),
|
|
3355
|
+
autoFitParams: getShapeTextAutoFitParams(shape),
|
|
3356
|
+
columns: getShapeTextColumns(shape),
|
|
2368
3357
|
wrap: null,
|
|
2369
3358
|
vert: getShapeTextDirection(shape),
|
|
2370
3359
|
margins: getShapeTextMargins(shape) ?? {
|
|
@@ -2384,32 +3373,23 @@ const resolveTextBodyModel = (pres, shape, bounds, theme, phType, measure, defau
|
|
|
2384
3373
|
const tIns = margins.top ?? DEFAULT_INSET_Y;
|
|
2385
3374
|
const rIns = margins.right ?? DEFAULT_INSET_X;
|
|
2386
3375
|
const bIns = margins.bottom ?? DEFAULT_INSET_Y;
|
|
2387
|
-
const
|
|
2388
|
-
|
|
2389
|
-
|
|
2390
|
-
|
|
2391
|
-
|
|
2392
|
-
|
|
2393
|
-
let innerY = rectY + tIns;
|
|
2394
|
-
let innerW = rectW - lIns - rIns;
|
|
2395
|
-
let innerH = rectH - tIns - bIns;
|
|
2396
|
-
if (pRect && (innerW <= 0 || innerH <= 0)) {
|
|
2397
|
-
innerX = rectX;
|
|
2398
|
-
innerY = rectY;
|
|
2399
|
-
innerW = rectW;
|
|
2400
|
-
innerH = rectH;
|
|
2401
|
-
}
|
|
3376
|
+
const { x: innerX, y: innerY, w: innerW, h: innerH } = resolveTextBodyRect(getShapePreset(shape), bounds, {
|
|
3377
|
+
left: lIns,
|
|
3378
|
+
top: tIns,
|
|
3379
|
+
right: rIns,
|
|
3380
|
+
bottom: bIns
|
|
3381
|
+
}, shapeCustomTextRect(getShapeCustomGeometry(shape), getShapeBounds(shape)));
|
|
2402
3382
|
if (innerW <= 0 || innerH <= 0) return null;
|
|
2403
|
-
const svgTextRect = (v) => v
|
|
2404
|
-
x: innerX,
|
|
2405
|
-
y: innerY,
|
|
2406
|
-
w: innerW,
|
|
2407
|
-
h: innerH
|
|
2408
|
-
} : {
|
|
3383
|
+
const svgTextRect = (v) => useSvgTextRect && v !== "none" && v !== "upright" ? {
|
|
2409
3384
|
x: bounds.x + tIns,
|
|
2410
3385
|
y: bounds.y + lIns,
|
|
2411
3386
|
w: Math.max(0, bounds.w - tIns - bIns),
|
|
2412
3387
|
h: Math.max(0, bounds.h - lIns - rIns)
|
|
3388
|
+
} : {
|
|
3389
|
+
x: innerX,
|
|
3390
|
+
y: innerY,
|
|
3391
|
+
w: innerW,
|
|
3392
|
+
h: innerH
|
|
2413
3393
|
};
|
|
2414
3394
|
const paraData = [];
|
|
2415
3395
|
let hasAnyText = false;
|
|
@@ -2434,15 +3414,12 @@ const resolveTextBodyModel = (pres, shape, bounds, theme, phType, measure, defau
|
|
|
2434
3414
|
const align = effective.align ?? defaultAlign;
|
|
2435
3415
|
const level = effective.level;
|
|
2436
3416
|
const bulletStyle = getParagraphBullet(shape, p) ?? effective.bullet;
|
|
2437
|
-
|
|
2438
|
-
|
|
2439
|
-
|
|
2440
|
-
|
|
2441
|
-
|
|
3417
|
+
const bulletDetail = effective.bulletDetail ?? {
|
|
3418
|
+
...getParagraphBulletStyle(pres, shape, p),
|
|
3419
|
+
colorFollowText: false,
|
|
3420
|
+
sizeFollowText: false,
|
|
3421
|
+
fontFollowText: false
|
|
2442
3422
|
};
|
|
2443
|
-
try {
|
|
2444
|
-
bulletDetail = getParagraphBulletStyle(pres, shape, p);
|
|
2445
|
-
} catch {}
|
|
2446
3423
|
let bulletIsPicture = false;
|
|
2447
3424
|
try {
|
|
2448
3425
|
bulletIsPicture = isParagraphBulletPicture(shape, p);
|
|
@@ -2479,16 +3456,19 @@ const resolveTextBodyModel = (pres, shape, bounds, theme, phType, measure, defau
|
|
|
2479
3456
|
elements = [];
|
|
2480
3457
|
}
|
|
2481
3458
|
let rIdx = 0;
|
|
3459
|
+
let fieldIndex = 0;
|
|
3460
|
+
let breakIndex = 0;
|
|
2482
3461
|
for (const el of elements) {
|
|
2483
3462
|
if (el.kind === "br") {
|
|
3463
|
+
const fmt = getShapeRunFormatEffective(pres, shape, p, { breakIndex: breakIndex++ });
|
|
2484
3464
|
runs.push({
|
|
2485
3465
|
text: "\n",
|
|
2486
|
-
fmt
|
|
2487
|
-
sizePt: defaultPt
|
|
3466
|
+
fmt,
|
|
3467
|
+
sizePt: fmt.size ?? defaultPt
|
|
2488
3468
|
});
|
|
2489
3469
|
continue;
|
|
2490
3470
|
}
|
|
2491
|
-
const txt = el.text;
|
|
3471
|
+
const txt = el.kind === "fld" && el.type === "slidenum" ? activeSlideNumber : el.text;
|
|
2492
3472
|
let fmt = el.format;
|
|
2493
3473
|
let href;
|
|
2494
3474
|
let hrefTip;
|
|
@@ -2500,19 +3480,13 @@ const resolveTextBodyModel = (pres, shape, bounds, theme, phType, measure, defau
|
|
|
2500
3480
|
}
|
|
2501
3481
|
try {
|
|
2502
3482
|
href = getShapeRunHyperlink(shape, p, rIdx) ?? void 0;
|
|
2503
|
-
if (!href)
|
|
2504
|
-
const act = getShapeRunClickAction(shape, p, rIdx);
|
|
2505
|
-
if (act?.kind === "slide") {
|
|
2506
|
-
const idx = getSlideIndex(pres, act.slide);
|
|
2507
|
-
if (idx >= 0) href = `#slide-${idx + 1}`;
|
|
2508
|
-
} else if (act?.kind === "url") href = act.url;
|
|
2509
|
-
}
|
|
3483
|
+
if (!href) href = clickActionHref(pres, getShapeRunClickAction(shape, p, rIdx));
|
|
2510
3484
|
if (href) hrefTip = getShapeRunHyperlinkTooltip(shape, p, rIdx) ?? void 0;
|
|
2511
3485
|
} catch {
|
|
2512
3486
|
href = void 0;
|
|
2513
3487
|
}
|
|
2514
3488
|
rIdx++;
|
|
2515
|
-
}
|
|
3489
|
+
} else fmt = getShapeRunFormatEffective(pres, shape, p, { fieldIndex: fieldIndex++ });
|
|
2516
3490
|
const sizePt = fmt?.size ?? defaultPt;
|
|
2517
3491
|
if (txt) hasAnyText = true;
|
|
2518
3492
|
runs.push({
|
|
@@ -2524,6 +3498,10 @@ const resolveTextBodyModel = (pres, shape, bounds, theme, phType, measure, defau
|
|
|
2524
3498
|
});
|
|
2525
3499
|
}
|
|
2526
3500
|
paraData.push({
|
|
3501
|
+
...runs.length === 0 ? { emptySizePt: getShapeRunFormatEffective(pres, shape, p, null).size ?? defaultPt } : {},
|
|
3502
|
+
latinLineBreak: effective.latinLineBreak,
|
|
3503
|
+
tabStops: effective.tabStops,
|
|
3504
|
+
defaultTabSizeEmu: effective.defaultTabSizeEmu,
|
|
2527
3505
|
align,
|
|
2528
3506
|
level,
|
|
2529
3507
|
bulletStyle,
|
|
@@ -2538,36 +3516,17 @@ const resolveTextBodyModel = (pres, shape, bounds, theme, phType, measure, defau
|
|
|
2538
3516
|
});
|
|
2539
3517
|
}
|
|
2540
3518
|
if (!hasAnyText) return null;
|
|
2541
|
-
const authoredAutofit =
|
|
3519
|
+
const authoredAutofit = effectiveBody.autoFitParams;
|
|
2542
3520
|
let autoFitScale = authoredAutofit?.fontScale ?? 1;
|
|
2543
3521
|
const lineHeightScale = 1 - (authoredAutofit?.lnSpcReduction ?? 0);
|
|
2544
3522
|
LINE_HEIGHT * lineHeightScale;
|
|
2545
|
-
const numberLabels =
|
|
2546
|
-
{
|
|
2547
|
-
const counters = [];
|
|
2548
|
-
const types = [];
|
|
2549
|
-
for (let i = 0; i < paraData.length; i++) {
|
|
2550
|
-
const para = paraData[i];
|
|
2551
|
-
const num = bulletAutoNumType(para.bulletStyle);
|
|
2552
|
-
const level = Math.max(0, para.level);
|
|
2553
|
-
for (let l = num === null ? level : level + 1; l < counters.length; l++) {
|
|
2554
|
-
counters[l] = 0;
|
|
2555
|
-
types[l] = null;
|
|
2556
|
-
}
|
|
2557
|
-
if (num === null) continue;
|
|
2558
|
-
if (types[level] !== num) {
|
|
2559
|
-
counters[level] = 1;
|
|
2560
|
-
types[level] = num;
|
|
2561
|
-
} else counters[level] = (counters[level] ?? 0) + 1;
|
|
2562
|
-
numberLabels[i] = formatAutoNum(num, counters[level]);
|
|
2563
|
-
}
|
|
2564
|
-
}
|
|
2565
|
-
if (authoredAutofit && autoFitScale === 1) {
|
|
3523
|
+
const numberLabels = paragraphNumberLabels(paraData);
|
|
3524
|
+
if (authoredAutofit && autoFitScale === 1 && getShapeTextAutoFit(shape) === "normal") {
|
|
2566
3525
|
const fitVert = verticalLayoutOf(effectiveBody.vert ?? getShapeTextDirection(shape));
|
|
2567
|
-
const fitCols =
|
|
3526
|
+
const fitCols = effectiveBody.columns;
|
|
2568
3527
|
const fitColumns = fitVert === "none" && fitCols && fitCols.count >= 2 ? {
|
|
2569
3528
|
count: fitCols.count,
|
|
2570
|
-
gapPx: fitCols.gapEmu !== void 0 ? fitCols.gapEmu / EMU_PER_PX :
|
|
3529
|
+
gapPx: fitCols.gapEmu !== void 0 ? fitCols.gapEmu / EMU_PER_PX : 0
|
|
2571
3530
|
} : null;
|
|
2572
3531
|
const fitRect = svgTextRect(fitVert);
|
|
2573
3532
|
const fitBoxPx = (fitVert === "cw90" || fitVert === "cw270" ? fitRect.w : fitRect.h) / EMU_PER_PX;
|
|
@@ -2582,6 +3541,7 @@ const resolveTextBodyModel = (pres, shape, bounds, theme, phType, measure, defau
|
|
|
2582
3541
|
themeFace,
|
|
2583
3542
|
defaultColor,
|
|
2584
3543
|
anchor: anchor === "center" || anchor === "bottom" ? anchor : "top",
|
|
3544
|
+
anchorCentered: effectiveBody.anchorCentered ?? false,
|
|
2585
3545
|
wrap: effectiveBody.wrap !== "none",
|
|
2586
3546
|
innerX: fitRect.x,
|
|
2587
3547
|
innerY: fitRect.y,
|
|
@@ -2601,10 +3561,55 @@ const resolveTextBodyModel = (pres, shape, bounds, theme, phType, measure, defau
|
|
|
2601
3561
|
}
|
|
2602
3562
|
autoFitScale = Math.max(AUTOFIT_FLOOR, s);
|
|
2603
3563
|
}
|
|
3564
|
+
let anchorOffset = {
|
|
3565
|
+
x: 0,
|
|
3566
|
+
y: 0
|
|
3567
|
+
};
|
|
3568
|
+
if (effectiveBody.anchorCentered) {
|
|
3569
|
+
const vert = verticalLayoutOf(effectiveBody.vert ?? getShapeTextDirection(shape));
|
|
3570
|
+
const rect = svgTextRect(vert);
|
|
3571
|
+
const cols = effectiveBody.columns;
|
|
3572
|
+
const { anchorShift } = layoutCore(buildSvgTextInput({
|
|
3573
|
+
pres,
|
|
3574
|
+
shape,
|
|
3575
|
+
theme,
|
|
3576
|
+
paraData,
|
|
3577
|
+
numberLabels,
|
|
3578
|
+
lineHeightScale,
|
|
3579
|
+
defaultPt,
|
|
3580
|
+
themeFace,
|
|
3581
|
+
defaultColor,
|
|
3582
|
+
anchor,
|
|
3583
|
+
anchorCentered: true,
|
|
3584
|
+
wrap: effectiveBody.wrap !== "none",
|
|
3585
|
+
innerX: rect.x,
|
|
3586
|
+
innerY: rect.y,
|
|
3587
|
+
innerW: rect.w,
|
|
3588
|
+
innerH: rect.h,
|
|
3589
|
+
measure,
|
|
3590
|
+
vert,
|
|
3591
|
+
autoFitScale,
|
|
3592
|
+
columns: vert === "none" && cols && cols.count >= 2 ? {
|
|
3593
|
+
count: cols.count,
|
|
3594
|
+
gapPx: cols.gapEmu !== void 0 ? cols.gapEmu / EMU_PER_PX : 0
|
|
3595
|
+
} : null
|
|
3596
|
+
}), measure);
|
|
3597
|
+
anchorOffset = vert === "cw90" ? {
|
|
3598
|
+
x: 0,
|
|
3599
|
+
y: anchorShift
|
|
3600
|
+
} : vert === "cw270" ? {
|
|
3601
|
+
x: 0,
|
|
3602
|
+
y: -anchorShift
|
|
3603
|
+
} : {
|
|
3604
|
+
x: anchorShift,
|
|
3605
|
+
y: 0
|
|
3606
|
+
};
|
|
3607
|
+
}
|
|
2604
3608
|
return {
|
|
2605
3609
|
paraData,
|
|
2606
3610
|
numberLabels,
|
|
2607
3611
|
authoredAutofit,
|
|
3612
|
+
anchorOffset,
|
|
2608
3613
|
autoFitScale,
|
|
2609
3614
|
lineHeightScale,
|
|
2610
3615
|
defaultPt,
|
|
@@ -2619,31 +3624,29 @@ const resolveTextBodyModel = (pres, shape, bounds, theme, phType, measure, defau
|
|
|
2619
3624
|
svgTextRect
|
|
2620
3625
|
};
|
|
2621
3626
|
};
|
|
2622
|
-
const
|
|
2623
|
-
const model = resolveTextBodyModel(pres, shape, bounds, theme, phType, ctx.measure, activeDeckTextColor);
|
|
2624
|
-
if (model === null) return "";
|
|
2625
|
-
const { paraData, numberLabels, authoredAutofit, autoFitScale, defaultPt, themeFace, effectiveDefaultFont, effectiveBody, anchor, innerX, innerY, innerW, innerH, svgTextRect } = model;
|
|
2626
|
-
const defaultColor = activeDeckTextColor;
|
|
3627
|
+
const renderHtmlParagraphs = (paraData, numberLabels, theme, autoFitScale, defaultPt, defaultColor, wrap = true) => {
|
|
2627
3628
|
const paragraphs = [];
|
|
2628
3629
|
for (let pi = 0; pi < paraData.length; pi++) {
|
|
2629
3630
|
const para = paraData[pi];
|
|
2630
|
-
const runHtmls = para.runs.map((run) => {
|
|
3631
|
+
const runHtmls = para.runs.map((run, runIndex) => {
|
|
2631
3632
|
let runFmt = run.fmt;
|
|
2632
3633
|
if (run.href) {
|
|
2633
3634
|
const hlinkColor = theme ? normalizeHex(theme.hyperlink) : "#0563C1";
|
|
3635
|
+
const { textFill: _linkFill, ...linkFmt } = runFmt ?? {};
|
|
2634
3636
|
runFmt = {
|
|
2635
|
-
...
|
|
3637
|
+
...linkFmt,
|
|
2636
3638
|
color: hlinkColor,
|
|
2637
3639
|
underline: runFmt?.underline ?? true
|
|
2638
3640
|
};
|
|
2639
3641
|
}
|
|
2640
|
-
const span = renderRun(run.text, runFmt, theme, run.sizePt * autoFitScale, run.fmt?.size === void 0);
|
|
3642
|
+
const span = renderRun(run.text, runFmt, theme, run.sizePt * autoFitScale, defaultColor, run.fmt?.size === void 0, run.text === "\n" && /[^\n]$/.test(para.runs[runIndex - 1]?.text ?? ""), para.lineSpacing?.kind === "pct" ? para.lineSpacing.value.toFixed(3) : para.lineSpacing?.kind === "pts" ? "0" : void 0);
|
|
2641
3643
|
if (!run.href) return span;
|
|
2642
3644
|
const targetAttrs = run.href.startsWith("#") ? "" : " target=\"_blank\" rel=\"noopener noreferrer\"";
|
|
2643
3645
|
const titleAttr = run.hrefTip ? ` title="${escapeXml(run.hrefTip)}"` : "";
|
|
2644
3646
|
return `<a href="${escapeXml(run.href)}"${targetAttrs}${titleAttr} style="color:inherit;text-decoration:inherit">${span}</a>`;
|
|
2645
3647
|
});
|
|
2646
|
-
|
|
3648
|
+
const firstRunPt = para.runs.find((run) => run.text !== "\n" && run.text !== "")?.sizePt ?? defaultPt;
|
|
3649
|
+
let lineHeightCss = para.emptySizePt !== void 0 ? `line-height:${LINE_HEIGHT}` : "";
|
|
2647
3650
|
if (para.lineSpacing?.kind === "pct") lineHeightCss = `line-height:${para.lineSpacing.value.toFixed(3)}`;
|
|
2648
3651
|
else if (para.lineSpacing?.kind === "pts") lineHeightCss = `line-height:${(para.lineSpacing.value * PX_PER_PT * autoFitScale).toFixed(2)}px`;
|
|
2649
3652
|
const marginTopCss = para.spcBefPts !== null && para.spcBefPts > 0 ? `margin-top:${(para.spcBefPts * PX_PER_PT * autoFitScale).toFixed(2)}px` : "";
|
|
@@ -2652,7 +3655,12 @@ const renderTextBody = (pres, shape, bounds, theme, phType, ctx) => {
|
|
|
2652
3655
|
const rightPx = para.indent.rightEmu !== null ? para.indent.rightEmu / EMU_PER_PX * autoFitScale : 0;
|
|
2653
3656
|
const firstLinePx = para.indent.firstLineEmu !== null ? para.indent.firstLineEmu / EMU_PER_PX * autoFitScale : 0;
|
|
2654
3657
|
const pStyles = [
|
|
2655
|
-
|
|
3658
|
+
"margin:0",
|
|
3659
|
+
para.lineSpacing?.kind === "pts" ? `font-size:${(firstRunPt * PX_PER_PT * autoFitScale).toFixed(2)}px` : "font-size:0",
|
|
3660
|
+
...para.lineSpacing?.kind === "pts" && para.runs[0]?.fmt?.font ? [`font-family:${escapeXml(para.runs[0].fmt.font)}, ${DEFAULT_FONT}`] : [],
|
|
3661
|
+
...para.emptySizePt !== void 0 ? [`font-size:${(para.emptySizePt * PX_PER_PT * autoFitScale).toFixed(2)}px`] : [],
|
|
3662
|
+
...para.runs.some((run) => run.text.includes(" ")) ? [`white-space:${wrap ? "pre-wrap" : "pre"}`, `tab-size:${((para.defaultTabSizeEmu ?? 914400) / EMU_PER_PX * autoFitScale).toFixed(2)}px`] : [],
|
|
3663
|
+
marginTopCss,
|
|
2656
3664
|
marginBottomCss,
|
|
2657
3665
|
"padding:0",
|
|
2658
3666
|
`text-align:${ALIGNMENT_TO_CSS[para.align] ?? "left"}`,
|
|
@@ -2664,8 +3672,8 @@ const renderTextBody = (pres, shape, bounds, theme, phType, ctx) => {
|
|
|
2664
3672
|
let prefix = "";
|
|
2665
3673
|
const explicitChar = para.bulletStyle !== null && typeof para.bulletStyle === "object" && "char" in para.bulletStyle ? para.bulletStyle.char : null;
|
|
2666
3674
|
const numberLabel = numberLabels[pi];
|
|
2667
|
-
const showBullet = para.bulletStyle === "bullet" || explicitChar !== null || numberLabel !== null || para.bulletIsPicture || para.bulletStyle !== "none" && para.level > 0;
|
|
2668
|
-
const baseBulletPx =
|
|
3675
|
+
const showBullet = para.runs.some((run) => run.text.length > 0) && (para.bulletStyle === "bullet" || explicitChar !== null || numberLabel !== null || para.bulletIsPicture || para.bulletStyle !== "none" && para.level > 0);
|
|
3676
|
+
const baseBulletPx = firstRunPt * PX_PER_PT * autoFitScale;
|
|
2669
3677
|
if (showBullet && para.bulletImageHref) {
|
|
2670
3678
|
const bulletPx = para.bulletDetail.sizePct !== null ? baseBulletPx * para.bulletDetail.sizePct : para.bulletDetail.sizePts !== null ? para.bulletDetail.sizePts * PX_PER_PT * autoFitScale : baseBulletPx;
|
|
2671
3679
|
const imgStyles = [
|
|
@@ -2680,22 +3688,32 @@ const renderTextBody = (pres, shape, bounds, theme, phType, ctx) => {
|
|
|
2680
3688
|
const char = para.bulletIsPicture ? "■" : numberLabel ?? explicitChar ?? bulletChar(para.level);
|
|
2681
3689
|
const bulletStyles = [`margin-right:${(.4 * defaultPt * PX_PER_PT * autoFitScale).toFixed(2)}px`];
|
|
2682
3690
|
bulletStyles.push(`color:${bulletFillOf(para, theme, defaultColor)}`);
|
|
2683
|
-
if (para.bulletDetail.sizePct !== null) bulletStyles.push(`font-size:${(para.bulletDetail.sizePct
|
|
3691
|
+
if (para.bulletDetail.sizePct !== null) bulletStyles.push(`font-size:${(baseBulletPx * para.bulletDetail.sizePct).toFixed(2)}px`);
|
|
2684
3692
|
else if (para.bulletDetail.sizePts !== null) bulletStyles.push(`font-size:${(para.bulletDetail.sizePts * PX_PER_PT * autoFitScale).toFixed(2)}px`);
|
|
2685
3693
|
else bulletStyles.push(`font-size:${baseBulletPx.toFixed(2)}px`);
|
|
2686
|
-
|
|
3694
|
+
const bulletFont = para.bulletDetail.fontFollowText ? para.runs.find((r) => r.text !== "\n" && r.text !== "")?.fmt?.font ?? DEFAULT_BULLET_FONT : para.bulletDetail.font ?? DEFAULT_BULLET_FONT;
|
|
3695
|
+
bulletStyles.push(`font-family:${escapeXml(bulletFont)}, ${DEFAULT_FONT}`);
|
|
2687
3696
|
prefix = `<span style="${bulletStyles.join(";")}">${escapeXml(char)}</span>`;
|
|
2688
3697
|
}
|
|
2689
|
-
paragraphs.push(`<p style="${pStyles.join(";")}">${prefix}${runHtmls.join("") || "​"}</p>`);
|
|
3698
|
+
paragraphs.push(`<p data-pptx-paragraph="${pi}" style="${pStyles.join(";")}">${prefix}${runHtmls.join("") || "​"}</p>`);
|
|
2690
3699
|
}
|
|
3700
|
+
return paragraphs;
|
|
3701
|
+
};
|
|
3702
|
+
const renderTextBody = (pres, shape, bounds, theme, phType, ctx, output = "body") => {
|
|
3703
|
+
const model = resolveTextBodyModel(pres, shape, bounds, theme, phType, ctx.measure, activeDeckTextColor, ctx.mode === "svg" && output === "body");
|
|
3704
|
+
if (model === null) return "";
|
|
3705
|
+
const { paraData, numberLabels, authoredAutofit, autoFitScale, defaultPt, themeFace, effectiveDefaultFont, effectiveBody, anchor, innerX, innerY, innerW, innerH, svgTextRect } = model;
|
|
3706
|
+
const defaultColor = activeDeckTextColor;
|
|
3707
|
+
const paragraphs = renderHtmlParagraphs(paraData, numberLabels, theme, autoFitScale, defaultPt, defaultColor, effectiveBody.wrap !== "none");
|
|
2691
3708
|
const justify = ANCHOR_TO_CSS[anchor] ?? "flex-start";
|
|
2692
|
-
|
|
3709
|
+
const customTabs = paraData.some((para) => para.tabStops?.length && para.runs.some((run) => run.text.includes(" ")));
|
|
3710
|
+
if (ctx.mode === "svg" || customTabs) {
|
|
2693
3711
|
const svgLineScale = 1 - (authoredAutofit?.lnSpcReduction ?? 0);
|
|
2694
3712
|
const svgVert = verticalLayoutOf(effectiveBody.vert ?? getShapeTextDirection(shape));
|
|
2695
|
-
const svgCols =
|
|
3713
|
+
const svgCols = effectiveBody.columns;
|
|
2696
3714
|
const svgColumns = svgVert === "none" && svgCols && svgCols.count >= 2 ? {
|
|
2697
3715
|
count: svgCols.count,
|
|
2698
|
-
gapPx: svgCols.gapEmu !== void 0 ? svgCols.gapEmu / EMU_PER_PX :
|
|
3716
|
+
gapPx: svgCols.gapEmu !== void 0 ? svgCols.gapEmu / EMU_PER_PX : 0
|
|
2699
3717
|
} : null;
|
|
2700
3718
|
const { x: vInnerX, y: vInnerY, w: vInnerW, h: vInnerH } = svgTextRect(svgVert);
|
|
2701
3719
|
if (vInnerW <= 0 || vInnerH <= 0) return "";
|
|
@@ -2710,15 +3728,46 @@ const renderTextBody = (pres, shape, bounds, theme, phType, ctx) => {
|
|
|
2710
3728
|
themeFace,
|
|
2711
3729
|
defaultColor,
|
|
2712
3730
|
anchor: anchor === "center" || anchor === "bottom" ? anchor : "top",
|
|
3731
|
+
anchorCentered: effectiveBody.anchorCentered ?? false,
|
|
2713
3732
|
wrap: effectiveBody.wrap !== "none",
|
|
2714
3733
|
innerX: vInnerX,
|
|
2715
3734
|
innerY: vInnerY,
|
|
2716
3735
|
innerW: vInnerW,
|
|
2717
3736
|
innerH: vInnerH,
|
|
2718
|
-
measure: ctx.measure,
|
|
3737
|
+
measure: ctx.mode === "svg" ? ctx.measure : browserTextMeasurer() ?? ctx.measure,
|
|
3738
|
+
...ctx.mode === "foreignObject" ? { resolveFamily: browserFontFamily } : {},
|
|
2719
3739
|
vert: svgVert,
|
|
2720
3740
|
columns: svgColumns
|
|
2721
3741
|
};
|
|
3742
|
+
if (output === "effects") {
|
|
3743
|
+
const effectArgs = {
|
|
3744
|
+
...svgArgsBase,
|
|
3745
|
+
autoFitScale: authoredAutofit ? autoFitScale : 1,
|
|
3746
|
+
effectsOnly: true,
|
|
3747
|
+
resolveFamily: browserFontFamily
|
|
3748
|
+
};
|
|
3749
|
+
const effects = [
|
|
3750
|
+
paraData.some((para) => para.runs.some((run) => run.fmt?.reflection != null)) ? buildAndLayoutSvgText({
|
|
3751
|
+
...effectArgs,
|
|
3752
|
+
effectKind: "reflection"
|
|
3753
|
+
}) : "",
|
|
3754
|
+
paraData.some((para) => para.runs.some(hasPaintedTextFill)) ? buildAndLayoutSvgText({
|
|
3755
|
+
...effectArgs,
|
|
3756
|
+
effectKind: "fill"
|
|
3757
|
+
}) : "",
|
|
3758
|
+
paraData.some((para) => para.runs.some((run) => run.fmt?.innerShadow != null)) ? buildAndLayoutSvgText({
|
|
3759
|
+
...effectArgs,
|
|
3760
|
+
effectKind: "innerShadow"
|
|
3761
|
+
}) : ""
|
|
3762
|
+
].join("");
|
|
3763
|
+
const bodyRotDegSvg = getShapeTextBodyRotationDeg(shape);
|
|
3764
|
+
if (bodyRotDegSvg !== null && bodyRotDegSvg !== 0) {
|
|
3765
|
+
const pivotX = vInnerX + vInnerW / 2;
|
|
3766
|
+
const pivotY = vInnerY + vInnerH / 2;
|
|
3767
|
+
return `<g transform="rotate(${bodyRotDegSvg} ${E(pivotX)} ${E(pivotY)})">${effects}</g>`;
|
|
3768
|
+
}
|
|
3769
|
+
return effects;
|
|
3770
|
+
}
|
|
2722
3771
|
const svgScale = authoredAutofit ? autoFitScale : 1;
|
|
2723
3772
|
const svgInner = buildAndLayoutSvgText({
|
|
2724
3773
|
...svgArgsBase,
|
|
@@ -2732,31 +3781,144 @@ const renderTextBody = (pres, shape, bounds, theme, phType, ctx) => {
|
|
|
2732
3781
|
}
|
|
2733
3782
|
return svgInner;
|
|
2734
3783
|
}
|
|
2735
|
-
const
|
|
2736
|
-
|
|
2737
|
-
|
|
2738
|
-
|
|
2739
|
-
|
|
2740
|
-
writingMode = "writing-mode:vertical-lr";
|
|
2741
|
-
if (vert === "vert270") extraTransform = ";transform:rotate(180deg)";
|
|
2742
|
-
} else if (vert === "wordArtVert") writingMode = "writing-mode:vertical-rl;text-orientation:upright";
|
|
2743
|
-
else if (vert === "wordArtVertRtl") writingMode = "writing-mode:vertical-rl;text-orientation:upright;direction:rtl";
|
|
2744
|
-
const cols = getShapeTextColumns(shape);
|
|
2745
|
-
let colStyles = "";
|
|
2746
|
-
if (cols && cols.count >= 2) {
|
|
2747
|
-
const gapPx = cols.gapEmu !== void 0 ? (cols.gapEmu / EMU_PER_PX).toFixed(2) : "12";
|
|
2748
|
-
colStyles = `;column-count:${cols.count};column-gap:${gapPx}px`;
|
|
2749
|
-
}
|
|
3784
|
+
const vertical = verticalTextStyle(effectiveBody.vert ?? getShapeTextDirection(shape));
|
|
3785
|
+
const writingMode = vertical.declarations;
|
|
3786
|
+
const extraTransform = vertical.transform ? `;transform:${vertical.transform}` : "";
|
|
3787
|
+
const columns = textColumnsStyle(effectiveBody.columns);
|
|
3788
|
+
const colStyles = columns ? `;${columns}` : "";
|
|
2750
3789
|
const vertStyles = (writingMode ? `;${writingMode}${extraTransform}` : "") + colStyles;
|
|
2751
|
-
const
|
|
3790
|
+
const wrapStyle = effectiveBody.wrap === "none" ? "white-space:nowrap" : "word-break:break-word";
|
|
3791
|
+
const content = paragraphs.join("");
|
|
3792
|
+
const body = `<div xmlns="http://www.w3.org/1999/xhtml" style="display:flex;flex-direction:column;justify-content:${justify};width:100%;height:100%;box-sizing:border-box;overflow:visible;font-family:${effectiveDefaultFont};color:${defaultColor};${`translate:${model.anchorOffset.x}px ${model.anchorOffset.y}px;`}${wrapStyle}${vertStyles}">${content}</div>`;
|
|
2752
3793
|
const foreign = `<foreignObject x="${E(innerX)}" y="${E(innerY)}" width="${E(innerW)}" height="${E(innerH)}" overflow="visible">${body}</foreignObject>`;
|
|
3794
|
+
const hasCharacterEffect = paraData.some((para) => para.runs.some((run) => run.fmt?.reflection !== void 0 && run.fmt.reflection !== null || run.fmt?.innerShadow !== void 0 && run.fmt.innerShadow !== null || hasPaintedTextFill(run)));
|
|
3795
|
+
let reflectionOverlay = "";
|
|
3796
|
+
let fillOverlay = "";
|
|
3797
|
+
let innerShadowOverlay = "";
|
|
3798
|
+
if (hasCharacterEffect) {
|
|
3799
|
+
const svgVert = verticalLayoutOf(effectiveBody.vert ?? getShapeTextDirection(shape));
|
|
3800
|
+
const { x: rX, y: rY, w: rW, h: rH } = svgTextRect(svgVert);
|
|
3801
|
+
const effectArgs = {
|
|
3802
|
+
pres,
|
|
3803
|
+
shape,
|
|
3804
|
+
theme,
|
|
3805
|
+
paraData,
|
|
3806
|
+
numberLabels,
|
|
3807
|
+
autoFitScale: authoredAutofit ? autoFitScale : 1,
|
|
3808
|
+
lineHeightScale: 1 - (authoredAutofit?.lnSpcReduction ?? 0),
|
|
3809
|
+
defaultPt,
|
|
3810
|
+
themeFace,
|
|
3811
|
+
defaultColor,
|
|
3812
|
+
anchor: anchor === "center" || anchor === "bottom" ? anchor : "top",
|
|
3813
|
+
anchorCentered: effectiveBody.anchorCentered ?? false,
|
|
3814
|
+
wrap: effectiveBody.wrap !== "none",
|
|
3815
|
+
innerX: rX,
|
|
3816
|
+
innerY: rY,
|
|
3817
|
+
innerW: rW,
|
|
3818
|
+
innerH: rH,
|
|
3819
|
+
measure: browserTextMeasurer() ?? ctx.measure,
|
|
3820
|
+
vert: svgVert,
|
|
3821
|
+
columns: null,
|
|
3822
|
+
effectsOnly: true,
|
|
3823
|
+
resolveFamily: browserFontFamily
|
|
3824
|
+
};
|
|
3825
|
+
const hasCharacterReflection = paraData.some((para) => para.runs.some((run) => run.fmt?.reflection !== void 0 && run.fmt.reflection !== null));
|
|
3826
|
+
const hasCharacterInnerShadow = paraData.some((para) => para.runs.some((run) => run.fmt?.innerShadow !== void 0 && run.fmt.innerShadow !== null));
|
|
3827
|
+
if (hasCharacterReflection) reflectionOverlay = buildAndLayoutSvgText({
|
|
3828
|
+
...effectArgs,
|
|
3829
|
+
effectKind: "reflection"
|
|
3830
|
+
});
|
|
3831
|
+
if (paraData.some((para) => para.runs.some(hasPaintedTextFill))) fillOverlay = buildAndLayoutSvgText({
|
|
3832
|
+
...effectArgs,
|
|
3833
|
+
effectKind: "fill"
|
|
3834
|
+
});
|
|
3835
|
+
if (hasCharacterInnerShadow) innerShadowOverlay = buildAndLayoutSvgText({
|
|
3836
|
+
...effectArgs,
|
|
3837
|
+
effectKind: "innerShadow"
|
|
3838
|
+
});
|
|
3839
|
+
}
|
|
2753
3840
|
const bodyRotDeg = getShapeTextBodyRotationDeg(shape);
|
|
2754
3841
|
if (bodyRotDeg !== null && bodyRotDeg !== 0) {
|
|
2755
3842
|
const pivotX = innerX + innerW / 2;
|
|
2756
3843
|
const pivotY = innerY + innerH / 2;
|
|
2757
|
-
|
|
3844
|
+
const content = output === "effects" ? `${reflectionOverlay}${fillOverlay}${innerShadowOverlay}` : `${reflectionOverlay}${foreign}${fillOverlay}${innerShadowOverlay}`;
|
|
3845
|
+
return `<g transform="rotate(${bodyRotDeg} ${E(pivotX)} ${E(pivotY)})">${content}</g>`;
|
|
2758
3846
|
}
|
|
2759
|
-
return foreign;
|
|
3847
|
+
return output === "effects" ? reflectionOverlay + fillOverlay + innerShadowOverlay : reflectionOverlay + foreign + fillOverlay + innerShadowOverlay;
|
|
3848
|
+
};
|
|
3849
|
+
/**
|
|
3850
|
+
* Paint only character reflection/inner-shadow effects for an editable text
|
|
3851
|
+
* body. The caller supplies the local bounds of the editable box in EMUs. For
|
|
3852
|
+
* a table, `cell` selects a zero-based row/column and bounds describe that cell.
|
|
3853
|
+
* Body rotation and vertical text handling remain canonical here; the editor only
|
|
3854
|
+
* supplies the outer shape/group transform around this SVG.
|
|
3855
|
+
*/
|
|
3856
|
+
const renderTextEffectsSvg = (pres, slide, shape, bounds, opts = {}) => {
|
|
3857
|
+
if (opts.cell) return renderTableCellEffectsSvgInternal(pres, slide, shape, opts.cell, bounds, opts);
|
|
3858
|
+
activeColorMap = getEffectiveColorMap(slide);
|
|
3859
|
+
activeDeckTextColor = resolveDeckBodyTextColor(slide) ?? "#000000";
|
|
3860
|
+
const theme = getPresentationTheme(pres);
|
|
3861
|
+
const localBounds = {
|
|
3862
|
+
x: 0,
|
|
3863
|
+
y: 0,
|
|
3864
|
+
w: bounds.w,
|
|
3865
|
+
h: bounds.h
|
|
3866
|
+
};
|
|
3867
|
+
const ctx = {
|
|
3868
|
+
groupScale: {
|
|
3869
|
+
sx: 1,
|
|
3870
|
+
sy: 1
|
|
3871
|
+
},
|
|
3872
|
+
groupReflected: false,
|
|
3873
|
+
mode: "foreignObject",
|
|
3874
|
+
measure: opts.measureText ?? browserTextMeasurer() ?? defaultMeasurer,
|
|
3875
|
+
ownShapes: true,
|
|
3876
|
+
background: {
|
|
3877
|
+
id: "",
|
|
3878
|
+
width: localBounds.w / EMU_PER_PX,
|
|
3879
|
+
height: localBounds.h / EMU_PER_PX
|
|
3880
|
+
},
|
|
3881
|
+
inverseGroupTransform: ""
|
|
3882
|
+
};
|
|
3883
|
+
const body = renderTextBody(pres, shape, localBounds, theme, getShapePlaceholderType(shape), ctx, "effects");
|
|
3884
|
+
if (!body) return "";
|
|
3885
|
+
return `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 ${E(localBounds.w)} ${E(localBounds.h)}" width="100%" height="100%" overflow="visible" aria-hidden="true">${body}</svg>`;
|
|
3886
|
+
};
|
|
3887
|
+
/** Paint only character effects for an editable table cell. */
|
|
3888
|
+
const renderTableCellEffectsSvgInternal = (pres, slide, shape, cellPosition, bounds, opts = {}) => {
|
|
3889
|
+
activeColorMap = getEffectiveColorMap(slide);
|
|
3890
|
+
activeDeckTextColor = resolveDeckBodyTextColor(slide) ?? "#000000";
|
|
3891
|
+
const cell = getTableCells(shape)[cellPosition.row]?.[cellPosition.col];
|
|
3892
|
+
if (!cell) return "";
|
|
3893
|
+
const paragraphs = getTableCellParagraphs(cell);
|
|
3894
|
+
const theme = getPresentationTheme(pres);
|
|
3895
|
+
const margins = getTableCellMargins(cell);
|
|
3896
|
+
const anchor = getTableCellAnchor(cell) ?? "top";
|
|
3897
|
+
const localBounds = {
|
|
3898
|
+
x: 0,
|
|
3899
|
+
y: 0,
|
|
3900
|
+
w: bounds.w,
|
|
3901
|
+
h: bounds.h
|
|
3902
|
+
};
|
|
3903
|
+
const ctx = {
|
|
3904
|
+
groupScale: {
|
|
3905
|
+
sx: 1,
|
|
3906
|
+
sy: 1
|
|
3907
|
+
},
|
|
3908
|
+
groupReflected: false,
|
|
3909
|
+
mode: "foreignObject",
|
|
3910
|
+
measure: opts.measureText ?? browserTextMeasurer() ?? defaultMeasurer,
|
|
3911
|
+
ownShapes: true,
|
|
3912
|
+
background: {
|
|
3913
|
+
id: "",
|
|
3914
|
+
width: localBounds.w / EMU_PER_PX,
|
|
3915
|
+
height: localBounds.h / EMU_PER_PX
|
|
3916
|
+
},
|
|
3917
|
+
inverseGroupTransform: ""
|
|
3918
|
+
};
|
|
3919
|
+
const body = renderTableCellText(cell, paragraphs, 0, 0, localBounds.w, localBounds.h, activeDeckTextColor, pres, shape, theme, getPresentationFonts(pres)?.minorLatin ?? null, ctx, anchor, margins, true);
|
|
3920
|
+
if (!body) return "";
|
|
3921
|
+
return `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 ${E(localBounds.w)} ${E(localBounds.h)}" width="100%" height="100%" overflow="visible" aria-hidden="true">${body}</svg>`;
|
|
2760
3922
|
};
|
|
2761
3923
|
const E = (n) => (n / EMU_PER_PX).toFixed(2);
|
|
2762
3924
|
const px = (n) => n.toFixed(2);
|
|
@@ -2781,6 +3943,16 @@ const accentSequence = (theme) => {
|
|
|
2781
3943
|
return hexes.length > 0 ? hexes : fallbacks;
|
|
2782
3944
|
};
|
|
2783
3945
|
const DEFAULT_CHART_TITLE_PT = 13;
|
|
3946
|
+
const chartText = (f, x, y, attrs, content) => {
|
|
3947
|
+
if (f.reflected) {
|
|
3948
|
+
const reflection = `translate(${px(2 * x)} 0) scale(-1 1)`;
|
|
3949
|
+
const rotation = /transform="([^"]*)"/.exec(attrs);
|
|
3950
|
+
attrs = rotation ? attrs.replace(rotation[0], `transform="${rotation[1]} ${reflection}"`) : `${attrs} transform="${reflection}"`;
|
|
3951
|
+
const anchor = /text-anchor="(start|middle|end)"/.exec(attrs);
|
|
3952
|
+
attrs = anchor ? attrs.replace(anchor[0], `text-anchor="${anchor[1] === "start" ? "end" : anchor[1] === "end" ? "start" : "middle"}"`) : `${attrs} text-anchor="end"`;
|
|
3953
|
+
}
|
|
3954
|
+
return `<text x="${px(x)}" y="${px(y)}" ${attrs}>${content}</text>`;
|
|
3955
|
+
};
|
|
2784
3956
|
const layoutChart = (xEmu, yEmu, wEmu, hEmu, hasTitle, hasAxes, titleOverlay = false, legendOverlay = false, hasLegend = true, titlePx = DEFAULT_CHART_TITLE_PT * PX_PER_PT, plotAreaLayout) => {
|
|
2785
3957
|
const x = xEmu / EMU_PER_PX;
|
|
2786
3958
|
const y = yEmu / EMU_PER_PX;
|
|
@@ -2814,14 +3986,25 @@ const niceStep = (range, target = 5) => {
|
|
|
2814
3986
|
const m = rawStep / base;
|
|
2815
3987
|
return (m < 1.5 ? 1 : m < 3 ? 2 : m < 7 ? 5 : 10) * base;
|
|
2816
3988
|
};
|
|
3989
|
+
const MAX_AXIS_TICKS = 1e3;
|
|
3990
|
+
const intervalTicks = (min, max, step) => {
|
|
3991
|
+
if (!(step > 0) || !Number.isFinite(step)) return [];
|
|
3992
|
+
const start = Math.ceil(min / step) * step;
|
|
3993
|
+
const count = Math.floor((max - start) / step + 1e-9) + 1;
|
|
3994
|
+
if (!Number.isFinite(count) || count <= 0 || count > MAX_AXIS_TICKS) return [];
|
|
3995
|
+
const ticks = [];
|
|
3996
|
+
for (let i = 0; i < count; i++) {
|
|
3997
|
+
const value = start + i * step;
|
|
3998
|
+
if (Number.isFinite(value) && value !== ticks.at(-1)) ticks.push(value);
|
|
3999
|
+
}
|
|
4000
|
+
return ticks;
|
|
4001
|
+
};
|
|
2817
4002
|
const niceTicks = (min, max, target = 5) => {
|
|
2818
4003
|
const range = max - min;
|
|
2819
4004
|
if (range <= 0) return [min];
|
|
2820
4005
|
const step = niceStep(range, target);
|
|
2821
|
-
const
|
|
2822
|
-
|
|
2823
|
-
for (let v = start; v <= max + step / 2; v += step) ticks.push(v);
|
|
2824
|
-
return ticks;
|
|
4006
|
+
const ticks = intervalTicks(min, max + step / 2, step);
|
|
4007
|
+
return ticks.length > 0 ? ticks : [min, max];
|
|
2825
4008
|
};
|
|
2826
4009
|
const formatTick = (v) => {
|
|
2827
4010
|
if (v === 0) return "0";
|
|
@@ -2901,12 +4084,8 @@ const axisTickAttrs = (style) => {
|
|
|
2901
4084
|
return `font-family="sans-serif" font-size="${chartFontPx(sz)}" fill="${fill}"${weight}${italic}`;
|
|
2902
4085
|
};
|
|
2903
4086
|
const renderValueAxis = (f, axis) => {
|
|
2904
|
-
const
|
|
2905
|
-
|
|
2906
|
-
const start = Math.ceil(axis.min / axis.majorUnit) * axis.majorUnit;
|
|
2907
|
-
for (let t = start; t <= axis.max + 1e-9; t += axis.majorUnit) out.push(t);
|
|
2908
|
-
return out.length > 0 ? out : niceTicks(axis.min, axis.max);
|
|
2909
|
-
})() : niceTicks(axis.min, axis.max);
|
|
4087
|
+
const authoredTicks = axis.majorUnit ? intervalTicks(axis.min, axis.max, axis.majorUnit) : [];
|
|
4088
|
+
const ticks = authoredTicks.length > 0 ? authoredTicks : niceTicks(axis.min, axis.max);
|
|
2910
4089
|
const out = [];
|
|
2911
4090
|
const range = axis.max - axis.min || 1;
|
|
2912
4091
|
const fmtTick = (t) => axis.percent ? `${Math.round(t * 100)}%` : formatAxisLabel(axis.displayUnits ? t / DISPLAY_UNIT_DIVISOR[axis.displayUnits] : t, axis.numberFormat);
|
|
@@ -2915,6 +4094,26 @@ const renderValueAxis = (f, axis) => {
|
|
|
2915
4094
|
const axisColor = axis.lineColor ?? DEFAULT_AXIS_COLOR;
|
|
2916
4095
|
const tickMark = axis.majorTickMark ?? "out";
|
|
2917
4096
|
const tickLen = AXIS_TICK_LEN;
|
|
4097
|
+
if (axis.minorGridlines) {
|
|
4098
|
+
const automaticMinorUnit = (ticks.length > 1 ? ticks[1] - ticks[0] : range) / 5;
|
|
4099
|
+
const authoredMinorTicks = axis.minorUnit ? intervalTicks(axis.min, axis.max, axis.minorUnit) : [];
|
|
4100
|
+
const minorTicks = authoredMinorTicks.length > 0 ? authoredMinorTicks : intervalTicks(axis.min, axis.max, automaticMinorUnit);
|
|
4101
|
+
const positionKey = (value) => ((value - axis.min) / range).toFixed(10);
|
|
4102
|
+
const majorPositions = new Set(ticks.map(positionKey));
|
|
4103
|
+
const stroke = axis.minorGridlineColor ?? DEFAULT_GRID_COLOR;
|
|
4104
|
+
const width = axis.minorGridlineWidthEmu === void 0 ? .5 : axis.minorGridlineWidthEmu / EMU_PER_PX;
|
|
4105
|
+
for (const t of minorTicks) {
|
|
4106
|
+
if (majorPositions.has(positionKey(t))) continue;
|
|
4107
|
+
const fraction = (t - axis.min) / range;
|
|
4108
|
+
if (axis.orientation === "vertical") {
|
|
4109
|
+
const y = f.plotY + f.plotH - fraction * f.plotH;
|
|
4110
|
+
out.push(`<line data-chart-gridline="minor" x1="${px(f.plotX)}" y1="${px(y)}" x2="${px(f.plotX + f.plotW)}" y2="${px(y)}" stroke="${stroke}" stroke-width="${width}"/>`);
|
|
4111
|
+
} else {
|
|
4112
|
+
const x = f.plotX + fraction * f.plotW;
|
|
4113
|
+
out.push(`<line data-chart-gridline="minor" x1="${px(x)}" y1="${px(f.plotY)}" x2="${px(x)}" y2="${px(f.plotY + f.plotH)}" stroke="${stroke}" stroke-width="${width}"/>`);
|
|
4114
|
+
}
|
|
4115
|
+
}
|
|
4116
|
+
}
|
|
2918
4117
|
for (const t of ticks) if (axis.orientation === "vertical") {
|
|
2919
4118
|
const yp = f.plotY + f.plotH - (t - axis.min) / range * f.plotH;
|
|
2920
4119
|
const onRight = axis.side === "right";
|
|
@@ -2930,7 +4129,7 @@ const renderValueAxis = (f, axis) => {
|
|
|
2930
4129
|
const labelX = onRight ? edgeX + 4 : edgeX - 4;
|
|
2931
4130
|
const rot = axis.labelRotationDeg ?? 0;
|
|
2932
4131
|
const transform = rot ? ` transform="rotate(${rot} ${px(labelX)} ${px(yp)})"` : "";
|
|
2933
|
-
out.push(
|
|
4132
|
+
out.push(chartText(f, labelX, yp, `text-anchor="${onRight ? "start" : "end"}" dominant-baseline="middle" ${axisTickAttrs(axis.labelStyle)}${transform}`, `${escapeXml(fmtTick(t))}`));
|
|
2934
4133
|
} else {
|
|
2935
4134
|
const xp = f.plotX + (t - axis.min) / range * f.plotW;
|
|
2936
4135
|
if (showGrid) out.push(`<line x1="${px(xp)}" y1="${px(f.plotY)}" x2="${px(xp)}" y2="${px(f.plotY + f.plotH)}" stroke="${gridStroke}" stroke-width="0.5"/>`);
|
|
@@ -2943,15 +4142,15 @@ const renderValueAxis = (f, axis) => {
|
|
|
2943
4142
|
const horizLabelY = f.plotY + f.plotH + 12;
|
|
2944
4143
|
const rotH = axis.labelRotationDeg ?? 0;
|
|
2945
4144
|
const transformH = rotH ? ` transform="rotate(${rotH} ${px(xp)} ${px(horizLabelY)})"` : "";
|
|
2946
|
-
out.push(
|
|
4145
|
+
out.push(chartText(f, xp, horizLabelY, `text-anchor="middle" dominant-baseline="middle" ${axisTickAttrs(axis.labelStyle)}${transformH}`, `${escapeXml(fmtTick(t))}`));
|
|
2947
4146
|
}
|
|
2948
4147
|
if (axis.displayUnits) {
|
|
2949
4148
|
const lbl = DISPLAY_UNIT_LABEL[axis.displayUnits];
|
|
2950
4149
|
if (axis.orientation === "vertical") {
|
|
2951
4150
|
const lblX = f.plotX - 26;
|
|
2952
4151
|
const lblY = f.plotY + f.plotH / 2;
|
|
2953
|
-
out.push(
|
|
2954
|
-
} else out.push(
|
|
4152
|
+
out.push(chartText(f, lblX, lblY, `text-anchor="middle" font-family="sans-serif" font-size="9" fill="#6B7280" font-style="italic" transform="rotate(-90 ${px(lblX)} ${px(lblY)})"`, `${escapeXml(lbl)}`));
|
|
4153
|
+
} else out.push(chartText(f, f.plotX + f.plotW, f.plotY + f.plotH + 22, `text-anchor="end" font-family="sans-serif" font-size="9" fill="#6B7280" font-style="italic"`, `${escapeXml(lbl)}`));
|
|
2955
4154
|
}
|
|
2956
4155
|
if (!axis.lineHidden) if (axis.orientation === "vertical") out.push(`<line x1="${px(f.plotX)}" y1="${px(f.plotY)}" x2="${px(f.plotX)}" y2="${px(f.plotY + f.plotH)}" stroke="${axisColor}" stroke-width="1"/>`);
|
|
2957
4156
|
else {
|
|
@@ -2975,7 +4174,7 @@ const renderCategoryAxis = (f, orientation, cats, pointCount, skip = 1, labelSty
|
|
|
2975
4174
|
const truncated = labels[i] !== void 0 && labels[i].length > truncLen ? `${labels[i].slice(0, truncLen - 2)}…` : labels[i] ?? "";
|
|
2976
4175
|
const transform = labelRotationDeg && labelRotationDeg !== 0 ? ` transform="rotate(${labelRotationDeg} ${px(cx)} ${px(cy)})"` : "";
|
|
2977
4176
|
const anchor = labelAlign === "l" ? "start" : labelAlign === "r" ? "end" : labelAlign === "ctr" ? "middle" : labelRotationDeg && labelRotationDeg > 0 ? "end" : labelRotationDeg && labelRotationDeg < 0 ? "start" : "middle";
|
|
2978
|
-
out.push(
|
|
4177
|
+
out.push(chartText(f, cx, cy, `text-anchor="${anchor}" dominant-baseline="middle" ${axisTickAttrs(labelStyle)}${transform}`, `${escapeXml(truncated)}`));
|
|
2979
4178
|
}
|
|
2980
4179
|
} else {
|
|
2981
4180
|
const step = pointCount > 0 ? f.plotH / pointCount : 0;
|
|
@@ -2986,7 +4185,7 @@ const renderCategoryAxis = (f, orientation, cats, pointCount, skip = 1, labelSty
|
|
|
2986
4185
|
const lx = f.plotX - 4;
|
|
2987
4186
|
const truncated = labels[i] !== void 0 && labels[i].length > truncLen ? `${labels[i].slice(0, truncLen - 2)}…` : labels[i] ?? "";
|
|
2988
4187
|
const transform = labelRotationDeg && labelRotationDeg !== 0 ? ` transform="rotate(${labelRotationDeg} ${px(lx)} ${px(cy)})"` : "";
|
|
2989
|
-
out.push(
|
|
4188
|
+
out.push(chartText(f, lx, cy, `text-anchor="end" dominant-baseline="middle" ${axisTickAttrs(labelStyle)}${transform}`, `${escapeXml(truncated)}`));
|
|
2990
4189
|
}
|
|
2991
4190
|
}
|
|
2992
4191
|
const axisColor = lineColor ?? DEFAULT_AXIS_COLOR;
|
|
@@ -3060,7 +4259,7 @@ const renderChartTitle = (f, title, style) => {
|
|
|
3060
4259
|
const fill = style?.color ?? "#1F2937";
|
|
3061
4260
|
const weight = style?.bold === false ? "400" : "600";
|
|
3062
4261
|
const fontStyleAttr = style?.italic ? " font-style=\"italic\"" : "";
|
|
3063
|
-
return
|
|
4262
|
+
return chartText(f, f.x + f.w / 2, f.titleY, `text-anchor="middle" dominant-baseline="middle" font-family="sans-serif" font-size="${chartFontPx(sz)}" fill="${fill}" font-weight="${weight}"${fontStyleAttr}`, `${escapeXml(title)}`);
|
|
3064
4263
|
};
|
|
3065
4264
|
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;
|
|
3066
4265
|
const approxLegendTextPx = (text, fontPx) => {
|
|
@@ -3095,7 +4294,7 @@ const renderChartLegend = (f, names, colors, position = "b", textStyle, markers)
|
|
|
3095
4294
|
const rowY = position === "b" ? f.legendY : f.y + 12;
|
|
3096
4295
|
let cursor = f.x + Math.max(0, (f.w - naturalTotal * scale) / 2);
|
|
3097
4296
|
for (let i = 0; i < names.length; i++) {
|
|
3098
|
-
out.push(swatch(i, cursor, rowY - 4),
|
|
4297
|
+
out.push(swatch(i, cursor, rowY - 4), chartText(f, cursor + swatchGapPx * scale, rowY, `dominant-baseline="middle" ${effAttrs}`, `${escapeXml(names[i] ?? `Series ${i + 1}`)}`));
|
|
3099
4298
|
cursor += (swatchGapPx + (labelWidths[i] ?? 0) + itemGapPx) * scale;
|
|
3100
4299
|
}
|
|
3101
4300
|
return out.join("");
|
|
@@ -3108,7 +4307,7 @@ const renderChartLegend = (f, names, colors, position = "b", textStyle, markers)
|
|
|
3108
4307
|
const xCol = position === "l" ? f.x + 6 : f.x + f.w - rightColW;
|
|
3109
4308
|
for (let i = 0; i < names.length; i++) {
|
|
3110
4309
|
const yp = yStart + i * lineH;
|
|
3111
|
-
out.push(swatch(i, xCol, yp - 4),
|
|
4310
|
+
out.push(swatch(i, xCol, yp - 4), chartText(f, xCol + 14, yp + 4, `dominant-baseline="middle" ${textAttrs}`, `${escapeXml(names[i] ?? `Series ${i + 1}`)}`));
|
|
3112
4311
|
}
|
|
3113
4312
|
return out.join("");
|
|
3114
4313
|
};
|
|
@@ -3120,6 +4319,50 @@ const pointCount = (spec) => {
|
|
|
3120
4319
|
};
|
|
3121
4320
|
const chartFillOpacityAttr = (opacity = 1) => opacity === 1 ? "" : ` fill-opacity="${opacity.toFixed(3)}"`;
|
|
3122
4321
|
const chartPointBaseColor = (spec, colors, seriesIndex, pointIndex) => spec.series[seriesIndex]?.pointColors?.[pointIndex] ?? (spec.varyColors && spec.series.length === 1 ? colors[pointIndex % colors.length] : spec.series[seriesIndex]?.color ?? colors[seriesIndex % colors.length]);
|
|
4322
|
+
const columnLabelLayout = (y, h, v, pos) => {
|
|
4323
|
+
let coordinate;
|
|
4324
|
+
let fill = "#374151";
|
|
4325
|
+
if (pos === "ctr") {
|
|
4326
|
+
coordinate = y + h / 2 + 3;
|
|
4327
|
+
fill = "#FFFFFF";
|
|
4328
|
+
} else if (pos === "inEnd") {
|
|
4329
|
+
coordinate = v >= 0 ? y + 9 : y + h - 3;
|
|
4330
|
+
fill = "#FFFFFF";
|
|
4331
|
+
} else if (pos === "inBase") {
|
|
4332
|
+
coordinate = v >= 0 ? y + h - 3 : y + 9;
|
|
4333
|
+
fill = "#FFFFFF";
|
|
4334
|
+
} else coordinate = v >= 0 ? y - 2 : y + h + 9;
|
|
4335
|
+
return {
|
|
4336
|
+
coordinate,
|
|
4337
|
+
fill
|
|
4338
|
+
};
|
|
4339
|
+
};
|
|
4340
|
+
const barLabelLayout = (x, w, v, pos) => {
|
|
4341
|
+
let coordinate;
|
|
4342
|
+
let anchor;
|
|
4343
|
+
let fill = "#374151";
|
|
4344
|
+
if (pos === "ctr") {
|
|
4345
|
+
coordinate = x + w / 2;
|
|
4346
|
+
anchor = "middle";
|
|
4347
|
+
fill = "#FFFFFF";
|
|
4348
|
+
} else if (pos === "inEnd") {
|
|
4349
|
+
coordinate = v >= 0 ? x + w - 4 : x + 4;
|
|
4350
|
+
anchor = v >= 0 ? "end" : "start";
|
|
4351
|
+
fill = "#FFFFFF";
|
|
4352
|
+
} else if (pos === "inBase") {
|
|
4353
|
+
coordinate = v >= 0 ? x + 4 : x + w - 4;
|
|
4354
|
+
anchor = v >= 0 ? "start" : "end";
|
|
4355
|
+
fill = "#FFFFFF";
|
|
4356
|
+
} else {
|
|
4357
|
+
coordinate = v >= 0 ? x + w + 2 : x - 2;
|
|
4358
|
+
anchor = v >= 0 ? "start" : "end";
|
|
4359
|
+
}
|
|
4360
|
+
return {
|
|
4361
|
+
coordinate,
|
|
4362
|
+
anchor,
|
|
4363
|
+
fill
|
|
4364
|
+
};
|
|
4365
|
+
};
|
|
3123
4366
|
const renderColumnChart = (f, spec, colors) => {
|
|
3124
4367
|
const N = pointCount(spec);
|
|
3125
4368
|
if (N === 0 || spec.series.length === 0) return "";
|
|
@@ -3135,7 +4378,6 @@ const renderColumnChart = (f, spec, colors) => {
|
|
|
3135
4378
|
const clusterUnitsC = isStacked ? 1 : 1 + (Sc - 1) * (1 - overlapPctC);
|
|
3136
4379
|
const barW = groupW / Math.max(.5, clusterUnitsC + gapPctC);
|
|
3137
4380
|
const baseY = f.plotY + f.plotH - (0 - min) / range * f.plotH;
|
|
3138
|
-
const showLabelFor = (s) => spec.series[s]?.dataLabels?.showValue ?? spec.dataLabels?.showValue ?? false;
|
|
3139
4381
|
const out = [];
|
|
3140
4382
|
for (let c = 0; c < N; c++) if (isStacked) {
|
|
3141
4383
|
let total = 0;
|
|
@@ -3156,10 +4398,10 @@ const renderColumnChart = (f, spec, colors) => {
|
|
|
3156
4398
|
const y1 = f.plotY + f.plotH - (Math.min(stackedTop, stackedBase) - min) / range * f.plotH;
|
|
3157
4399
|
const h = Math.abs(y1 - y0);
|
|
3158
4400
|
out.push(`<rect x="${px(x0)}" y="${px(y0)}" width="${px(barW)}" height="${px(h)}" fill="${chartPointBaseColor(spec, colors, s, c)}"${chartFillOpacityAttr(spec.series[s]?.fillOpacity)}/>`);
|
|
3159
|
-
|
|
3160
|
-
|
|
3161
|
-
const
|
|
3162
|
-
out.push(
|
|
4401
|
+
const labelText = cartesianDataLabelText(spec, s, c, spec.series[s]?.values[c] ?? 0);
|
|
4402
|
+
if (labelText) {
|
|
4403
|
+
const { coordinate: labelY, fill } = columnLabelLayout(y0, h, v, chartPointLabelOptions(spec, s, c).position ?? "ctr");
|
|
4404
|
+
out.push(chartText(f, x0 + barW / 2, labelY, `text-anchor="middle" ${dataLabelTextAttrs(spec, s, fill, 9, true, c)}`, `${escapeXml(labelText)}`));
|
|
3163
4405
|
}
|
|
3164
4406
|
if (v >= 0) posAcc = stackedTop;
|
|
3165
4407
|
else negAcc = stackedTop;
|
|
@@ -3177,21 +4419,10 @@ const renderColumnChart = (f, spec, colors) => {
|
|
|
3177
4419
|
const baseColor = chartPointBaseColor(spec, colors, s, c);
|
|
3178
4420
|
const fillColor = v < 0 && spec.series[s]?.invertIfNegative ? mixHex(baseColor, "#000000", .55) : baseColor;
|
|
3179
4421
|
out.push(`<rect x="${px(x0)}" y="${px(y0)}" width="${px(barW)}" height="${px(h)}" fill="${fillColor}"${chartFillOpacityAttr(spec.series[s]?.fillOpacity)}/>`);
|
|
3180
|
-
|
|
3181
|
-
|
|
3182
|
-
|
|
3183
|
-
|
|
3184
|
-
if (pos === "ctr") {
|
|
3185
|
-
labelY = y0 + h / 2 + 3;
|
|
3186
|
-
fill = "#FFFFFF";
|
|
3187
|
-
} else if (pos === "inEnd") {
|
|
3188
|
-
labelY = v >= 0 ? y0 + 9 : y0 + h - 3;
|
|
3189
|
-
fill = "#FFFFFF";
|
|
3190
|
-
} else if (pos === "inBase") {
|
|
3191
|
-
labelY = v >= 0 ? y0 + h - 3 : y0 + 9;
|
|
3192
|
-
fill = "#FFFFFF";
|
|
3193
|
-
} else labelY = v >= 0 ? y0 - 2 : y0 + h + 9;
|
|
3194
|
-
out.push(`<text x="${px(x0 + barW / 2)}" y="${px(labelY)}" text-anchor="middle" ${dataLabelTextAttrs(spec, s, fill)}>${formatDataLabelValue(spec, s, v)}</text>`);
|
|
4422
|
+
const labelText = cartesianDataLabelText(spec, s, c, v);
|
|
4423
|
+
if (labelText) {
|
|
4424
|
+
const { coordinate: labelY, fill } = columnLabelLayout(y0, h, v, chartPointLabelOptions(spec, s, c).position);
|
|
4425
|
+
out.push(chartText(f, x0 + barW / 2, labelY, `text-anchor="middle" ${dataLabelTextAttrs(spec, s, fill, 9, false, c)}`, `${escapeXml(labelText)}`));
|
|
3195
4426
|
}
|
|
3196
4427
|
}
|
|
3197
4428
|
}
|
|
@@ -3212,11 +4443,11 @@ const renderColumnChart = (f, spec, colors) => {
|
|
|
3212
4443
|
ys.push(cy);
|
|
3213
4444
|
}
|
|
3214
4445
|
if (xs.length < 2) continue;
|
|
3215
|
-
out.push(trendlinePath(xs, ys, series.trendline, tlColor));
|
|
4446
|
+
out.push(trendlinePath(f, xs, ys, series.trendline, tlColor));
|
|
3216
4447
|
}
|
|
3217
4448
|
return out.join("");
|
|
3218
4449
|
};
|
|
3219
|
-
const trendlinePath = (xs, ys, tl, color) => {
|
|
4450
|
+
const trendlinePath = (f, xs, ys, tl, color) => {
|
|
3220
4451
|
const n = xs.length;
|
|
3221
4452
|
const stepX = n >= 2 ? (xs[n - 1] - xs[0]) / (n - 1) : 0;
|
|
3222
4453
|
const extendBefore = (tl.backward ?? 0) * stepX;
|
|
@@ -3267,7 +4498,7 @@ const trendlinePath = (xs, ys, tl, color) => {
|
|
|
3267
4498
|
const path = `<path d="${pts.map(([x, y], i) => `${i === 0 ? "M" : "L"}${px(x)},${px(y)}`).join(" ")}" fill="none" stroke="${color}" stroke-width="1.5" stroke-dasharray="6 3" stroke-linecap="round"/>`;
|
|
3268
4499
|
if (tl.name !== void 0 && tl.name.length > 0) {
|
|
3269
4500
|
const [lx, ly] = pts[pts.length - 1];
|
|
3270
|
-
return path +
|
|
4501
|
+
return path + chartText(f, lx + 4, ly, `dominant-baseline="middle" font-family="sans-serif" font-size="9" fill="${color}"`, `${escapeXml(tl.name)}`);
|
|
3271
4502
|
}
|
|
3272
4503
|
return path;
|
|
3273
4504
|
};
|
|
@@ -3352,12 +4583,28 @@ const formatChartValue = (v) => {
|
|
|
3352
4583
|
if (abs >= 10) return v.toFixed(1);
|
|
3353
4584
|
return v.toFixed(2).replace(/\.?0+$/, "");
|
|
3354
4585
|
};
|
|
3355
|
-
const
|
|
3356
|
-
|
|
4586
|
+
const chartPointLabelOptions = (spec, seriesIdx, pointIdx) => ({
|
|
4587
|
+
...spec.dataLabels,
|
|
4588
|
+
...spec.series[seriesIdx]?.dataLabels,
|
|
4589
|
+
...spec.series[seriesIdx]?.pointDataLabels?.[pointIdx]
|
|
4590
|
+
});
|
|
4591
|
+
const cartesianDataLabelText = (spec, seriesIdx, pointIdx, value) => {
|
|
4592
|
+
const options = chartPointLabelOptions(spec, seriesIdx, pointIdx);
|
|
4593
|
+
if (options.text !== void 0) return options.text;
|
|
4594
|
+
const parts = [];
|
|
4595
|
+
const name = spec.series[seriesIdx]?.name;
|
|
4596
|
+
const category = spec.categories[pointIdx];
|
|
4597
|
+
if (options.showSeriesName && name) parts.push(name);
|
|
4598
|
+
if (options.showCategory && category) parts.push(category);
|
|
4599
|
+
if (options.showValue) parts.push(formatDataLabelValue(spec, seriesIdx, value, pointIdx));
|
|
4600
|
+
return parts.join(options.separator ?? " ");
|
|
4601
|
+
};
|
|
4602
|
+
const formatDataLabelValue = (spec, seriesIdx, v, pointIdx) => {
|
|
4603
|
+
const nf = (pointIdx === void 0 ? void 0 : spec.series[seriesIdx]?.pointDataLabels?.[pointIdx])?.numberFormat ?? spec.series[seriesIdx]?.dataLabels?.numberFormat ?? spec.dataLabels?.numberFormat;
|
|
3357
4604
|
return nf ? formatAxisLabel(v, nf) : formatChartValue(v);
|
|
3358
4605
|
};
|
|
3359
|
-
const dataLabelTextAttrs = (spec, seriesIdx, fallbackFill, fallbackSizePt = 9, fallbackBold = false) => {
|
|
3360
|
-
const style = spec.series[seriesIdx]?.dataLabels?.textStyle ?? spec.dataLabels?.textStyle;
|
|
4606
|
+
const dataLabelTextAttrs = (spec, seriesIdx, fallbackFill, fallbackSizePt = 9, fallbackBold = false, pointIdx) => {
|
|
4607
|
+
const style = (pointIdx === void 0 ? void 0 : spec.series[seriesIdx]?.pointDataLabels?.[pointIdx])?.textStyle ?? spec.series[seriesIdx]?.dataLabels?.textStyle ?? spec.dataLabels?.textStyle;
|
|
3361
4608
|
const sz = style?.sizePt ?? fallbackSizePt;
|
|
3362
4609
|
const fill = style?.color ?? fallbackFill;
|
|
3363
4610
|
const weight = style?.bold ?? fallbackBold ? " font-weight=\"600\"" : "";
|
|
@@ -3379,7 +4626,6 @@ const renderBarChart = (f, spec, colors) => {
|
|
|
3379
4626
|
const clusterUnitsB = isStacked ? 1 : 1 + (Sb - 1) * (1 - overlapPctB);
|
|
3380
4627
|
const barH = groupH / Math.max(.5, clusterUnitsB + gapPctB);
|
|
3381
4628
|
const baseX = f.plotX + (0 - min) / range * f.plotW;
|
|
3382
|
-
const showLabelForBar = (s) => spec.series[s]?.dataLabels?.showValue ?? spec.dataLabels?.showValue ?? false;
|
|
3383
4629
|
const out = [];
|
|
3384
4630
|
for (let c = 0; c < N; c++) if (isStacked) {
|
|
3385
4631
|
let total = 0;
|
|
@@ -3399,10 +4645,10 @@ const renderBarChart = (f, spec, colors) => {
|
|
|
3399
4645
|
const x1 = f.plotX + (Math.max(base, stackedTop) - min) / range * f.plotW;
|
|
3400
4646
|
const w = Math.abs(x1 - x0);
|
|
3401
4647
|
out.push(`<rect x="${px(x0)}" y="${px(y0)}" width="${px(w)}" height="${px(barH)}" fill="${chartPointBaseColor(spec, colors, s, c)}"${chartFillOpacityAttr(spec.series[s]?.fillOpacity)}/>`);
|
|
3402
|
-
|
|
3403
|
-
|
|
3404
|
-
const
|
|
3405
|
-
out.push(
|
|
4648
|
+
const labelText = cartesianDataLabelText(spec, s, c, spec.series[s]?.values[c] ?? 0);
|
|
4649
|
+
if (labelText) {
|
|
4650
|
+
const { coordinate: labelX, anchor, fill } = barLabelLayout(x0, w, v, chartPointLabelOptions(spec, s, c).position ?? "ctr");
|
|
4651
|
+
out.push(chartText(f, labelX, y0 + barH / 2 + 3, `text-anchor="${anchor}" ${dataLabelTextAttrs(spec, s, fill, 9, true, c)}`, `${escapeXml(labelText)}`));
|
|
3406
4652
|
}
|
|
3407
4653
|
if (v >= 0) posAcc = stackedTop;
|
|
3408
4654
|
else negAcc = stackedTop;
|
|
@@ -3420,28 +4666,10 @@ const renderBarChart = (f, spec, colors) => {
|
|
|
3420
4666
|
const baseColor = chartPointBaseColor(spec, colors, s, c);
|
|
3421
4667
|
const fillColor = v < 0 && spec.series[s]?.invertIfNegative ? mixHex(baseColor, "#000000", .55) : baseColor;
|
|
3422
4668
|
out.push(`<rect x="${px(x0)}" y="${px(y0)}" width="${px(w)}" height="${px(barH)}" fill="${fillColor}"${chartFillOpacityAttr(spec.series[s]?.fillOpacity)}/>`);
|
|
3423
|
-
|
|
3424
|
-
|
|
3425
|
-
|
|
3426
|
-
|
|
3427
|
-
let fill = "#374151";
|
|
3428
|
-
if (pos === "ctr") {
|
|
3429
|
-
labelX = x0 + w / 2;
|
|
3430
|
-
anchor = "middle";
|
|
3431
|
-
fill = "#FFFFFF";
|
|
3432
|
-
} else if (pos === "inEnd") {
|
|
3433
|
-
labelX = v >= 0 ? x0 + w - 4 : x0 + 4;
|
|
3434
|
-
anchor = v >= 0 ? "end" : "start";
|
|
3435
|
-
fill = "#FFFFFF";
|
|
3436
|
-
} else if (pos === "inBase") {
|
|
3437
|
-
labelX = v >= 0 ? x0 + 4 : x0 + w - 4;
|
|
3438
|
-
anchor = v >= 0 ? "start" : "end";
|
|
3439
|
-
fill = "#FFFFFF";
|
|
3440
|
-
} else {
|
|
3441
|
-
labelX = v >= 0 ? x0 + w + 2 : x0 - 2;
|
|
3442
|
-
anchor = v >= 0 ? "start" : "end";
|
|
3443
|
-
}
|
|
3444
|
-
out.push(`<text x="${px(labelX)}" y="${px(y0 + barH / 2 + 3)}" text-anchor="${anchor}" ${dataLabelTextAttrs(spec, s, fill)}>${formatDataLabelValue(spec, s, v)}</text>`);
|
|
4669
|
+
const labelText = cartesianDataLabelText(spec, s, c, v);
|
|
4670
|
+
if (labelText) {
|
|
4671
|
+
const { coordinate: labelX, anchor, fill } = barLabelLayout(x0, w, v, chartPointLabelOptions(spec, s, c).position);
|
|
4672
|
+
out.push(chartText(f, labelX, y0 + barH / 2 + 3, `text-anchor="${anchor}" ${dataLabelTextAttrs(spec, s, fill, 9, false, c)}`, `${escapeXml(labelText)}`));
|
|
3445
4673
|
}
|
|
3446
4674
|
}
|
|
3447
4675
|
}
|
|
@@ -3495,36 +4723,36 @@ const renderLineChart = (f, spec, colors, fill) => {
|
|
|
3495
4723
|
basePtsRaw.push([xp, yBase]);
|
|
3496
4724
|
if (isStacked) accumulated[c] = top;
|
|
3497
4725
|
}
|
|
3498
|
-
const pts =
|
|
3499
|
-
const basePts =
|
|
3500
|
-
const
|
|
3501
|
-
|
|
3502
|
-
|
|
3503
|
-
|
|
3504
|
-
|
|
3505
|
-
|
|
3506
|
-
|
|
3507
|
-
|
|
3508
|
-
|
|
3509
|
-
|
|
3510
|
-
|
|
3511
|
-
|
|
3512
|
-
|
|
4726
|
+
const pts = ptsRaw.filter((p) => p !== null);
|
|
4727
|
+
const basePts = basePtsRaw.filter((p) => p !== null);
|
|
4728
|
+
const segments = [];
|
|
4729
|
+
let segment = [];
|
|
4730
|
+
for (const point of ptsRaw) if (point !== null) segment.push(point);
|
|
4731
|
+
else if (dba === "gap" && segment.length > 0) {
|
|
4732
|
+
segments.push(segment);
|
|
4733
|
+
segment = [];
|
|
4734
|
+
}
|
|
4735
|
+
if (segment.length > 0) segments.push(segment);
|
|
4736
|
+
const paths = segments.map((points) => series.smooth && points.length > 2 ? smoothPath(points) : points.map(([x, y], i) => `${i === 0 ? "M" : "L"}${px(x)},${px(y)}`).join(" "));
|
|
4737
|
+
const dPath = paths.join(" ");
|
|
4738
|
+
let offset = 0;
|
|
4739
|
+
const areaPath = fill ? segments.map((points, i) => {
|
|
4740
|
+
const back = basePts.slice(offset, offset + points.length).reverse().map(([x, y]) => `L${px(x)},${px(y)}`).join(" ");
|
|
4741
|
+
offset += points.length;
|
|
4742
|
+
return `${paths[i]} ${back} Z`;
|
|
4743
|
+
}).join(" ") : "";
|
|
3513
4744
|
perSeries.push({
|
|
3514
4745
|
s,
|
|
3515
4746
|
series,
|
|
3516
4747
|
color,
|
|
3517
4748
|
ptsRaw,
|
|
3518
4749
|
pts,
|
|
3519
|
-
|
|
4750
|
+
areaPath,
|
|
3520
4751
|
dPath
|
|
3521
4752
|
});
|
|
3522
4753
|
}
|
|
3523
4754
|
const paintOrder = fill && !isStacked ? perSeries.slice().reverse() : perSeries;
|
|
3524
|
-
if (fill) for (const { color,
|
|
3525
|
-
const back = basePts.slice().reverse().map(([xp, yp]) => `L${px(xp)},${px(yp)}`).join(" ");
|
|
3526
|
-
out.push(`<path d="${dPath} ${back} Z" fill="${color}" stroke="none"/>`);
|
|
3527
|
-
}
|
|
4755
|
+
if (fill) for (const { color, areaPath } of paintOrder) out.push(`<path d="${areaPath}" fill="${color}" stroke="none"/>`);
|
|
3528
4756
|
for (const { s, series, color, ptsRaw, pts, dPath } of paintOrder) {
|
|
3529
4757
|
const lineWPx = series.lineWidthEmu ? Math.max(.3, series.lineWidthEmu / EMU_PER_PX) : 1.5;
|
|
3530
4758
|
const dashAttr = series.lineDash ? (() => {
|
|
@@ -3534,55 +4762,26 @@ const renderLineChart = (f, spec, colors, fill) => {
|
|
|
3534
4762
|
return ` stroke-dasharray="${pat.split(" ").map((n) => (Number.parseFloat(n) * sw).toFixed(2)).join(" ")}"`;
|
|
3535
4763
|
})() : "";
|
|
3536
4764
|
out.push(`<path d="${dPath}" fill="none" stroke="${series.lineColor ?? color}" stroke-width="${lineWPx.toFixed(2)}" stroke-linejoin="round" stroke-linecap="round"${dashAttr}/>`);
|
|
3537
|
-
|
|
3538
|
-
|
|
3539
|
-
|
|
3540
|
-
|
|
3541
|
-
|
|
3542
|
-
|
|
3543
|
-
for (const [xp, yp] of pts) out.push(seriesMarker(symbol, xp, yp, r, ...markerColors(series, color)));
|
|
3544
|
-
}
|
|
4765
|
+
const explicitSymbol = series.markerSymbol !== void 0 && series.markerSymbol !== "auto" && series.markerSymbol !== "none";
|
|
4766
|
+
if (series.markerSymbol !== "none" && !fill && (spec.lineMarkers === true || explicitSymbol)) {
|
|
4767
|
+
const symbol = autoMarkerSymbol(series.markerSymbol, s);
|
|
4768
|
+
const size = series.markerSizePt ?? 5;
|
|
4769
|
+
const r = Math.max(1, size * .5);
|
|
4770
|
+
for (const [xp, yp] of pts) out.push(seriesMarker(symbol, xp, yp, r, ...markerColors(series, color)));
|
|
3545
4771
|
}
|
|
3546
|
-
|
|
3547
|
-
const
|
|
3548
|
-
|
|
3549
|
-
|
|
3550
|
-
|
|
3551
|
-
|
|
3552
|
-
|
|
3553
|
-
|
|
3554
|
-
|
|
3555
|
-
|
|
3556
|
-
|
|
3557
|
-
|
|
3558
|
-
|
|
3559
|
-
};
|
|
3560
|
-
case "l": return {
|
|
3561
|
-
x: xp - 6,
|
|
3562
|
-
y: yp + 3,
|
|
3563
|
-
anchor: "end"
|
|
3564
|
-
};
|
|
3565
|
-
case "r": return {
|
|
3566
|
-
x: xp + 6,
|
|
3567
|
-
y: yp + 3,
|
|
3568
|
-
anchor: "start"
|
|
3569
|
-
};
|
|
3570
|
-
default: return {
|
|
3571
|
-
x: xp,
|
|
3572
|
-
y: yp - 5,
|
|
3573
|
-
anchor: "middle"
|
|
3574
|
-
};
|
|
3575
|
-
}
|
|
3576
|
-
};
|
|
3577
|
-
for (let c = 0; c < N; c++) {
|
|
3578
|
-
const p = ptsRaw[c];
|
|
3579
|
-
if (p == null) continue;
|
|
3580
|
-
const v = series.values[c];
|
|
3581
|
-
if (v === null || v === void 0 || !Number.isFinite(v)) continue;
|
|
3582
|
-
const [xp, yp] = p;
|
|
3583
|
-
const { x: lx, y: ly, anchor } = computeAttrs(xp, yp);
|
|
3584
|
-
out.push(`<text x="${px(lx)}" y="${px(ly)}" text-anchor="${anchor}" ${dataLabelTextAttrs(spec, s, "#374151")}>${formatDataLabelValue(spec, s, v)}</text>`);
|
|
3585
|
-
}
|
|
4772
|
+
for (let c = 0; c < N; c++) {
|
|
4773
|
+
const p = ptsRaw[c];
|
|
4774
|
+
if (p == null) continue;
|
|
4775
|
+
const v = series.values[c];
|
|
4776
|
+
if (v == null || !Number.isFinite(v)) continue;
|
|
4777
|
+
const labelText = cartesianDataLabelText(spec, s, c, v);
|
|
4778
|
+
if (!labelText) continue;
|
|
4779
|
+
const [xp, yp] = p;
|
|
4780
|
+
const pos = chartPointLabelOptions(spec, s, c).position;
|
|
4781
|
+
const lx = pos === "l" ? xp - 6 : pos === "r" ? xp + 6 : xp;
|
|
4782
|
+
const ly = pos === "b" ? yp + 13 : pos === "ctr" || pos === "l" || pos === "r" ? yp + 3 : yp - 5;
|
|
4783
|
+
const anchor = pos === "l" ? "end" : pos === "r" ? "start" : "middle";
|
|
4784
|
+
out.push(chartText(f, lx, ly, `text-anchor="${anchor}" ${dataLabelTextAttrs(spec, s, "#374151", 9, false, c)}`, `${escapeXml(labelText)}`));
|
|
3586
4785
|
}
|
|
3587
4786
|
if (!isStacked && series.trendline) {
|
|
3588
4787
|
const finiteXs = [];
|
|
@@ -3593,7 +4792,7 @@ const renderLineChart = (f, spec, colors, fill) => {
|
|
|
3593
4792
|
}
|
|
3594
4793
|
if (finiteXs.length >= 2) {
|
|
3595
4794
|
const tlColor = series.trendline.color ?? color;
|
|
3596
|
-
out.push(trendlinePath(finiteXs, finiteYs, series.trendline, tlColor));
|
|
4795
|
+
out.push(trendlinePath(f, finiteXs, finiteYs, series.trendline, tlColor));
|
|
3597
4796
|
}
|
|
3598
4797
|
}
|
|
3599
4798
|
}
|
|
@@ -3665,7 +4864,8 @@ const renderPieChart = (f, spec, colors, doughnut) => {
|
|
|
3665
4864
|
out.push(`<path d="${d}" fill="${color}" stroke="#FFFFFF" stroke-width="0.6"/>`);
|
|
3666
4865
|
}
|
|
3667
4866
|
const labelMid = (start + end) / 2;
|
|
3668
|
-
const
|
|
4867
|
+
const labelOptions = chartPointLabelOptions(spec, 0, i);
|
|
4868
|
+
const pos = labelOptions.position;
|
|
3669
4869
|
let labelR;
|
|
3670
4870
|
let labelFill = "#FFFFFF";
|
|
3671
4871
|
if (pos === "inEnd") labelR = radius - 12;
|
|
@@ -3676,13 +4876,15 @@ const renderPieChart = (f, spec, colors, doughnut) => {
|
|
|
3676
4876
|
const labelX = sx + labelR * Math.cos(labelMid);
|
|
3677
4877
|
const labelY = sy + labelR * Math.sin(labelMid);
|
|
3678
4878
|
const labels = [];
|
|
3679
|
-
if (
|
|
4879
|
+
if (labelOptions.showSeriesName && series.name) labels.push(series.name);
|
|
4880
|
+
if (labelOptions.showCategory) {
|
|
3680
4881
|
const catLabel = spec.categories[i];
|
|
3681
4882
|
if (catLabel) labels.push(catLabel);
|
|
3682
4883
|
}
|
|
3683
|
-
if (
|
|
3684
|
-
if (
|
|
3685
|
-
|
|
4884
|
+
if (labelOptions.showValue) labels.push(formatDataLabelValue(spec, 0, v, i));
|
|
4885
|
+
if (labelOptions.showPercent) labels.push(`${(v / total * 100).toFixed(0)}%`);
|
|
4886
|
+
const labelText = labelOptions.text ?? labels.join(labelOptions.separator ?? " ");
|
|
4887
|
+
if (labelText) out.push(chartText(f, labelX, labelY, `text-anchor="middle" dominant-baseline="middle" ${dataLabelTextAttrs(spec, 0, labelFill, 10, true, i)}`, `${escapeXml(labelText)}`));
|
|
3686
4888
|
}
|
|
3687
4889
|
return out.join("");
|
|
3688
4890
|
};
|
|
@@ -3860,7 +5062,7 @@ const renderRadarChart = (f, spec, colors) => {
|
|
|
3860
5062
|
ring.push(`${px(cx + rr * Math.cos(a))},${px(cy + rr * Math.sin(a))}`);
|
|
3861
5063
|
}
|
|
3862
5064
|
out.push(`<polygon points="${ring.join(" ")}" fill="none" stroke="#E5E7EB" stroke-width="0.5"/>`);
|
|
3863
|
-
out.push(
|
|
5065
|
+
out.push(chartText(f, cx + 3, cy - rr, `dominant-baseline="middle" font-family="sans-serif" font-size="8" fill="#9CA3AF"`, `${escapeXml(formatTick(t))}`));
|
|
3864
5066
|
}
|
|
3865
5067
|
for (let i = 0; i < N; i++) {
|
|
3866
5068
|
const a = angleAt(i);
|
|
@@ -3871,7 +5073,7 @@ const renderRadarChart = (f, spec, colors) => {
|
|
|
3871
5073
|
const cosA = Math.cos(a);
|
|
3872
5074
|
const anchor = Math.abs(cosA) < .3 ? "middle" : cosA > 0 ? "start" : "end";
|
|
3873
5075
|
const label = cat.length > 12 ? `${cat.slice(0, 11)}…` : cat;
|
|
3874
|
-
out.push(
|
|
5076
|
+
out.push(chartText(f, lx, ly, `text-anchor="${anchor}" dominant-baseline="middle" ${axisTickAttrs(spec.categoryAxisLabelStyle)}`, `${escapeXml(label)}`));
|
|
3875
5077
|
}
|
|
3876
5078
|
const filled = spec.radarStyle === "filled";
|
|
3877
5079
|
const showMarker = spec.radarStyle === "marker";
|
|
@@ -3899,7 +5101,7 @@ const renderRadarChart = (f, spec, colors) => {
|
|
|
3899
5101
|
}
|
|
3900
5102
|
return out.join("");
|
|
3901
5103
|
};
|
|
3902
|
-
const renderChart = (shape, x, y, w, h, transform, theme) => {
|
|
5104
|
+
const renderChart = (shape, x, y, w, h, transform, theme, groupReflected) => {
|
|
3903
5105
|
let spec = null;
|
|
3904
5106
|
try {
|
|
3905
5107
|
spec = getShapeChartSpec(shape);
|
|
@@ -3919,7 +5121,11 @@ const renderChart = (shape, x, y, w, h, transform, theme) => {
|
|
|
3919
5121
|
const isCartesian = spec.kind === "column" || spec.kind === "bar" || spec.kind === "line" || spec.kind === "area";
|
|
3920
5122
|
const hasAxes = isCartesian || spec.kind === "scatter" || spec.kind === "bubble";
|
|
3921
5123
|
const hasLegend = spec.legend !== void 0 && spec.legend.position !== null;
|
|
3922
|
-
const
|
|
5124
|
+
const flip = getShapeFlip(shape);
|
|
5125
|
+
const f = {
|
|
5126
|
+
reflected: groupReflected !== Boolean(flip && flip.horizontal !== flip.vertical),
|
|
5127
|
+
...layoutChart(x, y, w, h, !!spec.title, hasAxes, spec.titleOverlay ?? false, spec.legend?.overlay ?? false, hasLegend, (spec.titleStyle?.sizePt ?? DEFAULT_CHART_TITLE_PT) * PX_PER_PT, spec.plotAreaLayout)
|
|
5128
|
+
};
|
|
3923
5129
|
const allNamesForLegend = spec.kind === "pie" || spec.kind === "doughnut" ? Array.from(spec.categories) : spec.series.map((s) => s.name);
|
|
3924
5130
|
const allColorsForLegend = spec.kind === "pie" || spec.kind === "doughnut" ? spec.categories.map((_, i) => spec.series[0]?.pointColors?.[i] ?? spec.series[0]?.color ?? colors[i % colors.length] ?? "#888") : spec.series.map((s, i) => s.color ?? colors[i % colors.length] ?? "#888");
|
|
3925
5131
|
const hiddenSet = new Set(spec.legend?.hiddenIndices ?? []);
|
|
@@ -3954,6 +5160,10 @@ const renderChart = (shape, x, y, w, h, transform, theme) => {
|
|
|
3954
5160
|
min: primaryScale.min,
|
|
3955
5161
|
max: primaryScale.max,
|
|
3956
5162
|
majorUnit: spec.valueAxis?.majorUnit ?? primaryScale.step,
|
|
5163
|
+
...spec.valueAxis?.minorUnit !== void 0 ? { minorUnit: spec.valueAxis.minorUnit } : {},
|
|
5164
|
+
...spec.valueAxisMinorGridlines !== void 0 ? { minorGridlines: spec.valueAxisMinorGridlines } : {},
|
|
5165
|
+
...spec.valueAxisMinorGridlineColor !== void 0 ? { minorGridlineColor: spec.valueAxisMinorGridlineColor } : {},
|
|
5166
|
+
...spec.valueAxisMinorGridlineWidthEmu !== void 0 ? { minorGridlineWidthEmu: spec.valueAxisMinorGridlineWidthEmu } : {},
|
|
3957
5167
|
...spec.valueAxisLineHidden !== void 0 ? { lineHidden: spec.valueAxisLineHidden } : {},
|
|
3958
5168
|
...spec.valueAxisLineColor !== void 0 ? { lineColor: spec.valueAxisLineColor } : {},
|
|
3959
5169
|
...spec.valueAxisMajorTickMark !== void 0 ? { majorTickMark: spec.valueAxisMajorTickMark } : {},
|
|
@@ -4005,6 +5215,10 @@ const renderChart = (shape, x, y, w, h, transform, theme) => {
|
|
|
4005
5215
|
const majorUnit = spec.valueAxis?.majorUnit ?? step;
|
|
4006
5216
|
const numberFormat = spec.valueAxis?.numberFormat;
|
|
4007
5217
|
const axisExtras = {
|
|
5218
|
+
...spec.valueAxis?.minorUnit !== void 0 ? { minorUnit: spec.valueAxis.minorUnit } : {},
|
|
5219
|
+
...spec.valueAxisMinorGridlines !== void 0 ? { minorGridlines: spec.valueAxisMinorGridlines } : {},
|
|
5220
|
+
...spec.valueAxisMinorGridlineColor !== void 0 ? { minorGridlineColor: spec.valueAxisMinorGridlineColor } : {},
|
|
5221
|
+
...spec.valueAxisMinorGridlineWidthEmu !== void 0 ? { minorGridlineWidthEmu: spec.valueAxisMinorGridlineWidthEmu } : {},
|
|
4008
5222
|
...spec.valueAxisLineHidden !== void 0 ? { lineHidden: spec.valueAxisLineHidden } : {},
|
|
4009
5223
|
...majorUnit !== void 0 ? { majorUnit } : {},
|
|
4010
5224
|
...numberFormat !== void 0 ? { numberFormat } : {},
|
|
@@ -4062,7 +5276,7 @@ const renderChart = (shape, x, y, w, h, transform, theme) => {
|
|
|
4062
5276
|
break;
|
|
4063
5277
|
default: return null;
|
|
4064
5278
|
}
|
|
4065
|
-
const emptyHint = finiteCount === 0 ?
|
|
5279
|
+
const emptyHint = finiteCount === 0 ? chartText(f, f.plotX + f.plotW / 2, f.plotY + f.plotH / 2, `text-anchor="middle" dominant-baseline="middle" font-family="sans-serif" font-size="12" fill="#9CA3AF"`, `${escapeXml(`chart (${spec.kind}) — no data`)}`) : "";
|
|
4066
5280
|
const axisTitleAttrs = (style) => {
|
|
4067
5281
|
const sz = style?.sizePt ?? 11;
|
|
4068
5282
|
const fill = style?.color ?? "#374151";
|
|
@@ -4071,12 +5285,12 @@ const renderChart = (shape, x, y, w, h, transform, theme) => {
|
|
|
4071
5285
|
return `font-family="sans-serif" font-size="${chartFontPx(sz)}" fill="${fill}" font-weight="${weight}"${italicAttr}`;
|
|
4072
5286
|
};
|
|
4073
5287
|
const valueAxisTitleRot = spec.valueAxisTitleRotationDeg ?? -90;
|
|
4074
|
-
const valueAxisTitleSvg = spec.valueAxisTitle ?
|
|
5288
|
+
const valueAxisTitleSvg = spec.valueAxisTitle ? chartText(f, f.plotX - 26, f.plotY + f.plotH / 2, `text-anchor="middle" ${axisTitleAttrs(spec.valueAxisTitleStyle)} transform="rotate(${valueAxisTitleRot} ${px(f.plotX - 26)} ${px(f.plotY + f.plotH / 2)})"`, `${escapeXml(spec.valueAxisTitle)}`) : "";
|
|
4075
5289
|
const catTitleRot = spec.categoryAxisTitleRotationDeg ?? 0;
|
|
4076
5290
|
const catTitleCx = f.plotX + f.plotW / 2;
|
|
4077
5291
|
const catTitleCy = f.plotY + f.plotH + 28;
|
|
4078
5292
|
const catTitleTransform = catTitleRot !== 0 ? ` transform="rotate(${catTitleRot} ${px(catTitleCx)} ${px(catTitleCy)})"` : "";
|
|
4079
|
-
const categoryAxisTitleSvg = spec.categoryAxisTitle ?
|
|
5293
|
+
const categoryAxisTitleSvg = spec.categoryAxisTitle ? chartText(f, catTitleCx, catTitleCy, `text-anchor="middle" ${axisTitleAttrs(spec.categoryAxisTitleStyle)}${catTitleTransform}`, `${escapeXml(spec.categoryAxisTitle)}`) : "";
|
|
4080
5294
|
return [
|
|
4081
5295
|
`<g${transform}>`,
|
|
4082
5296
|
`<rect x="${px(f.x)}" y="${px(f.y)}" width="${px(f.w)}" height="${px(f.h)}" fill="${spec.chartAreaFill ?? "#FFFFFF"}" stroke="${spec.chartAreaStrokeColor ?? "none"}" stroke-width="0.6"${spec.roundedCorners ? " rx=\"6\" ry=\"6\"" : ""}/>`,
|
|
@@ -4091,72 +5305,90 @@ const renderChart = (shape, x, y, w, h, transform, theme) => {
|
|
|
4091
5305
|
"</g>"
|
|
4092
5306
|
].join("");
|
|
4093
5307
|
};
|
|
4094
|
-
const cellParaData = (paragraphs) => {
|
|
5308
|
+
const cellParaData = (paragraphs, cell, pres) => {
|
|
4095
5309
|
let hasText = false;
|
|
4096
5310
|
return {
|
|
4097
|
-
paraData: paragraphs.map((para) => {
|
|
5311
|
+
paraData: paragraphs.map((para, index) => {
|
|
5312
|
+
const properties = getParagraphPropertiesEffective(pres, cell, index);
|
|
5313
|
+
const bulletIsPicture = isParagraphBulletPicture(cell, index);
|
|
5314
|
+
const bulletImageBytes = bulletIsPicture ? getParagraphBulletImageBytes(cell, index) : null;
|
|
4098
5315
|
const runs = [];
|
|
5316
|
+
let rIdx = 0;
|
|
5317
|
+
let fieldIndex = 0;
|
|
5318
|
+
let breakIndex = 0;
|
|
4099
5319
|
for (const el of para.elements) {
|
|
4100
5320
|
if (el.kind === "br") {
|
|
5321
|
+
const fmt = getTableCellRunFormatEffective(pres, cell, index, { breakIndex: breakIndex++ });
|
|
4101
5322
|
runs.push({
|
|
4102
5323
|
text: "\n",
|
|
4103
|
-
fmt
|
|
4104
|
-
sizePt: DEFAULT_BODY_PT
|
|
5324
|
+
fmt,
|
|
5325
|
+
sizePt: fmt.size ?? DEFAULT_BODY_PT
|
|
4105
5326
|
});
|
|
4106
5327
|
continue;
|
|
4107
5328
|
}
|
|
4108
5329
|
if (el.text.trim()) hasText = true;
|
|
5330
|
+
const href = el.clickAction ? clickActionHref(pres, el.clickAction) : null;
|
|
5331
|
+
const fmt = getTableCellRunFormatEffective(pres, cell, index, el.kind === "r" ? rIdx++ : { fieldIndex: fieldIndex++ });
|
|
4109
5332
|
runs.push({
|
|
4110
5333
|
text: el.text,
|
|
4111
|
-
fmt
|
|
4112
|
-
sizePt:
|
|
5334
|
+
fmt,
|
|
5335
|
+
sizePt: fmt?.size ?? DEFAULT_BODY_PT,
|
|
5336
|
+
...href ? {
|
|
5337
|
+
href,
|
|
5338
|
+
...el.tooltip !== void 0 ? { hrefTip: el.tooltip } : {}
|
|
5339
|
+
} : {}
|
|
4113
5340
|
});
|
|
4114
5341
|
}
|
|
4115
5342
|
return {
|
|
4116
|
-
|
|
4117
|
-
|
|
4118
|
-
|
|
4119
|
-
|
|
4120
|
-
|
|
4121
|
-
|
|
4122
|
-
|
|
4123
|
-
|
|
5343
|
+
...runs.length === 0 ? { emptySizePt: getTableCellRunFormatEffective(pres, cell, index, null).size ?? DEFAULT_BODY_PT } : {},
|
|
5344
|
+
latinLineBreak: properties.latinLineBreak,
|
|
5345
|
+
tabStops: properties.tabStops,
|
|
5346
|
+
defaultTabSizeEmu: properties.defaultTabSizeEmu,
|
|
5347
|
+
align: properties.align ?? "left",
|
|
5348
|
+
level: properties.level,
|
|
5349
|
+
bulletStyle: properties.bullet,
|
|
5350
|
+
bulletDetail: properties.bulletDetail ?? {
|
|
5351
|
+
...getParagraphBulletStyle(pres, cell, index),
|
|
5352
|
+
colorFollowText: false,
|
|
5353
|
+
sizeFollowText: false,
|
|
5354
|
+
fontFollowText: false
|
|
4124
5355
|
},
|
|
4125
|
-
bulletIsPicture
|
|
4126
|
-
bulletImageHref: null,
|
|
5356
|
+
bulletIsPicture,
|
|
5357
|
+
bulletImageHref: bulletImageBytes ? bytesToDataUrl(bulletImageBytes) : null,
|
|
4127
5358
|
runs,
|
|
4128
|
-
lineSpacing:
|
|
4129
|
-
spcBefPts:
|
|
4130
|
-
spcAftPts:
|
|
5359
|
+
lineSpacing: properties.lineSpacing,
|
|
5360
|
+
spcBefPts: properties.spcBefPts,
|
|
5361
|
+
spcAftPts: properties.spcAftPts,
|
|
4131
5362
|
indent: {
|
|
4132
|
-
leftEmu:
|
|
4133
|
-
rightEmu:
|
|
4134
|
-
firstLineEmu:
|
|
5363
|
+
leftEmu: properties.marL,
|
|
5364
|
+
rightEmu: properties.marR,
|
|
5365
|
+
firstLineEmu: properties.indent
|
|
4135
5366
|
}
|
|
4136
5367
|
};
|
|
4137
5368
|
}),
|
|
4138
5369
|
hasText
|
|
4139
5370
|
};
|
|
4140
5371
|
};
|
|
4141
|
-
const renderTableCellText = (paragraphs, cx, cy, cw, ch, color, pres, shape, theme, themeFace, ctx, vAnchor, margins) => {
|
|
4142
|
-
const { paraData, hasText } = cellParaData(paragraphs);
|
|
5372
|
+
const renderTableCellText = (cell, paragraphs, cx, cy, cw, ch, color, pres, shape, theme, themeFace, ctx, vAnchor, margins, effectsOnly = false) => {
|
|
5373
|
+
const { paraData, hasText } = cellParaData(paragraphs, cell, pres);
|
|
4143
5374
|
if (!hasText) return "";
|
|
4144
|
-
const
|
|
4145
|
-
const padL = margins.left
|
|
4146
|
-
const padR = margins.right
|
|
4147
|
-
const padT = margins.top
|
|
4148
|
-
const padB = margins.bottom
|
|
5375
|
+
const numberLabels = paragraphNumberLabels(paraData);
|
|
5376
|
+
const padL = (margins.left ?? 91440) / EMU_PER_PX;
|
|
5377
|
+
const padR = (margins.right ?? 91440) / EMU_PER_PX;
|
|
5378
|
+
const padT = (margins.top ?? 45720) / EMU_PER_PX;
|
|
5379
|
+
const padB = (margins.bottom ?? 45720) / EMU_PER_PX;
|
|
4149
5380
|
const innerX = cx + padL;
|
|
4150
5381
|
const innerY = cy + padT;
|
|
4151
5382
|
const innerW = Math.max(0, cw - padL - padR);
|
|
4152
5383
|
const innerH = Math.max(0, ch - padT - padB);
|
|
4153
5384
|
if (innerW <= 0 || innerH <= 0) return "";
|
|
4154
|
-
|
|
5385
|
+
const customTabs = paraData.some((para) => para.tabStops?.length && para.runs.some((run) => run.text.includes(" ")));
|
|
5386
|
+
const svgArgs = {
|
|
4155
5387
|
pres,
|
|
4156
5388
|
shape,
|
|
4157
5389
|
theme,
|
|
4158
5390
|
paraData,
|
|
4159
|
-
numberLabels
|
|
5391
|
+
numberLabels,
|
|
4160
5392
|
autoFitScale: 1,
|
|
4161
5393
|
lineHeightScale: 1,
|
|
4162
5394
|
defaultPt: DEFAULT_BODY_PT,
|
|
@@ -4168,17 +5400,37 @@ const renderTableCellText = (paragraphs, cx, cy, cw, ch, color, pres, shape, the
|
|
|
4168
5400
|
innerY: innerY * EMU_PER_PX,
|
|
4169
5401
|
innerW: innerW * EMU_PER_PX,
|
|
4170
5402
|
innerH: innerH * EMU_PER_PX,
|
|
4171
|
-
measure: ctx.measure,
|
|
4172
|
-
|
|
4173
|
-
|
|
4174
|
-
|
|
5403
|
+
measure: ctx.mode === "svg" ? ctx.measure : browserTextMeasurer() ?? ctx.measure,
|
|
5404
|
+
...ctx.mode === "foreignObject" ? { resolveFamily: browserFontFamily } : {},
|
|
5405
|
+
vert: verticalLayoutOf(getTableCellTextDirection(cell)),
|
|
5406
|
+
columns: null,
|
|
5407
|
+
...effectsOnly ? { effectsOnly: true } : {}
|
|
5408
|
+
};
|
|
5409
|
+
const fillOverlay = () => paraData.some((para) => para.runs.some(hasPaintedTextFill)) ? buildAndLayoutSvgText({
|
|
5410
|
+
...svgArgs,
|
|
5411
|
+
effectsOnly: true,
|
|
5412
|
+
effectKind: "fill"
|
|
5413
|
+
}) : "";
|
|
5414
|
+
if (ctx.mode === "svg" || customTabs || effectsOnly) {
|
|
5415
|
+
if (effectsOnly) {
|
|
5416
|
+
const reflection = paraData.some((para) => para.runs.some((run) => run.fmt?.reflection != null)) ? buildAndLayoutSvgText({
|
|
5417
|
+
...svgArgs,
|
|
5418
|
+
effectKind: "reflection"
|
|
5419
|
+
}) : "";
|
|
5420
|
+
const innerShadow = paraData.some((para) => para.runs.some((run) => run.fmt?.innerShadow != null)) ? buildAndLayoutSvgText({
|
|
5421
|
+
...svgArgs,
|
|
5422
|
+
effectKind: "innerShadow"
|
|
5423
|
+
}) : "";
|
|
5424
|
+
return reflection + fillOverlay() + innerShadow;
|
|
5425
|
+
}
|
|
5426
|
+
return buildAndLayoutSvgText(svgArgs);
|
|
5427
|
+
}
|
|
5428
|
+
const vertical = verticalTextStyle(getTableCellTextDirection(cell));
|
|
5429
|
+
const directionStyle = `${vertical.declarations}${vertical.transform ? `;transform:${vertical.transform}` : ""}`;
|
|
4175
5430
|
const justify = vAnchor === "top" ? "flex-start" : vAnchor === "bottom" ? "flex-end" : "center";
|
|
4176
5431
|
const familyFont = themeFace ? `${escapeXml(themeFace)}, ${DEFAULT_FONT}` : DEFAULT_FONT;
|
|
4177
|
-
const body = paraData.
|
|
4178
|
-
|
|
4179
|
-
return `<p style="margin:0;padding:0;text-align:${ALIGNMENT_TO_CSS[para.align] ?? "left"};line-height:1.2">${runHtml || "​"}</p>`;
|
|
4180
|
-
}).join("");
|
|
4181
|
-
return `<foreignObject x="${px(innerX)}" y="${px(innerY)}" width="${px(innerW)}" height="${px(innerH)}"><div xmlns="http://www.w3.org/1999/xhtml" style="display:flex;flex-direction:column;justify-content:${justify};width:100%;height:100%;box-sizing:border-box;overflow:hidden;font-family:${familyFont};color:${color};word-break:break-word">${body}</div></foreignObject>`;
|
|
5432
|
+
const body = renderHtmlParagraphs(paraData, numberLabels, theme, 1, DEFAULT_BODY_PT, color).join("");
|
|
5433
|
+
return `<foreignObject x="${px(innerX)}" y="${px(innerY)}" width="${px(innerW)}" height="${px(innerH)}"><div xmlns="http://www.w3.org/1999/xhtml" style="display:flex;flex-direction:column;justify-content:${justify};width:100%;height:100%;box-sizing:border-box;overflow:hidden;line-height:${LINE_HEIGHT};font-family:${familyFont};color:${color};word-break:break-word;${directionStyle}">${body}</div></foreignObject>${fillOverlay()}`;
|
|
4182
5434
|
};
|
|
4183
5435
|
const renderTable = (shape, pres, x, y, w, h, transform, theme, ctx) => {
|
|
4184
5436
|
let dims;
|
|
@@ -4194,6 +5446,8 @@ const renderTable = (shape, pres, x, y, w, h, transform, theme, ctx) => {
|
|
|
4194
5446
|
return null;
|
|
4195
5447
|
}
|
|
4196
5448
|
if (dims.rows === 0 || dims.cols === 0) return null;
|
|
5449
|
+
const flip = getShapeFlip(shape);
|
|
5450
|
+
const textReflected = ctx.groupReflected !== Boolean(flip && flip.horizontal !== flip.vertical);
|
|
4197
5451
|
const xPx = x / EMU_PER_PX;
|
|
4198
5452
|
const yPx = y / EMU_PER_PX;
|
|
4199
5453
|
const widthsPx = widths.map((w0) => w0 / EMU_PER_PX);
|
|
@@ -4206,17 +5460,12 @@ const renderTable = (shape, pres, x, y, w, h, transform, theme, ctx) => {
|
|
|
4206
5460
|
for (let c = 0; c < widthsPx.length; c++) colXs.push((colXs[c] ?? xPx) + (widthsPx[c] ?? 0) * wScale);
|
|
4207
5461
|
const rowYs = [yPx];
|
|
4208
5462
|
for (let r = 0; r < heightsPx.length; r++) rowYs.push((rowYs[r] ?? yPx) + (heightsPx[r] ?? 0) * hScale);
|
|
4209
|
-
const flags = getTableStyleFlags(shape);
|
|
4210
|
-
const accent = theme ? normalizeHex(theme.accent1) : "#4472C4";
|
|
4211
|
-
const headerFill = accent;
|
|
4212
|
-
const bandFill = mixHex(accent, "#FFFFFF", .12);
|
|
4213
|
-
const bandFill2 = mixHex(accent, "#FFFFFF", .27);
|
|
4214
5463
|
const textColor = activeDeckTextColor;
|
|
4215
5464
|
const tableThemeFace = getPresentationFonts(pres)?.minorLatin ?? null;
|
|
4216
5465
|
const out = [];
|
|
4217
5466
|
out.push(`<g${transform}>`);
|
|
4218
|
-
out.push(`<rect x="${px(xPx)}" y="${px(yPx)}" width="${px((colXs[widthsPx.length] ?? xPx) - xPx)}" height="${px((rowYs[heightsPx.length] ?? yPx) - yPx)}" fill="#FFFFFF"/>`);
|
|
4219
5467
|
const borderEdges = [];
|
|
5468
|
+
const borderEdgeCandidates = /* @__PURE__ */ new Map();
|
|
4220
5469
|
for (let r = 0; r < dims.rows; r++) for (let c = 0; c < dims.cols; c++) {
|
|
4221
5470
|
const cell = cells[r]?.[c];
|
|
4222
5471
|
if (!cell) continue;
|
|
@@ -4229,19 +5478,16 @@ const renderTable = (shape, pres, x, y, w, h, transform, theme, ctx) => {
|
|
|
4229
5478
|
const endRow = Math.min(dims.rows, r + span.rowSpan);
|
|
4230
5479
|
const cw = (colXs[endCol] ?? cx) - cx;
|
|
4231
5480
|
const ch = (rowYs[endRow] ?? cy) - cy;
|
|
4232
|
-
const
|
|
5481
|
+
const appearance = getTableCellAppearanceEffective(pres, typedCell);
|
|
5482
|
+
const fill = appearance.fill;
|
|
4233
5483
|
let resolvedFill;
|
|
4234
|
-
if (fill) resolvedFill = resolveColor(fill, theme, "#FFFFFF");
|
|
4235
|
-
else if (
|
|
4236
|
-
else if (
|
|
4237
|
-
else if (flags.firstCol && c === 0) resolvedFill = bandFill;
|
|
4238
|
-
else if (flags.lastCol && c === dims.cols - 1) resolvedFill = bandFill;
|
|
4239
|
-
else if (flags.bandRow) resolvedFill = (r - (flags.firstRow ? 1 : 0)) % 2 === 0 ? bandFill : bandFill2;
|
|
4240
|
-
else if (flags.bandCol) resolvedFill = (c - (flags.firstCol ? 1 : 0)) % 2 === 0 ? bandFill : bandFill2;
|
|
5484
|
+
if (fill.kind === "solid") resolvedFill = resolveColor(fill.color, theme, "#FFFFFF");
|
|
5485
|
+
else if (fill.kind === "none") resolvedFill = "none";
|
|
5486
|
+
else if (fill.kind !== "inherit") resolvedFill = "none";
|
|
4241
5487
|
else resolvedFill = "none";
|
|
4242
|
-
const cellTextColor =
|
|
4243
|
-
out.push(`<rect x="${px(cx)}" y="${px(cy)}" width="${px(cw)}" height="${px(ch)}" fill="${resolvedFill}"/>`);
|
|
4244
|
-
const borders =
|
|
5488
|
+
const cellTextColor = textColor;
|
|
5489
|
+
out.push(`<g data-pptx-cell="${r},${c}"><rect x="${px(cx)}" y="${px(cy)}" width="${px(cw)}" height="${px(ch)}" fill="${resolvedFill}"/>`);
|
|
5490
|
+
const borders = appearance.borders;
|
|
4245
5491
|
const dashAttr = (dash, widthPx) => {
|
|
4246
5492
|
if (!dash || dash === "solid") return "";
|
|
4247
5493
|
const pat = DASH_PATTERNS[dash];
|
|
@@ -4251,9 +5497,22 @@ const renderTable = (shape, pres, x, y, w, h, transform, theme, ctx) => {
|
|
|
4251
5497
|
const edge = (side, x1, y1, x2, y2) => {
|
|
4252
5498
|
const b = borders[side];
|
|
4253
5499
|
if (!b) return;
|
|
5500
|
+
const key = `${Math.round(Math.min(x1, x2) * 100)},${Math.round(Math.min(y1, y2) * 100)},${Math.round(Math.max(x1, x2) * 100)},${Math.round(Math.max(y1, y2) * 100)}`;
|
|
4254
5501
|
const sw = b.widthEmu ? Math.max(.4, b.widthEmu / EMU_PER_PX) : .5;
|
|
4255
5502
|
const col = b.color ?? "#9CA3AF";
|
|
4256
|
-
|
|
5503
|
+
const line = `<line x1="${px(x1)}" y1="${px(y1)}" x2="${px(x2)}" y2="${px(y2)}" stroke="${col}" stroke-width="${px(sw)}"${dashAttr(b.dash, sw)}/>`;
|
|
5504
|
+
const existing = borderEdgeCandidates.get(key);
|
|
5505
|
+
if (existing) {
|
|
5506
|
+
if (sw <= existing.width) return;
|
|
5507
|
+
borderEdges[existing.index] = line;
|
|
5508
|
+
existing.width = sw;
|
|
5509
|
+
return;
|
|
5510
|
+
}
|
|
5511
|
+
borderEdgeCandidates.set(key, {
|
|
5512
|
+
index: borderEdges.length,
|
|
5513
|
+
width: sw
|
|
5514
|
+
});
|
|
5515
|
+
borderEdges.push(line);
|
|
4257
5516
|
};
|
|
4258
5517
|
edge("left", cx, cy, cx, cy + ch);
|
|
4259
5518
|
edge("right", cx + cw, cy, cx + cw, cy + ch);
|
|
@@ -4267,15 +5526,8 @@ const renderTable = (shape, pres, x, y, w, h, transform, theme, ctx) => {
|
|
|
4267
5526
|
const sw = borders.blToTr.widthEmu ? Math.max(.4, borders.blToTr.widthEmu / EMU_PER_PX) : .5;
|
|
4268
5527
|
borderEdges.push(`<line x1="${px(cx)}" y1="${px(cy + ch)}" x2="${px(cx + cw)}" y2="${px(cy)}" stroke="${borders.blToTr.color ?? "#9CA3AF"}" stroke-width="${px(sw)}"${dashAttr(borders.blToTr.dash, sw)}/>`);
|
|
4269
5528
|
}
|
|
4270
|
-
const
|
|
4271
|
-
|
|
4272
|
-
if (!borders.right) borderEdges.push(`<line x1="${px(cx + cw)}" y1="${px(cy)}" x2="${px(cx + cw)}" y2="${px(cy + ch)}" stroke="${defaultColor}" stroke-width="0.4" opacity="0.6"/>`);
|
|
4273
|
-
if (!borders.top) borderEdges.push(`<line x1="${px(cx)}" y1="${px(cy)}" x2="${px(cx + cw)}" y2="${px(cy)}" stroke="${defaultColor}" stroke-width="0.4" opacity="0.6"/>`);
|
|
4274
|
-
if (!borders.bottom) borderEdges.push(`<line x1="${px(cx)}" y1="${px(cy + ch)}" x2="${px(cx + cw)}" y2="${px(cy + ch)}" stroke="${defaultColor}" stroke-width="0.4" opacity="0.6"/>`);
|
|
4275
|
-
const cellParagraphs = getTableCellParagraphs(cell);
|
|
4276
|
-
const vAnchor = getTableCellAnchor(typedCell) ?? "top";
|
|
4277
|
-
const cellMargins = getTableCellMargins(typedCell);
|
|
4278
|
-
out.push(renderTableCellText(cellParagraphs, cx, cy, cw, ch, cellTextColor, pres, shape, theme, tableThemeFace, ctx, vAnchor, cellMargins));
|
|
5529
|
+
const cellText = renderTableCellText(typedCell, getTableCellParagraphs(cell), cx, cy, cw, ch, cellTextColor, pres, shape, theme, tableThemeFace, ctx, getTableCellAnchor(typedCell) ?? "top", getTableCellMargins(typedCell));
|
|
5530
|
+
out.push(textReflected && cellText ? `<g transform="translate(${px(2 * cx + cw)} 0) scale(-1 1)">${cellText}</g>` : cellText, "</g>");
|
|
4279
5531
|
}
|
|
4280
5532
|
out.push(borderEdges.join(""));
|
|
4281
5533
|
out.push("</g>");
|
|
@@ -4397,7 +5649,7 @@ const customGeometryToSvg = (geom, x, y, w, h, fill, fillExtra, stroke, strokeWi
|
|
|
4397
5649
|
}
|
|
4398
5650
|
return out.join("");
|
|
4399
5651
|
};
|
|
4400
|
-
const
|
|
5652
|
+
const renderShapeContent = (shape, pres, theme, ctx) => {
|
|
4401
5653
|
const bounds = getShapeBoundsResolved(pres, shape);
|
|
4402
5654
|
if (!bounds) return "";
|
|
4403
5655
|
const x = bounds.x;
|
|
@@ -4422,7 +5674,10 @@ const renderShape = (shape, pres, theme, ctx) => {
|
|
|
4422
5674
|
if (flip.vertical) transforms.push(`translate(0 ${E(2 * cy)}) scale(1 -1)`);
|
|
4423
5675
|
const transform = transforms.length > 0 ? ` transform="${transforms.join(" ")}"` : "";
|
|
4424
5676
|
const textRotation = (flip.vertical ? rotation + 180 : rotation) % 360;
|
|
4425
|
-
const
|
|
5677
|
+
const textTransforms = [];
|
|
5678
|
+
if (textRotation !== 0) textTransforms.push(`rotate(${textRotation} ${E(cx)} ${E(cy)})`);
|
|
5679
|
+
if (ctx.groupReflected) textTransforms.push(`translate(${E(2 * cx)} 0) scale(-1 1)`);
|
|
5680
|
+
const textTransform = textTransforms.length > 0 ? ` transform="${textTransforms.join(" ")}"` : "";
|
|
4426
5681
|
const { sx: gsx, sy: gsy } = ctx.groupScale;
|
|
4427
5682
|
const groupScaled = gsx !== 1 || gsy !== 1;
|
|
4428
5683
|
const textBounds = groupScaled ? {
|
|
@@ -4438,8 +5693,8 @@ const renderShape = (shape, pres, theme, ctx) => {
|
|
|
4438
5693
|
};
|
|
4439
5694
|
const rawTextOverlay = kind === "shape" || kind === "graphicFrame" ? renderTextBody(pres, shape, textBounds, theme, phType, ctx) : "";
|
|
4440
5695
|
const textOverlay = rawTextOverlay !== "" && groupScaled ? `<g transform="scale(${(1 / gsx).toFixed(6)} ${(1 / gsy).toFixed(6)})">${rawTextOverlay}</g>` : rawTextOverlay;
|
|
4441
|
-
if (kind === "picture") return renderPicture(shape, pres, x, y, w, h, transform, textOverlay, getShapeImageBytes(shape), getShapeImageFormat(shape));
|
|
4442
|
-
if (kind === "shape" && fill.kind === "image") return renderPicture(shape, pres, x, y, w, h, transform, textOverlay, getShapeImageFillBytes(shape), getShapeImageFormat(shape));
|
|
5696
|
+
if (kind === "picture") return renderPicture(shape, pres, x, y, w, h, transform, textOverlay, getShapeImageBytes(shape), getShapeImageFormat(shape), paint(shape, fill, stroke, theme, false, pres));
|
|
5697
|
+
if (kind === "shape" && fill.kind === "image") return renderPicture(shape, pres, x, y, w, h, transform, textOverlay, getShapeImageFillBytes(shape), getShapeImageFormat(shape), paint(shape, fill, stroke, theme, false, pres));
|
|
4443
5698
|
if (kind === "connector") {
|
|
4444
5699
|
const p = paint(shape, fill, stroke, theme, false, pres);
|
|
4445
5700
|
const sw = p.strokeWidth || 19050;
|
|
@@ -4494,20 +5749,24 @@ const renderShape = (shape, pres, theme, ctx) => {
|
|
|
4494
5749
|
const children = getGroupChildren(shape);
|
|
4495
5750
|
if (children.length === 0) return "";
|
|
4496
5751
|
const tParts = [];
|
|
5752
|
+
const inverseParts = [];
|
|
4497
5753
|
let groupScaleX = 1;
|
|
4498
5754
|
let groupScaleY = 1;
|
|
4499
5755
|
if (xform && rotation !== 0) {
|
|
4500
5756
|
const cxG = (xform.outer.x + xform.outer.w / 2) / EMU_PER_PX;
|
|
4501
5757
|
const cyG = (xform.outer.y + xform.outer.h / 2) / EMU_PER_PX;
|
|
4502
5758
|
tParts.push(`rotate(${rotation} ${cxG.toFixed(2)} ${cyG.toFixed(2)})`);
|
|
5759
|
+
inverseParts.unshift(`rotate(${-rotation} ${cxG.toFixed(2)} ${cyG.toFixed(2)})`);
|
|
4503
5760
|
}
|
|
4504
5761
|
if (xform && flip.horizontal) {
|
|
4505
5762
|
const cxG = (xform.outer.x + xform.outer.w / 2) / EMU_PER_PX;
|
|
4506
5763
|
tParts.push(`translate(${(2 * cxG).toFixed(2)} 0) scale(-1 1)`);
|
|
5764
|
+
inverseParts.unshift(`translate(${(2 * cxG).toFixed(2)} 0) scale(-1 1)`);
|
|
4507
5765
|
}
|
|
4508
5766
|
if (xform && flip.vertical) {
|
|
4509
5767
|
const cyG = (xform.outer.y + xform.outer.h / 2) / EMU_PER_PX;
|
|
4510
5768
|
tParts.push(`translate(0 ${(2 * cyG).toFixed(2)}) scale(1 -1)`);
|
|
5769
|
+
inverseParts.unshift(`translate(0 ${(2 * cyG).toFixed(2)}) scale(1 -1)`);
|
|
4511
5770
|
}
|
|
4512
5771
|
if (xform) {
|
|
4513
5772
|
const ox = xform.outer.x;
|
|
@@ -4525,10 +5784,13 @@ const renderShape = (shape, pres, theme, ctx) => {
|
|
|
4525
5784
|
const tx = ((ox - ix * (ow / iw)) / EMU_PER_PX).toFixed(2);
|
|
4526
5785
|
const ty = ((oy - iy * (oh / ih)) / EMU_PER_PX).toFixed(2);
|
|
4527
5786
|
tParts.push(`translate(${tx} ${ty})`, `scale(${sx} ${sy})`);
|
|
5787
|
+
inverseParts.unshift(`scale(${1 / Number(sx)} ${1 / Number(sy)}) translate(${-Number(tx)} ${-Number(ty)})`);
|
|
4528
5788
|
}
|
|
4529
5789
|
const groupTransform = tParts.length > 0 ? ` transform="${tParts.join(" ")}"` : "";
|
|
4530
|
-
const childCtx =
|
|
5790
|
+
const childCtx = tParts.length === 0 ? ctx : {
|
|
4531
5791
|
...ctx,
|
|
5792
|
+
inverseGroupTransform: `${inverseParts.join(" ")} ${ctx.inverseGroupTransform}`,
|
|
5793
|
+
groupReflected: ctx.groupReflected !== Boolean(xform && flip.horizontal !== flip.vertical),
|
|
4532
5794
|
groupScale: {
|
|
4533
5795
|
sx: ctx.groupScale.sx * groupScaleX,
|
|
4534
5796
|
sy: ctx.groupScale.sy * groupScaleY
|
|
@@ -4537,9 +5799,21 @@ const renderShape = (shape, pres, theme, ctx) => {
|
|
|
4537
5799
|
return `<g${groupTransform}>${children.map((c) => renderShape(c, pres, theme, childCtx)).join("")}</g>`;
|
|
4538
5800
|
}
|
|
4539
5801
|
const p = paint(shape, fill, stroke, theme, phType !== null, pres);
|
|
5802
|
+
if (fill.kind === "background") {
|
|
5803
|
+
ctx.background.id ||= mintId();
|
|
5804
|
+
const id = mintId();
|
|
5805
|
+
const inverse = [];
|
|
5806
|
+
if (flip.vertical) inverse.push(`translate(0 ${E(2 * cy)}) scale(1 -1)`);
|
|
5807
|
+
if (flip.horizontal) inverse.push(`translate(${E(2 * cx)} 0) scale(-1 1)`);
|
|
5808
|
+
if (rotation !== 0) inverse.push(`rotate(${-rotation} ${E(cx)} ${E(cy)})`);
|
|
5809
|
+
inverse.push(ctx.inverseGroupTransform);
|
|
5810
|
+
p.fill = `url(#${id})`;
|
|
5811
|
+
p.fillAttrs = "";
|
|
5812
|
+
p.defs += `<defs><pattern id="${id}" patternUnits="userSpaceOnUse" width="${ctx.background.width}" height="${ctx.background.height}" patternTransform="${inverse.join(" ")}"><use href="#${ctx.background.id}" xlink:href="#${ctx.background.id}"/></pattern></defs>`;
|
|
5813
|
+
}
|
|
4540
5814
|
if (kind === "graphicFrame") {
|
|
4541
5815
|
if (isChartShape(shape)) {
|
|
4542
|
-
const chartSvg = renderChart(shape, x, y, w, h, transform, theme);
|
|
5816
|
+
const chartSvg = renderChart(shape, x, y, w, h, transform, theme, ctx.groupReflected);
|
|
4543
5817
|
if (chartSvg) return chartSvg;
|
|
4544
5818
|
}
|
|
4545
5819
|
if (isTableShape(shape)) {
|
|
@@ -4594,8 +5868,6 @@ const renderShape = (shape, pres, theme, ctx) => {
|
|
|
4594
5868
|
geomSvg = reflection.svg + geomSvg;
|
|
4595
5869
|
fxDefs += reflection.defs;
|
|
4596
5870
|
}
|
|
4597
|
-
const url = getShapeHyperlink(shape);
|
|
4598
|
-
const tooltip = getShapeHyperlinkTooltip(shape);
|
|
4599
5871
|
const shapeName = (() => {
|
|
4600
5872
|
try {
|
|
4601
5873
|
return getShapeName(shape);
|
|
@@ -4622,22 +5894,39 @@ const renderShape = (shape, pres, theme, ctx) => {
|
|
|
4622
5894
|
const ariaAttr = a11yLabel ? ` role="img" aria-label="${escapeXml(a11yLabel)}"` : "";
|
|
4623
5895
|
const placedText = textOverlay ? `<g${textTransform}>${textOverlay}</g>` : "";
|
|
4624
5896
|
const custGeomAttr = isCustGeom ? " data-pptx-fallback=\"custGeom\"" : "";
|
|
4625
|
-
|
|
5897
|
+
return `${p.defs}${fxDefs}<g${nameAttr}${ariaAttr}${custGeomAttr}><g${transform}>${geomSvg}</g>${placedText}</g>`;
|
|
5898
|
+
};
|
|
5899
|
+
const renderShape = (shape, pres, theme, ctx) => {
|
|
5900
|
+
if (isShapeHidden(shape)) return "";
|
|
5901
|
+
const inner = renderShapeContent(shape, pres, theme, ctx);
|
|
5902
|
+
if (!inner) return inner;
|
|
5903
|
+
const href = clickActionHref(pres, getShapeClickAction(shape));
|
|
5904
|
+
const tooltip = href === void 0 ? null : getShapeHyperlinkTooltip(shape);
|
|
4626
5905
|
const titleEl = tooltip ? `<title>${escapeXml(tooltip)}</title>` : "";
|
|
4627
|
-
|
|
4628
|
-
|
|
4629
|
-
|
|
4630
|
-
|
|
4631
|
-
|
|
4632
|
-
|
|
4633
|
-
|
|
4634
|
-
|
|
4635
|
-
|
|
4636
|
-
|
|
4637
|
-
|
|
5906
|
+
const targetAttrs = href === void 0 || href.startsWith("#") ? "" : " target=\"_blank\" rel=\"noopener noreferrer\"";
|
|
5907
|
+
return wrapWithShapeId(shape, href === void 0 ? inner : `<a href="${escapeXml(href)}"${targetAttrs}>${titleEl}${inner}</a>`, ctx);
|
|
5908
|
+
};
|
|
5909
|
+
/**
|
|
5910
|
+
* Names the object the slide's animation timing would target, so a player can
|
|
5911
|
+
* find it without knowing how the shape was drawn. One wrapper per object,
|
|
5912
|
+
* whatever its kind — a group carries its own id and its children theirs, so a
|
|
5913
|
+
* build on a group child is reachable inside a group that is animated itself.
|
|
5914
|
+
*
|
|
5915
|
+
* The id is the shape's `<p:cNvPr id>`, which is what `<p:spTgt spid>` names.
|
|
5916
|
+
* A deck may repeat one (nothing in the schema forbids it), and the attribute
|
|
5917
|
+
* repeats with it rather than inventing a unique number the timing could not
|
|
5918
|
+
* refer to.
|
|
5919
|
+
*/
|
|
5920
|
+
const wrapWithShapeId = (shape, svg, ctx) => {
|
|
5921
|
+
if (!ctx.ownShapes) return svg;
|
|
5922
|
+
const id = (() => {
|
|
5923
|
+
try {
|
|
5924
|
+
return getShapeId(shape);
|
|
5925
|
+
} catch {
|
|
5926
|
+
return null;
|
|
4638
5927
|
}
|
|
4639
|
-
}
|
|
4640
|
-
return
|
|
5928
|
+
})();
|
|
5929
|
+
return id === null ? svg : `<g data-pptx-shape-id="${id}">${svg}</g>`;
|
|
4641
5930
|
};
|
|
4642
5931
|
const buildReflection = (pres, shape, geomRaw, box) => {
|
|
4643
5932
|
let effects;
|
|
@@ -4672,6 +5961,7 @@ const buildEffectsFilter = (pres, shape) => {
|
|
|
4672
5961
|
const id = mintId();
|
|
4673
5962
|
const primitives = [];
|
|
4674
5963
|
const layers = [];
|
|
5964
|
+
const innerShadows = [];
|
|
4675
5965
|
for (const e of effects) if (e.kind === "outerShdw") {
|
|
4676
5966
|
const rad = e.angleDeg * Math.PI / 180;
|
|
4677
5967
|
const dx = e.distEmu * Math.cos(rad) / EMU_PER_PX;
|
|
@@ -4691,7 +5981,7 @@ const buildEffectsFilter = (pres, shape) => {
|
|
|
4691
5981
|
const opacity = e.opacity ?? 1;
|
|
4692
5982
|
const i = primitives.length;
|
|
4693
5983
|
primitives.push(`<feGaussianBlur in="SourceAlpha" stdDeviation="${blurPx.toFixed(2)}" result="innerBlur${i}"/>`, `<feOffset in="innerBlur${i}" dx="${dx.toFixed(2)}" dy="${dy.toFixed(2)}" result="innerOff${i}"/>`, `<feComposite in="innerOff${i}" in2="SourceAlpha" operator="arithmetic" k2="-1" k3="1" result="innerMask${i}"/>`, `<feFlood flood-color="${color}" flood-opacity="${opacity.toFixed(3)}" result="innerCol${i}"/>`, `<feComposite in="innerCol${i}" in2="innerMask${i}" operator="in" result="innerOut${i}"/>`);
|
|
4694
|
-
|
|
5984
|
+
innerShadows.push(`innerOut${i}`);
|
|
4695
5985
|
} else if (e.kind === "glow") {
|
|
4696
5986
|
const radiusPx = e.radiusEmu / EMU_PER_PX;
|
|
4697
5987
|
const dilatePx = radiusPx * .5;
|
|
@@ -4714,11 +6004,12 @@ const buildEffectsFilter = (pres, shape) => {
|
|
|
4714
6004
|
layers.length = 0;
|
|
4715
6005
|
layers.push(`blurOut${i}`);
|
|
4716
6006
|
}
|
|
4717
|
-
if (layers.length === 0) return null;
|
|
6007
|
+
if (layers.length === 0 && innerShadows.length === 0) return null;
|
|
4718
6008
|
const replacedSource = effects.some((e) => e.kind === "softEdge" || e.kind === "blur");
|
|
4719
6009
|
const mergeChildren = layers.map((l) => `<feMergeNode in="${l}"/>`).join("");
|
|
4720
6010
|
const sourceMerge = replacedSource ? "" : "<feMergeNode in=\"SourceGraphic\"/>";
|
|
4721
|
-
|
|
6011
|
+
const innerMerge = innerShadows.map((layer) => `<feMergeNode in="${layer}"/>`).join("");
|
|
6012
|
+
primitives.push(`<feMerge>${mergeChildren}${sourceMerge}${innerMerge}</feMerge>`);
|
|
4722
6013
|
return {
|
|
4723
6014
|
id,
|
|
4724
6015
|
defs: `<defs><filter id="${id}" x="-25%" y="-25%" width="150%" height="150%">${primitives.join("")}</filter></defs>`
|
|
@@ -4752,13 +6043,23 @@ const renderSlideSvg = (pres, slide, opts = {}) => {
|
|
|
4752
6043
|
const theme = getPresentationTheme(pres);
|
|
4753
6044
|
activeColorMap = getEffectiveColorMap(slide);
|
|
4754
6045
|
activeDeckTextColor = resolveDeckBodyTextColor(slide) ?? "#000000";
|
|
6046
|
+
const position = getSlides(pres).indexOf(slide);
|
|
6047
|
+
activeSlideNumber = String(getPresentationFirstSlideNumber(pres) + (position < 0 ? 0 : position));
|
|
4755
6048
|
const ctx = {
|
|
4756
6049
|
groupScale: {
|
|
4757
6050
|
sx: 1,
|
|
4758
6051
|
sy: 1
|
|
4759
6052
|
},
|
|
6053
|
+
groupReflected: false,
|
|
4760
6054
|
mode: opts.textLayout ?? "foreignObject",
|
|
4761
|
-
measure: opts.measureText ?? defaultMeasurer
|
|
6055
|
+
measure: opts.measureText ?? defaultMeasurer,
|
|
6056
|
+
ownShapes: true,
|
|
6057
|
+
background: {
|
|
6058
|
+
id: "",
|
|
6059
|
+
width: W / EMU_PER_PX,
|
|
6060
|
+
height: H / EMU_PER_PX
|
|
6061
|
+
},
|
|
6062
|
+
inverseGroupTransform: ""
|
|
4762
6063
|
};
|
|
4763
6064
|
let bg = getSlideBackground(slide);
|
|
4764
6065
|
if (bg.kind === "inherit") {
|
|
@@ -4773,6 +6074,8 @@ const renderSlideSvg = (pres, slide, opts = {}) => {
|
|
|
4773
6074
|
}
|
|
4774
6075
|
}
|
|
4775
6076
|
let bgColor = "#FFFFFF";
|
|
6077
|
+
const bgOpacity = bg.kind === "solid" ? bg.opacity : void 0;
|
|
6078
|
+
const bgOpacityAttr = bgOpacity === void 0 ? "" : ` fill-opacity="${bgOpacity}"`;
|
|
4776
6079
|
let bgGradient = "";
|
|
4777
6080
|
let bgGradientDefs = "";
|
|
4778
6081
|
if (bg.kind === "solid") bgColor = resolveColor(bg.color, theme, "#FFFFFF");
|
|
@@ -4818,25 +6121,50 @@ const renderSlideSvg = (pres, slide, opts = {}) => {
|
|
|
4818
6121
|
if (bytes) {
|
|
4819
6122
|
const fmt = detectImageFormatLocal(bytes);
|
|
4820
6123
|
const dataUrl = `data:${fmt ? imageMime[fmt] ?? "image/png" : "image/png"};base64,${u8ToBase64(bytes)}`;
|
|
4821
|
-
|
|
6124
|
+
const layout = getSlideBackgroundImageFillLayout(slide);
|
|
6125
|
+
const crop = getSlideBackgroundImageCrop(slide);
|
|
6126
|
+
const cropLeft = crop?.left ?? 0, cropTop = crop?.top ?? 0;
|
|
6127
|
+
const cropRight = crop?.right ?? 0, cropBottom = crop?.bottom ?? 0;
|
|
6128
|
+
const opacity = getSlideBackgroundImageOpacity(slide) ?? 1;
|
|
6129
|
+
const intrinsic = layout?.mode === "tile" ? getSlideBackgroundImageIntrinsicSize(slide) : null;
|
|
6130
|
+
if (layout?.mode === "tile" && intrinsic) {
|
|
6131
|
+
const pattern = imageTilePattern(dataUrl, layout, intrinsic, 0, 0, W, H, cropLeft, cropTop, cropRight, cropBottom);
|
|
6132
|
+
bgImage = `${pattern.defs}<rect width="${E(W)}" height="${E(H)}" fill="${pattern.fill}" opacity="${opacity}"/>`;
|
|
6133
|
+
} else {
|
|
6134
|
+
const stretch = layout?.mode === "stretch" ? layout : null;
|
|
6135
|
+
const left = stretch?.left ?? 0;
|
|
6136
|
+
const top = stretch?.top ?? 0;
|
|
6137
|
+
const width = Math.max(0, W * (1 - left - (stretch?.right ?? 0)));
|
|
6138
|
+
const height = Math.max(0, H * (1 - top - (stretch?.bottom ?? 0)));
|
|
6139
|
+
const sourceWidth = 1 - cropLeft - cropRight;
|
|
6140
|
+
const sourceHeight = 1 - cropTop - cropBottom;
|
|
6141
|
+
const imageWidth = sourceWidth > 0 ? width / sourceWidth : 0;
|
|
6142
|
+
const imageHeight = sourceHeight > 0 ? height / sourceHeight : 0;
|
|
6143
|
+
const clipId = mintId();
|
|
6144
|
+
const sourceClipId = mintId();
|
|
6145
|
+
bgImage = `<defs><clipPath id="${clipId}"><rect width="${E(W)}" height="${E(H)}"/></clipPath><clipPath id="${sourceClipId}"><rect x="${E(W * left)}" y="${E(H * top)}" width="${E(width)}" height="${E(height)}"/></clipPath></defs><g clip-path="url(#${clipId})"><g clip-path="url(#${sourceClipId})"><image opacity="${opacity}" x="${E(W * left - imageWidth * cropLeft)}" y="${E(H * top - imageHeight * cropTop)}" width="${E(imageWidth)}" height="${E(imageHeight)}" href="${dataUrl}" xlink:href="${dataUrl}" preserveAspectRatio="none"/></g></g>`;
|
|
6146
|
+
}
|
|
4822
6147
|
}
|
|
4823
6148
|
}
|
|
4824
6149
|
let layoutBgShapes = "";
|
|
4825
6150
|
const layoutForBg = getSlideLayout(slide);
|
|
4826
|
-
if (layoutForBg) try {
|
|
4827
|
-
const masterShapes = topLevelShapes(getSlideMasterShapes(pres, layoutForBg), { dropPlaceholders: true });
|
|
6151
|
+
if (layoutForBg && !isSlideBackgroundGraphicsHidden(slide)) try {
|
|
6152
|
+
const masterShapes = topLevelShapes(isSlideLayoutBackgroundGraphicsHidden(layoutForBg) ? [] : getSlideMasterShapes(pres, layoutForBg), { dropPlaceholders: true });
|
|
4828
6153
|
const layoutShapes = topLevelShapes(getSlideLayoutShapes(pres, layoutForBg), { dropPlaceholders: true });
|
|
4829
|
-
|
|
6154
|
+
const bgCtx = {
|
|
6155
|
+
...ctx,
|
|
6156
|
+
ownShapes: false
|
|
6157
|
+
};
|
|
6158
|
+
layoutBgShapes = [...masterShapes, ...layoutShapes].map((s) => renderShape(s, pres, theme, bgCtx)).join("");
|
|
4830
6159
|
} catch {
|
|
4831
6160
|
layoutBgShapes = "";
|
|
4832
6161
|
}
|
|
4833
6162
|
const shapesSvg = topLevelShapes(getSlideShapes(slide), { dropPlaceholders: false }).map((s) => renderShape(s, pres, theme, ctx)).join("");
|
|
6163
|
+
const backgroundSvg = `${bgOpacity === void 0 ? "" : `<rect width="${E(W)}" height="${E(H)}" fill="#FFFFFF"/>`}<rect width="${E(W)}" height="${E(H)}" fill="${bgColor}"${bgOpacityAttr}/>${bgGradient}${bgImage}`;
|
|
4834
6164
|
return [
|
|
4835
6165
|
`<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 ${E(W)} ${E(H)}" preserveAspectRatio="xMidYMid meet">`,
|
|
4836
6166
|
bgGradientDefs,
|
|
4837
|
-
`<
|
|
4838
|
-
bgGradient,
|
|
4839
|
-
bgImage,
|
|
6167
|
+
ctx.background.id ? `<defs><g id="${ctx.background.id}">${backgroundSvg}</g></defs><use href="#${ctx.background.id}" xlink:href="#${ctx.background.id}"/>` : backgroundSvg,
|
|
4840
6168
|
layoutBgShapes,
|
|
4841
6169
|
shapesSvg,
|
|
4842
6170
|
"</svg>"
|
|
@@ -4872,10 +6200,10 @@ const auditShape = (pres, theme, shape, slideIndex, measure, tolerancePx, report
|
|
|
4872
6200
|
}, theme, getShapePlaceholderType(shape), measure, "#000000");
|
|
4873
6201
|
if (model === null) return;
|
|
4874
6202
|
const vert = verticalLayoutOf(model.effectiveBody.vert ?? getShapeTextDirection(shape));
|
|
4875
|
-
const cols =
|
|
6203
|
+
const cols = model.effectiveBody.columns;
|
|
4876
6204
|
const columns = vert === "none" && cols && cols.count >= 2 ? {
|
|
4877
6205
|
count: cols.count,
|
|
4878
|
-
gapPx: cols.gapEmu !== void 0 ? cols.gapEmu / EMU_PER_PX :
|
|
6206
|
+
gapPx: cols.gapEmu !== void 0 ? cols.gapEmu / EMU_PER_PX : 0
|
|
4879
6207
|
} : null;
|
|
4880
6208
|
const rect = model.svgTextRect(vert);
|
|
4881
6209
|
if (rect.w <= 0 || rect.h <= 0) return;
|
|
@@ -4892,6 +6220,7 @@ const auditShape = (pres, theme, shape, slideIndex, measure, tolerancePx, report
|
|
|
4892
6220
|
themeFace: model.themeFace,
|
|
4893
6221
|
defaultColor: "#000000",
|
|
4894
6222
|
anchor: model.anchor,
|
|
6223
|
+
anchorCentered: model.effectiveBody.anchorCentered ?? false,
|
|
4895
6224
|
wrap: model.effectiveBody.wrap !== "none",
|
|
4896
6225
|
innerX: rect.x,
|
|
4897
6226
|
innerY: rect.y,
|
|
@@ -4968,7 +6297,7 @@ const detectApproximate = (paragraphs, measure) => {
|
|
|
4968
6297
|
const seen = /* @__PURE__ */ new Set();
|
|
4969
6298
|
for (const para of paragraphs) for (const piece of para.pieces) {
|
|
4970
6299
|
if (piece.isBreak || piece.text === "") continue;
|
|
4971
|
-
const key = `${piece.family}|${piece.sizePx}|${piece.bold}|${piece.italic}|${piece.text}`;
|
|
6300
|
+
const key = `${piece.family}|${piece.sizePx}|${piece.bold}|${piece.italic}|${piece.kerning ?? true}|${piece.text}`;
|
|
4972
6301
|
if (seen.has(key)) continue;
|
|
4973
6302
|
seen.add(key);
|
|
4974
6303
|
const spec = {
|
|
@@ -4976,7 +6305,8 @@ const detectApproximate = (paragraphs, measure) => {
|
|
|
4976
6305
|
sizePx: piece.sizePx,
|
|
4977
6306
|
bold: piece.bold,
|
|
4978
6307
|
italic: piece.italic,
|
|
4979
|
-
letterSpacingPx: piece.letterSpacingPx
|
|
6308
|
+
letterSpacingPx: piece.letterSpacingPx,
|
|
6309
|
+
kerning: piece.kerning ?? true
|
|
4980
6310
|
};
|
|
4981
6311
|
const r = measure(piece.text, spec);
|
|
4982
6312
|
if (r.approximate === true || r.ascentPx === void 0) return true;
|
|
@@ -5002,6 +6332,47 @@ const auditTextLayout = (pres, options = {}) => {
|
|
|
5002
6332
|
return issues;
|
|
5003
6333
|
};
|
|
5004
6334
|
//#endregion
|
|
5005
|
-
|
|
6335
|
+
//#region src/autofit.ts
|
|
6336
|
+
/**
|
|
6337
|
+
* How much the preview shrinks `shape`'s text to fit its box: a multiplier on
|
|
6338
|
+
* every authored size — font size, line spacing in points, paragraph spacing
|
|
6339
|
+
* and indents.
|
|
6340
|
+
*
|
|
6341
|
+
* `1` for a shape with no `<a:normAutofit>`: `<a:noAutofit>` overflows and
|
|
6342
|
+
* `<a:spAutoFit>` grows the box instead, so neither shrinks the text. A shape
|
|
6343
|
+
* with a baked `fontScale` reports that, unchanged — it is what PowerPoint
|
|
6344
|
+
* itself applied.
|
|
6345
|
+
*/
|
|
6346
|
+
function shapeAutoFitScale(pres, shape, options = {}) {
|
|
6347
|
+
const bounds = options.bounds ?? getShapeBoundsResolved(pres, shape);
|
|
6348
|
+
if (!bounds) return 1;
|
|
6349
|
+
return resolveTextBodyModel(pres, shape, {
|
|
6350
|
+
x: bounds.x,
|
|
6351
|
+
y: bounds.y,
|
|
6352
|
+
w: bounds.w,
|
|
6353
|
+
h: bounds.h
|
|
6354
|
+
}, getPresentationTheme(pres), getShapePlaceholderType(shape), options.measureText ?? defaultMeasurer, "#000000")?.autoFitScale ?? 1;
|
|
6355
|
+
}
|
|
6356
|
+
/** Translation in preview pixels applied to the entire text block by centered
|
|
6357
|
+
* text anchoring. Paragraph widths and alignment remain unchanged. Editing
|
|
6358
|
+
* surfaces must apply the same offset to keep the caret over the preview. */
|
|
6359
|
+
function shapeTextAnchorOffset(pres, shape, options = {}) {
|
|
6360
|
+
const bounds = options.bounds ?? getShapeBoundsResolved(pres, shape);
|
|
6361
|
+
if (!bounds) return {
|
|
6362
|
+
x: 0,
|
|
6363
|
+
y: 0
|
|
6364
|
+
};
|
|
6365
|
+
return resolveTextBodyModel(pres, shape, {
|
|
6366
|
+
x: bounds.x,
|
|
6367
|
+
y: bounds.y,
|
|
6368
|
+
w: bounds.w,
|
|
6369
|
+
h: bounds.h
|
|
6370
|
+
}, getPresentationTheme(pres), getShapePlaceholderType(shape), options.measureText ?? defaultMeasurer, "#000000")?.anchorOffset ?? {
|
|
6371
|
+
x: 0,
|
|
6372
|
+
y: 0
|
|
6373
|
+
};
|
|
6374
|
+
}
|
|
6375
|
+
//#endregion
|
|
6376
|
+
export { textColumnsStyle as _, renderTextEffectsSvg as a, shapeCustomTextRect as b, MONO as c, TIMES as d, defaultMeasurer as f, textUnderlineStyle as g, paragraphNumberLabels as h, renderSlideSvg as i, SANS as l, substituteFamily as m, shapeTextAnchorOffset as n, ARIAL as o, isCjk as p, auditTextLayout as r, AVG_GLYPH_W_RATIO as s, shapeAutoFitScale as t, SERIF as u, verticalTextStyle as v, resolveTextBodyRect as y };
|
|
5006
6377
|
|
|
5007
|
-
//# sourceMappingURL=src-
|
|
6378
|
+
//# sourceMappingURL=src-DYfIZIh8.js.map
|