@office-kit/pptx-preview 0.6.2 → 0.8.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];
@@ -4318,6 +4342,27 @@ const buildEffectsFilter = (pres, shape) => {
4318
4342
  defs: `<defs><filter id="${id}" x="-25%" y="-25%" width="150%" height="150%">${primitives.join("")}</filter></defs>`
4319
4343
  };
4320
4344
  };
4345
+ const flattenedDescendantCount = (group, dropPlaceholders) => {
4346
+ let count = 0;
4347
+ for (const child of getGroupChildren(group)) {
4348
+ if (!dropPlaceholders || !isShapePlaceholder(child)) count += 1;
4349
+ if (getShapeKind(child) === "group") count += flattenedDescendantCount(child, dropPlaceholders);
4350
+ }
4351
+ return count;
4352
+ };
4353
+ const topLevelShapes = (shapes, opts) => {
4354
+ const out = [];
4355
+ let skip = 0;
4356
+ for (const shape of shapes) {
4357
+ if (skip > 0) {
4358
+ skip -= 1;
4359
+ continue;
4360
+ }
4361
+ out.push(shape);
4362
+ if (getShapeKind(shape) === "group") skip = flattenedDescendantCount(shape, opts.dropPlaceholders);
4363
+ }
4364
+ return out;
4365
+ };
4321
4366
  const renderSlideSvg = (pres, slide, opts = {}) => {
4322
4367
  const size = getSlideSize(pres) ?? DEFAULT_SIZE;
4323
4368
  const W = size.width;
@@ -4393,13 +4438,13 @@ const renderSlideSvg = (pres, slide, opts = {}) => {
4393
4438
  let layoutBgShapes = "";
4394
4439
  const layoutForBg = getSlideLayout(slide);
4395
4440
  if (layoutForBg) try {
4396
- const masterShapes = getSlideMasterShapes(pres, layoutForBg);
4397
- const layoutShapes = getSlideLayoutShapes(pres, layoutForBg);
4441
+ const masterShapes = topLevelShapes(getSlideMasterShapes(pres, layoutForBg), { dropPlaceholders: true });
4442
+ const layoutShapes = topLevelShapes(getSlideLayoutShapes(pres, layoutForBg), { dropPlaceholders: true });
4398
4443
  layoutBgShapes = [...masterShapes, ...layoutShapes].map((s) => renderShape(s, pres, theme, ctx)).join("");
4399
4444
  } catch {
4400
4445
  layoutBgShapes = "";
4401
4446
  }
4402
- const shapesSvg = getSlideShapes(slide).map((s) => renderShape(s, pres, theme, ctx)).join("");
4447
+ const shapesSvg = topLevelShapes(getSlideShapes(slide), { dropPlaceholders: false }).map((s) => renderShape(s, pres, theme, ctx)).join("");
4403
4448
  return [
4404
4449
  `<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 ${E(W)} ${E(H)}" preserveAspectRatio="xMidYMid meet">`,
4405
4450
  bgGradientDefs,
@@ -4421,6 +4466,156 @@ const detectImageFormatLocal = (bytes) => {
4421
4466
  return null;
4422
4467
  };
4423
4468
  //#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 };
4469
+ //#region src/audit.ts
4470
+ const DEFAULT_TOLERANCE_PX = 1;
4471
+ const round2 = (n) => Math.round(n * 100) / 100;
4472
+ function* walkShapes(shapes) {
4473
+ for (const shape of shapes) if (getShapeKind(shape) !== "group") yield shape;
4474
+ }
4475
+ const passthroughFamily = (family) => family ?? "Carlito";
4476
+ const auditShape = (pres, theme, shape, slideIndex, measure, tolerancePx, reportSoftWraps, issues) => {
4477
+ const kind = getShapeKind(shape);
4478
+ if (kind !== "shape" && kind !== "graphicFrame") return;
4479
+ const bounds = getShapeBoundsResolved(pres, shape);
4480
+ if (!bounds) return;
4481
+ const model = resolveTextBodyModel(pres, shape, {
4482
+ x: bounds.x,
4483
+ y: bounds.y,
4484
+ w: bounds.w,
4485
+ h: bounds.h
4486
+ }, theme, getShapePlaceholderType(shape), measure, "#000000");
4487
+ if (model === null) return;
4488
+ const vert = verticalLayoutOf(model.effectiveBody.vert ?? getShapeTextDirection(shape));
4489
+ const cols = getShapeTextColumns(shape);
4490
+ const columns = vert === "none" && cols && cols.count >= 2 ? {
4491
+ count: cols.count,
4492
+ gapPx: cols.gapEmu !== void 0 ? cols.gapEmu / EMU_PER_PX : 12
4493
+ } : null;
4494
+ const rect = model.svgTextRect(vert);
4495
+ if (rect.w <= 0 || rect.h <= 0) return;
4496
+ const autoFitScale = model.authoredAutofit ? model.autoFitScale : 1;
4497
+ const input = buildSvgTextInput({
4498
+ pres,
4499
+ shape,
4500
+ theme,
4501
+ paraData: model.paraData,
4502
+ numberLabels: model.numberLabels,
4503
+ autoFitScale,
4504
+ lineHeightScale: model.lineHeightScale,
4505
+ defaultPt: model.defaultPt,
4506
+ themeFace: model.themeFace,
4507
+ defaultColor: "#000000",
4508
+ anchor: model.anchor,
4509
+ wrap: model.effectiveBody.wrap !== "none",
4510
+ innerX: rect.x,
4511
+ innerY: rect.y,
4512
+ innerW: rect.w,
4513
+ innerH: rect.h,
4514
+ measure,
4515
+ vert,
4516
+ columns,
4517
+ resolveFamily: passthroughFamily
4518
+ });
4519
+ const core = layoutCore(input, measure);
4520
+ const frame = vert === "cw90" || vert === "cw270" ? {
4521
+ x: input.boxXpx + input.boxWpx / 2 - input.boxHpx / 2,
4522
+ y: input.boxYpx + input.boxHpx / 2 - input.boxWpx / 2,
4523
+ w: input.boxHpx,
4524
+ h: input.boxWpx
4525
+ } : {
4526
+ x: input.boxXpx,
4527
+ y: input.boxYpx,
4528
+ w: input.boxWpx,
4529
+ h: input.boxHpx
4530
+ };
4531
+ let inkTop = Infinity;
4532
+ let inkBottom = -Infinity;
4533
+ let overflowX = 0;
4534
+ let anyInk = false;
4535
+ for (const p of core.placements) {
4536
+ const toks = [...p.line.tokens];
4537
+ while (toks.length > 0 && (toks[toks.length - 1].isSpace || toks[toks.length - 1].isBreak)) toks.pop();
4538
+ if (!toks.some((t) => !t.isSpace && !t.isBreak)) continue;
4539
+ anyInk = true;
4540
+ let lineW = 0;
4541
+ for (const t of toks) if (!t.isBreak) lineW += t.width;
4542
+ const left = p.line.textAnchor === "middle" ? p.line.anchorX - lineW / 2 : p.line.textAnchor === "end" ? p.line.anchorX - lineW : p.line.anchorX;
4543
+ const over = Math.max(frame.x - (left + p.dx), left + p.dx + lineW - (frame.x + frame.w));
4544
+ if (over > overflowX) overflowX = over;
4545
+ inkTop = Math.min(inkTop, p.baselineY - p.line.ascent);
4546
+ inkBottom = Math.max(inkBottom, p.baselineY + p.line.descent);
4547
+ }
4548
+ if (!anyInk) return;
4549
+ const approximate = detectApproximate(input.paragraphs, measure);
4550
+ const slideRef = {
4551
+ slideIndex,
4552
+ shapeName: getShapeName(shape),
4553
+ approximate
4554
+ };
4555
+ if (overflowX > tolerancePx) issues.push({
4556
+ ...slideRef,
4557
+ kind: "overflow-x",
4558
+ overflowPx: round2(overflowX)
4559
+ });
4560
+ const overflowY = Math.max(frame.y - inkTop, inkBottom - (frame.y + frame.h));
4561
+ if (overflowY > tolerancePx) issues.push({
4562
+ ...slideRef,
4563
+ kind: "overflow-y",
4564
+ overflowPx: round2(overflowY)
4565
+ });
4566
+ if (reportSoftWraps && vert === "none") {
4567
+ const lineCounts = /* @__PURE__ */ new Map();
4568
+ for (const p of core.placements) lineCounts.set(p.line.paraIndex, (lineCounts.get(p.line.paraIndex) ?? 0) + 1);
4569
+ input.paragraphs.forEach((para, pi) => {
4570
+ const breaks = para.pieces.reduce((n, pc) => n + (pc.isBreak ? 1 : 0), 0);
4571
+ const extraLines = (lineCounts.get(pi) ?? 0) - 1 - breaks;
4572
+ if (extraLines > 0) issues.push({
4573
+ ...slideRef,
4574
+ kind: "soft-wrap",
4575
+ paragraphIndex: pi,
4576
+ extraLines
4577
+ });
4578
+ });
4579
+ }
4580
+ };
4581
+ const detectApproximate = (paragraphs, measure) => {
4582
+ const seen = /* @__PURE__ */ new Set();
4583
+ for (const para of paragraphs) for (const piece of para.pieces) {
4584
+ if (piece.isBreak || piece.text === "") continue;
4585
+ const key = `${piece.family}|${piece.sizePx}|${piece.bold}|${piece.italic}|${piece.text}`;
4586
+ if (seen.has(key)) continue;
4587
+ seen.add(key);
4588
+ const spec = {
4589
+ family: piece.family,
4590
+ sizePx: piece.sizePx,
4591
+ bold: piece.bold,
4592
+ italic: piece.italic,
4593
+ letterSpacingPx: piece.letterSpacingPx
4594
+ };
4595
+ const r = measure(piece.text, spec);
4596
+ if (r.approximate === true || r.ascentPx === void 0) return true;
4597
+ }
4598
+ return false;
4599
+ };
4600
+ /**
4601
+ * Measures every text body in the deck and reports text that escapes its box
4602
+ * (`overflow-x` / `overflow-y`) and, opt-in, paragraphs that soft-wrap
4603
+ * (`soft-wrap`, 段落ち).
4604
+ *
4605
+ * Table cell text is not audited (v1 covers shape text bodies, including
4606
+ * placeholders and shapes inside groups).
4607
+ */
4608
+ const auditTextLayout = (pres, options = {}) => {
4609
+ const measure = options.measureText ?? defaultMeasurer;
4610
+ const tolerancePx = options.tolerancePx ?? DEFAULT_TOLERANCE_PX;
4611
+ const reportSoftWraps = options.reportSoftWraps ?? false;
4612
+ const theme = getPresentationTheme(pres);
4613
+ const issues = [];
4614
+ const slides = getSlides(pres);
4615
+ 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);
4616
+ return issues;
4617
+ };
4618
+ //#endregion
4619
+ 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
4620
 
4426
- //# sourceMappingURL=src-CVYeIBFN.js.map
4621
+ //# sourceMappingURL=src-D95l1T9U.js.map