@office-kit/pptx-preview 0.15.0 → 0.17.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +44 -6
- package/dist/{index-74n14k3Y.d.ts → index-C8dvep3H.d.ts} +44 -6
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/node.d.ts +1 -1
- package/dist/node.js +1 -1
- package/dist/{src-pb3C61GD.js → src-ayE1F9lH.js} +227 -54
- package/dist/src-ayE1F9lH.js.map +1 -0
- package/package.json +3 -3
- package/dist/src-pb3C61GD.js.map +0 -1
|
@@ -484,6 +484,21 @@ const renderEmfToSvg = (bytes) => {
|
|
|
484
484
|
return null;
|
|
485
485
|
};
|
|
486
486
|
//#endregion
|
|
487
|
+
//#region src/link-href.ts
|
|
488
|
+
const ALLOWED_PROTOCOLS = new Set([
|
|
489
|
+
"http:",
|
|
490
|
+
"https:",
|
|
491
|
+
"mailto:"
|
|
492
|
+
]);
|
|
493
|
+
const safeLinkHref = (href) => {
|
|
494
|
+
if (href.startsWith("#")) return href;
|
|
495
|
+
try {
|
|
496
|
+
return ALLOWED_PROTOCOLS.has(new URL(href).protocol) ? href : void 0;
|
|
497
|
+
} catch {
|
|
498
|
+
return;
|
|
499
|
+
}
|
|
500
|
+
};
|
|
501
|
+
//#endregion
|
|
487
502
|
//#region src/text-layout.ts
|
|
488
503
|
const SANS = "Carlito";
|
|
489
504
|
const SERIF = "Caladea";
|
|
@@ -564,6 +579,8 @@ const escapeXml$1 = (s) => s.replace(/[&<>"]/g, (c) => ({
|
|
|
564
579
|
">": ">",
|
|
565
580
|
"\"": """
|
|
566
581
|
})[c] ?? c);
|
|
582
|
+
const CSS_IDENT = /^-?[A-Za-z_\u0080-\u{10FFFF}][\w\u0080-\u{10FFFF}-]*$/u;
|
|
583
|
+
const cssFamilyName = (family) => family.split(" ").every((word) => CSS_IDENT.test(word)) ? family : `"${family.replace(/["\\]/g, "\\$&")}"`;
|
|
567
584
|
const fmt = (n) => {
|
|
568
585
|
const r = Math.round(n * 100) / 100;
|
|
569
586
|
return Object.is(r, -0) ? "0" : String(r);
|
|
@@ -649,8 +666,27 @@ const layoutCore = (input, measure) => {
|
|
|
649
666
|
}
|
|
650
667
|
}
|
|
651
668
|
const canBreak = EAST_ASIAN_CHAR.test(segment) ? width > avail - bulletLead : para.latinLineBreak === true || width > avail - bulletLead;
|
|
652
|
-
if (input.wrap && canBreak && [...segment].length > 1)
|
|
653
|
-
|
|
669
|
+
if (input.wrap && canBreak && [...segment].length > 1) {
|
|
670
|
+
const breakAnywhere = para.latinLineBreak === true || EAST_ASIAN_CHAR.test(segment);
|
|
671
|
+
let chunk = [];
|
|
672
|
+
let chunkWidth = 0;
|
|
673
|
+
const flushChunk = () => {
|
|
674
|
+
const keepWhole = chunk.length > 1 && chunkWidth <= avail - bulletLead;
|
|
675
|
+
chunk.forEach((token, ci) => tokens.push(keepWhole && ci > 0 ? {
|
|
676
|
+
...token,
|
|
677
|
+
continuesSegment: true
|
|
678
|
+
} : token));
|
|
679
|
+
chunk = [];
|
|
680
|
+
chunkWidth = 0;
|
|
681
|
+
};
|
|
682
|
+
for (const fragment of fragments) for (const ch of fragment.text) {
|
|
683
|
+
const token = makeToken(ch, fragment.piece);
|
|
684
|
+
chunk.push(token);
|
|
685
|
+
chunkWidth += token.width;
|
|
686
|
+
if (breakAnywhere || ch === "-" || ch === "/") flushChunk();
|
|
687
|
+
}
|
|
688
|
+
flushChunk();
|
|
689
|
+
} else for (const fragment of fragments) tokens.push(fragment);
|
|
654
690
|
}
|
|
655
691
|
wordParts = [];
|
|
656
692
|
};
|
|
@@ -1015,7 +1051,7 @@ const emitPlacements = (placements, effectsOnly = false, effectKind = "all") =>
|
|
|
1015
1051
|
if (line.bullet && !effectsOnly) {
|
|
1016
1052
|
const b = line.bullet.b;
|
|
1017
1053
|
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"/>`);
|
|
1018
|
-
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>`);
|
|
1054
|
+
else parts.push(`<text x="${fmt(line.bullet.x + dx + GRID_NUDGE_X)}" y="${fmt(baselineY)}" font-family="${escapeXml$1(b.cssFamily ?? b.family)}" font-size="${fmt(b.sizePx)}" fill="${b.fillHex}" xml:space="preserve">${escapeXml$1(b.text)}</text>`);
|
|
1019
1055
|
}
|
|
1020
1056
|
parts.push(emitLine(line, baselineY, dx, line.descent, (kind) => nextEffectId(kind), effectsOnly, effectKind));
|
|
1021
1057
|
}
|
|
@@ -1042,10 +1078,11 @@ const emitLine = (line, baselineY, dx, descent, nextEffectId, effectsOnly, effec
|
|
|
1042
1078
|
const groups = groupTokens(content);
|
|
1043
1079
|
const tspans = groups.map((g) => {
|
|
1044
1080
|
const span = tspan(g);
|
|
1045
|
-
|
|
1046
|
-
|
|
1081
|
+
const href = g.piece.href ? safeLinkHref(g.piece.href) : void 0;
|
|
1082
|
+
if (!href) return span;
|
|
1083
|
+
const target = href.startsWith("#") ? "" : " target=\"_blank\" rel=\"noopener noreferrer\"";
|
|
1047
1084
|
const title = g.piece.hrefTip ? `<title>${escapeXml$1(g.piece.hrefTip)}</title>` : "";
|
|
1048
|
-
return `<a href="${escapeXml$1(
|
|
1085
|
+
return `<a href="${escapeXml$1(href)}"${target}>${title}${span}</a>`;
|
|
1049
1086
|
}).join("");
|
|
1050
1087
|
if (tspans === "") return "";
|
|
1051
1088
|
const x0 = line.anchorX + dx + GRID_NUDGE_X;
|
|
@@ -1376,7 +1413,7 @@ const tspan = (g) => {
|
|
|
1376
1413
|
const p = g.piece;
|
|
1377
1414
|
const sizePx = renderedSizePxOf(p);
|
|
1378
1415
|
const attrs = [
|
|
1379
|
-
`font-family="${escapeXml$1(p.family)}"`,
|
|
1416
|
+
`font-family="${escapeXml$1(p.cssFamily ?? p.family)}"`,
|
|
1380
1417
|
`font-size="${fmt(sizePx)}"`,
|
|
1381
1418
|
`fill="${p.fillHex}"`
|
|
1382
1419
|
];
|
|
@@ -1689,7 +1726,7 @@ const bevelFilterPrimitives = (shape3d) => {
|
|
|
1689
1726
|
//#region src/render-slide.ts
|
|
1690
1727
|
const clickActionHref = (pres, action) => {
|
|
1691
1728
|
if (!action) return void 0;
|
|
1692
|
-
if (action.kind === "url") return action.url;
|
|
1729
|
+
if (action.kind === "url") return safeLinkHref(action.url);
|
|
1693
1730
|
if (action.kind === "customShow") return `#pptx-custom-show?id=${action.id}&return=${action.returnToShow}`;
|
|
1694
1731
|
if (action.kind === "endShow") return "#pptx-end-show";
|
|
1695
1732
|
if (action.kind === "lastSlideViewed") return "#pptx-last-slide-viewed";
|
|
@@ -1714,6 +1751,11 @@ const DEFAULT_BODY_PT = 18;
|
|
|
1714
1751
|
const DEFAULT_TITLE_PT = 44;
|
|
1715
1752
|
const DEFAULT_FONT = "Calibri, 'Helvetica Neue', Arial, sans-serif";
|
|
1716
1753
|
const browserFontFamily = (family) => family ? `${JSON.stringify(family)}, ${DEFAULT_FONT}` : DEFAULT_FONT;
|
|
1754
|
+
const fontFamilyStyles = (font) => font.startsWith("+") ? [] : [`font-family:${escapeXml(browserFontFamily(font))}`];
|
|
1755
|
+
const BROWSER_FAMILIES = {
|
|
1756
|
+
resolveFamily: browserFontFamily,
|
|
1757
|
+
familyIsCssList: true
|
|
1758
|
+
};
|
|
1717
1759
|
const DEFAULT_BULLET_FONT = "Arial";
|
|
1718
1760
|
const DEFAULT_INSET_X = 91440;
|
|
1719
1761
|
const DEFAULT_INSET_Y = 45720;
|
|
@@ -2436,7 +2478,7 @@ const renderRun = (text, format, theme, effectivePt, defaultColor, _wasDefault =
|
|
|
2436
2478
|
const styles = [];
|
|
2437
2479
|
styles.push(`font-size:${(effectivePt * PX_PER_PT).toFixed(2)}px`);
|
|
2438
2480
|
styles.push(suppressLineHeight ? "line-height:0;vertical-align:top" : lineHeightOverride !== void 0 ? `line-height:${lineHeightOverride}` : "line-height:1.05");
|
|
2439
|
-
if (format?.font) styles.push(
|
|
2481
|
+
if (format?.font) styles.push(...fontFamilyStyles(format.font));
|
|
2440
2482
|
if (format?.bold) styles.push("font-weight:700");
|
|
2441
2483
|
if (format?.italic) styles.push("font-style:italic");
|
|
2442
2484
|
const underline = format?.underline;
|
|
@@ -2557,6 +2599,13 @@ const smallCapsParts = (text, base) => {
|
|
|
2557
2599
|
flush();
|
|
2558
2600
|
return parts;
|
|
2559
2601
|
};
|
|
2602
|
+
const faceOf = (a, font) => {
|
|
2603
|
+
const family = (a.resolveFamily ?? substituteFamily)(font);
|
|
2604
|
+
return a.familyIsCssList ? { family } : {
|
|
2605
|
+
family,
|
|
2606
|
+
cssFamily: cssFamilyName(family)
|
|
2607
|
+
};
|
|
2608
|
+
};
|
|
2560
2609
|
const buildSvgTextInput = (a) => {
|
|
2561
2610
|
const scale = a.autoFitScale;
|
|
2562
2611
|
const paragraphs = a.paraData.map((para, pi) => {
|
|
@@ -2565,7 +2614,7 @@ const buildSvgTextInput = (a) => {
|
|
|
2565
2614
|
if (run.text === "\n") {
|
|
2566
2615
|
pieces.push({
|
|
2567
2616
|
...breakPiece(),
|
|
2568
|
-
|
|
2617
|
+
...faceOf(a, run.fmt?.font ?? a.themeFace),
|
|
2569
2618
|
sizePx: run.sizePt * scale * PX_PER_PT,
|
|
2570
2619
|
bold: run.fmt?.bold ?? false,
|
|
2571
2620
|
italic: run.fmt?.italic ?? false
|
|
@@ -2581,7 +2630,7 @@ const buildSvgTextInput = (a) => {
|
|
|
2581
2630
|
underline: fmt?.underline ?? true
|
|
2582
2631
|
};
|
|
2583
2632
|
}
|
|
2584
|
-
const
|
|
2633
|
+
const face = faceOf(a, fmt?.font ?? a.themeFace);
|
|
2585
2634
|
const sizePx = run.sizePt * scale * PX_PER_PT;
|
|
2586
2635
|
const fillHex = fmt?.color !== void 0 && fmt.color !== null ? resolveColor(fmt.color, a.theme, "#000000") : a.defaultColor;
|
|
2587
2636
|
const baseline = fmt?.baseline ?? 0;
|
|
@@ -2590,7 +2639,7 @@ const buildSvgTextInput = (a) => {
|
|
|
2590
2639
|
const cap = fmt?.cap;
|
|
2591
2640
|
const caps = cap === "all" || cap === "small";
|
|
2592
2641
|
const base = {
|
|
2593
|
-
|
|
2642
|
+
...face,
|
|
2594
2643
|
sizePx,
|
|
2595
2644
|
bold: fmt?.bold ?? false,
|
|
2596
2645
|
italic: fmt?.italic ?? false,
|
|
@@ -2736,10 +2785,9 @@ const buildBullet = (a, para, pi) => {
|
|
|
2736
2785
|
const baseSizePx = (para.runs.find((r) => r.text !== "\n" && r.text !== "")?.sizePt ?? a.defaultPt) * PX_PER_PT * a.autoFitScale;
|
|
2737
2786
|
const sizePx = para.bulletDetail.sizePct !== null ? baseSizePx * para.bulletDetail.sizePct : para.bulletDetail.sizePts !== null ? para.bulletDetail.sizePts * PX_PER_PT * a.autoFitScale : baseSizePx;
|
|
2738
2787
|
const fillHex = bulletFillOf(para, a.theme, a.defaultColor);
|
|
2739
|
-
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;
|
|
2740
2788
|
return {
|
|
2741
2789
|
text: char,
|
|
2742
|
-
|
|
2790
|
+
...faceOf(a, para.bulletDetail.fontFollowText ? para.runs.find((r) => r.text !== "\n" && r.text !== "")?.fmt?.font ?? DEFAULT_BULLET_FONT : para.bulletDetail.font ?? DEFAULT_BULLET_FONT),
|
|
2743
2791
|
sizePx,
|
|
2744
2792
|
fillHex,
|
|
2745
2793
|
...para.bulletImageHref ? { imageHref: para.bulletImageHref } : {}
|
|
@@ -2750,7 +2798,7 @@ const bulletFillOf = (para, theme, defaultColor) => {
|
|
|
2750
2798
|
const runColor = para.runs.find((r) => r.text !== "\n" && r.text !== "")?.fmt?.color;
|
|
2751
2799
|
return runColor === void 0 || runColor === null ? defaultColor : resolveColor(runColor, theme, defaultColor);
|
|
2752
2800
|
};
|
|
2753
|
-
const resolveTextBodyModel = (pres, shape, bounds, theme, phType, measure, defaultColor, useSvgTextRect = false) => {
|
|
2801
|
+
const resolveTextBodyModel = (pres, shape, bounds, theme, phType, measure, defaultColor, useSvgTextRect = false, resolveFamily = substituteFamily) => {
|
|
2754
2802
|
let paragraphCount;
|
|
2755
2803
|
try {
|
|
2756
2804
|
paragraphCount = getShapeParagraphCount(shape);
|
|
@@ -2761,7 +2809,7 @@ const resolveTextBodyModel = (pres, shape, bounds, theme, phType, measure, defau
|
|
|
2761
2809
|
const defaultPt = placeholderDefaultPt(phType);
|
|
2762
2810
|
const themeFonts = getPresentationFonts(pres);
|
|
2763
2811
|
const themeFace = phType === "title" || phType === "ctrTitle" ? themeFonts?.majorLatin ?? null : themeFonts?.minorLatin ?? null;
|
|
2764
|
-
const effectiveDefaultFont =
|
|
2812
|
+
const effectiveDefaultFont = escapeXml(browserFontFamily(themeFace));
|
|
2765
2813
|
let effectiveBody;
|
|
2766
2814
|
try {
|
|
2767
2815
|
effectiveBody = getShapeBodyPrEffective(pres, shape);
|
|
@@ -2969,7 +3017,8 @@ const resolveTextBodyModel = (pres, shape, bounds, theme, phType, measure, defau
|
|
|
2969
3017
|
innerH: fitRect.h,
|
|
2970
3018
|
measure,
|
|
2971
3019
|
vert: fitVert,
|
|
2972
|
-
columns: fitColumns
|
|
3020
|
+
columns: fitColumns,
|
|
3021
|
+
resolveFamily
|
|
2973
3022
|
};
|
|
2974
3023
|
let s = 1;
|
|
2975
3024
|
while (s > AUTOFIT_FLOOR) {
|
|
@@ -3009,6 +3058,7 @@ const resolveTextBodyModel = (pres, shape, bounds, theme, phType, measure, defau
|
|
|
3009
3058
|
measure,
|
|
3010
3059
|
vert,
|
|
3011
3060
|
autoFitScale,
|
|
3061
|
+
resolveFamily,
|
|
3012
3062
|
columns: vert === "none" && cols && cols.count >= 2 ? {
|
|
3013
3063
|
count: cols.count,
|
|
3014
3064
|
gapPx: cols.gapEmu !== void 0 ? cols.gapEmu / EMU_PER_PX : 0
|
|
@@ -3066,10 +3116,11 @@ const renderHtmlParagraphs = (paraData, numberLabels, theme, autoFitScale, defau
|
|
|
3066
3116
|
};
|
|
3067
3117
|
}
|
|
3068
3118
|
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);
|
|
3069
|
-
|
|
3070
|
-
|
|
3119
|
+
const href = run.href ? safeLinkHref(run.href) : void 0;
|
|
3120
|
+
if (!href) return span;
|
|
3121
|
+
const targetAttrs = href.startsWith("#") ? "" : " target=\"_blank\" rel=\"noopener noreferrer\"";
|
|
3071
3122
|
const titleAttr = run.hrefTip ? ` title="${escapeXml(run.hrefTip)}"` : "";
|
|
3072
|
-
return `<a href="${escapeXml(
|
|
3123
|
+
return `<a href="${escapeXml(href)}"${targetAttrs}${titleAttr} style="color:inherit;text-decoration:inherit">${span}</a>`;
|
|
3073
3124
|
});
|
|
3074
3125
|
const firstRunPt = para.runs.find((run) => run.text !== "\n" && run.text !== "")?.sizePt ?? defaultPt;
|
|
3075
3126
|
let lineHeightCss = para.emptySizePt !== void 0 ? `line-height:${LINE_HEIGHT}` : "";
|
|
@@ -3083,7 +3134,7 @@ const renderHtmlParagraphs = (paraData, numberLabels, theme, autoFitScale, defau
|
|
|
3083
3134
|
const pStyles = [
|
|
3084
3135
|
"margin:0",
|
|
3085
3136
|
para.lineSpacing?.kind === "pts" ? `font-size:${(firstRunPt * PX_PER_PT * autoFitScale).toFixed(2)}px` : "font-size:0",
|
|
3086
|
-
...para.lineSpacing?.kind === "pts" && para.runs[0]?.fmt?.font ?
|
|
3137
|
+
...para.lineSpacing?.kind === "pts" && para.runs[0]?.fmt?.font ? fontFamilyStyles(para.runs[0].fmt.font) : [],
|
|
3087
3138
|
...para.emptySizePt !== void 0 ? [`font-size:${(para.emptySizePt * PX_PER_PT * autoFitScale).toFixed(2)}px`] : [],
|
|
3088
3139
|
...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`] : [],
|
|
3089
3140
|
marginTopCss,
|
|
@@ -3118,7 +3169,7 @@ const renderHtmlParagraphs = (paraData, numberLabels, theme, autoFitScale, defau
|
|
|
3118
3169
|
else if (para.bulletDetail.sizePts !== null) bulletStyles.push(`font-size:${(para.bulletDetail.sizePts * PX_PER_PT * autoFitScale).toFixed(2)}px`);
|
|
3119
3170
|
else bulletStyles.push(`font-size:${baseBulletPx.toFixed(2)}px`);
|
|
3120
3171
|
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;
|
|
3121
|
-
bulletStyles.push(
|
|
3172
|
+
bulletStyles.push(...fontFamilyStyles(bulletFont));
|
|
3122
3173
|
prefix = `<span style="${bulletStyles.join(";")}">${escapeXml(char)}</span>`;
|
|
3123
3174
|
}
|
|
3124
3175
|
paragraphs.push(`<p data-pptx-paragraph="${pi}" style="${pStyles.join(";")}">${prefix}${runHtmls.join("") || "​"}</p>`);
|
|
@@ -3126,7 +3177,7 @@ const renderHtmlParagraphs = (paraData, numberLabels, theme, autoFitScale, defau
|
|
|
3126
3177
|
return paragraphs;
|
|
3127
3178
|
};
|
|
3128
3179
|
const renderTextBody = (pres, shape, bounds, theme, phType, ctx, output = "body") => {
|
|
3129
|
-
const model = resolveTextBodyModel(pres, shape, bounds, theme, phType, ctx.measure, activeDeckTextColor, ctx.mode === "svg" && output === "body");
|
|
3180
|
+
const model = resolveTextBodyModel(pres, shape, bounds, theme, phType, ctx.measure, activeDeckTextColor, ctx.mode === "svg" && output === "body", ctx.resolveFamily);
|
|
3130
3181
|
if (model === null) return "";
|
|
3131
3182
|
const bevel = textBevelOf(getShapeText3D(shape));
|
|
3132
3183
|
const { paraData, numberLabels, authoredAutofit, autoFitScale, defaultPt, themeFace, effectiveDefaultFont, effectiveBody, anchor, innerX, innerY, innerW, innerH, svgTextRect } = model;
|
|
@@ -3162,7 +3213,7 @@ const renderTextBody = (pres, shape, bounds, theme, phType, ctx, output = "body"
|
|
|
3162
3213
|
innerW: vInnerW,
|
|
3163
3214
|
innerH: vInnerH,
|
|
3164
3215
|
measure: ctx.mode === "svg" ? ctx.measure : browserTextMeasurer() ?? ctx.measure,
|
|
3165
|
-
...ctx.mode === "foreignObject" ? { resolveFamily:
|
|
3216
|
+
...ctx.mode === "foreignObject" ? BROWSER_FAMILIES : { resolveFamily: ctx.resolveFamily },
|
|
3166
3217
|
vert: svgVert,
|
|
3167
3218
|
columns: svgColumns,
|
|
3168
3219
|
bevel
|
|
@@ -3172,7 +3223,7 @@ const renderTextBody = (pres, shape, bounds, theme, phType, ctx, output = "body"
|
|
|
3172
3223
|
...svgArgsBase,
|
|
3173
3224
|
autoFitScale: authoredAutofit ? autoFitScale : 1,
|
|
3174
3225
|
effectsOnly: true,
|
|
3175
|
-
|
|
3226
|
+
...BROWSER_FAMILIES
|
|
3176
3227
|
};
|
|
3177
3228
|
const effects = [
|
|
3178
3229
|
paraData.some((para) => para.runs.some((run) => run.fmt?.reflection != null)) ? buildAndLayoutSvgText({
|
|
@@ -3253,7 +3304,7 @@ const renderTextBody = (pres, shape, bounds, theme, phType, ctx, output = "body"
|
|
|
3253
3304
|
vert: svgVert,
|
|
3254
3305
|
columns: null,
|
|
3255
3306
|
effectsOnly: true,
|
|
3256
|
-
|
|
3307
|
+
...BROWSER_FAMILIES,
|
|
3257
3308
|
bevel
|
|
3258
3309
|
};
|
|
3259
3310
|
const hasCharacterReflection = paraData.some((para) => para.runs.some((run) => run.fmt?.reflection !== void 0 && run.fmt.reflection !== null));
|
|
@@ -3310,6 +3361,7 @@ const renderTextEffectsSvg = (pres, slide, shape, bounds, opts = {}) => {
|
|
|
3310
3361
|
groupReflected: false,
|
|
3311
3362
|
mode: "foreignObject",
|
|
3312
3363
|
measure: opts.measureText ?? browserTextMeasurer() ?? defaultMeasurer,
|
|
3364
|
+
resolveFamily: substituteFamily,
|
|
3313
3365
|
ownShapes: true,
|
|
3314
3366
|
background: {
|
|
3315
3367
|
id: "",
|
|
@@ -3346,6 +3398,7 @@ const renderTableCellEffectsSvgInternal = (pres, slide, shape, cellPosition, bou
|
|
|
3346
3398
|
groupReflected: false,
|
|
3347
3399
|
mode: "foreignObject",
|
|
3348
3400
|
measure: opts.measureText ?? browserTextMeasurer() ?? defaultMeasurer,
|
|
3401
|
+
resolveFamily: substituteFamily,
|
|
3349
3402
|
ownShapes: true,
|
|
3350
3403
|
background: {
|
|
3351
3404
|
id: "",
|
|
@@ -4841,7 +4894,7 @@ const renderTableCellText = (cell, paragraphs, cx, cy, cw, ch, color, pres, shap
|
|
|
4841
4894
|
innerW: innerW * EMU_PER_PX,
|
|
4842
4895
|
innerH: innerH * EMU_PER_PX,
|
|
4843
4896
|
measure: ctx.mode === "svg" ? ctx.measure : browserTextMeasurer() ?? ctx.measure,
|
|
4844
|
-
...ctx.mode === "foreignObject" ? { resolveFamily:
|
|
4897
|
+
...ctx.mode === "foreignObject" ? BROWSER_FAMILIES : { resolveFamily: ctx.resolveFamily },
|
|
4845
4898
|
vert: verticalLayoutOf(getTableCellTextDirection(cell)),
|
|
4846
4899
|
columns: null,
|
|
4847
4900
|
...effectsOnly ? { effectsOnly: true } : {},
|
|
@@ -4874,7 +4927,7 @@ const renderTableCellText = (cell, paragraphs, cx, cy, cw, ch, color, pres, shap
|
|
|
4874
4927
|
const vertical = verticalTextStyle(getTableCellTextDirection(cell));
|
|
4875
4928
|
const directionStyle = `${vertical.declarations}${vertical.transform ? `;transform:${vertical.transform}` : ""}`;
|
|
4876
4929
|
const justify = vAnchor === "top" ? "flex-start" : vAnchor === "bottom" ? "flex-end" : "center";
|
|
4877
|
-
const familyFont =
|
|
4930
|
+
const familyFont = escapeXml(browserFontFamily(themeFace));
|
|
4878
4931
|
const body = renderHtmlParagraphs(paraData, numberLabels, theme, 1, DEFAULT_BODY_PT, color).join("");
|
|
4879
4932
|
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()}${bevelOverlay()}`;
|
|
4880
4933
|
};
|
|
@@ -5720,6 +5773,7 @@ const renderSurface = (pres, surface, opts) => {
|
|
|
5720
5773
|
groupReflected: false,
|
|
5721
5774
|
mode: opts.textLayout ?? "foreignObject",
|
|
5722
5775
|
measure: opts.measureText ?? defaultMeasurer,
|
|
5776
|
+
resolveFamily: opts.textLayout === "svg" ? opts.resolveFamily ?? substituteFamily : substituteFamily,
|
|
5723
5777
|
ownShapes: true,
|
|
5724
5778
|
background: {
|
|
5725
5779
|
id: "",
|
|
@@ -5850,12 +5904,23 @@ const detectImageFormatLocal = (bytes) => {
|
|
|
5850
5904
|
//#endregion
|
|
5851
5905
|
//#region src/audit.ts
|
|
5852
5906
|
const DEFAULT_TOLERANCE_PX = 1;
|
|
5907
|
+
const PT_PER_PX = 72 / 96;
|
|
5908
|
+
const SHORT_LAST_LINE_MAX_CHARS = 2;
|
|
5909
|
+
const LETTER_OR_DIGIT = /[\p{Script=Latin}\p{Script=Greek}\p{Script=Cyrillic}\p{Nd}]/u;
|
|
5910
|
+
const KATAKANA = /[\p{Script=Katakana}\u30FC]/u;
|
|
5911
|
+
const isWordChar = (ch) => LETTER_OR_DIGIT.test(ch) || KATAKANA.test(ch);
|
|
5912
|
+
const splitsWord = (before, after) => LETTER_OR_DIGIT.test(before) && LETTER_OR_DIGIT.test(after) || KATAKANA.test(before) && KATAKANA.test(after);
|
|
5913
|
+
const charBefore = (s, i) => {
|
|
5914
|
+
return (i >= 2 ? s.codePointAt(i - 2) : 0) > 65535 ? s.slice(i - 2, i) : s.slice(i - 1, i);
|
|
5915
|
+
};
|
|
5916
|
+
const charAt = (s, i) => i < s.length ? String.fromCodePoint(s.codePointAt(i)) : "";
|
|
5853
5917
|
const round2 = (n) => Math.round(n * 100) / 100;
|
|
5854
5918
|
function* walkShapes(shapes) {
|
|
5855
5919
|
for (const shape of shapes) if (getShapeKind(shape) !== "group") yield shape;
|
|
5856
5920
|
}
|
|
5857
5921
|
const passthroughFamily = (family) => family ?? "Carlito";
|
|
5858
|
-
const auditShape = (pres, theme, shape, slideIndex, measure,
|
|
5922
|
+
const auditShape = (pres, theme, shape, slideIndex, measure, options, issues) => {
|
|
5923
|
+
const { tolerancePx, reportSoftWraps, reportBadWraps, minFontSizePt } = options;
|
|
5859
5924
|
const kind = getShapeKind(shape);
|
|
5860
5925
|
if (kind !== "shape" && kind !== "graphicFrame") return;
|
|
5861
5926
|
const bounds = getShapeBoundsResolved(pres, shape);
|
|
@@ -5865,7 +5930,7 @@ const auditShape = (pres, theme, shape, slideIndex, measure, tolerancePx, report
|
|
|
5865
5930
|
y: bounds.y,
|
|
5866
5931
|
w: bounds.w,
|
|
5867
5932
|
h: bounds.h
|
|
5868
|
-
}, theme, getShapePlaceholderType(shape), measure, "#000000");
|
|
5933
|
+
}, theme, getShapePlaceholderType(shape), measure, "#000000", false, passthroughFamily);
|
|
5869
5934
|
if (model === null) return;
|
|
5870
5935
|
const vert = verticalLayoutOf(model.effectiveBody.vert ?? getShapeTextDirection(shape));
|
|
5871
5936
|
const cols = model.effectiveBody.columns;
|
|
@@ -5900,51 +5965,65 @@ const auditShape = (pres, theme, shape, slideIndex, measure, tolerancePx, report
|
|
|
5900
5965
|
resolveFamily: passthroughFamily
|
|
5901
5966
|
});
|
|
5902
5967
|
const core = layoutCore(input, measure);
|
|
5903
|
-
const
|
|
5904
|
-
|
|
5905
|
-
|
|
5906
|
-
|
|
5907
|
-
|
|
5968
|
+
const rotated = vert === "cw90" || vert === "cw270";
|
|
5969
|
+
const frameOf = (x, y, w, h) => rotated ? {
|
|
5970
|
+
x: x + w / 2 - h / 2,
|
|
5971
|
+
y: y + h / 2 - w / 2,
|
|
5972
|
+
w: h,
|
|
5973
|
+
h: w
|
|
5908
5974
|
} : {
|
|
5909
|
-
x
|
|
5910
|
-
y
|
|
5911
|
-
w
|
|
5912
|
-
h
|
|
5975
|
+
x,
|
|
5976
|
+
y,
|
|
5977
|
+
w,
|
|
5978
|
+
h
|
|
5913
5979
|
};
|
|
5980
|
+
const frame = frameOf(input.boxXpx, input.boxYpx, input.boxWpx, input.boxHpx);
|
|
5981
|
+
const shapeFrame = frameOf(bounds.x / EMU_PER_PX, bounds.y / EMU_PER_PX, bounds.w / EMU_PER_PX, bounds.h / EMU_PER_PX);
|
|
5982
|
+
let inkLeft = Infinity;
|
|
5983
|
+
let inkRight = -Infinity;
|
|
5914
5984
|
let inkTop = Infinity;
|
|
5915
5985
|
let inkBottom = -Infinity;
|
|
5916
|
-
let
|
|
5917
|
-
let anyInk = false;
|
|
5986
|
+
let lineHeight = 0;
|
|
5918
5987
|
for (const p of core.placements) {
|
|
5919
5988
|
const toks = [...p.line.tokens];
|
|
5920
5989
|
while (toks.length > 0 && (toks[toks.length - 1].isSpace || toks[toks.length - 1].isBreak)) toks.pop();
|
|
5921
5990
|
if (!toks.some((t) => !t.isSpace && !t.isBreak)) continue;
|
|
5922
|
-
anyInk = true;
|
|
5923
5991
|
let lineW = 0;
|
|
5924
5992
|
for (const t of toks) if (!t.isBreak) lineW += t.width;
|
|
5925
|
-
const left = p.line.textAnchor === "middle" ? p.line.anchorX - lineW / 2 : p.line.textAnchor === "end" ? p.line.anchorX - lineW : p.line.anchorX;
|
|
5926
|
-
|
|
5927
|
-
|
|
5993
|
+
const left = (p.line.textAnchor === "middle" ? p.line.anchorX - lineW / 2 : p.line.textAnchor === "end" ? p.line.anchorX - lineW : p.line.anchorX) + p.dx;
|
|
5994
|
+
inkLeft = Math.min(inkLeft, left);
|
|
5995
|
+
inkRight = Math.max(inkRight, left + lineW);
|
|
5928
5996
|
inkTop = Math.min(inkTop, p.baselineY - p.line.ascent);
|
|
5929
5997
|
inkBottom = Math.max(inkBottom, p.baselineY + p.line.descent);
|
|
5998
|
+
lineHeight = Math.max(lineHeight, p.line.ascent + p.line.descent);
|
|
5930
5999
|
}
|
|
5931
|
-
if (
|
|
6000
|
+
if (inkLeft === Infinity) return;
|
|
5932
6001
|
const approximate = detectApproximate(input.paragraphs, measure);
|
|
5933
6002
|
const slideRef = {
|
|
5934
6003
|
slideIndex,
|
|
5935
6004
|
shapeName: getShapeName(shape),
|
|
5936
6005
|
approximate
|
|
5937
6006
|
};
|
|
6007
|
+
const overflowXOf = (f) => Math.max(0, f.x - inkLeft, inkRight - (f.x + f.w));
|
|
6008
|
+
const overflowYOf = (f) => Math.max(0, f.y - inkTop, inkBottom - (f.y + f.h));
|
|
6009
|
+
const shapeOverflowXOf = (f) => Math.max(0, f.x - inkLeft) + Math.max(0, inkRight - (f.x + f.w));
|
|
6010
|
+
const shapeOverflowYOf = (f) => Math.max(0, f.y - inkTop) + Math.max(0, inkBottom - (f.y + f.h));
|
|
6011
|
+
const lineHeightPx = round2(lineHeight);
|
|
6012
|
+
const overflowX = overflowXOf(frame);
|
|
5938
6013
|
if (overflowX > tolerancePx) issues.push({
|
|
5939
6014
|
...slideRef,
|
|
5940
6015
|
kind: "overflow-x",
|
|
5941
|
-
overflowPx: round2(overflowX)
|
|
6016
|
+
overflowPx: round2(overflowX),
|
|
6017
|
+
shapeOverflowPx: round2(shapeOverflowXOf(shapeFrame)),
|
|
6018
|
+
lineHeightPx
|
|
5942
6019
|
});
|
|
5943
|
-
const overflowY =
|
|
6020
|
+
const overflowY = overflowYOf(frame);
|
|
5944
6021
|
if (overflowY > tolerancePx) issues.push({
|
|
5945
6022
|
...slideRef,
|
|
5946
6023
|
kind: "overflow-y",
|
|
5947
|
-
overflowPx: round2(overflowY)
|
|
6024
|
+
overflowPx: round2(overflowY),
|
|
6025
|
+
shapeOverflowPx: round2(shapeOverflowYOf(shapeFrame)),
|
|
6026
|
+
lineHeightPx
|
|
5948
6027
|
});
|
|
5949
6028
|
if (reportSoftWraps && vert === "none") {
|
|
5950
6029
|
const lineCounts = /* @__PURE__ */ new Map();
|
|
@@ -5960,6 +6039,94 @@ const auditShape = (pres, theme, shape, slideIndex, measure, tolerancePx, report
|
|
|
5960
6039
|
});
|
|
5961
6040
|
});
|
|
5962
6041
|
}
|
|
6042
|
+
if (reportBadWraps && vert === "none") auditLineBreaks(input.paragraphs, core.placements, slideRef, issues);
|
|
6043
|
+
if (minFontSizePt !== void 0) {
|
|
6044
|
+
let smallestPx = Infinity;
|
|
6045
|
+
for (const para of input.paragraphs) for (const piece of para.pieces) if (!piece.isBreak && piece.text.trim() !== "" && piece.sizePx < smallestPx) smallestPx = piece.sizePx;
|
|
6046
|
+
const sizePt = Math.round(smallestPx * PT_PER_PX * 10) / 10;
|
|
6047
|
+
if (sizePt < minFontSizePt) issues.push({
|
|
6048
|
+
...slideRef,
|
|
6049
|
+
kind: "small-font",
|
|
6050
|
+
sizePt
|
|
6051
|
+
});
|
|
6052
|
+
}
|
|
6053
|
+
};
|
|
6054
|
+
const auditLineBreaks = (paragraphs, placements, slideRef, issues) => {
|
|
6055
|
+
const linesByParagraph = /* @__PURE__ */ new Map();
|
|
6056
|
+
for (const { line } of placements) {
|
|
6057
|
+
const text = line.tokens.map((t) => t.isTab ? " " : t.text).join("");
|
|
6058
|
+
if (text.trim() === "") continue;
|
|
6059
|
+
const lines = linesByParagraph.get(line.paraIndex);
|
|
6060
|
+
if (lines) lines.push(text);
|
|
6061
|
+
else linesByParagraph.set(line.paraIndex, [text]);
|
|
6062
|
+
}
|
|
6063
|
+
for (const [paragraphIndex, lines] of linesByParagraph) auditParagraphBreaks(paragraphs[paragraphIndex].pieces.map((p) => p.isBreak ? "\n" : p.text).join(""), lines, {
|
|
6064
|
+
...slideRef,
|
|
6065
|
+
paragraphIndex
|
|
6066
|
+
}, issues);
|
|
6067
|
+
};
|
|
6068
|
+
const auditParagraphBreaks = (source, lines, ref, issues) => {
|
|
6069
|
+
const found = [];
|
|
6070
|
+
const wordsByStart = /* @__PURE__ */ new Map();
|
|
6071
|
+
let previousEnd = -1;
|
|
6072
|
+
let wrappedLines = 0;
|
|
6073
|
+
const closeSegment = (lastLine) => {
|
|
6074
|
+
const visible = [...lastLine.replace(/\s/gu, "")];
|
|
6075
|
+
if (wrappedLines > 0 && visible.length <= SHORT_LAST_LINE_MAX_CHARS) found.push({
|
|
6076
|
+
...ref,
|
|
6077
|
+
kind: "short-last-line",
|
|
6078
|
+
text: lastLine.trim()
|
|
6079
|
+
});
|
|
6080
|
+
};
|
|
6081
|
+
for (let i = 0; i < lines.length; i++) {
|
|
6082
|
+
const start = source.indexOf(lines[i], Math.max(previousEnd, 0));
|
|
6083
|
+
if (start < 0) return;
|
|
6084
|
+
if (previousEnd >= 0) {
|
|
6085
|
+
const gap = source.slice(previousEnd, start);
|
|
6086
|
+
if (gap.includes("\n")) {
|
|
6087
|
+
closeSegment(lines[i - 1]);
|
|
6088
|
+
wrappedLines = 0;
|
|
6089
|
+
} else {
|
|
6090
|
+
wrappedLines++;
|
|
6091
|
+
if (gap === "" && splitsWord(charBefore(source, start), charAt(source, start))) {
|
|
6092
|
+
let wordStart = start;
|
|
6093
|
+
while (wordStart > 0 && isWordChar(charBefore(source, wordStart))) wordStart -= charBefore(source, wordStart).length;
|
|
6094
|
+
let word = wordsByStart.get(wordStart);
|
|
6095
|
+
if (!word) {
|
|
6096
|
+
let end = start;
|
|
6097
|
+
while (end < source.length && isWordChar(charAt(source, end))) end += charAt(source, end).length;
|
|
6098
|
+
word = {
|
|
6099
|
+
start: wordStart,
|
|
6100
|
+
end,
|
|
6101
|
+
breaks: []
|
|
6102
|
+
};
|
|
6103
|
+
wordsByStart.set(wordStart, word);
|
|
6104
|
+
found.push(word);
|
|
6105
|
+
}
|
|
6106
|
+
word.breaks.push(start);
|
|
6107
|
+
}
|
|
6108
|
+
}
|
|
6109
|
+
}
|
|
6110
|
+
previousEnd = start + lines[i].length;
|
|
6111
|
+
}
|
|
6112
|
+
closeSegment(lines[lines.length - 1]);
|
|
6113
|
+
for (const entry of found) {
|
|
6114
|
+
if ("kind" in entry) {
|
|
6115
|
+
issues.push(entry);
|
|
6116
|
+
continue;
|
|
6117
|
+
}
|
|
6118
|
+
let text = "";
|
|
6119
|
+
let from = entry.start;
|
|
6120
|
+
for (const at of entry.breaks) {
|
|
6121
|
+
text += `${source.slice(from, at)}|`;
|
|
6122
|
+
from = at;
|
|
6123
|
+
}
|
|
6124
|
+
issues.push({
|
|
6125
|
+
...ref,
|
|
6126
|
+
kind: "word-break",
|
|
6127
|
+
text: text + source.slice(from, entry.end)
|
|
6128
|
+
});
|
|
6129
|
+
}
|
|
5963
6130
|
};
|
|
5964
6131
|
const detectApproximate = (paragraphs, measure) => {
|
|
5965
6132
|
const seen = /* @__PURE__ */ new Set();
|
|
@@ -5984,19 +6151,25 @@ const detectApproximate = (paragraphs, measure) => {
|
|
|
5984
6151
|
/**
|
|
5985
6152
|
* Measures every text body in the deck and reports text that escapes its box
|
|
5986
6153
|
* (`overflow-x` / `overflow-y`) and, opt-in, paragraphs that soft-wrap
|
|
5987
|
-
* (`soft-wrap`, 段落ち)
|
|
6154
|
+
* (`soft-wrap`, 段落ち), wraps that split a word or strand a line of one or
|
|
6155
|
+
* two characters (`word-break`, `short-last-line`), and text below a minimum
|
|
6156
|
+
* size (`small-font`).
|
|
5988
6157
|
*
|
|
5989
6158
|
* Table cell text is not audited (v1 covers shape text bodies, including
|
|
5990
6159
|
* placeholders and shapes inside groups).
|
|
5991
6160
|
*/
|
|
5992
6161
|
const auditTextLayout = (pres, options = {}) => {
|
|
5993
6162
|
const measure = options.measureText ?? defaultMeasurer;
|
|
5994
|
-
const
|
|
5995
|
-
|
|
6163
|
+
const resolved = {
|
|
6164
|
+
tolerancePx: options.tolerancePx ?? DEFAULT_TOLERANCE_PX,
|
|
6165
|
+
reportSoftWraps: options.reportSoftWraps ?? false,
|
|
6166
|
+
reportBadWraps: options.reportBadWraps ?? false,
|
|
6167
|
+
minFontSizePt: options.minFontSizePt
|
|
6168
|
+
};
|
|
5996
6169
|
const theme = getPresentationTheme(pres);
|
|
5997
6170
|
const issues = [];
|
|
5998
6171
|
const slides = getSlides(pres);
|
|
5999
|
-
for (let slideIndex = 0; slideIndex < slides.length; slideIndex++) for (const shape of walkShapes(getSlideShapes(slides[slideIndex]))) auditShape(pres, theme, shape, slideIndex, measure,
|
|
6172
|
+
for (let slideIndex = 0; slideIndex < slides.length; slideIndex++) for (const shape of walkShapes(getSlideShapes(slides[slideIndex]))) auditShape(pres, theme, shape, slideIndex, measure, resolved, issues);
|
|
6000
6173
|
return issues;
|
|
6001
6174
|
};
|
|
6002
6175
|
//#endregion
|
|
@@ -6043,4 +6216,4 @@ function shapeTextAnchorOffset(pres, shape, options = {}) {
|
|
|
6043
6216
|
//#endregion
|
|
6044
6217
|
export { shapeTextRect as S, paragraphNumberLabels as _, renderSlideSvg as a, verticalTextStyle as b, AVG_GLYPH_W_RATIO as c, SERIF as d, TIMES as f, decimalSeparatorOf as g, substituteFamily as h, renderSlideLayoutSvg as i, MONO as l, isCjk as m, shapeTextAnchorOffset as n, renderTextEffectsSvg as o, defaultMeasurer as p, auditTextLayout as r, ARIAL as s, shapeAutoFitScale as t, SANS as u, textUnderlineStyle as v, resolveTextBodyRect as x, textColumnsStyle as y };
|
|
6045
6218
|
|
|
6046
|
-
//# sourceMappingURL=src-
|
|
6219
|
+
//# sourceMappingURL=src-ayE1F9lH.js.map
|