@office-kit/pptx-preview 0.6.2 → 0.7.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.
@@ -1,4 +1,4 @@
1
- import { getEffectiveColorMap, getGroupChildren, getGroupTransform, getParagraphAlignment, getParagraphBullet, getParagraphBulletImageBytes, getParagraphBulletStyle, getParagraphIndent, getParagraphLevel, getParagraphPropertiesEffective, getParagraphSpacing, getPresentationFonts, getPresentationTheme, getShapeAdjustValues, getShapeAltTitle, getShapeBodyPrEffective, getShapeBoundsResolved, getShapeChartSpec, getShapeClickAction, getShapeCustomGeometry, getShapeDescription, getShapeEffectsEffective, getShapeFillColorResolved, getShapeFillEffective, getShapeFlip, getShapeGradientFill, getShapeGradientFillEffective, getShapeHyperlink, getShapeHyperlinkTooltip, getShapeImageBiLevelThreshold, getShapeImageBrightness, getShapeImageBytes, getShapeImageContrast, getShapeImageCrop, getShapeImageDuotone, getShapeImageFillBytes, getShapeImageFormat, getShapeImageLinkUrl, getShapeImageOpacity, getShapeImagePartName, getShapeKind, getShapeName, getShapeParagraphCount, getShapeParagraphElements, getShapePatternFill, getShapePlaceholderType, getShapePreset, getShapeRotation, getShapeRunClickAction, getShapeRunFormat, getShapeRunFormatEffective, getShapeRunHyperlink, getShapeRunHyperlinkTooltip, getShapeStrokeArrow, getShapeStrokeCap, getShapeStrokeColorResolved, getShapeStrokeCompound, getShapeStrokeDash, getShapeStrokeEffective, getShapeStrokeJoin, getShapeTextAnchor, getShapeTextAutoFitParams, getShapeTextBodyRotationDeg, getShapeTextColumns, getShapeTextDirection, getShapeTextMargins, getShapeXmlString, getSlideBackground, getSlideBackgroundGradientFill, getSlideBackgroundImageBytes, getSlideBackgroundPatternFill, getSlideIndex, getSlideLayout, getSlideLayoutBackground, getSlideLayoutBackgroundGradientFill, getSlideLayoutBackgroundImageBytes, getSlideLayoutBackgroundPatternFill, getSlideLayoutShapes, getSlideMasterBackground, getSlideMasterBackgroundGradientFill, getSlideMasterBackgroundImageBytes, getSlideMasterBackgroundPatternFill, getSlideMasterShapes, getSlideShapes, getSlideSize, getTableCellAnchor, getTableCellBorders, getTableCellFill, getTableCellMargins, getTableCellParagraphs, getTableCellSpan, getTableCells, getTableColumnWidths, getTableDimensions, getTableRowHeights, getTableStyleFlags, isChartShape, isParagraphBulletPicture, isShapeImageGrayscale, isShapePlaceholder, isShapeTextBox, isTableShape, resolveDeckBodyTextColor } from "@office-kit/pptx";
1
+ import { getEffectiveColorMap, getGroupChildren, getGroupTransform, getParagraphAlignment, getParagraphBullet, getParagraphBulletImageBytes, getParagraphBulletStyle, getParagraphIndent, getParagraphLevel, getParagraphPropertiesEffective, getParagraphSpacing, getPresentationFonts, getPresentationTheme, getShapeAdjustValues, getShapeAltTitle, getShapeBodyPrEffective, getShapeBoundsResolved, getShapeChartSpec, getShapeClickAction, getShapeCustomGeometry, getShapeDescription, getShapeEffectsEffective, getShapeFillColorResolved, getShapeFillEffective, getShapeFlip, getShapeGradientFill, getShapeGradientFillEffective, getShapeHyperlink, getShapeHyperlinkTooltip, getShapeImageBiLevelThreshold, getShapeImageBrightness, getShapeImageBytes, getShapeImageContrast, getShapeImageCrop, getShapeImageDuotone, getShapeImageFillBytes, getShapeImageFormat, getShapeImageLinkUrl, getShapeImageOpacity, getShapeImagePartName, getShapeKind, getShapeName, getShapeParagraphCount, getShapeParagraphElements, getShapePatternFill, getShapePlaceholderType, getShapePreset, getShapeRotation, getShapeRunClickAction, getShapeRunFormat, getShapeRunFormatEffective, getShapeRunHyperlink, getShapeRunHyperlinkTooltip, getShapeStrokeArrow, getShapeStrokeCap, getShapeStrokeColorResolved, getShapeStrokeCompound, getShapeStrokeDash, getShapeStrokeEffective, getShapeStrokeJoin, getShapeTextAnchor, getShapeTextAutoFitParams, getShapeTextBodyRotationDeg, getShapeTextColumns, getShapeTextDirection, getShapeTextMargins, getShapeXmlString, getSlideBackground, getSlideBackgroundGradientFill, getSlideBackgroundImageBytes, getSlideBackgroundPatternFill, getSlideIndex, getSlideLayout, getSlideLayoutBackground, getSlideLayoutBackgroundGradientFill, getSlideLayoutBackgroundImageBytes, getSlideLayoutBackgroundPatternFill, getSlideLayoutShapes, getSlideMasterBackground, getSlideMasterBackgroundGradientFill, getSlideMasterBackgroundImageBytes, getSlideMasterBackgroundPatternFill, getSlideMasterShapes, getSlideShapes, getSlideSize, getSlides, getTableCellAnchor, getTableCellBorders, getTableCellFill, getTableCellMargins, getTableCellParagraphs, getTableCellSpan, getTableCells, getTableColumnWidths, getTableDimensions, getTableRowHeights, getTableStyleFlags, isChartShape, isParagraphBulletPicture, isShapeImageGrayscale, isShapePlaceholder, isShapeTextBox, isTableShape, resolveDeckBodyTextColor } from "@office-kit/pptx";
2
2
  //#region src/text-layout.ts
3
3
  const SANS = "Carlito";
4
4
  const SERIF = "Caladea";
@@ -91,7 +91,8 @@ const layoutCore = (input, measure) => {
91
91
  const buildLines = (contentLeft, contentRight) => {
92
92
  const lines = [];
93
93
  let cursorY = 0;
94
- for (const para of input.paragraphs) {
94
+ for (let paraIndex = 0; paraIndex < input.paragraphs.length; paraIndex++) {
95
+ const para = input.paragraphs[paraIndex];
95
96
  cursorY += para.spcBefPx;
96
97
  const wrapLeft = contentLeft + para.marLpx;
97
98
  const wrapRight = contentRight - para.marRpx;
@@ -154,6 +155,7 @@ const layoutCore = (input, measure) => {
154
155
  const lineLeft = bullet ? Math.max(wrapLeft, firstLeft + bulletLead) : (isFirst ? firstLeft : wrapLeft) + bulletLead;
155
156
  const line = {
156
157
  tokens: toks,
158
+ paraIndex,
157
159
  ascent,
158
160
  descent,
159
161
  lineGap,
@@ -1917,7 +1919,7 @@ const buildSvgTextInput = (a) => {
1917
1919
  underline: fmt?.underline ?? true
1918
1920
  };
1919
1921
  }
1920
- const family = substituteFamily(fmt?.font ?? a.themeFace);
1922
+ const family = (a.resolveFamily ?? substituteFamily)(fmt?.font ?? a.themeFace);
1921
1923
  const sizePx = run.sizePt * scale * PX_PER_PT;
1922
1924
  const fillHex = fmt?.color !== void 0 && fmt.color !== null ? resolveColor(fmt.color, a.theme, "#000000") : a.defaultColor;
1923
1925
  const superSub = fmt?.baseline !== void 0 && fmt.baseline !== 0 ? fmt.baseline > 0 ? 1 : -1 : 0;
@@ -2018,7 +2020,7 @@ const buildBullet = (a, para, pi) => {
2018
2020
  const fillHex = para.bulletDetail.color ? resolveColor(para.bulletDetail.color, a.theme, "#000000") : a.defaultColor;
2019
2021
  return {
2020
2022
  text: char,
2021
- family: substituteFamily(para.bulletDetail.font ?? DEFAULT_BULLET_FONT),
2023
+ family: (a.resolveFamily ?? substituteFamily)(para.bulletDetail.font ?? DEFAULT_BULLET_FONT),
2022
2024
  sizePx,
2023
2025
  fillHex,
2024
2026
  ...para.bulletImageHref ? { imageHref: para.bulletImageHref } : {}
@@ -2059,14 +2061,14 @@ const presetTextRect = (preset) => {
2059
2061
  default: return null;
2060
2062
  }
2061
2063
  };
2062
- const renderTextBody = (pres, shape, bounds, theme, phType, ctx) => {
2064
+ const resolveTextBodyModel = (pres, shape, bounds, theme, phType, measure, defaultColor) => {
2063
2065
  let paragraphCount;
2064
2066
  try {
2065
2067
  paragraphCount = getShapeParagraphCount(shape);
2066
2068
  } catch {
2067
- return "";
2069
+ return null;
2068
2070
  }
2069
- if (paragraphCount === 0) return "";
2071
+ if (paragraphCount === 0) return null;
2070
2072
  const defaultPt = placeholderDefaultPt(phType);
2071
2073
  const themeFonts = getPresentationFonts(pres);
2072
2074
  const themeFace = phType === "title" || phType === "ctrTitle" ? themeFonts?.majorLatin ?? null : themeFonts?.minorLatin ?? null;
@@ -2111,7 +2113,7 @@ const renderTextBody = (pres, shape, bounds, theme, phType, ctx) => {
2111
2113
  innerW = rectW;
2112
2114
  innerH = rectH;
2113
2115
  }
2114
- if (innerW <= 0 || innerH <= 0) return "";
2116
+ if (innerW <= 0 || innerH <= 0) return null;
2115
2117
  const svgTextRect = (v) => v === "none" || v === "upright" ? {
2116
2118
  x: innerX,
2117
2119
  y: innerY,
@@ -2249,7 +2251,7 @@ const renderTextBody = (pres, shape, bounds, theme, phType, ctx) => {
2249
2251
  indent
2250
2252
  });
2251
2253
  }
2252
- if (!hasAnyText) return "";
2254
+ if (!hasAnyText) return null;
2253
2255
  const authoredAutofit = getShapeTextAutoFitParams(shape);
2254
2256
  let autoFitScale = authoredAutofit?.fontScale ?? 1;
2255
2257
  let lineHeightScale = 1 - (authoredAutofit?.lnSpcReduction ?? 0);
@@ -2320,14 +2322,14 @@ const renderTextBody = (pres, shape, bounds, theme, phType, ctx) => {
2320
2322
  lineHeightScale,
2321
2323
  defaultPt,
2322
2324
  themeFace,
2323
- defaultColor: activeDeckTextColor,
2325
+ defaultColor,
2324
2326
  anchor: anchor === "center" || anchor === "bottom" ? anchor : "top",
2325
2327
  wrap: effectiveBody.wrap !== "none",
2326
2328
  innerX: fitRect.x,
2327
2329
  innerY: fitRect.y,
2328
2330
  innerW: fitRect.w,
2329
2331
  innerH: fitRect.h,
2330
- measure: ctx.measure,
2332
+ measure,
2331
2333
  vert: fitVert,
2332
2334
  columns: fitColumns
2333
2335
  };
@@ -2336,11 +2338,33 @@ const renderTextBody = (pres, shape, bounds, theme, phType, ctx) => {
2336
2338
  if (measureTextBodyHeight(buildSvgTextInput({
2337
2339
  ...fitArgsBase,
2338
2340
  autoFitScale: s
2339
- }), ctx.measure) <= fitBoxPx) break;
2341
+ }), measure) <= fitBoxPx) break;
2340
2342
  s -= AUTOFIT_STEP;
2341
2343
  }
2342
2344
  autoFitScale = Math.max(AUTOFIT_FLOOR, s);
2343
2345
  }
2346
+ return {
2347
+ paraData,
2348
+ numberLabels,
2349
+ authoredAutofit,
2350
+ autoFitScale,
2351
+ lineHeightScale,
2352
+ defaultPt,
2353
+ themeFace,
2354
+ effectiveDefaultFont,
2355
+ effectiveBody,
2356
+ anchor,
2357
+ innerX,
2358
+ innerY,
2359
+ innerW,
2360
+ innerH,
2361
+ svgTextRect
2362
+ };
2363
+ };
2364
+ const renderTextBody = (pres, shape, bounds, theme, phType, ctx) => {
2365
+ const model = resolveTextBodyModel(pres, shape, bounds, theme, phType, ctx.measure, activeDeckTextColor);
2366
+ if (model === null) return "";
2367
+ const { paraData, numberLabels, authoredAutofit, autoFitScale, defaultPt, themeFace, effectiveDefaultFont, effectiveBody, anchor, innerX, innerY, innerW, innerH, svgTextRect } = model;
2344
2368
  const paragraphs = [];
2345
2369
  for (let pi = 0; pi < paraData.length; pi++) {
2346
2370
  const para = paraData[pi];
@@ -4421,6 +4445,157 @@ const detectImageFormatLocal = (bytes) => {
4421
4445
  return null;
4422
4446
  };
4423
4447
  //#endregion
4424
- export { SERIF as a, substituteFamily as c, SANS as i, ARIAL as n, TIMES as o, MONO as r, defaultMeasurer as s, renderSlideSvg as t };
4448
+ //#region src/audit.ts
4449
+ const DEFAULT_TOLERANCE_PX = 1;
4450
+ const round2 = (n) => Math.round(n * 100) / 100;
4451
+ function* walkShapes(shapes) {
4452
+ for (const shape of shapes) if (getShapeKind(shape) === "group") yield* walkShapes(getGroupChildren(shape));
4453
+ else yield shape;
4454
+ }
4455
+ const passthroughFamily = (family) => family ?? "Carlito";
4456
+ const auditShape = (pres, theme, shape, slideIndex, measure, tolerancePx, reportSoftWraps, issues) => {
4457
+ const kind = getShapeKind(shape);
4458
+ if (kind !== "shape" && kind !== "graphicFrame") return;
4459
+ const bounds = getShapeBoundsResolved(pres, shape);
4460
+ if (!bounds) return;
4461
+ const model = resolveTextBodyModel(pres, shape, {
4462
+ x: bounds.x,
4463
+ y: bounds.y,
4464
+ w: bounds.w,
4465
+ h: bounds.h
4466
+ }, theme, getShapePlaceholderType(shape), measure, "#000000");
4467
+ if (model === null) return;
4468
+ const vert = verticalLayoutOf(model.effectiveBody.vert ?? getShapeTextDirection(shape));
4469
+ const cols = getShapeTextColumns(shape);
4470
+ const columns = vert === "none" && cols && cols.count >= 2 ? {
4471
+ count: cols.count,
4472
+ gapPx: cols.gapEmu !== void 0 ? cols.gapEmu / EMU_PER_PX : 12
4473
+ } : null;
4474
+ const rect = model.svgTextRect(vert);
4475
+ if (rect.w <= 0 || rect.h <= 0) return;
4476
+ const autoFitScale = model.authoredAutofit ? model.autoFitScale : 1;
4477
+ const input = buildSvgTextInput({
4478
+ pres,
4479
+ shape,
4480
+ theme,
4481
+ paraData: model.paraData,
4482
+ numberLabels: model.numberLabels,
4483
+ autoFitScale,
4484
+ lineHeightScale: model.lineHeightScale,
4485
+ defaultPt: model.defaultPt,
4486
+ themeFace: model.themeFace,
4487
+ defaultColor: "#000000",
4488
+ anchor: model.anchor,
4489
+ wrap: model.effectiveBody.wrap !== "none",
4490
+ innerX: rect.x,
4491
+ innerY: rect.y,
4492
+ innerW: rect.w,
4493
+ innerH: rect.h,
4494
+ measure,
4495
+ vert,
4496
+ columns,
4497
+ resolveFamily: passthroughFamily
4498
+ });
4499
+ const core = layoutCore(input, measure);
4500
+ const frame = vert === "cw90" || vert === "cw270" ? {
4501
+ x: input.boxXpx + input.boxWpx / 2 - input.boxHpx / 2,
4502
+ y: input.boxYpx + input.boxHpx / 2 - input.boxWpx / 2,
4503
+ w: input.boxHpx,
4504
+ h: input.boxWpx
4505
+ } : {
4506
+ x: input.boxXpx,
4507
+ y: input.boxYpx,
4508
+ w: input.boxWpx,
4509
+ h: input.boxHpx
4510
+ };
4511
+ let inkTop = Infinity;
4512
+ let inkBottom = -Infinity;
4513
+ let overflowX = 0;
4514
+ let anyInk = false;
4515
+ for (const p of core.placements) {
4516
+ const toks = [...p.line.tokens];
4517
+ while (toks.length > 0 && (toks[toks.length - 1].isSpace || toks[toks.length - 1].isBreak)) toks.pop();
4518
+ if (!toks.some((t) => !t.isSpace && !t.isBreak)) continue;
4519
+ anyInk = true;
4520
+ let lineW = 0;
4521
+ for (const t of toks) if (!t.isBreak) lineW += t.width;
4522
+ const left = p.line.textAnchor === "middle" ? p.line.anchorX - lineW / 2 : p.line.textAnchor === "end" ? p.line.anchorX - lineW : p.line.anchorX;
4523
+ const over = Math.max(frame.x - (left + p.dx), left + p.dx + lineW - (frame.x + frame.w));
4524
+ if (over > overflowX) overflowX = over;
4525
+ inkTop = Math.min(inkTop, p.baselineY - p.line.ascent);
4526
+ inkBottom = Math.max(inkBottom, p.baselineY + p.line.descent);
4527
+ }
4528
+ if (!anyInk) return;
4529
+ const approximate = detectApproximate(input.paragraphs, measure);
4530
+ const slideRef = {
4531
+ slideIndex,
4532
+ shapeName: getShapeName(shape),
4533
+ approximate
4534
+ };
4535
+ if (overflowX > tolerancePx) issues.push({
4536
+ ...slideRef,
4537
+ kind: "overflow-x",
4538
+ overflowPx: round2(overflowX)
4539
+ });
4540
+ const overflowY = Math.max(frame.y - inkTop, inkBottom - (frame.y + frame.h));
4541
+ if (overflowY > tolerancePx) issues.push({
4542
+ ...slideRef,
4543
+ kind: "overflow-y",
4544
+ overflowPx: round2(overflowY)
4545
+ });
4546
+ if (reportSoftWraps && vert === "none") {
4547
+ const lineCounts = /* @__PURE__ */ new Map();
4548
+ for (const p of core.placements) lineCounts.set(p.line.paraIndex, (lineCounts.get(p.line.paraIndex) ?? 0) + 1);
4549
+ input.paragraphs.forEach((para, pi) => {
4550
+ const breaks = para.pieces.reduce((n, pc) => n + (pc.isBreak ? 1 : 0), 0);
4551
+ const extraLines = (lineCounts.get(pi) ?? 0) - 1 - breaks;
4552
+ if (extraLines > 0) issues.push({
4553
+ ...slideRef,
4554
+ kind: "soft-wrap",
4555
+ paragraphIndex: pi,
4556
+ extraLines
4557
+ });
4558
+ });
4559
+ }
4560
+ };
4561
+ const detectApproximate = (paragraphs, measure) => {
4562
+ const seen = /* @__PURE__ */ new Set();
4563
+ for (const para of paragraphs) for (const piece of para.pieces) {
4564
+ if (piece.isBreak || piece.text === "") continue;
4565
+ const key = `${piece.family}|${piece.sizePx}|${piece.bold}|${piece.italic}|${piece.text}`;
4566
+ if (seen.has(key)) continue;
4567
+ seen.add(key);
4568
+ const spec = {
4569
+ family: piece.family,
4570
+ sizePx: piece.sizePx,
4571
+ bold: piece.bold,
4572
+ italic: piece.italic,
4573
+ letterSpacingPx: piece.letterSpacingPx
4574
+ };
4575
+ const r = measure(piece.text, spec);
4576
+ if (r.approximate === true || r.ascentPx === void 0) return true;
4577
+ }
4578
+ return false;
4579
+ };
4580
+ /**
4581
+ * Measures every text body in the deck and reports text that escapes its box
4582
+ * (`overflow-x` / `overflow-y`) and, opt-in, paragraphs that soft-wrap
4583
+ * (`soft-wrap`, 段落ち).
4584
+ *
4585
+ * Table cell text is not audited (v1 covers shape text bodies, including
4586
+ * placeholders and shapes inside groups).
4587
+ */
4588
+ const auditTextLayout = (pres, options = {}) => {
4589
+ const measure = options.measureText ?? defaultMeasurer;
4590
+ const tolerancePx = options.tolerancePx ?? DEFAULT_TOLERANCE_PX;
4591
+ const reportSoftWraps = options.reportSoftWraps ?? false;
4592
+ const theme = getPresentationTheme(pres);
4593
+ const issues = [];
4594
+ const slides = getSlides(pres);
4595
+ for (let slideIndex = 0; slideIndex < slides.length; slideIndex++) for (const shape of walkShapes(getSlideShapes(slides[slideIndex]))) auditShape(pres, theme, shape, slideIndex, measure, tolerancePx, reportSoftWraps, issues);
4596
+ return issues;
4597
+ };
4598
+ //#endregion
4599
+ export { MONO as a, TIMES as c, substituteFamily as d, AVG_GLYPH_W_RATIO$1 as i, defaultMeasurer as l, renderSlideSvg as n, SANS as o, ARIAL as r, SERIF as s, auditTextLayout as t, isCjk as u };
4425
4600
 
4426
- //# sourceMappingURL=src-CVYeIBFN.js.map
4601
+ //# sourceMappingURL=src-CKGlEIdk.js.map