@office-kit/pptx-preview 0.16.0 → 0.18.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 +22 -6
- package/dist/{index-D1r4NkUr.d.ts → index-C8dvep3H.d.ts} +26 -3
- 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-u1-ERSHC.js → src-BGzSNWjt.js} +330 -74
- package/dist/src-BGzSNWjt.js.map +1 -0
- package/package.json +3 -3
- package/dist/src-u1-ERSHC.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";
|
|
@@ -651,8 +666,27 @@ const layoutCore = (input, measure) => {
|
|
|
651
666
|
}
|
|
652
667
|
}
|
|
653
668
|
const canBreak = EAST_ASIAN_CHAR.test(segment) ? width > avail - bulletLead : para.latinLineBreak === true || width > avail - bulletLead;
|
|
654
|
-
if (input.wrap && canBreak && [...segment].length > 1)
|
|
655
|
-
|
|
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);
|
|
656
690
|
}
|
|
657
691
|
wordParts = [];
|
|
658
692
|
};
|
|
@@ -1044,10 +1078,11 @@ const emitLine = (line, baselineY, dx, descent, nextEffectId, effectsOnly, effec
|
|
|
1044
1078
|
const groups = groupTokens(content);
|
|
1045
1079
|
const tspans = groups.map((g) => {
|
|
1046
1080
|
const span = tspan(g);
|
|
1047
|
-
|
|
1048
|
-
|
|
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\"";
|
|
1049
1084
|
const title = g.piece.hrefTip ? `<title>${escapeXml$1(g.piece.hrefTip)}</title>` : "";
|
|
1050
|
-
return `<a href="${escapeXml$1(
|
|
1085
|
+
return `<a href="${escapeXml$1(href)}"${target}>${title}${span}</a>`;
|
|
1051
1086
|
}).join("");
|
|
1052
1087
|
if (tspans === "") return "";
|
|
1053
1088
|
const x0 = line.anchorX + dx + GRID_NUDGE_X;
|
|
@@ -1691,7 +1726,7 @@ const bevelFilterPrimitives = (shape3d) => {
|
|
|
1691
1726
|
//#region src/render-slide.ts
|
|
1692
1727
|
const clickActionHref = (pres, action) => {
|
|
1693
1728
|
if (!action) return void 0;
|
|
1694
|
-
if (action.kind === "url") return action.url;
|
|
1729
|
+
if (action.kind === "url") return safeLinkHref(action.url);
|
|
1695
1730
|
if (action.kind === "customShow") return `#pptx-custom-show?id=${action.id}&return=${action.returnToShow}`;
|
|
1696
1731
|
if (action.kind === "endShow") return "#pptx-end-show";
|
|
1697
1732
|
if (action.kind === "lastSlideViewed") return "#pptx-last-slide-viewed";
|
|
@@ -1716,6 +1751,7 @@ const DEFAULT_BODY_PT = 18;
|
|
|
1716
1751
|
const DEFAULT_TITLE_PT = 44;
|
|
1717
1752
|
const DEFAULT_FONT = "Calibri, 'Helvetica Neue', Arial, sans-serif";
|
|
1718
1753
|
const browserFontFamily = (family) => family ? `${JSON.stringify(family)}, ${DEFAULT_FONT}` : DEFAULT_FONT;
|
|
1754
|
+
const fontFamilyStyles = (font) => font.startsWith("+") ? [] : [`font-family:${escapeXml(browserFontFamily(font))}`];
|
|
1719
1755
|
const BROWSER_FAMILIES = {
|
|
1720
1756
|
resolveFamily: browserFontFamily,
|
|
1721
1757
|
familyIsCssList: true
|
|
@@ -2442,7 +2478,7 @@ const renderRun = (text, format, theme, effectivePt, defaultColor, _wasDefault =
|
|
|
2442
2478
|
const styles = [];
|
|
2443
2479
|
styles.push(`font-size:${(effectivePt * PX_PER_PT).toFixed(2)}px`);
|
|
2444
2480
|
styles.push(suppressLineHeight ? "line-height:0;vertical-align:top" : lineHeightOverride !== void 0 ? `line-height:${lineHeightOverride}` : "line-height:1.05");
|
|
2445
|
-
if (format?.font) styles.push(
|
|
2481
|
+
if (format?.font) styles.push(...fontFamilyStyles(format.font));
|
|
2446
2482
|
if (format?.bold) styles.push("font-weight:700");
|
|
2447
2483
|
if (format?.italic) styles.push("font-style:italic");
|
|
2448
2484
|
const underline = format?.underline;
|
|
@@ -2490,6 +2526,7 @@ const renderRun = (text, format, theme, effectivePt, defaultColor, _wasDefault =
|
|
|
2490
2526
|
return `<span style="${styles.join(";")}">${content}</span>`;
|
|
2491
2527
|
};
|
|
2492
2528
|
const LINE_HEIGHT = 1.05;
|
|
2529
|
+
const NO_AUTOSPACE = "text-autospace:no-autospace;";
|
|
2493
2530
|
const AUTOFIT_FLOOR = .25;
|
|
2494
2531
|
const AUTOFIT_STEP = .05;
|
|
2495
2532
|
const decimalSeparatorField = (lang) => {
|
|
@@ -2773,7 +2810,7 @@ const resolveTextBodyModel = (pres, shape, bounds, theme, phType, measure, defau
|
|
|
2773
2810
|
const defaultPt = placeholderDefaultPt(phType);
|
|
2774
2811
|
const themeFonts = getPresentationFonts(pres);
|
|
2775
2812
|
const themeFace = phType === "title" || phType === "ctrTitle" ? themeFonts?.majorLatin ?? null : themeFonts?.minorLatin ?? null;
|
|
2776
|
-
const effectiveDefaultFont =
|
|
2813
|
+
const effectiveDefaultFont = escapeXml(browserFontFamily(themeFace));
|
|
2777
2814
|
let effectiveBody;
|
|
2778
2815
|
try {
|
|
2779
2816
|
effectiveBody = getShapeBodyPrEffective(pres, shape);
|
|
@@ -3080,10 +3117,11 @@ const renderHtmlParagraphs = (paraData, numberLabels, theme, autoFitScale, defau
|
|
|
3080
3117
|
};
|
|
3081
3118
|
}
|
|
3082
3119
|
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);
|
|
3083
|
-
|
|
3084
|
-
|
|
3120
|
+
const href = run.href ? safeLinkHref(run.href) : void 0;
|
|
3121
|
+
if (!href) return span;
|
|
3122
|
+
const targetAttrs = href.startsWith("#") ? "" : " target=\"_blank\" rel=\"noopener noreferrer\"";
|
|
3085
3123
|
const titleAttr = run.hrefTip ? ` title="${escapeXml(run.hrefTip)}"` : "";
|
|
3086
|
-
return `<a href="${escapeXml(
|
|
3124
|
+
return `<a href="${escapeXml(href)}"${targetAttrs}${titleAttr} style="color:inherit;text-decoration:inherit">${span}</a>`;
|
|
3087
3125
|
});
|
|
3088
3126
|
const firstRunPt = para.runs.find((run) => run.text !== "\n" && run.text !== "")?.sizePt ?? defaultPt;
|
|
3089
3127
|
let lineHeightCss = para.emptySizePt !== void 0 ? `line-height:${LINE_HEIGHT}` : "";
|
|
@@ -3097,7 +3135,7 @@ const renderHtmlParagraphs = (paraData, numberLabels, theme, autoFitScale, defau
|
|
|
3097
3135
|
const pStyles = [
|
|
3098
3136
|
"margin:0",
|
|
3099
3137
|
para.lineSpacing?.kind === "pts" ? `font-size:${(firstRunPt * PX_PER_PT * autoFitScale).toFixed(2)}px` : "font-size:0",
|
|
3100
|
-
...para.lineSpacing?.kind === "pts" && para.runs[0]?.fmt?.font ?
|
|
3138
|
+
...para.lineSpacing?.kind === "pts" && para.runs[0]?.fmt?.font ? fontFamilyStyles(para.runs[0].fmt.font) : [],
|
|
3101
3139
|
...para.emptySizePt !== void 0 ? [`font-size:${(para.emptySizePt * PX_PER_PT * autoFitScale).toFixed(2)}px`] : [],
|
|
3102
3140
|
...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`] : [],
|
|
3103
3141
|
marginTopCss,
|
|
@@ -3132,7 +3170,7 @@ const renderHtmlParagraphs = (paraData, numberLabels, theme, autoFitScale, defau
|
|
|
3132
3170
|
else if (para.bulletDetail.sizePts !== null) bulletStyles.push(`font-size:${(para.bulletDetail.sizePts * PX_PER_PT * autoFitScale).toFixed(2)}px`);
|
|
3133
3171
|
else bulletStyles.push(`font-size:${baseBulletPx.toFixed(2)}px`);
|
|
3134
3172
|
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;
|
|
3135
|
-
bulletStyles.push(
|
|
3173
|
+
bulletStyles.push(...fontFamilyStyles(bulletFont));
|
|
3136
3174
|
prefix = `<span style="${bulletStyles.join(";")}">${escapeXml(char)}</span>`;
|
|
3137
3175
|
}
|
|
3138
3176
|
paragraphs.push(`<p data-pptx-paragraph="${pi}" style="${pStyles.join(";")}">${prefix}${runHtmls.join("") || "​"}</p>`);
|
|
@@ -3235,7 +3273,7 @@ const renderTextBody = (pres, shape, bounds, theme, phType, ctx, output = "body"
|
|
|
3235
3273
|
const vertStyles = (writingMode ? `;${writingMode}${extraTransform}` : "") + colStyles;
|
|
3236
3274
|
const wrapStyle = effectiveBody.wrap === "none" ? "white-space:nowrap" : "word-break:break-word";
|
|
3237
3275
|
const content = paragraphs.join("");
|
|
3238
|
-
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>`;
|
|
3276
|
+
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};${NO_AUTOSPACE}${`translate:${model.anchorOffset.x}px ${model.anchorOffset.y}px;`}${wrapStyle}${vertStyles}">${content}</div>`;
|
|
3239
3277
|
const foreign = `<foreignObject x="${E(innerX)}" y="${E(innerY)}" width="${E(innerW)}" height="${E(innerH)}" overflow="visible">${body}</foreignObject>`;
|
|
3240
3278
|
const hasCharacterEffect = bevel !== null || 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)));
|
|
3241
3279
|
let reflectionOverlay = "";
|
|
@@ -3407,7 +3445,7 @@ const chartText = (f, x, y, attrs, content) => {
|
|
|
3407
3445
|
}
|
|
3408
3446
|
return `<text x="${px(x)}" y="${px(y)}" ${attrs}>${content}</text>`;
|
|
3409
3447
|
};
|
|
3410
|
-
const layoutChart = (xEmu, yEmu, wEmu, hEmu, hasTitle, hasAxes, titleOverlay = false, legendOverlay = false, hasLegend = true, titlePx = DEFAULT_CHART_TITLE_PT * PX_PER_PT, plotAreaLayout) => {
|
|
3448
|
+
const layoutChart = (xEmu, yEmu, wEmu, hEmu, hasTitle, hasAxes, titleOverlay = false, legendOverlay = false, hasLegend = true, titlePx = DEFAULT_CHART_TITLE_PT * PX_PER_PT, plotAreaLayout, yAxisGutter = hasAxes ? DEFAULT_Y_AXIS_GUTTER : 0, xAxisGutter = hasAxes ? BAR_VALUE_LABEL_ROW : 0) => {
|
|
3411
3449
|
const x = xEmu / EMU_PER_PX;
|
|
3412
3450
|
const y = yEmu / EMU_PER_PX;
|
|
3413
3451
|
const w = wEmu / EMU_PER_PX;
|
|
@@ -3415,8 +3453,6 @@ const layoutChart = (xEmu, yEmu, wEmu, hEmu, hasTitle, hasAxes, titleOverlay = f
|
|
|
3415
3453
|
const titleStrip = hasTitle && !titleOverlay ? Math.round(titlePx * 2.4) : 0;
|
|
3416
3454
|
const legendStrip = hasLegend && !legendOverlay ? 18 : 0;
|
|
3417
3455
|
const padding = 8;
|
|
3418
|
-
const yAxisGutter = hasAxes ? 32 : 0;
|
|
3419
|
-
const xAxisGutter = hasAxes ? 18 : 0;
|
|
3420
3456
|
const inner = plotAreaLayout?.target === "inner" ? plotAreaLayout : void 0;
|
|
3421
3457
|
const plotX = inner ? x + Math.max(0, Math.min(1, inner.x)) * w : x + padding + yAxisGutter;
|
|
3422
3458
|
const plotY = inner ? y + Math.max(0, Math.min(1, inner.y)) * h : y + titleStrip + padding;
|
|
@@ -3529,9 +3565,10 @@ const DEFAULT_AXIS_COLOR = "#000000";
|
|
|
3529
3565
|
const DEFAULT_GRID_COLOR = "#D9D9D9";
|
|
3530
3566
|
const AXIS_TICK_LEN = 5;
|
|
3531
3567
|
const AXIS_MINOR_TICK_LEN = 3;
|
|
3568
|
+
const AXIS_LABEL_GAP = 3;
|
|
3532
3569
|
const chartFontPx = (sizePt) => (sizePt * PX_PER_PT).toFixed(1);
|
|
3533
3570
|
const axisTickAttrs = (style) => {
|
|
3534
|
-
const sz = style?.sizePt ??
|
|
3571
|
+
const sz = style?.sizePt ?? DEFAULT_AXIS_LABEL_PT;
|
|
3535
3572
|
const fill = style?.color ?? "#6B7280";
|
|
3536
3573
|
const weight = style?.bold ? " font-weight=\"600\"" : "";
|
|
3537
3574
|
const italic = style?.italic ? " font-style=\"italic\"" : "";
|
|
@@ -3580,7 +3617,8 @@ const renderValueAxis = (f, axis) => {
|
|
|
3580
3617
|
const tx2 = tickMark === "out" ? edgeX : edgeX + inward;
|
|
3581
3618
|
out.push(`<line x1="${px(tx1)}" y1="${px(yp)}" x2="${px(tx2)}" y2="${px(yp)}" stroke="${axisColor}" stroke-width="1"/>`);
|
|
3582
3619
|
}
|
|
3583
|
-
const
|
|
3620
|
+
const labelOffset = (tickMark === "out" || tickMark === "cross" ? tickLen : 0) + AXIS_LABEL_GAP;
|
|
3621
|
+
const labelX = onRight ? edgeX + labelOffset : edgeX - labelOffset;
|
|
3584
3622
|
const rot = axis.labelRotationDeg ?? 0;
|
|
3585
3623
|
const transform = rot ? ` transform="rotate(${rot} ${px(labelX)} ${px(yp)})"` : "";
|
|
3586
3624
|
out.push(chartText(f, labelX, yp, `text-anchor="${onRight ? "start" : "end"}" dominant-baseline="middle" ${axisTickAttrs(axis.labelStyle)}${transform}`, `${escapeXml(fmtTick(t))}`));
|
|
@@ -3601,20 +3639,81 @@ const renderValueAxis = (f, axis) => {
|
|
|
3601
3639
|
if (axis.displayUnits) {
|
|
3602
3640
|
const lbl = DISPLAY_UNIT_LABEL[axis.displayUnits];
|
|
3603
3641
|
if (axis.orientation === "vertical") {
|
|
3604
|
-
const lblX = f.plotX - 26;
|
|
3642
|
+
const lblX = axis.side === "right" ? f.plotX + f.plotW + 26 : f.plotX - 26;
|
|
3605
3643
|
const lblY = f.plotY + f.plotH / 2;
|
|
3606
3644
|
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)}`));
|
|
3607
3645
|
} 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)}`));
|
|
3608
3646
|
}
|
|
3609
|
-
if (!axis.lineHidden) if (axis.orientation === "vertical")
|
|
3610
|
-
|
|
3647
|
+
if (!axis.lineHidden) if (axis.orientation === "vertical") {
|
|
3648
|
+
const spineX = axis.side === "right" ? f.plotX + f.plotW : f.plotX;
|
|
3649
|
+
out.push(`<line x1="${px(spineX)}" y1="${px(f.plotY)}" x2="${px(spineX)}" y2="${px(f.plotY + f.plotH)}" stroke="${axisColor}" stroke-width="1"/>`);
|
|
3650
|
+
} else {
|
|
3611
3651
|
const zeroY = f.plotY + f.plotH - (0 - axis.min) / range * f.plotH;
|
|
3612
3652
|
const spineY = axis.min <= 0 && axis.max >= 0 ? zeroY : f.plotY + f.plotH;
|
|
3613
3653
|
out.push(`<line x1="${px(f.plotX)}" y1="${px(spineY)}" x2="${px(f.plotX + f.plotW)}" y2="${px(spineY)}" stroke="${axisColor}" stroke-width="1"/>`);
|
|
3614
3654
|
}
|
|
3615
3655
|
return out.join("");
|
|
3616
3656
|
};
|
|
3617
|
-
const
|
|
3657
|
+
const CATEGORY_LABEL_GAP = 6;
|
|
3658
|
+
const CATEGORY_LABEL_LINE_HEIGHT = 1.2;
|
|
3659
|
+
const MAX_BAR_CATEGORY_LABEL_SHARE = .5;
|
|
3660
|
+
const DEFAULT_Y_AXIS_GUTTER = 32;
|
|
3661
|
+
const BAR_VALUE_LABEL_ROW = 18;
|
|
3662
|
+
const DEFAULT_AXIS_TITLE_PT = 11;
|
|
3663
|
+
const DEFAULT_AXIS_LABEL_PT = 10;
|
|
3664
|
+
const categoryLabelFontPx = (style) => (style?.sizePt ?? DEFAULT_AXIS_LABEL_PT) * PX_PER_PT;
|
|
3665
|
+
const axisTitleRowPx = (style) => (style?.sizePt ?? DEFAULT_AXIS_TITLE_PT) * PX_PER_PT * CATEGORY_LABEL_LINE_HEIGHT;
|
|
3666
|
+
const NO_LINE_START = /* @__PURE__ */ new Set("、。,.,.))]]」』】〉》%%!!??::;;ー〜…・");
|
|
3667
|
+
const chartLabelSegments = (text) => {
|
|
3668
|
+
const segments = [];
|
|
3669
|
+
let word = "";
|
|
3670
|
+
for (const ch of text) if (NO_LINE_START.has(ch) && word === "" && segments.length > 0) segments[segments.length - 1] += ch;
|
|
3671
|
+
else if (FULLWIDTH_CHAR_PATTERN.test(ch) && !NO_LINE_START.has(ch)) {
|
|
3672
|
+
if (word !== "") segments.push(word);
|
|
3673
|
+
segments.push(ch);
|
|
3674
|
+
word = "";
|
|
3675
|
+
} else {
|
|
3676
|
+
word += ch;
|
|
3677
|
+
if (/\s/u.test(ch)) {
|
|
3678
|
+
segments.push(word);
|
|
3679
|
+
word = "";
|
|
3680
|
+
}
|
|
3681
|
+
}
|
|
3682
|
+
if (word !== "") segments.push(word);
|
|
3683
|
+
return segments;
|
|
3684
|
+
};
|
|
3685
|
+
const wrapChartLabel = (text, maxPx, widthOf) => {
|
|
3686
|
+
const lines = [];
|
|
3687
|
+
let line = "";
|
|
3688
|
+
for (const segment of chartLabelSegments(text)) {
|
|
3689
|
+
if (line !== "" && widthOf((line + segment).trimEnd()) > maxPx) {
|
|
3690
|
+
lines.push(line.trimEnd());
|
|
3691
|
+
line = "";
|
|
3692
|
+
}
|
|
3693
|
+
line += segment;
|
|
3694
|
+
}
|
|
3695
|
+
if (line.trim() !== "") lines.push(line.trimEnd());
|
|
3696
|
+
return lines;
|
|
3697
|
+
};
|
|
3698
|
+
const layoutBarCategoryLabels = (spec, chartWidthPx, measure) => {
|
|
3699
|
+
const style = spec.categoryAxisLabelStyle;
|
|
3700
|
+
const font = {
|
|
3701
|
+
family: SANS,
|
|
3702
|
+
sizePx: categoryLabelFontPx(style),
|
|
3703
|
+
bold: style?.bold === true,
|
|
3704
|
+
italic: style?.italic === true,
|
|
3705
|
+
letterSpacingPx: 0
|
|
3706
|
+
};
|
|
3707
|
+
const widthOf = (text) => measure(text, font).widthPx;
|
|
3708
|
+
const labels = Array.from({ length: pointCount(spec) }, (_, i) => spec.categories[i] ?? (i + 1).toString());
|
|
3709
|
+
const maxPx = chartWidthPx * MAX_BAR_CATEGORY_LABEL_SHARE;
|
|
3710
|
+
const lines = labels.map((label) => wrapChartLabel(label, maxPx, widthOf));
|
|
3711
|
+
return {
|
|
3712
|
+
widthPx: Math.max(0, ...lines.flatMap((ls) => ls.map(widthOf))),
|
|
3713
|
+
lines
|
|
3714
|
+
};
|
|
3715
|
+
};
|
|
3716
|
+
const renderCategoryAxis = (f, orientation, cats, pointCount, skip = 1, labelStyle, labelRotationDeg, labelAlign, lineColor, lineHidden = false, majorTickMark = "out", minorTickMark = "none", wrappedLabels) => {
|
|
3618
3717
|
const labels = [];
|
|
3619
3718
|
for (let i = 0; i < pointCount; i++) labels.push(cats[i] ?? (i + 1).toString());
|
|
3620
3719
|
const out = [];
|
|
@@ -3636,6 +3735,14 @@ const renderCategoryAxis = (f, orientation, cats, pointCount, skip = 1, labelSty
|
|
|
3636
3735
|
for (let i = 0; i < pointCount; i++) {
|
|
3637
3736
|
if (skip > 1 && i % skip !== 0) continue;
|
|
3638
3737
|
const cy = f.plotY + (pointCount - 1 - i + .5) * step;
|
|
3738
|
+
const lines = wrappedLabels?.[i];
|
|
3739
|
+
if (lines) {
|
|
3740
|
+
const lx = f.plotX - CATEGORY_LABEL_GAP;
|
|
3741
|
+
const lineH = categoryLabelFontPx(labelStyle) * CATEGORY_LABEL_LINE_HEIGHT;
|
|
3742
|
+
const content = lines.length === 1 ? escapeXml(lines[0]) : lines.map((line, k) => `<tspan x="${px(lx)}" dy="${px(k === 0 ? -(lines.length - 1) * lineH / 2 : lineH)}">${escapeXml(line)}</tspan>`).join("");
|
|
3743
|
+
out.push(chartText(f, lx, cy, `text-anchor="end" dominant-baseline="middle" ${axisTickAttrs(labelStyle)}`, content));
|
|
3744
|
+
continue;
|
|
3745
|
+
}
|
|
3639
3746
|
const lx = f.plotX - 4;
|
|
3640
3747
|
const truncated = labels[i] !== void 0 && labels[i].length > truncLen ? `${labels[i].slice(0, truncLen - 2)}…` : labels[i] ?? "";
|
|
3641
3748
|
const transform = labelRotationDeg && labelRotationDeg !== 0 ? ` transform="rotate(${labelRotationDeg} ${px(lx)} ${px(cy)})"` : "";
|
|
@@ -3690,16 +3797,21 @@ const seriesMinMax = (spec) => {
|
|
|
3690
3797
|
if (!Number.isFinite(min)) min = 0;
|
|
3691
3798
|
if (!Number.isFinite(max)) max = 1;
|
|
3692
3799
|
if (max === min) max = min + 1;
|
|
3693
|
-
if (min > 0) min = 0;
|
|
3694
3800
|
if (isPercent) return {
|
|
3695
3801
|
min: 0,
|
|
3696
3802
|
max: 1,
|
|
3697
3803
|
step: .2
|
|
3698
3804
|
};
|
|
3699
|
-
const
|
|
3700
|
-
|
|
3701
|
-
|
|
3702
|
-
|
|
3805
|
+
const authoredMin = spec.valueAxis?.min;
|
|
3806
|
+
const authoredMax = spec.valueAxis?.max;
|
|
3807
|
+
const lo = authoredMin ?? min;
|
|
3808
|
+
const hi = authoredMax ?? max;
|
|
3809
|
+
const pad = (hi - lo) / AUTO_AXIS_PAD_DIVISOR;
|
|
3810
|
+
const autoMin = lo < 0 ? lo - pad : lo >= hi * AUTO_AXIS_ZERO_THRESHOLD ? lo - pad : 0;
|
|
3811
|
+
const autoMax = hi > 0 ? hi + pad : hi <= lo * AUTO_AXIS_ZERO_THRESHOLD ? hi + pad : 0;
|
|
3812
|
+
const step = niceStep((authoredMax ?? autoMax) - (authoredMin ?? autoMin));
|
|
3813
|
+
min = authoredMin ?? snapToStep(autoMin, step, Math.floor);
|
|
3814
|
+
max = authoredMax ?? snapToStep(autoMax, step, Math.ceil);
|
|
3703
3815
|
if (max === min) max = min + 1;
|
|
3704
3816
|
return {
|
|
3705
3817
|
min,
|
|
@@ -3707,6 +3819,10 @@ const seriesMinMax = (spec) => {
|
|
|
3707
3819
|
step
|
|
3708
3820
|
};
|
|
3709
3821
|
};
|
|
3822
|
+
const AUTO_AXIS_PAD_DIVISOR = 20;
|
|
3823
|
+
const AUTO_AXIS_ZERO_THRESHOLD = 5 / 6;
|
|
3824
|
+
const AXIS_BOUND_PRECISION = 12;
|
|
3825
|
+
const snapToStep = (value, step, round) => Number((round(value / step) * step).toPrecision(AXIS_BOUND_PRECISION));
|
|
3710
3826
|
const renderChartTitle = (f, title, style) => {
|
|
3711
3827
|
if (!title) return "";
|
|
3712
3828
|
const sz = style?.sizePt ?? DEFAULT_CHART_TITLE_PT;
|
|
@@ -4555,7 +4671,7 @@ const renderRadarChart = (f, spec, colors) => {
|
|
|
4555
4671
|
}
|
|
4556
4672
|
return out.join("");
|
|
4557
4673
|
};
|
|
4558
|
-
const renderChart = (shape, x, y, w, h, transform, theme, groupReflected) => {
|
|
4674
|
+
const renderChart = (shape, x, y, w, h, transform, theme, groupReflected, measure) => {
|
|
4559
4675
|
let spec = null;
|
|
4560
4676
|
try {
|
|
4561
4677
|
spec = getShapeChartSpec(shape);
|
|
@@ -4575,10 +4691,13 @@ const renderChart = (shape, x, y, w, h, transform, theme, groupReflected) => {
|
|
|
4575
4691
|
const isCartesian = spec.kind === "column" || spec.kind === "bar" || spec.kind === "line" || spec.kind === "area";
|
|
4576
4692
|
const hasAxes = isCartesian || spec.kind === "scatter" || spec.kind === "bubble";
|
|
4577
4693
|
const hasLegend = spec.legend !== void 0 && spec.legend.position !== null;
|
|
4694
|
+
const barLabels = spec.kind === "bar" && !spec.categoryAxisHidden && spec.categoryAxisTickLabelPos !== "none" && !spec.categoryAxisLabelRotationDeg ? layoutBarCategoryLabels(spec, w / EMU_PER_PX, measure) : void 0;
|
|
4695
|
+
const barValueTitleRow = spec.kind === "bar" && spec.valueAxisTitle ? axisTitleRowPx(spec.valueAxisTitleStyle) : 0;
|
|
4696
|
+
const barCategoryTitleRow = spec.kind === "bar" && spec.categoryAxisTitle ? axisTitleRowPx(spec.categoryAxisTitleStyle) : 0;
|
|
4578
4697
|
const flip = getShapeFlip(shape);
|
|
4579
4698
|
const f = {
|
|
4580
4699
|
reflected: groupReflected !== Boolean(flip && flip.horizontal !== flip.vertical),
|
|
4581
|
-
...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)
|
|
4700
|
+
...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, barLabels ? Math.max(DEFAULT_Y_AXIS_GUTTER, barLabels.widthPx + CATEGORY_LABEL_GAP) + barCategoryTitleRow : void 0, barValueTitleRow > 0 ? BAR_VALUE_LABEL_ROW + barValueTitleRow : void 0)
|
|
4582
4701
|
};
|
|
4583
4702
|
const allNamesForLegend = spec.kind === "pie" || spec.kind === "doughnut" ? Array.from(spec.categories) : spec.series.map((s) => s.name);
|
|
4584
4703
|
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");
|
|
@@ -4603,10 +4722,11 @@ const renderChart = (shape, x, y, w, h, transform, theme, groupReflected) => {
|
|
|
4603
4722
|
...spec,
|
|
4604
4723
|
series: primarySeries
|
|
4605
4724
|
});
|
|
4606
|
-
const
|
|
4725
|
+
const secondaryAxis = spec.secondaryValueAxis;
|
|
4607
4726
|
const secondaryScale = secondarySeries.length > 0 ? seriesMinMax({
|
|
4608
|
-
...
|
|
4609
|
-
series: secondarySeries
|
|
4727
|
+
...spec,
|
|
4728
|
+
series: secondarySeries,
|
|
4729
|
+
valueAxis: secondaryAxis ?? {}
|
|
4610
4730
|
}) : null;
|
|
4611
4731
|
const N = pointCount(spec);
|
|
4612
4732
|
if (!spec.valueAxisHidden) axes = renderValueAxis(f, {
|
|
@@ -4625,14 +4745,20 @@ const renderChart = (shape, x, y, w, h, transform, theme, groupReflected) => {
|
|
|
4625
4745
|
...spec.valueAxisMajorGridlines !== void 0 ? { majorGridlines: spec.valueAxisMajorGridlines } : {},
|
|
4626
4746
|
...spec.valueAxisLabelStyle !== void 0 ? { labelStyle: spec.valueAxisLabelStyle } : {}
|
|
4627
4747
|
});
|
|
4628
|
-
|
|
4748
|
+
const secondaryLabelStyle = secondaryAxis?.labelStyle ?? spec.valueAxisLabelStyle;
|
|
4749
|
+
if (secondaryScale && secondaryAxis?.tickLabelPos !== "none") axes += renderValueAxis(f, {
|
|
4629
4750
|
orientation: "vertical",
|
|
4630
4751
|
side: "right",
|
|
4631
4752
|
min: secondaryScale.min,
|
|
4632
4753
|
max: secondaryScale.max,
|
|
4633
|
-
majorUnit: secondaryScale.step,
|
|
4634
|
-
majorGridlines: false,
|
|
4635
|
-
...
|
|
4754
|
+
majorUnit: secondaryAxis?.majorUnit ?? secondaryScale.step,
|
|
4755
|
+
majorGridlines: secondaryAxis?.majorGridlines ?? false,
|
|
4756
|
+
...secondaryAxis?.numberFormat !== void 0 ? { numberFormat: secondaryAxis.numberFormat } : {},
|
|
4757
|
+
...secondaryAxis?.displayUnits !== void 0 ? { displayUnits: secondaryAxis.displayUnits } : {},
|
|
4758
|
+
...secondaryAxis?.majorGridlineColor !== void 0 ? { majorGridlineColor: secondaryAxis.majorGridlineColor } : {},
|
|
4759
|
+
...secondaryAxis?.majorTickMark !== void 0 ? { majorTickMark: secondaryAxis.majorTickMark } : {},
|
|
4760
|
+
...secondaryAxis?.lineColor !== void 0 ? { lineColor: secondaryAxis.lineColor } : {},
|
|
4761
|
+
...secondaryLabelStyle !== void 0 ? { labelStyle: secondaryLabelStyle } : {}
|
|
4636
4762
|
});
|
|
4637
4763
|
if (N > 0 && !(spec.categoryAxisHidden || spec.categoryAxisTickLabelPos === "none")) axes += renderCategoryAxis(f, "horizontal", spec.categories, N, spec.categoryAxisTickLabelSkip ?? 1, spec.categoryAxisLabelStyle, spec.categoryAxisLabelRotationDeg, spec.categoryAxisLabelAlign, spec.categoryAxisLineColor, spec.categoryAxisLineHidden, spec.categoryAxisMajorTickMark, spec.categoryAxisMinorTickMark);
|
|
4638
4764
|
const groups = /* @__PURE__ */ new Map();
|
|
@@ -4700,7 +4826,7 @@ const renderChart = (shape, x, y, w, h, transform, theme, groupReflected) => {
|
|
|
4700
4826
|
};
|
|
4701
4827
|
if (!spec.valueAxisHidden) axes = renderValueAxis(f, valueAxis);
|
|
4702
4828
|
const labelsHidden = spec.categoryAxisHidden || spec.categoryAxisTickLabelPos === "none";
|
|
4703
|
-
if (N > 0 && !labelsHidden) axes += renderCategoryAxis(f, spec.kind === "bar" ? "vertical" : "horizontal", spec.categories, N, spec.categoryAxisTickLabelSkip ?? 1, spec.categoryAxisLabelStyle, spec.categoryAxisLabelRotationDeg, spec.categoryAxisLabelAlign, spec.categoryAxisLineColor, spec.categoryAxisLineHidden, spec.categoryAxisMajorTickMark, spec.categoryAxisMinorTickMark);
|
|
4829
|
+
if (N > 0 && !labelsHidden) axes += renderCategoryAxis(f, spec.kind === "bar" ? "vertical" : "horizontal", spec.categories, N, spec.categoryAxisTickLabelSkip ?? 1, spec.categoryAxisLabelStyle, spec.categoryAxisLabelRotationDeg, spec.categoryAxisLabelAlign, spec.categoryAxisLineColor, spec.categoryAxisLineHidden, spec.categoryAxisMajorTickMark, spec.categoryAxisMinorTickMark, barLabels?.lines);
|
|
4704
4830
|
}
|
|
4705
4831
|
if (!isCombo) switch (spec.kind) {
|
|
4706
4832
|
case "column":
|
|
@@ -4732,19 +4858,30 @@ const renderChart = (shape, x, y, w, h, transform, theme, groupReflected) => {
|
|
|
4732
4858
|
}
|
|
4733
4859
|
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`)}`) : "";
|
|
4734
4860
|
const axisTitleAttrs = (style) => {
|
|
4735
|
-
const sz = style?.sizePt ??
|
|
4861
|
+
const sz = style?.sizePt ?? DEFAULT_AXIS_TITLE_PT;
|
|
4736
4862
|
const fill = style?.color ?? "#374151";
|
|
4737
4863
|
const weight = style?.bold === false ? "400" : "600";
|
|
4738
4864
|
const italicAttr = style?.italic ? " font-style=\"italic\"" : "";
|
|
4739
4865
|
return `font-family="sans-serif" font-size="${chartFontPx(sz)}" fill="${fill}" font-weight="${weight}"${italicAttr}`;
|
|
4740
4866
|
};
|
|
4741
|
-
const
|
|
4742
|
-
|
|
4743
|
-
|
|
4744
|
-
|
|
4745
|
-
const
|
|
4746
|
-
|
|
4747
|
-
|
|
4867
|
+
const bottomTitle = {
|
|
4868
|
+
x: f.plotX + f.plotW / 2,
|
|
4869
|
+
y: f.plotY + f.plotH + 28
|
|
4870
|
+
};
|
|
4871
|
+
const sideTitle = {
|
|
4872
|
+
x: barLabels ? f.plotX - CATEGORY_LABEL_GAP - barLabels.widthPx - barCategoryTitleRow / 2 : f.plotX - 26,
|
|
4873
|
+
y: f.plotY + f.plotH / 2
|
|
4874
|
+
};
|
|
4875
|
+
const isBar = spec.kind === "bar";
|
|
4876
|
+
const axisTitleSvg = (title, style, rotationDeg, side) => {
|
|
4877
|
+
if (!title) return "";
|
|
4878
|
+
const at = side ? sideTitle : bottomTitle;
|
|
4879
|
+
const rot = rotationDeg ?? (side ? -90 : 0);
|
|
4880
|
+
const transform = rot !== 0 ? ` transform="rotate(${rot} ${px(at.x)} ${px(at.y)})"` : "";
|
|
4881
|
+
return chartText(f, at.x, at.y, `text-anchor="middle" ${axisTitleAttrs(style)}${transform}`, escapeXml(title));
|
|
4882
|
+
};
|
|
4883
|
+
const valueAxisTitleSvg = axisTitleSvg(spec.valueAxisTitle, spec.valueAxisTitleStyle, spec.valueAxisTitleRotationDeg, !isBar);
|
|
4884
|
+
const categoryAxisTitleSvg = axisTitleSvg(spec.categoryAxisTitle, spec.categoryAxisTitleStyle, spec.categoryAxisTitleRotationDeg, isBar);
|
|
4748
4885
|
return [
|
|
4749
4886
|
`<g${transform}>`,
|
|
4750
4887
|
`<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\"" : ""}/>`,
|
|
@@ -4890,9 +5027,9 @@ const renderTableCellText = (cell, paragraphs, cx, cy, cw, ch, color, pres, shap
|
|
|
4890
5027
|
const vertical = verticalTextStyle(getTableCellTextDirection(cell));
|
|
4891
5028
|
const directionStyle = `${vertical.declarations}${vertical.transform ? `;transform:${vertical.transform}` : ""}`;
|
|
4892
5029
|
const justify = vAnchor === "top" ? "flex-start" : vAnchor === "bottom" ? "flex-end" : "center";
|
|
4893
|
-
const familyFont =
|
|
5030
|
+
const familyFont = escapeXml(browserFontFamily(themeFace));
|
|
4894
5031
|
const body = renderHtmlParagraphs(paraData, numberLabels, theme, 1, DEFAULT_BODY_PT, color).join("");
|
|
4895
|
-
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}
|
|
5032
|
+
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};${NO_AUTOSPACE}word-break:break-word;${directionStyle}">${body}</div></foreignObject>${fillOverlay()}${bevelOverlay()}`;
|
|
4896
5033
|
};
|
|
4897
5034
|
const TWO_STOP_TABLE_GRADIENT_EXPONENT = 1.85;
|
|
4898
5035
|
const TWO_STOP_TABLE_GRADIENT_SAMPLES = 16;
|
|
@@ -5479,7 +5616,7 @@ const renderShapeContent = (shape, pres, theme, ctx) => {
|
|
|
5479
5616
|
}
|
|
5480
5617
|
if (kind === "graphicFrame") {
|
|
5481
5618
|
if (isChartShape(shape)) {
|
|
5482
|
-
const chartSvg = renderChart(shape, x, y, w, h, transform, theme, ctx.groupReflected);
|
|
5619
|
+
const chartSvg = renderChart(shape, x, y, w, h, transform, theme, ctx.groupReflected, ctx.measure);
|
|
5483
5620
|
if (chartSvg) return chartSvg;
|
|
5484
5621
|
}
|
|
5485
5622
|
if (isTableShape(shape)) {
|
|
@@ -5867,12 +6004,23 @@ const detectImageFormatLocal = (bytes) => {
|
|
|
5867
6004
|
//#endregion
|
|
5868
6005
|
//#region src/audit.ts
|
|
5869
6006
|
const DEFAULT_TOLERANCE_PX = 1;
|
|
6007
|
+
const PT_PER_PX = 72 / 96;
|
|
6008
|
+
const SHORT_LAST_LINE_MAX_CHARS = 2;
|
|
6009
|
+
const LETTER_OR_DIGIT = /[\p{Script=Latin}\p{Script=Greek}\p{Script=Cyrillic}\p{Nd}]/u;
|
|
6010
|
+
const KATAKANA = /[\p{Script=Katakana}\u30FC]/u;
|
|
6011
|
+
const isWordChar = (ch) => LETTER_OR_DIGIT.test(ch) || KATAKANA.test(ch);
|
|
6012
|
+
const splitsWord = (before, after) => LETTER_OR_DIGIT.test(before) && LETTER_OR_DIGIT.test(after) || KATAKANA.test(before) && KATAKANA.test(after);
|
|
6013
|
+
const charBefore = (s, i) => {
|
|
6014
|
+
return (i >= 2 ? s.codePointAt(i - 2) : 0) > 65535 ? s.slice(i - 2, i) : s.slice(i - 1, i);
|
|
6015
|
+
};
|
|
6016
|
+
const charAt = (s, i) => i < s.length ? String.fromCodePoint(s.codePointAt(i)) : "";
|
|
5870
6017
|
const round2 = (n) => Math.round(n * 100) / 100;
|
|
5871
6018
|
function* walkShapes(shapes) {
|
|
5872
6019
|
for (const shape of shapes) if (getShapeKind(shape) !== "group") yield shape;
|
|
5873
6020
|
}
|
|
5874
6021
|
const passthroughFamily = (family) => family ?? "Carlito";
|
|
5875
|
-
const auditShape = (pres, theme, shape, slideIndex, measure,
|
|
6022
|
+
const auditShape = (pres, theme, shape, slideIndex, measure, options, issues) => {
|
|
6023
|
+
const { tolerancePx, reportSoftWraps, reportBadWraps, minFontSizePt } = options;
|
|
5876
6024
|
const kind = getShapeKind(shape);
|
|
5877
6025
|
if (kind !== "shape" && kind !== "graphicFrame") return;
|
|
5878
6026
|
const bounds = getShapeBoundsResolved(pres, shape);
|
|
@@ -5917,51 +6065,65 @@ const auditShape = (pres, theme, shape, slideIndex, measure, tolerancePx, report
|
|
|
5917
6065
|
resolveFamily: passthroughFamily
|
|
5918
6066
|
});
|
|
5919
6067
|
const core = layoutCore(input, measure);
|
|
5920
|
-
const
|
|
5921
|
-
|
|
5922
|
-
|
|
5923
|
-
|
|
5924
|
-
|
|
6068
|
+
const rotated = vert === "cw90" || vert === "cw270";
|
|
6069
|
+
const frameOf = (x, y, w, h) => rotated ? {
|
|
6070
|
+
x: x + w / 2 - h / 2,
|
|
6071
|
+
y: y + h / 2 - w / 2,
|
|
6072
|
+
w: h,
|
|
6073
|
+
h: w
|
|
5925
6074
|
} : {
|
|
5926
|
-
x
|
|
5927
|
-
y
|
|
5928
|
-
w
|
|
5929
|
-
h
|
|
6075
|
+
x,
|
|
6076
|
+
y,
|
|
6077
|
+
w,
|
|
6078
|
+
h
|
|
5930
6079
|
};
|
|
6080
|
+
const frame = frameOf(input.boxXpx, input.boxYpx, input.boxWpx, input.boxHpx);
|
|
6081
|
+
const shapeFrame = frameOf(bounds.x / EMU_PER_PX, bounds.y / EMU_PER_PX, bounds.w / EMU_PER_PX, bounds.h / EMU_PER_PX);
|
|
6082
|
+
let inkLeft = Infinity;
|
|
6083
|
+
let inkRight = -Infinity;
|
|
5931
6084
|
let inkTop = Infinity;
|
|
5932
6085
|
let inkBottom = -Infinity;
|
|
5933
|
-
let
|
|
5934
|
-
let anyInk = false;
|
|
6086
|
+
let lineHeight = 0;
|
|
5935
6087
|
for (const p of core.placements) {
|
|
5936
6088
|
const toks = [...p.line.tokens];
|
|
5937
6089
|
while (toks.length > 0 && (toks[toks.length - 1].isSpace || toks[toks.length - 1].isBreak)) toks.pop();
|
|
5938
6090
|
if (!toks.some((t) => !t.isSpace && !t.isBreak)) continue;
|
|
5939
|
-
anyInk = true;
|
|
5940
6091
|
let lineW = 0;
|
|
5941
6092
|
for (const t of toks) if (!t.isBreak) lineW += t.width;
|
|
5942
|
-
const left = p.line.textAnchor === "middle" ? p.line.anchorX - lineW / 2 : p.line.textAnchor === "end" ? p.line.anchorX - lineW : p.line.anchorX;
|
|
5943
|
-
|
|
5944
|
-
|
|
6093
|
+
const left = (p.line.textAnchor === "middle" ? p.line.anchorX - lineW / 2 : p.line.textAnchor === "end" ? p.line.anchorX - lineW : p.line.anchorX) + p.dx;
|
|
6094
|
+
inkLeft = Math.min(inkLeft, left);
|
|
6095
|
+
inkRight = Math.max(inkRight, left + lineW);
|
|
5945
6096
|
inkTop = Math.min(inkTop, p.baselineY - p.line.ascent);
|
|
5946
6097
|
inkBottom = Math.max(inkBottom, p.baselineY + p.line.descent);
|
|
6098
|
+
lineHeight = Math.max(lineHeight, p.line.ascent + p.line.descent);
|
|
5947
6099
|
}
|
|
5948
|
-
if (
|
|
6100
|
+
if (inkLeft === Infinity) return;
|
|
5949
6101
|
const approximate = detectApproximate(input.paragraphs, measure);
|
|
5950
6102
|
const slideRef = {
|
|
5951
6103
|
slideIndex,
|
|
5952
6104
|
shapeName: getShapeName(shape),
|
|
5953
6105
|
approximate
|
|
5954
6106
|
};
|
|
6107
|
+
const overflowXOf = (f) => Math.max(0, f.x - inkLeft, inkRight - (f.x + f.w));
|
|
6108
|
+
const overflowYOf = (f) => Math.max(0, f.y - inkTop, inkBottom - (f.y + f.h));
|
|
6109
|
+
const shapeOverflowXOf = (f) => Math.max(0, f.x - inkLeft) + Math.max(0, inkRight - (f.x + f.w));
|
|
6110
|
+
const shapeOverflowYOf = (f) => Math.max(0, f.y - inkTop) + Math.max(0, inkBottom - (f.y + f.h));
|
|
6111
|
+
const lineHeightPx = round2(lineHeight);
|
|
6112
|
+
const overflowX = overflowXOf(frame);
|
|
5955
6113
|
if (overflowX > tolerancePx) issues.push({
|
|
5956
6114
|
...slideRef,
|
|
5957
6115
|
kind: "overflow-x",
|
|
5958
|
-
overflowPx: round2(overflowX)
|
|
6116
|
+
overflowPx: round2(overflowX),
|
|
6117
|
+
shapeOverflowPx: round2(shapeOverflowXOf(shapeFrame)),
|
|
6118
|
+
lineHeightPx
|
|
5959
6119
|
});
|
|
5960
|
-
const overflowY =
|
|
6120
|
+
const overflowY = overflowYOf(frame);
|
|
5961
6121
|
if (overflowY > tolerancePx) issues.push({
|
|
5962
6122
|
...slideRef,
|
|
5963
6123
|
kind: "overflow-y",
|
|
5964
|
-
overflowPx: round2(overflowY)
|
|
6124
|
+
overflowPx: round2(overflowY),
|
|
6125
|
+
shapeOverflowPx: round2(shapeOverflowYOf(shapeFrame)),
|
|
6126
|
+
lineHeightPx
|
|
5965
6127
|
});
|
|
5966
6128
|
if (reportSoftWraps && vert === "none") {
|
|
5967
6129
|
const lineCounts = /* @__PURE__ */ new Map();
|
|
@@ -5977,6 +6139,94 @@ const auditShape = (pres, theme, shape, slideIndex, measure, tolerancePx, report
|
|
|
5977
6139
|
});
|
|
5978
6140
|
});
|
|
5979
6141
|
}
|
|
6142
|
+
if (reportBadWraps && vert === "none") auditLineBreaks(input.paragraphs, core.placements, slideRef, issues);
|
|
6143
|
+
if (minFontSizePt !== void 0) {
|
|
6144
|
+
let smallestPx = Infinity;
|
|
6145
|
+
for (const para of input.paragraphs) for (const piece of para.pieces) if (!piece.isBreak && piece.text.trim() !== "" && piece.sizePx < smallestPx) smallestPx = piece.sizePx;
|
|
6146
|
+
const sizePt = Math.round(smallestPx * PT_PER_PX * 10) / 10;
|
|
6147
|
+
if (sizePt < minFontSizePt) issues.push({
|
|
6148
|
+
...slideRef,
|
|
6149
|
+
kind: "small-font",
|
|
6150
|
+
sizePt
|
|
6151
|
+
});
|
|
6152
|
+
}
|
|
6153
|
+
};
|
|
6154
|
+
const auditLineBreaks = (paragraphs, placements, slideRef, issues) => {
|
|
6155
|
+
const linesByParagraph = /* @__PURE__ */ new Map();
|
|
6156
|
+
for (const { line } of placements) {
|
|
6157
|
+
const text = line.tokens.map((t) => t.isTab ? " " : t.text).join("");
|
|
6158
|
+
if (text.trim() === "") continue;
|
|
6159
|
+
const lines = linesByParagraph.get(line.paraIndex);
|
|
6160
|
+
if (lines) lines.push(text);
|
|
6161
|
+
else linesByParagraph.set(line.paraIndex, [text]);
|
|
6162
|
+
}
|
|
6163
|
+
for (const [paragraphIndex, lines] of linesByParagraph) auditParagraphBreaks(paragraphs[paragraphIndex].pieces.map((p) => p.isBreak ? "\n" : p.text).join(""), lines, {
|
|
6164
|
+
...slideRef,
|
|
6165
|
+
paragraphIndex
|
|
6166
|
+
}, issues);
|
|
6167
|
+
};
|
|
6168
|
+
const auditParagraphBreaks = (source, lines, ref, issues) => {
|
|
6169
|
+
const found = [];
|
|
6170
|
+
const wordsByStart = /* @__PURE__ */ new Map();
|
|
6171
|
+
let previousEnd = -1;
|
|
6172
|
+
let wrappedLines = 0;
|
|
6173
|
+
const closeSegment = (lastLine) => {
|
|
6174
|
+
const visible = [...lastLine.replace(/\s/gu, "")];
|
|
6175
|
+
if (wrappedLines > 0 && visible.length <= SHORT_LAST_LINE_MAX_CHARS) found.push({
|
|
6176
|
+
...ref,
|
|
6177
|
+
kind: "short-last-line",
|
|
6178
|
+
text: lastLine.trim()
|
|
6179
|
+
});
|
|
6180
|
+
};
|
|
6181
|
+
for (let i = 0; i < lines.length; i++) {
|
|
6182
|
+
const start = source.indexOf(lines[i], Math.max(previousEnd, 0));
|
|
6183
|
+
if (start < 0) return;
|
|
6184
|
+
if (previousEnd >= 0) {
|
|
6185
|
+
const gap = source.slice(previousEnd, start);
|
|
6186
|
+
if (gap.includes("\n")) {
|
|
6187
|
+
closeSegment(lines[i - 1]);
|
|
6188
|
+
wrappedLines = 0;
|
|
6189
|
+
} else {
|
|
6190
|
+
wrappedLines++;
|
|
6191
|
+
if (gap === "" && splitsWord(charBefore(source, start), charAt(source, start))) {
|
|
6192
|
+
let wordStart = start;
|
|
6193
|
+
while (wordStart > 0 && isWordChar(charBefore(source, wordStart))) wordStart -= charBefore(source, wordStart).length;
|
|
6194
|
+
let word = wordsByStart.get(wordStart);
|
|
6195
|
+
if (!word) {
|
|
6196
|
+
let end = start;
|
|
6197
|
+
while (end < source.length && isWordChar(charAt(source, end))) end += charAt(source, end).length;
|
|
6198
|
+
word = {
|
|
6199
|
+
start: wordStart,
|
|
6200
|
+
end,
|
|
6201
|
+
breaks: []
|
|
6202
|
+
};
|
|
6203
|
+
wordsByStart.set(wordStart, word);
|
|
6204
|
+
found.push(word);
|
|
6205
|
+
}
|
|
6206
|
+
word.breaks.push(start);
|
|
6207
|
+
}
|
|
6208
|
+
}
|
|
6209
|
+
}
|
|
6210
|
+
previousEnd = start + lines[i].length;
|
|
6211
|
+
}
|
|
6212
|
+
closeSegment(lines[lines.length - 1]);
|
|
6213
|
+
for (const entry of found) {
|
|
6214
|
+
if ("kind" in entry) {
|
|
6215
|
+
issues.push(entry);
|
|
6216
|
+
continue;
|
|
6217
|
+
}
|
|
6218
|
+
let text = "";
|
|
6219
|
+
let from = entry.start;
|
|
6220
|
+
for (const at of entry.breaks) {
|
|
6221
|
+
text += `${source.slice(from, at)}|`;
|
|
6222
|
+
from = at;
|
|
6223
|
+
}
|
|
6224
|
+
issues.push({
|
|
6225
|
+
...ref,
|
|
6226
|
+
kind: "word-break",
|
|
6227
|
+
text: text + source.slice(from, entry.end)
|
|
6228
|
+
});
|
|
6229
|
+
}
|
|
5980
6230
|
};
|
|
5981
6231
|
const detectApproximate = (paragraphs, measure) => {
|
|
5982
6232
|
const seen = /* @__PURE__ */ new Set();
|
|
@@ -6001,19 +6251,25 @@ const detectApproximate = (paragraphs, measure) => {
|
|
|
6001
6251
|
/**
|
|
6002
6252
|
* Measures every text body in the deck and reports text that escapes its box
|
|
6003
6253
|
* (`overflow-x` / `overflow-y`) and, opt-in, paragraphs that soft-wrap
|
|
6004
|
-
* (`soft-wrap`, 段落ち)
|
|
6254
|
+
* (`soft-wrap`, 段落ち), wraps that split a word or strand a line of one or
|
|
6255
|
+
* two characters (`word-break`, `short-last-line`), and text below a minimum
|
|
6256
|
+
* size (`small-font`).
|
|
6005
6257
|
*
|
|
6006
6258
|
* Table cell text is not audited (v1 covers shape text bodies, including
|
|
6007
6259
|
* placeholders and shapes inside groups).
|
|
6008
6260
|
*/
|
|
6009
6261
|
const auditTextLayout = (pres, options = {}) => {
|
|
6010
6262
|
const measure = options.measureText ?? defaultMeasurer;
|
|
6011
|
-
const
|
|
6012
|
-
|
|
6263
|
+
const resolved = {
|
|
6264
|
+
tolerancePx: options.tolerancePx ?? DEFAULT_TOLERANCE_PX,
|
|
6265
|
+
reportSoftWraps: options.reportSoftWraps ?? false,
|
|
6266
|
+
reportBadWraps: options.reportBadWraps ?? false,
|
|
6267
|
+
minFontSizePt: options.minFontSizePt
|
|
6268
|
+
};
|
|
6013
6269
|
const theme = getPresentationTheme(pres);
|
|
6014
6270
|
const issues = [];
|
|
6015
6271
|
const slides = getSlides(pres);
|
|
6016
|
-
for (let slideIndex = 0; slideIndex < slides.length; slideIndex++) for (const shape of walkShapes(getSlideShapes(slides[slideIndex]))) auditShape(pres, theme, shape, slideIndex, measure,
|
|
6272
|
+
for (let slideIndex = 0; slideIndex < slides.length; slideIndex++) for (const shape of walkShapes(getSlideShapes(slides[slideIndex]))) auditShape(pres, theme, shape, slideIndex, measure, resolved, issues);
|
|
6017
6273
|
return issues;
|
|
6018
6274
|
};
|
|
6019
6275
|
//#endregion
|
|
@@ -6060,4 +6316,4 @@ function shapeTextAnchorOffset(pres, shape, options = {}) {
|
|
|
6060
6316
|
//#endregion
|
|
6061
6317
|
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 };
|
|
6062
6318
|
|
|
6063
|
-
//# sourceMappingURL=src-
|
|
6319
|
+
//# sourceMappingURL=src-BGzSNWjt.js.map
|