pptx-ts 3.7.0 → 4.0.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 +116 -269
- package/dist/autopage-odPz7zZO.js +536 -0
- package/dist/autopage-odPz7zZO.js.map +1 -0
- package/dist/binary-string-CKVAYI3M.js +24 -0
- package/dist/binary-string-CKVAYI3M.js.map +1 -0
- package/dist/body-insets-Dw_BeWD_.js +54 -0
- package/dist/body-insets-Dw_BeWD_.js.map +1 -0
- package/dist/browser.d.ts +29 -9
- package/dist/browser.d.ts.map +1 -1
- package/dist/browser.js +41 -14
- package/dist/browser.js.map +1 -1
- package/dist/clip-Bl01BPcN.d.ts +67 -0
- package/dist/clip-Bl01BPcN.d.ts.map +1 -0
- package/dist/diagnostics-D8ot4Ei7.d.ts +244 -0
- package/dist/diagnostics-D8ot4Ei7.d.ts.map +1 -0
- package/dist/embedded-fonts-DarT9aoa.d.ts +20 -0
- package/dist/embedded-fonts-DarT9aoa.d.ts.map +1 -0
- package/dist/entry-compose-DCnvBZz2.d.ts +1046 -0
- package/dist/entry-compose-DCnvBZz2.d.ts.map +1 -0
- package/dist/{enums-CvRYniUA.d.ts → enums-BUKTUB6V.d.ts} +21 -13
- package/dist/{enums-CvRYniUA.d.ts.map → enums-BUKTUB6V.d.ts.map} +1 -1
- package/dist/{errors-Bvln0H-7.js → errors-C4oENPcu.js} +92 -2
- package/dist/errors-C4oENPcu.js.map +1 -0
- package/dist/families.d.ts +260 -0
- package/dist/families.d.ts.map +1 -0
- package/dist/families.js +57 -0
- package/dist/families.js.map +1 -0
- package/dist/{fit-BKs0zxMI.js → fit-Cb7naFj6.js} +249 -167
- package/dist/fit-Cb7naFj6.js.map +1 -0
- package/dist/font-metrics-v5fn7s7e.d.ts +613 -0
- package/dist/font-metrics-v5fn7s7e.d.ts.map +1 -0
- package/dist/grid-DikTjFey.js +187 -0
- package/dist/grid-DikTjFey.js.map +1 -0
- package/dist/{html-dom-B0I0QSbU.js → html-dom-Iuog2wdK.js} +121 -91
- package/dist/html-dom-Iuog2wdK.js.map +1 -0
- package/dist/html.d.ts +6 -6
- package/dist/html.d.ts.map +1 -1
- package/dist/html.js +7 -6
- package/dist/html.js.map +1 -1
- package/dist/image-size-CvN2iCA4.js +1611 -0
- package/dist/image-size-CvN2iCA4.js.map +1 -0
- package/dist/index.d.ts +24 -10
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +31 -11
- package/dist/index.js.map +1 -1
- package/dist/inspect.d.ts +40 -31
- package/dist/inspect.d.ts.map +1 -1
- package/dist/inspect.js +40 -48
- package/dist/inspect.js.map +1 -1
- package/dist/math.d.ts +7 -5
- package/dist/math.d.ts.map +1 -1
- package/dist/math.js +16 -7
- package/dist/math.js.map +1 -1
- package/dist/measure.d.ts +12 -225
- package/dist/measure.d.ts.map +1 -1
- package/dist/measure.js +3 -3
- package/dist/node.d.ts +22 -8
- package/dist/node.d.ts.map +1 -1
- package/dist/node.js +27 -7
- package/dist/node.js.map +1 -1
- package/dist/notes-Dh12JAbn.js +3264 -0
- package/dist/notes-Dh12JAbn.js.map +1 -0
- package/dist/options-internal-WqgMf720.js +43 -0
- package/dist/options-internal-WqgMf720.js.map +1 -0
- package/dist/{package-CaOLXSNi.d.ts → package-J49Ck6S2.d.ts} +27 -12
- package/dist/package-J49Ck6S2.d.ts.map +1 -0
- package/dist/playbtn-CUqQdoDf.js +22 -0
- package/dist/playbtn-CUqQdoDf.js.map +1 -0
- package/dist/{slide-ZqAdVvHV.d.ts → pres-C6qgOFAa.d.ts} +1021 -1423
- package/dist/pres-C6qgOFAa.d.ts.map +1 -0
- package/dist/{presentation-DrJtj3-C.js → presentation-DGYlAWhl.js} +3208 -2727
- package/dist/presentation-DGYlAWhl.js.map +1 -0
- package/dist/{presentation-types-CDjScsev.d.ts → presentation-types-8jLQHI4M.d.ts} +1070 -683
- package/dist/presentation-types-8jLQHI4M.d.ts.map +1 -0
- package/dist/read.d.ts +9 -9
- package/dist/read.d.ts.map +1 -1
- package/dist/read.js +4 -4
- package/dist/script.d.ts +80 -66
- package/dist/script.d.ts.map +1 -1
- package/dist/script.js +2399 -1139
- package/dist/script.js.map +1 -1
- package/dist/{shapes-BemroU-z.js → shapes-re0KyQk_.js} +2807 -1609
- package/dist/shapes-re0KyQk_.js.map +1 -0
- package/dist/slide-Dx2ZeVZ6.d.ts +1256 -0
- package/dist/slide-Dx2ZeVZ6.d.ts.map +1 -0
- package/dist/slide-size-BalTjFuv.js +88 -0
- package/dist/slide-size-BalTjFuv.js.map +1 -0
- package/dist/{units-internal-Dg95w_YD.js → table-cell-margins-EVg0EwxT.js} +235 -273
- package/dist/table-cell-margins-EVg0EwxT.js.map +1 -0
- package/dist/table-dom-BTDP3lEP.js +21 -0
- package/dist/table-dom-BTDP3lEP.js.map +1 -0
- package/dist/text-KnLOtEEu.js +3980 -0
- package/dist/text-KnLOtEEu.js.map +1 -0
- package/dist/text-anchor-CrYXAfUu.js +176 -0
- package/dist/text-anchor-CrYXAfUu.js.map +1 -0
- package/dist/transition-speed-kUEWDWTx.js +14 -0
- package/dist/transition-speed-kUEWDWTx.js.map +1 -0
- package/dist/units-internal-XJlf-hOl.js +649 -0
- package/dist/units-internal-XJlf-hOl.js.map +1 -0
- package/dist/zip-C0leZZMj.d.ts +67 -0
- package/dist/zip-C0leZZMj.d.ts.map +1 -0
- package/dist/zip-CKd8ihcf.js +177 -0
- package/dist/zip-CKd8ihcf.js.map +1 -0
- package/dist/zip.d.ts +3 -68
- package/dist/zip.js +3 -163
- package/dist/zoom-cX9sllpH.js +8865 -0
- package/dist/zoom-cX9sllpH.js.map +1 -0
- package/package.json +106 -19
- package/skills/ts-pptx-upstream/SKILL.md +58 -57
- package/dist/autopage-DkjYZUFD.js +0 -423
- package/dist/autopage-DkjYZUFD.js.map +0 -1
- package/dist/diagnostics-DfBdDHre.d.ts +0 -829
- package/dist/diagnostics-DfBdDHre.d.ts.map +0 -1
- package/dist/errors-Bvln0H-7.js.map +0 -1
- package/dist/errors-PflMPWBn.d.ts +0 -182
- package/dist/errors-PflMPWBn.d.ts.map +0 -1
- package/dist/fit-BKs0zxMI.js.map +0 -1
- package/dist/html-dom-B0I0QSbU.js.map +0 -1
- package/dist/namespaces-BVnNu7sg.js +0 -666
- package/dist/namespaces-BVnNu7sg.js.map +0 -1
- package/dist/notes-CJFJaG6M.js +0 -1724
- package/dist/notes-CJFJaG6M.js.map +0 -1
- package/dist/package-CaOLXSNi.d.ts.map +0 -1
- package/dist/pres-BcBR3CSM.d.ts +0 -202
- package/dist/pres-BcBR3CSM.d.ts.map +0 -1
- package/dist/presentation-DrJtj3-C.js.map +0 -1
- package/dist/presentation-types-CDjScsev.d.ts.map +0 -1
- package/dist/shapes-BemroU-z.js.map +0 -1
- package/dist/slide-ZqAdVvHV.d.ts.map +0 -1
- package/dist/text-B8SLEaZx.js +0 -11327
- package/dist/text-B8SLEaZx.js.map +0 -1
- package/dist/units-1kRzYShk.js +0 -231
- package/dist/units-1kRzYShk.js.map +0 -1
- package/dist/units-internal-Dg95w_YD.js.map +0 -1
- package/dist/zip.d.ts.map +0 -1
- package/dist/zip.js.map +0 -1
|
@@ -1,6 +1,9 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
1
|
+
import { l as warn, n as InvalidOptionError, o as UnsupportedFeatureError, r as MediaError } from "./errors-C4oENPcu.js";
|
|
2
|
+
import { _ as resolveCellMarginsInches, b as resolveTableColWidthsEmu, d as marginToEmu, s as getSmartParseNumber, t as autoPageLineHeightEmu, v as resolveInsetsEmu, x as resolveTableRowHeightEmu } from "./units-internal-XJlf-hOl.js";
|
|
3
|
+
import { D as emuToInches, I as EMU_PER_POINT } from "./table-cell-margins-EVg0EwxT.js";
|
|
4
|
+
import { t as pickDefined } from "./options-internal-WqgMf720.js";
|
|
5
|
+
import { t as BODY_INSET_DEFAULTS_EMU } from "./body-insets-Dw_BeWD_.js";
|
|
6
|
+
import { n as tableColCount, r as walkTableGrid } from "./grid-DikTjFey.js";
|
|
4
7
|
//#region src/measure/font-collection.ts
|
|
5
8
|
/**
|
|
6
9
|
* Font **collection** (`.ttc` / `.otc`) support for the write-side metrics.
|
|
@@ -272,7 +275,7 @@ function extractFontFace(data, index) {
|
|
|
272
275
|
* Width is summed from **raw `hmtx` advances with no kerning/shaping** (GPOS/GSUB
|
|
273
276
|
* are deliberately ignored): kerning almost always narrows a line, so raw advances
|
|
274
277
|
* over-estimate width — the conservative direction (shrink a touch too much, never
|
|
275
|
-
* overflow). See `docs/
|
|
278
|
+
* overflow). See `docs/contributing/design/text-fit.md` ("Font metrics provider").
|
|
276
279
|
*/
|
|
277
280
|
var OpentypeFontMetrics = class {
|
|
278
281
|
font;
|
|
@@ -342,7 +345,7 @@ let heuristicSingleton;
|
|
|
342
345
|
/**
|
|
343
346
|
* Shared, font-independent `FontMetrics` used when a named face has no registered
|
|
344
347
|
* metrics but the deck has opted into measured fit (some other face is registered).
|
|
345
|
-
* Approximate but conservative — see
|
|
348
|
+
* Approximate but conservative — see `heuristicCharRatio`. A deck with no
|
|
346
349
|
* registered metrics at all does not engage measured fit, so this never fires there.
|
|
347
350
|
*/
|
|
348
351
|
function getHeuristicFontMetrics() {
|
|
@@ -386,6 +389,7 @@ var FontMetricsRegistry = class {
|
|
|
386
389
|
return `${face.toLowerCase()}\0${bold ? 1 : 0}\0${italic ? 1 : 0}`;
|
|
387
390
|
}
|
|
388
391
|
set(face, metrics, opts) {
|
|
392
|
+
if (typeof face !== "string" || face.trim() === "") throw new InvalidOptionError("font/missing-typeface", `registerFontMetrics: \`face\` must be the non-empty family name your fontFace options use, got ${JSON.stringify(face)}.`);
|
|
389
393
|
this.map.set(this.key(face, !!opts?.bold, !!opts?.italic), metrics);
|
|
390
394
|
}
|
|
391
395
|
/** True if any variant of `face` has registered metrics. */
|
|
@@ -446,9 +450,9 @@ function makeRegistryResolver(registry, onHeuristic) {
|
|
|
446
450
|
* fallback and charges the registered font's `.notdef` advance instead, which is a single
|
|
447
451
|
* flat number unrelated to the glyph that actually paints. Unlike the other approximations
|
|
448
452
|
* here it is **not** conservative in a fixed direction: a `.notdef` wider than the real
|
|
449
|
-
* glyph over-reports (a phantom line
|
|
453
|
+
* glyph over-reports (a phantom line, which is safe), a narrower one under-reports and can drop a
|
|
450
454
|
* line, which is the overflow direction the resize bake has no safety net for. So it is
|
|
451
|
-
* surfaced rather than absorbed
|
|
455
|
+
* surfaced rather than absorbed. See `docs/contributing/design/text-fit.md` ("Font fallback").
|
|
452
456
|
*
|
|
453
457
|
* Runs whose face has no registered metrics at all are skipped: they measure through the
|
|
454
458
|
* cmap-less heuristic, which has no coverage to report, and the caller already flags them
|
|
@@ -481,12 +485,13 @@ function collectUncoveredCodepoints(paragraphs, registry, into) {
|
|
|
481
485
|
* Calibrated from `autofit-calibration.json` (Deck 1, `autofit-line-metrics`):
|
|
482
486
|
* the per-line `cy` delta is a **font-independent** 1.2117× at 12/18/32 pt across
|
|
483
487
|
* {Aptos, Aptos SemiBold, Calibri, Tahoma, Arial}, and PowerPoint vs LibreOffice
|
|
484
|
-
* agree to ≤ 0.041 pt. See
|
|
488
|
+
* agree to ≤ 0.041 pt. See "Findings behind the constants" in
|
|
489
|
+
* `docs/contributing/design/text-fit.md`.
|
|
485
490
|
*/
|
|
486
491
|
const SINGLE_LINE_PITCH = 1.2117;
|
|
487
|
-
/** PowerPoint's `fontScale` search grid: discrete 2.5% steps (
|
|
492
|
+
/** PowerPoint's `fontScale` search grid: discrete 2.5% steps (`docs/contributing/design/text-fit.md`). */
|
|
488
493
|
const FONT_SCALE_STEP_PCT = 2.5;
|
|
489
|
-
/** Floor for the shrink search (see `docs/
|
|
494
|
+
/** Floor for the shrink search (see `docs/contributing/design/text-fit.md` → Wrap simulator and solvers). */
|
|
490
495
|
const MIN_FONT_SCALE_PCT = 25;
|
|
491
496
|
/**
|
|
492
497
|
* Conservative safety factors applied by the shrink solver (not by the pure
|
|
@@ -526,7 +531,7 @@ const isWhitespace = (ch) => ch === " " || ch === " " || ch === "\xA0" || /\s/.t
|
|
|
526
531
|
* Known gap: no kinsoku (line-break prohibition) rules. PowerPoint will not start
|
|
527
532
|
* a line with `。` or `、` and hangs them past the right inset instead; this model
|
|
528
533
|
* breaks before them. That is a widest-line difference, not a line-count one, and
|
|
529
|
-
* `docs/
|
|
534
|
+
* `docs/contributing/design/text-fit.md` records it.
|
|
530
535
|
*/
|
|
531
536
|
function isCjkBreakCharacter(ch) {
|
|
532
537
|
const cp = ch.codePointAt(0) ?? 0;
|
|
@@ -593,8 +598,9 @@ function tokenizeParagraph(para, resolve, fontScalePct, widthSafety) {
|
|
|
593
598
|
* laid-out line width. `maxLineWidthPt` tracks the running line width and records
|
|
594
599
|
* it whenever a line completes (a wrap or the final line), so callers can measure
|
|
595
600
|
* the actual text extent, not just the box width. Trailing whitespace counts
|
|
596
|
-
* toward width
|
|
597
|
-
* direction, consistent with the wrap model erring wide.
|
|
601
|
+
* toward width, as PowerPoint counts it, which slightly over-reports — the
|
|
602
|
+
* conservative direction, consistent with the wrap model erring wide. The
|
|
603
|
+
* PowerPoint behaviour this follows is tabled in `docs/contributing/design/text-fit.md`.
|
|
598
604
|
*/
|
|
599
605
|
function countLines(tokens, innerWidthPt) {
|
|
600
606
|
let lines = 1;
|
|
@@ -637,7 +643,7 @@ function countLines(tokens, innerWidthPt) {
|
|
|
637
643
|
maxLineWidthPt
|
|
638
644
|
};
|
|
639
645
|
}
|
|
640
|
-
/** Max run size in a paragraph (line height follows the tallest run
|
|
646
|
+
/** Max run size in a paragraph (PowerPoint's line height follows the tallest run). */
|
|
641
647
|
function maxRunSizePt(para) {
|
|
642
648
|
let max = 0;
|
|
643
649
|
for (const run of para.runs) if (run.sizePt > max) max = run.sizePt;
|
|
@@ -651,7 +657,7 @@ function maxRunSizePt(para) {
|
|
|
651
657
|
* and the public layout-time measure API.
|
|
652
658
|
*/
|
|
653
659
|
function measureLayout(paragraphs, innerWidthPt, resolve, fontScalePct, lnSpcReductionPct, widthSafety = 1) {
|
|
654
|
-
if (innerWidthPt
|
|
660
|
+
if (!(innerWidthPt > 0)) return null;
|
|
655
661
|
let total = 0;
|
|
656
662
|
let lineCount = 0;
|
|
657
663
|
let widestLineWidthPt = 0;
|
|
@@ -681,14 +687,6 @@ function measureHeightPt(paragraphs, innerWidthPt, resolve, fontScalePct, lnSpcR
|
|
|
681
687
|
return result === null ? null : result.heightPt;
|
|
682
688
|
}
|
|
683
689
|
/**
|
|
684
|
-
* Inner height (points) the text needs at full size — the value to bake into the
|
|
685
|
-
* shape's `a:ext/@cy` for `fit: 'resize'` (`spAutoFit`). Unlike shrink, resize has
|
|
686
|
-
* **no safety net**: an under-estimate overflows (there is no text scaling
|
|
687
|
-
* fallback), so this errs **tall** — width is inflated (earlier wrap ⇒ more lines)
|
|
688
|
-
* and the laid-out height by the calibrated height factor, so the computed `cy` is
|
|
689
|
-
* ≥ PowerPoint's and ≥ the LibreOffice-rendered height across the resize oracle.
|
|
690
|
-
*/
|
|
691
|
-
/**
|
|
692
690
|
* Width used for wrap layout: the inner box width when wrapping, or unbounded for a
|
|
693
691
|
* non-wrapping frame (`wrap:false`) so each paragraph lays out as a single line. A
|
|
694
692
|
* non-wrapping frame's width constraint is enforced separately, against the widest
|
|
@@ -697,6 +695,14 @@ function measureHeightPt(paragraphs, innerWidthPt, resolve, fontScalePct, lnSpcR
|
|
|
697
695
|
function layoutWidthPt(box) {
|
|
698
696
|
return box.wrap === false ? Infinity : box.innerWidthPt;
|
|
699
697
|
}
|
|
698
|
+
/**
|
|
699
|
+
* Inner height (points) the text needs at full size — the value to bake into the
|
|
700
|
+
* shape's `a:ext/@cy` for `fit: 'resize'` (`spAutoFit`). Unlike shrink, resize has
|
|
701
|
+
* **no safety net**: an under-estimate overflows (there is no text scaling
|
|
702
|
+
* fallback), so this errs **tall** — width is inflated (earlier wrap ⇒ more lines)
|
|
703
|
+
* and the laid-out height by the calibrated height factor, so the computed `cy` is
|
|
704
|
+
* ≥ PowerPoint's and ≥ the LibreOffice-rendered height across the resize oracle.
|
|
705
|
+
*/
|
|
700
706
|
function solveResize(paragraphs, box, resolve) {
|
|
701
707
|
const h = measureHeightPt(paragraphs, layoutWidthPt(box), resolve, 100, 0, WIDTH_SAFETY_FACTOR);
|
|
702
708
|
if (h === null) return { kind: "unmeasurable" };
|
|
@@ -705,6 +711,16 @@ function solveResize(paragraphs, box, resolve) {
|
|
|
705
711
|
neededInnerHeightPt: h * HEIGHT_SAFETY_FACTOR
|
|
706
712
|
};
|
|
707
713
|
}
|
|
714
|
+
/**
|
|
715
|
+
* Largest `fontScale` (on PowerPoint's 2.5% grid) at which the text fits the box.
|
|
716
|
+
* Width and height both re-measured per scale. Conservative: because the model
|
|
717
|
+
* over-estimates, the returned scale is ≤ PowerPoint's and the text never overflows.
|
|
718
|
+
*
|
|
719
|
+
* `lnSpcReduction` is left at 0 — dropping `fontScale` alone is provably
|
|
720
|
+
* conservative (PowerPoint trades line-spacing reduction to keep the font *larger*,
|
|
721
|
+
* so a reduction-free fit is always ≤ PowerPoint's scale). The reduction ramp
|
|
722
|
+
* PowerPoint applies is recorded in `docs/contributing/design/text-fit.md`.
|
|
723
|
+
*/
|
|
708
724
|
function solveShrink(paragraphs, box, resolve) {
|
|
709
725
|
const width = layoutWidthPt(box);
|
|
710
726
|
const noWrap = box.wrap === false;
|
|
@@ -743,8 +759,6 @@ function solveShrink(paragraphs, box, resolve) {
|
|
|
743
759
|
* `ext.cy` for a given anchor. Everything here is about *reading* an authored object; the solving
|
|
744
760
|
* and the rewriting live in the siblings (`table-fit.ts`, `fit.ts`).
|
|
745
761
|
*/
|
|
746
|
-
const DEF_INS_LR_EMU = 91440;
|
|
747
|
-
const DEF_INS_TB_EMU = 45720;
|
|
748
762
|
const CRLF_RE = /\r*\n/g;
|
|
749
763
|
/** Normalize `slideObj.text` (string | TextProps | TextProps[]) to a run list. */
|
|
750
764
|
function normalizeRuns(obj) {
|
|
@@ -758,12 +772,12 @@ function normalizeRuns(obj) {
|
|
|
758
772
|
if (!Array.isArray(text) && typeof text === "object" && "text" in text) {
|
|
759
773
|
const t = text;
|
|
760
774
|
return [{
|
|
761
|
-
|
|
775
|
+
...pickDefined(t, ["text"]),
|
|
762
776
|
options: t.options ?? opts
|
|
763
777
|
}];
|
|
764
778
|
}
|
|
765
779
|
if (Array.isArray(text)) return text.map((t) => ({
|
|
766
|
-
|
|
780
|
+
...pickDefined(t, ["text"]),
|
|
767
781
|
options: t.options ?? opts
|
|
768
782
|
}));
|
|
769
783
|
return [];
|
|
@@ -778,7 +792,7 @@ function extractParagraphs(obj) {
|
|
|
778
792
|
/**
|
|
779
793
|
* Convert a run list (+ box-level default options) into a measurable
|
|
780
794
|
* `FitParagraph[]`, or null if empty. The single converter shared by the
|
|
781
|
-
* export-time pass (
|
|
795
|
+
* export-time pass (`extractParagraphs`) and the public layout-time
|
|
782
796
|
* `measureText` API, so a layout-time prediction and the baked export never drift.
|
|
783
797
|
*/
|
|
784
798
|
function buildFitParagraphs(runs, opts) {
|
|
@@ -840,27 +854,35 @@ function buildFitParagraphs(runs, opts) {
|
|
|
840
854
|
if (cur.length > 0) paras.push(toPara(cur, curParaOpts));
|
|
841
855
|
return paras.length > 0 ? paras : null;
|
|
842
856
|
}
|
|
843
|
-
/**
|
|
844
|
-
|
|
857
|
+
/**
|
|
858
|
+
* Resolve a text frame's insets (EMU): explicit `_bodyProp` (from `inset`) → `margin` →
|
|
859
|
+
* PowerPoint's defaults.
|
|
860
|
+
*
|
|
861
|
+
* Named for the question it answers, which is not the one `units-internal.resolveInsetsEmu`
|
|
862
|
+
* answers: that one takes a `margin` alone and returns `null` when the caller stated none, so
|
|
863
|
+
* an emitter can leave the attributes off. This one takes the whole option bag, consults
|
|
864
|
+
* `_bodyProp` first, and always produces four numbers, because a solver has to measure against
|
|
865
|
+
* something. Two exported functions with one name, imported from two places, was a coin-flip
|
|
866
|
+
* for anyone reading a call site.
|
|
867
|
+
*
|
|
868
|
+
* The `[T, R, B, L]` shuffle and the defaults both come from the modules that own them rather
|
|
869
|
+
* than being spelled again here.
|
|
870
|
+
*/
|
|
871
|
+
function resolveBodyInsetsEmu(opts) {
|
|
845
872
|
const bp = opts._bodyProp ?? {};
|
|
846
|
-
const margin = opts.margin;
|
|
847
873
|
let lIns = bp.lIns;
|
|
848
874
|
let rIns = bp.rIns;
|
|
849
875
|
let tIns = bp.tIns;
|
|
850
876
|
let bIns = bp.bIns;
|
|
851
|
-
if (lIns == null && rIns == null && tIns == null && bIns == null
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
rIns = marginToEmu(margin[1] ?? 0);
|
|
855
|
-
bIns = marginToEmu(margin[2] ?? 0);
|
|
856
|
-
lIns = marginToEmu(margin[3] ?? 0);
|
|
857
|
-
} else if (typeof margin === "number") lIns = rIns = tIns = bIns = marginToEmu(margin);
|
|
877
|
+
if (lIns == null && rIns == null && tIns == null && bIns == null) {
|
|
878
|
+
const insets = resolveInsetsEmu(opts.margin);
|
|
879
|
+
if (insets) ({l: lIns, r: rIns, t: tIns, b: bIns} = insets);
|
|
858
880
|
}
|
|
859
881
|
return {
|
|
860
|
-
lIns: lIns ??
|
|
861
|
-
rIns: rIns ??
|
|
862
|
-
tIns: tIns ??
|
|
863
|
-
bIns: bIns ??
|
|
882
|
+
lIns: lIns ?? BODY_INSET_DEFAULTS_EMU.left,
|
|
883
|
+
rIns: rIns ?? BODY_INSET_DEFAULTS_EMU.right,
|
|
884
|
+
tIns: tIns ?? BODY_INSET_DEFAULTS_EMU.top,
|
|
885
|
+
bIns: bIns ?? BODY_INSET_DEFAULTS_EMU.bottom
|
|
864
886
|
};
|
|
865
887
|
}
|
|
866
888
|
/**
|
|
@@ -878,7 +900,7 @@ function computeBox(obj, presLayout) {
|
|
|
878
900
|
const wEmu = getSmartParseNumber(opts.w, "X", presLayout);
|
|
879
901
|
const hEmu = getSmartParseNumber(opts.h, "Y", presLayout);
|
|
880
902
|
if (!(wEmu > 0) || !(hEmu > 0)) return null;
|
|
881
|
-
const { lIns, rIns, tIns, bIns } =
|
|
903
|
+
const { lIns, rIns, tIns, bIns } = resolveBodyInsetsEmu(opts);
|
|
882
904
|
const wrap = (opts._bodyProp ?? {}).wrap !== false;
|
|
883
905
|
const innerWidthPt = (wEmu - lIns - rIns) / EMU_PER_POINT;
|
|
884
906
|
const innerHeightPt = (hEmu - tIns - bIns) / EMU_PER_POINT;
|
|
@@ -897,46 +919,85 @@ function anchorTopShareOfDelta(opts) {
|
|
|
897
919
|
if (anchor === "b") return 1;
|
|
898
920
|
return .5;
|
|
899
921
|
}
|
|
900
|
-
/** Text/format options that a cell inherits from the table when it sets none itself (mirrors `gen/slide/objects/table.ts`). */
|
|
901
922
|
//#endregion
|
|
902
|
-
//#region src/
|
|
923
|
+
//#region src/gen/table/cell-inherit.ts
|
|
903
924
|
/**
|
|
904
|
-
* ts-pptx:
|
|
925
|
+
* ts-pptx: what a table cell inherits from its table.
|
|
905
926
|
*
|
|
906
|
-
* A
|
|
907
|
-
*
|
|
908
|
-
*
|
|
909
|
-
*
|
|
927
|
+
* A cell that states nothing takes the table's value, and two paths resolve that: the emitter,
|
|
928
|
+
* building the bag it hands to the text-body writer, and the measured-fit pass, building the
|
|
929
|
+
* effective text it lays out. They inherited **different lists** -- the measure side named
|
|
930
|
+
* `italic`, `charSpacing`, `lineSpacing` and `lineSpacingMultiple` that the emitter did not,
|
|
931
|
+
* while its docstring claimed to mirror the emitter's -- so a table-level `italic` was measured
|
|
932
|
+
* with italic metrics and emitted upright, and on `fit: 'shrink'` a table-level line spacing
|
|
933
|
+
* made the solver compute a taller layout and bake a smaller font that the cell then rendered
|
|
934
|
+
* at default spacing. `pptx.tableLayout()` reported geometry the file disagreed with.
|
|
935
|
+
*
|
|
936
|
+
* So the text keys are one list, shared. The second list is the emitter's alone, and is a
|
|
937
|
+
* statement rather than an omission: those keys either paint (`fill`, `color`, `border`,
|
|
938
|
+
* `underline`) or are not modelled by the fitter (`textDirection`, which does change how text
|
|
939
|
+
* lays out -- the measured-fit pass does not read it on a cell either way, and widening the
|
|
940
|
+
* fitter to handle vertical cell text is its own change).
|
|
941
|
+
*/
|
|
942
|
+
/**
|
|
943
|
+
* The keys both paths inherit: everything that decides how a cell's text is laid out.
|
|
944
|
+
*
|
|
945
|
+
* `margin` and `valign` are here rather than below because the fitter reads them -- insets
|
|
946
|
+
* shrink the box the text has to fit, and the anchor decides where a short line sits in it.
|
|
910
947
|
*/
|
|
911
|
-
const
|
|
948
|
+
const CELL_INHERITED_TEXT_KEYS = [
|
|
949
|
+
"align",
|
|
950
|
+
"bold",
|
|
951
|
+
"charSpacing",
|
|
912
952
|
"fontFace",
|
|
913
953
|
"fontSize",
|
|
914
|
-
"bold",
|
|
915
954
|
"italic",
|
|
916
|
-
"charSpacing",
|
|
917
|
-
"align",
|
|
918
955
|
"lineSpacing",
|
|
919
956
|
"lineSpacingMultiple",
|
|
920
|
-
"
|
|
921
|
-
"
|
|
957
|
+
"margin",
|
|
958
|
+
"valign"
|
|
959
|
+
];
|
|
960
|
+
/** The keys only the emitter inherits: they paint, or the fitter does not model them. */
|
|
961
|
+
const CELL_INHERITED_EMIT_KEYS = [
|
|
962
|
+
"border",
|
|
963
|
+
"color",
|
|
964
|
+
"fill",
|
|
965
|
+
"textDirection",
|
|
966
|
+
"underline"
|
|
922
967
|
];
|
|
923
|
-
/**
|
|
968
|
+
/** Every key the emitter inherits from the table onto a cell. */
|
|
969
|
+
const CELL_INHERITED_KEYS = [...CELL_INHERITED_TEXT_KEYS, ...CELL_INHERITED_EMIT_KEYS];
|
|
970
|
+
//#endregion
|
|
971
|
+
//#region src/measure/table-fit.ts
|
|
972
|
+
/**
|
|
973
|
+
* ts-pptx: measuring a table's row heights and cell fits
|
|
974
|
+
*
|
|
975
|
+
* A table is measured cell by cell against its resolved grid: each cell inherits the table's text
|
|
976
|
+
* options where it sets none, gets its own margins resolved to EMU insets, and is laid out inside
|
|
977
|
+
* the column span it actually occupies. A merged region is measured once, at its origin, across
|
|
978
|
+
* the columns it covers — which is what `walkTableGrid` exists to hand out.
|
|
979
|
+
*/
|
|
980
|
+
/**
|
|
981
|
+
* Effective cell options: the cell's own values, with table-level values filled in where unset.
|
|
982
|
+
*
|
|
983
|
+
* The keys are `CELL_INHERITED_TEXT_KEYS` (`gen/table/cell-inherit.ts`), the half of the
|
|
984
|
+
* emitter's list that decides how text lays out. This used to be its own list, claiming in its
|
|
985
|
+
* docstring to mirror the emitter's while naming four keys the emitter did not.
|
|
986
|
+
*
|
|
987
|
+
* PowerPoint has no text-autofit for a table cell — `a:tcPr` carries no autofit, and the app
|
|
988
|
+
* ignores a `normAutofit` inside one; rows auto-grow instead. So a cell's `fit: 'shrink'` is
|
|
989
|
+
* honoured by baking a *reduced literal font size* onto its runs, which both PowerPoint and
|
|
990
|
+
* LibreOffice render identically with no edit or resize. That is what makes these options worth
|
|
991
|
+
* resolving here: the shrink is computed against the cell's effective text, not the table's.
|
|
992
|
+
*/
|
|
924
993
|
function effectiveCellOpts(cellOpts, tableOpts) {
|
|
925
994
|
const merged = { ...cellOpts };
|
|
926
|
-
for (const k of
|
|
995
|
+
for (const k of CELL_INHERITED_TEXT_KEYS) if (merged[k] === void 0 && tableOpts[k] !== void 0) merged[k] = tableOpts[k];
|
|
927
996
|
return merged;
|
|
928
997
|
}
|
|
929
998
|
/** Resolve a cell's margins to EMU insets, mirroring `gen/slide/objects/table.ts` (array is `[T,R,B,L]`, inches; see `marginToEmu`). */
|
|
930
999
|
function resolveCellInsetsEmu(margin) {
|
|
931
|
-
|
|
932
|
-
if (typeof m === "number") m = [
|
|
933
|
-
m,
|
|
934
|
-
m,
|
|
935
|
-
m,
|
|
936
|
-
m
|
|
937
|
-
];
|
|
938
|
-
if (!Array.isArray(m) || m.length !== 4 || m.some((v) => typeof v !== "number" || !isFinite(v))) m = DEF_CELL_MARGIN_IN;
|
|
939
|
-
const arr = m;
|
|
1000
|
+
const arr = resolveCellMarginsInches(margin);
|
|
940
1001
|
return {
|
|
941
1002
|
marT: marginToEmu(arr[0]),
|
|
942
1003
|
marR: marginToEmu(arr[1]),
|
|
@@ -945,12 +1006,17 @@ function resolveCellInsetsEmu(margin) {
|
|
|
945
1006
|
};
|
|
946
1007
|
}
|
|
947
1008
|
/**
|
|
948
|
-
* Bake a reduced font size onto a cell's runs by factor `f` (< 1).
|
|
949
|
-
*
|
|
950
|
-
*
|
|
1009
|
+
* Bake a reduced font size onto a cell's runs by factor `f` (< 1). The cell's options and runs are
|
|
1010
|
+
* replaced with scaled copies, never edited in place. The cell itself is the stored slide model,
|
|
1011
|
+
* so `applyMeasuredFit` puts the authored options and runs back once the write is built.
|
|
1012
|
+
*
|
|
1013
|
+
* The shrunk size never goes below {@link MIN_FONT_SIZE_PT}, the emitter's own bound. It used to
|
|
1014
|
+
* have no minimum, so a 1pt cell taken to the 25% scale floor was baked as 0.2pt and
|
|
1015
|
+
* `clampFontSizeSz` then warned that "fontSize 0.2 is outside the valid range" -- a value the
|
|
1016
|
+
* caller never stated and cannot act on. Their input was valid; only our scaling was out of range.
|
|
951
1017
|
*/
|
|
952
1018
|
function scaleCellFontSizes(cell, eff, f) {
|
|
953
|
-
const shrink = (sizePt) => Math.floor(sizePt * f * 10) / 10;
|
|
1019
|
+
const shrink = (sizePt) => Math.max(1, Math.floor(sizePt * f * 10) / 10);
|
|
954
1020
|
const baseSize = Number(eff.fontSize ?? 12);
|
|
955
1021
|
cell.options = {
|
|
956
1022
|
...cell.options,
|
|
@@ -964,46 +1030,33 @@ function scaleCellFontSizes(cell, eff, f) {
|
|
|
964
1030
|
}
|
|
965
1031
|
} : run);
|
|
966
1032
|
}
|
|
967
|
-
/** Grid column count of a table (sums the first row's colspans), mirroring `gen/slide/objects/table.ts`. */
|
|
968
|
-
function tableColCount(rows) {
|
|
969
|
-
const first = rows[0];
|
|
970
|
-
return first ? first.reduce((n, c) => n + (Number(c?.options?.colspan) || 1), 0) : 0;
|
|
971
|
-
}
|
|
972
1033
|
/**
|
|
973
|
-
*
|
|
974
|
-
*
|
|
975
|
-
*
|
|
976
|
-
* the
|
|
977
|
-
*
|
|
1034
|
+
* Resolve a table's grid the one way every consumer has to see it.
|
|
1035
|
+
*
|
|
1036
|
+
* Three sites derived these four things: `pptx.tableLayout()`, the export-time measured-fit
|
|
1037
|
+
* pass, and the table emitter (the height alone). They already disagreed — only the fit pass
|
|
1038
|
+
* read `cy`, the EMU height the auto-pager and the fit pass itself stamp onto the options, so
|
|
1039
|
+
* `addTable(rows, { cy })` with no `h` gave a file whose rows are pinned and a
|
|
1040
|
+
* `pptx.tableLayout()` that reported every row auto-height. That is the same drift the one
|
|
1041
|
+
* reading of `rowH` closed, arriving through a second option, which is why the answer is a
|
|
1042
|
+
* shared resolver rather than a third correction.
|
|
1043
|
+
*
|
|
1044
|
+
* `w` defaults to `75%` of the slide, matching what the emitter falls back to.
|
|
1045
|
+
* @param rows - the table's authored rows
|
|
1046
|
+
* @param opts - the table's options
|
|
1047
|
+
* @param presLayout - the presentation layout, for resolving percentages
|
|
978
1048
|
*/
|
|
979
|
-
function
|
|
980
|
-
const
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
const colStart = col;
|
|
991
|
-
const colEnd = Math.min(colStart + colspan, numCols);
|
|
992
|
-
for (let c = colStart; c < colEnd; c++) occupied[c] = rowspan;
|
|
993
|
-
col = colEnd;
|
|
994
|
-
yield {
|
|
995
|
-
cell,
|
|
996
|
-
row: r,
|
|
997
|
-
col: colStart,
|
|
998
|
-
rowSpan: rowspan,
|
|
999
|
-
colSpan: colEnd - colStart
|
|
1000
|
-
};
|
|
1001
|
-
}
|
|
1002
|
-
for (let c = 0; c < numCols; c++) {
|
|
1003
|
-
const cur = occupied[c] ?? 0;
|
|
1004
|
-
if (cur > 0) occupied[c] = cur - 1;
|
|
1005
|
-
}
|
|
1006
|
-
}
|
|
1049
|
+
function resolveTableGridEmu(rows, opts, presLayout) {
|
|
1050
|
+
const numRows = rows.length;
|
|
1051
|
+
const numCols = tableColCount(rows);
|
|
1052
|
+
const cxEmu = opts.w != null ? getSmartParseNumber(opts.w, "X", presLayout) : getSmartParseNumber("75%", "X", presLayout);
|
|
1053
|
+
const tableHeightEmu = opts.h != null ? getSmartParseNumber(opts.h, "Y", presLayout) : typeof opts.cy === "number" ? opts.cy : 0;
|
|
1054
|
+
return {
|
|
1055
|
+
numCols,
|
|
1056
|
+
colWidthsEmu: resolveTableColWidthsEmu(opts.colW, cxEmu, numCols),
|
|
1057
|
+
tableHeightEmu,
|
|
1058
|
+
rowHeightEmu: (rowIndex) => resolveTableRowHeightEmu(opts.rowH, rowIndex, tableHeightEmu, numRows)
|
|
1059
|
+
};
|
|
1007
1060
|
}
|
|
1008
1061
|
/**
|
|
1009
1062
|
* Compute per-cell geometry (inches) for a table laid out at `opts.x`/`y`/`w` — the
|
|
@@ -1024,31 +1077,21 @@ function computeTableLayout(rows, opts, presLayout, registry) {
|
|
|
1024
1077
|
};
|
|
1025
1078
|
if (!rows || rows.length === 0 || !rows[0]) return empty;
|
|
1026
1079
|
const numRows = rows.length;
|
|
1027
|
-
const
|
|
1080
|
+
const grid = resolveTableGridEmu(rows, opts, presLayout);
|
|
1081
|
+
const numCols = grid.numCols;
|
|
1028
1082
|
if (!(numCols > 0)) return empty;
|
|
1029
1083
|
const o = opts;
|
|
1030
1084
|
const tableXEmu = opts.x != null ? getSmartParseNumber(opts.x, "X", presLayout) : 0;
|
|
1031
1085
|
const tableYEmu = opts.y != null ? getSmartParseNumber(opts.y, "Y", presLayout) : 0;
|
|
1032
|
-
const
|
|
1033
|
-
const colWidthsEmu = resolveTableColWidthsEmu(opts.colW, cxEmu, numCols);
|
|
1086
|
+
const colWidthsEmu = grid.colWidthsEmu;
|
|
1034
1087
|
const colXEmu = new Array(numCols + 1).fill(0);
|
|
1035
1088
|
for (let c = 0; c < numCols; c++) colXEmu[c + 1] = (colXEmu[c] ?? 0) + (colWidthsEmu[c] ?? 0);
|
|
1036
|
-
const
|
|
1037
|
-
const explicitRowHEmu = (r) => {
|
|
1038
|
-
if (Array.isArray(opts.rowH)) {
|
|
1039
|
-
const v = opts.rowH[r];
|
|
1040
|
-
if (typeof v === "number" && isFinite(v)) return inch2Emu(v);
|
|
1041
|
-
return tableHeightEmu > 0 ? Math.round(tableHeightEmu / numRows) : null;
|
|
1042
|
-
}
|
|
1043
|
-
if (opts.rowH != null && !isNaN(Number(opts.rowH))) return inch2Emu(Number(opts.rowH));
|
|
1044
|
-
if (tableHeightEmu > 0) return Math.round(tableHeightEmu / numRows);
|
|
1045
|
-
return null;
|
|
1046
|
-
};
|
|
1089
|
+
const explicitRowHEmu = grid.rowHeightEmu;
|
|
1047
1090
|
const resolve = makeRegistryResolver(registry);
|
|
1048
|
-
const defLineEmu =
|
|
1091
|
+
const defLineEmu = autoPageLineHeightEmu(12);
|
|
1049
1092
|
const estimateContentHeightEmu = (cell, eff, innerWidthPt) => {
|
|
1050
1093
|
const fontSizePt = Number(eff.fontSize ?? 12) || 12;
|
|
1051
|
-
const oneLineEmu =
|
|
1094
|
+
const oneLineEmu = autoPageLineHeightEmu(fontSizePt);
|
|
1052
1095
|
if (!(innerWidthPt > 0)) return oneLineEmu;
|
|
1053
1096
|
const paragraphs = extractParagraphs({
|
|
1054
1097
|
text: cell.text,
|
|
@@ -1087,7 +1130,10 @@ function computeTableLayout(rows, opts, presLayout, registry) {
|
|
|
1087
1130
|
if (contentEmu > (rowHeightsEmu[p.row] ?? 0)) rowHeightsEmu[p.row] = contentEmu;
|
|
1088
1131
|
}
|
|
1089
1132
|
}
|
|
1090
|
-
for (let r = 0; r < numRows; r++) if ((rowHeightsEmu[r] ?? 0) <= 0)
|
|
1133
|
+
for (let r = 0; r < numRows; r++) if ((rowHeightsEmu[r] ?? 0) <= 0) {
|
|
1134
|
+
rowHeightsEmu[r] = defLineEmu;
|
|
1135
|
+
rowExact[r] = false;
|
|
1136
|
+
}
|
|
1091
1137
|
const rowYEmu = new Array(numRows + 1).fill(0);
|
|
1092
1138
|
for (let r = 0; r < numRows; r++) rowYEmu[r + 1] = (rowYEmu[r] ?? 0) + (rowHeightsEmu[r] ?? 0);
|
|
1093
1139
|
return {
|
|
@@ -1112,28 +1158,21 @@ function computeTableLayout(rows, opts, presLayout, registry) {
|
|
|
1112
1158
|
heightExact: rowExact.every(Boolean)
|
|
1113
1159
|
};
|
|
1114
1160
|
}
|
|
1115
|
-
/**
|
|
1116
|
-
* Build the `MetricsResolver` both the export pass and `measureText` use, so they
|
|
1117
|
-
* agree run-for-run: exact registered metrics → conservative heuristic for any
|
|
1118
|
-
* **named** face without exact metrics → `undefined` only for an unnamed
|
|
1119
|
-
* (theme-default) face that cannot be guessed. `onHeuristic` is called with each
|
|
1120
|
-
* named face that fell back to the heuristic (for the export pass's warn-once).
|
|
1121
|
-
*/
|
|
1122
1161
|
//#endregion
|
|
1123
1162
|
//#region src/measure/fit.ts
|
|
1124
1163
|
/** Map the public {@link MeasureTextOptions} onto the internal run-option shape. */
|
|
1125
1164
|
function measureOptsToRunOpts(opts) {
|
|
1126
|
-
return
|
|
1127
|
-
fontSize
|
|
1128
|
-
fontFace
|
|
1129
|
-
bold
|
|
1130
|
-
italic
|
|
1131
|
-
charSpacing
|
|
1132
|
-
lineSpacing
|
|
1133
|
-
lineSpacingMultiple
|
|
1134
|
-
paraSpaceBefore
|
|
1135
|
-
paraSpaceAfter
|
|
1136
|
-
|
|
1165
|
+
return pickDefined(opts, [
|
|
1166
|
+
"fontSize",
|
|
1167
|
+
"fontFace",
|
|
1168
|
+
"bold",
|
|
1169
|
+
"italic",
|
|
1170
|
+
"charSpacing",
|
|
1171
|
+
"lineSpacing",
|
|
1172
|
+
"lineSpacingMultiple",
|
|
1173
|
+
"paraSpaceBefore",
|
|
1174
|
+
"paraSpaceAfter"
|
|
1175
|
+
]);
|
|
1137
1176
|
}
|
|
1138
1177
|
/** A fresh result each call: the arrays are caller-owned, so they cannot be shared. */
|
|
1139
1178
|
const unmeasurable = () => ({
|
|
@@ -1165,11 +1204,11 @@ function formatCodepoints(cps) {
|
|
|
1165
1204
|
* Layout-time text measurement against registered metrics — the public engine
|
|
1166
1205
|
* behind `pptx.measureText()`. Uses the **same** calibrated wrap model, resolver
|
|
1167
1206
|
* semantics, and conservative safety factors as the export-time bake
|
|
1168
|
-
* (
|
|
1207
|
+
* (`applyMeasuredFit` / {@link solveResize} / {@link solveShrink}), so a
|
|
1169
1208
|
* layout-time prediction matches the value the export would bake **for any deck
|
|
1170
1209
|
* that opted into measured fit (i.e. registered at least one face)**.
|
|
1171
1210
|
*
|
|
1172
|
-
* With an **empty** registry the two intentionally diverge:
|
|
1211
|
+
* With an **empty** registry the two intentionally diverge: `applyMeasuredFit`
|
|
1173
1212
|
* treats "no metrics" as "not opted in" and bakes nothing, while this returns
|
|
1174
1213
|
* heuristic numbers so the API is useful with zero setup. Check
|
|
1175
1214
|
* `approximatedFaces` if that distinction matters.
|
|
@@ -1182,9 +1221,13 @@ function formatCodepoints(cps) {
|
|
|
1182
1221
|
* rather than tall — there is no font fallback here, only the font's `.notdef` advance.
|
|
1183
1222
|
*/
|
|
1184
1223
|
function measureText(registry, text, opts) {
|
|
1224
|
+
const inset = opts.insetIn ?? 0;
|
|
1225
|
+
requireFiniteInches(opts.wIn, "wIn", "measureText");
|
|
1226
|
+
requireFiniteInches(inset, "insetIn", "measureText");
|
|
1227
|
+
if (opts.fontSize !== void 0 && !(Number.isFinite(opts.fontSize) && opts.fontSize > 0)) throw new InvalidOptionError("font/size-not-positive", `measureText: \`fontSize\` must be a positive number of points, got ${String(opts.fontSize)}.`);
|
|
1228
|
+
if (!(opts.wIn > 2 * inset)) throw new InvalidOptionError("coord/not-positive", `measureText: \`wIn\` ${opts.wIn} leaves no width inside an inset of ${inset} on each side.`);
|
|
1185
1229
|
const paragraphs = buildFitParagraphs(typeof text === "string" || typeof text === "number" ? [{ text: String(text) }] : Array.isArray(text) ? text : [], measureOptsToRunOpts(opts));
|
|
1186
1230
|
if (!paragraphs) return unmeasurable();
|
|
1187
|
-
const inset = opts.insetIn ?? 0;
|
|
1188
1231
|
const innerWidthPt = (opts.wIn - 2 * inset) * 72;
|
|
1189
1232
|
const heuristicFaces = /* @__PURE__ */ new Set();
|
|
1190
1233
|
const resolve = makeRegistryResolver(registry, (face) => heuristicFaces.add(face));
|
|
@@ -1200,8 +1243,12 @@ function measureText(registry, text, opts) {
|
|
|
1200
1243
|
measurable: true,
|
|
1201
1244
|
approximatedFaces: [...heuristicFaces],
|
|
1202
1245
|
uncoveredCodepoints: flattenCodepoints(uncoveredByFace),
|
|
1203
|
-
fitsBox: (hIn) =>
|
|
1246
|
+
fitsBox: (hIn) => {
|
|
1247
|
+
requireBoxHeight(hIn, "fitsBox");
|
|
1248
|
+
return heightPt <= hIn * 72;
|
|
1249
|
+
},
|
|
1204
1250
|
shrinkScaleFor: (hIn) => {
|
|
1251
|
+
requireBoxHeight(hIn, "shrinkScaleFor");
|
|
1205
1252
|
const box = {
|
|
1206
1253
|
innerWidthPt,
|
|
1207
1254
|
innerHeightPt: hIn * 72
|
|
@@ -1212,6 +1259,20 @@ function measureText(registry, text, opts) {
|
|
|
1212
1259
|
}
|
|
1213
1260
|
};
|
|
1214
1261
|
}
|
|
1262
|
+
/** Refuse a measure that is not a finite number of inches, naming the option and the call. */
|
|
1263
|
+
function requireFiniteInches(value, name, call) {
|
|
1264
|
+
if (typeof value !== "number" || !Number.isFinite(value)) throw new InvalidOptionError("coord/non-finite", `${call}: \`${name}\` must be a finite number of inches; got ${String(value)}.`);
|
|
1265
|
+
}
|
|
1266
|
+
/**
|
|
1267
|
+
* Refuse a box height a fit cannot be tested against: not a finite number, or not above 0. A `NaN`
|
|
1268
|
+
* height made `fitsBox` answer `false` and `shrinkScaleFor` answer `100`, both as if measured.
|
|
1269
|
+
* @param hIn - the caller's inner box height, in inches
|
|
1270
|
+
* @param call - the method it was passed to, opening the message
|
|
1271
|
+
*/
|
|
1272
|
+
function requireBoxHeight(hIn, call) {
|
|
1273
|
+
requireFiniteInches(hIn, "hIn", call);
|
|
1274
|
+
if (!(hIn > 0)) throw new InvalidOptionError("coord/not-positive", `${call}: \`hIn\` must be greater than 0; got ${String(hIn)}.`);
|
|
1275
|
+
}
|
|
1215
1276
|
/**
|
|
1216
1277
|
* Apply measured fit across every slide. For each text box that opts in via
|
|
1217
1278
|
* `fit:'shrink'` or `fit:'resize'` and whose font has registered metrics, this
|
|
@@ -1225,9 +1286,20 @@ function measureText(registry, text, opts) {
|
|
|
1225
1286
|
* Safe to call with an empty registry (no-op). Warns once if any opted-in box could
|
|
1226
1287
|
* not be measured (missing metrics), and once if one held code points its registered
|
|
1227
1288
|
* face has no glyph for, so neither kind of overflow is silently ignored.
|
|
1289
|
+
*
|
|
1290
|
+
* Every rewrite lands on the stored slide model, because that is what the emitters read.
|
|
1291
|
+
* The returned function puts each authored value back; call it once the XML is built. Left
|
|
1292
|
+
* baked, a second write found a text box's object-form `fit` and skipped it, and measured a
|
|
1293
|
+
* resized box or a shrunk cell from this write's result, so metrics registered in between
|
|
1294
|
+
* never reached those shapes.
|
|
1295
|
+
* @returns the step that restores every value this pass rewrote
|
|
1228
1296
|
*/
|
|
1229
1297
|
function applyMeasuredFit(slides, registry) {
|
|
1230
|
-
|
|
1298
|
+
const restores = [];
|
|
1299
|
+
const restore = () => {
|
|
1300
|
+
for (const undo of restores.splice(0).reverse()) undo();
|
|
1301
|
+
};
|
|
1302
|
+
if (registry.size === 0) return restore;
|
|
1231
1303
|
const heuristicFaces = /* @__PURE__ */ new Set();
|
|
1232
1304
|
const resolve = makeRegistryResolver(registry, (face) => heuristicFaces.add(face));
|
|
1233
1305
|
const unmeasuredShrink = /* @__PURE__ */ new Set();
|
|
@@ -1248,18 +1320,9 @@ function applyMeasuredFit(slides, registry) {
|
|
|
1248
1320
|
const rows = tableObj.arrTabRows;
|
|
1249
1321
|
if (!rows || rows.length === 0 || !rows[0]) return;
|
|
1250
1322
|
const tableOpts = tableObj.options ?? {};
|
|
1251
|
-
const
|
|
1252
|
-
const numCols = tableColCount(rows);
|
|
1323
|
+
const { numCols, colWidthsEmu, rowHeightEmu: pinnedRowHeightEmu } = resolveTableGridEmu(rows, tableOpts, layout);
|
|
1253
1324
|
if (!(numCols > 0)) return;
|
|
1254
|
-
const
|
|
1255
|
-
const colWidthsEmu = resolveTableColWidthsEmu(tableOpts.colW, cxEmu, numCols);
|
|
1256
|
-
const tableHeightEmu = tableOpts.h != null ? getSmartParseNumber(tableOpts.h, "Y", layout) : typeof tableOpts.cy === "number" ? tableOpts.cy : 0;
|
|
1257
|
-
const rowHeightEmu = (rIdx) => {
|
|
1258
|
-
if (Array.isArray(tableOpts.rowH) && tableOpts.rowH[rIdx]) return inch2Emu(Number(tableOpts.rowH[rIdx]));
|
|
1259
|
-
if (tableOpts.rowH != null && !Array.isArray(tableOpts.rowH) && !isNaN(Number(tableOpts.rowH))) return inch2Emu(Number(tableOpts.rowH));
|
|
1260
|
-
if (tableHeightEmu > 0) return Math.round(tableHeightEmu / numRows);
|
|
1261
|
-
return 0;
|
|
1262
|
-
};
|
|
1325
|
+
const rowHeightEmu = (rIdx) => pinnedRowHeightEmu(rIdx) ?? 0;
|
|
1263
1326
|
for (const { cell, row: r, col: colStart, colSpan, rowSpan } of walkTableGrid(rows, numCols)) {
|
|
1264
1327
|
const colEnd = colStart + colSpan;
|
|
1265
1328
|
const cellOpts = cell?.options ?? {};
|
|
@@ -1294,7 +1357,10 @@ function applyMeasuredFit(slides, registry) {
|
|
|
1294
1357
|
}, resolve);
|
|
1295
1358
|
if (outcome.kind === "shrink") {
|
|
1296
1359
|
const f = outcome.result.fontScalePct / 100;
|
|
1297
|
-
if (f < 1)
|
|
1360
|
+
if (f < 1) {
|
|
1361
|
+
restores.push(restorer(cell, "options"), restorer(cell, "text"));
|
|
1362
|
+
scaleCellFontSizes(cell, eff, f);
|
|
1363
|
+
}
|
|
1298
1364
|
} else if (outcome.kind === "unmeasurable") collectUnmeasured(paragraphs, unmeasuredShrink);
|
|
1299
1365
|
}
|
|
1300
1366
|
};
|
|
@@ -1321,21 +1387,24 @@ function applyMeasuredFit(slides, registry) {
|
|
|
1321
1387
|
const outcome = solveShrink(paragraphs, box, resolve);
|
|
1322
1388
|
if (outcome.kind === "shrink") {
|
|
1323
1389
|
const { fontScalePct, lnSpcReductionPct } = outcome.result;
|
|
1324
|
-
|
|
1390
|
+
const shrink = {
|
|
1325
1391
|
type: "shrink",
|
|
1326
|
-
fontScale: fontScalePct
|
|
1327
|
-
lnSpcReduction: lnSpcReductionPct || void 0
|
|
1392
|
+
fontScale: fontScalePct
|
|
1328
1393
|
};
|
|
1394
|
+
if (lnSpcReductionPct) shrink.lnSpcReduction = lnSpcReductionPct;
|
|
1395
|
+
restores.push(restorer(options, "fit"));
|
|
1396
|
+
options.fit = shrink;
|
|
1329
1397
|
} else if (outcome.kind === "unmeasurable") collectUnmeasured(paragraphs, unmeasuredShrink);
|
|
1330
1398
|
} else {
|
|
1331
1399
|
const outcome = solveResize(paragraphs, box, resolve);
|
|
1332
1400
|
if (outcome.kind === "resize") {
|
|
1333
1401
|
const opts = obj.options;
|
|
1334
|
-
const { tIns, bIns } =
|
|
1402
|
+
const { tIns, bIns } = resolveBodyInsetsEmu(opts);
|
|
1335
1403
|
const oldHeightEmu = getSmartParseNumber(opts.h, "Y", layout);
|
|
1336
1404
|
const newHeightEmu = Math.round(outcome.neededInnerHeightPt * EMU_PER_POINT) + tIns + bIns;
|
|
1337
1405
|
const oldYEmu = getSmartParseNumber(opts.y, "Y", layout);
|
|
1338
1406
|
const shiftEmu = Math.round((newHeightEmu - oldHeightEmu) * anchorTopShareOfDelta(opts));
|
|
1407
|
+
restores.push(restorer(opts, "h"), restorer(opts, "y"));
|
|
1339
1408
|
opts.h = `${newHeightEmu}emu`;
|
|
1340
1409
|
if (shiftEmu !== 0) opts.y = `${oldYEmu - shiftEmu}emu`;
|
|
1341
1410
|
} else collectUnmeasured(paragraphs, unmeasuredResize);
|
|
@@ -1349,8 +1418,21 @@ function applyMeasuredFit(slides, registry) {
|
|
|
1349
1418
|
warn("measure/uncovered-codepoints", `Measured fit charged the .notdef advance for code point(s) the registered face does not cover (${perFace}) — PowerPoint renders those from a substituted font, so the measured width can be wrong in EITHER direction. Too narrow drops a line and the text overflows the baked size, the one case measured fit does not err safe. Register a face that covers them, or set an explicit fontFace on those runs.`);
|
|
1350
1419
|
}
|
|
1351
1420
|
if (heuristicFaces.size > 0) warn("measure/heuristic-metrics", `Note: measured fit used a conservative average-advance estimate for font(s) [${[...heuristicFaces].join(", ")}] — no exact metrics registered. Fit is approximate (may shrink/grow more than necessary). Call pptx.registerFontMetrics(face, fontFilePathOrBytes) for an exact fit.`);
|
|
1421
|
+
return restore;
|
|
1422
|
+
}
|
|
1423
|
+
/**
|
|
1424
|
+
* The step that puts `target[key]` back as it stands now, an absent key included, so a restored
|
|
1425
|
+
* option bag is the authored one rather than one holding an `undefined` the caller never wrote.
|
|
1426
|
+
*/
|
|
1427
|
+
function restorer(target, key) {
|
|
1428
|
+
const had = Object.hasOwn(target, key);
|
|
1429
|
+
const value = target[key];
|
|
1430
|
+
return () => {
|
|
1431
|
+
if (had) target[key] = value;
|
|
1432
|
+
else delete target[key];
|
|
1433
|
+
};
|
|
1352
1434
|
}
|
|
1353
1435
|
//#endregion
|
|
1354
|
-
export {
|
|
1436
|
+
export { getHeuristicFontMetrics as _, CELL_INHERITED_KEYS as a, isFontCollection as b, HEIGHT_SAFETY_FACTOR as c, WIDTH_SAFETY_FACTOR as d, measureHeightPt as f, FontMetricsRegistry as g, solveShrink as h, computeTableLayout as i, MIN_FONT_SCALE_PCT as l, solveResize as m, measureText as n, buildFitParagraphs as o, measureLayout as p, requireBoxHeight as r, FONT_SCALE_STEP_PCT as s, applyMeasuredFit as t, SINGLE_LINE_PITCH as u, makeRegistryResolver as v, listFontFaces as x, parseFontMetrics as y };
|
|
1355
1437
|
|
|
1356
|
-
//# sourceMappingURL=fit-
|
|
1438
|
+
//# sourceMappingURL=fit-Cb7naFj6.js.map
|