@openpresentation/opf-pptx 0.12.0 → 0.12.1

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/dist/index.js CHANGED
@@ -3,6 +3,7 @@ import {autoNumScheme, deriveListNumbering, withDisplayedNumbers} from './number
3
3
  import {isFaceStyleSuffix} from './font-weights.js';
4
4
  import {importTableFrames} from './table-import.js';
5
5
  import {applyChartFonts, applyPitchFamilies, finalizeFontsUsed, fontPitchFamilies} from './package-fonts.js';
6
+ import {giveNotesMastersOwnThemes} from './master-themes.js';
6
7
  import {readChartCategoryHeading,writeChartCategoryHeading} from './chart-workbook.js';
7
8
  import {CHARTEX_FALLBACK,resolveChartType,chartTypeFromNative,applyChartConstruct,NATIVE_CHART_ELEMENTS} from './chart-types.js';
8
9
  import {attachChartexParts,chartFromChartex,CHARTEX_GRAPHIC_DATA_URI} from './chartex.js';
@@ -34,7 +35,7 @@ import {placeLogos, importLogo, importLogoPlaceholders, logoName, LOGO_TAG} from
34
35
  import {nativeBackgroundFill, nativeImageBackgroundFill, nativePatternPreset} from './background.js';
35
36
  import {importBackground} from './background-import.js';
36
37
  import {themeSlotColors, writeThemeColors, schemeColorValue, schemeBackgroundFill, schemeBackgroundValue, defaultTextSchemeValues, tableTextSchemeValue, solidColorXml, writeMasterBackground, inheritLayoutBackground, readThemeSlotColors, recoverColorScheme, recoverTheme, presentationThemePath} from './theme-colors.js';
37
- import {languageDiagnostics, observeLanguage, observedRtl, partScriptFonts, physicalAlignment, planScriptFonts, reconcileLanguage} from './script-fonts.js';
38
+ import {languageDiagnostics, observeLanguage, observedRtl, partScriptFonts, physicalAlignment, planScriptFonts, reconcileLanguage, stripRunScriptFonts, themeEastAsianFromLatin} from './script-fonts.js';
38
39
  import { webpToPng, svgToPng, readLocalFile } from '#image-fallback';
39
40
  import { prepareSvg, svgDataUriBytes, svgRasterScale, svgBlipRelationship, attachSvgPictures } from './svg-image.js';
40
41
  import { rasterMetadata, pictureTransform, normalizeImageOrientation } from './image-geometry.js';
@@ -3322,6 +3323,7 @@ async function normalizePptxZip(raw, context) {
3322
3323
  }
3323
3324
 
3324
3325
  writeNativeFurnitureMasters(output, context);
3326
+ giveNotesMastersOwnThemes(output);
3325
3327
  finalizeFontsUsed(output);
3326
3328
  // Document references record evidence from the final normalized parts.
3327
3329
  if (context.documentProvenance) {
@@ -3400,6 +3402,7 @@ function normalizePartBytes(path, bytes, context, renameMaps, entries, imageMeta
3400
3402
  if (/^ppt\/slideMasters\/slideMaster\d+\.xml$/.test(path)) xml = themeMasterBulletFonts(xml);
3401
3403
  if (context.masterBackground && path === 'ppt/slideMasters/slideMaster1.xml') xml = writeMasterBackground(xml, context.masterBackground);
3402
3404
  if (context.masterBackground && /^ppt\/slideLayouts\/slideLayout\d+\.xml$/.test(path)) xml = inheritLayoutBackground(xml);
3405
+ if (path === 'ppt/presentation.xml') xml = presentationInSchemaOrder(xml);
3403
3406
  if (path === 'ppt/theme/theme1.xml') xml = writeThemeColors(xml, {colors: context.themeColors, schemeName: context.schemeName, themeName: context.themeName});
3404
3407
  if (/^ppt\/slides\/slide\d+\.xml$/.test(path)) {
3405
3408
  xml = writeLinkSentinels(xml, context.linkSentinels);
@@ -3544,6 +3547,8 @@ function normalizePartBytes(path, bytes, context, renameMaps, entries, imageMeta
3544
3547
  }
3545
3548
  // theme1.xml: FF-24 colors and names above, then FF-07 fonts here; they touch disjoint elements.
3546
3549
  if (context.scriptFonts) xml = partScriptFonts(path, xml, context.scriptFonts, context.partSlides.get(path) ?? 0);
3550
+ else if (/^ppt\/theme\/theme\d+\.xml$/.test(path)) xml = themeEastAsianFromLatin(xml);
3551
+ if (/^ppt\/(?:slides\/slide|notesSlides\/notesSlide)\d+\.xml$/.test(path)) xml = stripRunScriptFonts(xml);
3547
3552
  return encodeText(normalizePartReferences(xml, renameMaps));
3548
3553
  }
3549
3554
  return bytes;
@@ -3562,6 +3567,16 @@ function themeMasterBulletFonts(xml) {
3562
3567
  bodyStyle.split(VENDOR_MASTER_BULLET_FONT).join(THEME_MINOR_BULLET_FONT));
3563
3568
  }
3564
3569
 
3570
+ // FF-05: PptxGenJS 4.0.1 writes p:notesMasterIdLst after p:sldIdLst, but CT_Presentation is a sequence
3571
+ // (sldMasterIdLst, notesMasterIdLst, handoutMasterIdLst, sldIdLst, sldSz, ...). PowerPoint reads the out-of-order
3572
+ // list as no notes master and lists a default-theme font (Aptos) in Presentation.Fonts; schema order restores
3573
+ // the exporter's own notes master. Only the list's position changes.
3574
+ function presentationInSchemaOrder(xml) {
3575
+ const notes = /<p:notesMasterIdLst>[\s\S]*?<\/p:notesMasterIdLst>/.exec(xml)?.[0];
3576
+ if (!notes || xml.indexOf(notes) < xml.indexOf('</p:sldMasterIdLst>')) return xml;
3577
+ return xml.replace(notes, '').replace('</p:sldMasterIdLst>', `</p:sldMasterIdLst>${notes}`);
3578
+ }
3579
+
3565
3580
  function isXmlPart(path) {
3566
3581
  return path.endsWith(".xml") || path.endsWith(".rels") || path === "[Content_Types].xml";
3567
3582
  }
@@ -0,0 +1,49 @@
1
+ const decoder = new TextDecoder(), encoder = new TextEncoder();
2
+ const text = bytes => decoder.decode(bytes);
3
+
4
+ const THEME_TYPE = 'application/vnd.openxmlformats-officedocument.theme+xml';
5
+ const THEME_TARGET = /Target="\.\.\/theme\/(theme\d+\.xml)"/;
6
+ const NOTES_MASTER = /^ppt\/notesMasters\/notesMaster\d+\.xml$/;
7
+ const SLIDE_MASTER = /^ppt\/slideMasters\/slideMaster\d+\.xml$/;
8
+
9
+ const relsOf = path => path.replace(/([^/]+)$/, '_rels/$1.rels');
10
+
11
+ /**
12
+ * FF-05 (font-fidelity-everywhere). PptxGenJS 4.0.1 points the notes master at the slide master's theme part
13
+ * (`ppt/theme/theme1.xml`). Every master owns its theme part in a PowerPoint package (theme1 for the slide master,
14
+ * theme2 for the notes master, theme3 for a handout master), and PowerPoint refuses to read a package whose notes
15
+ * master it recognises when that master shares the slide theme ("The file or directory is corrupted and unreadable",
16
+ * 0x80070570). The vendored `p:notesMasterIdLst` sits after `p:sldIdLst` (see presentationInSchemaOrder in index.js),
17
+ * which PowerPoint ignored; it then synthesised a default notes master with the default Office theme, and that
18
+ * made the default font (Aptos) appear in Presentation.Fonts although no part names it.
19
+ *
20
+ * Each notes master that shares a theme part with another master gets its own copy of that theme, with the same
21
+ * fonts, colours and format scheme, a relationship to it and a content-type override. The copy is taken after
22
+ * every theme rewrite, so it carries the deck's final fonts. Copies are numbered after the existing themes.
23
+ */
24
+ export function giveNotesMastersOwnThemes(output) {
25
+ const types = output['[Content_Types].xml'];
26
+ if (!types) return;
27
+ const used = new Set();
28
+ const themeOf = master => THEME_TARGET.exec(text(output[relsOf(master)]?.[0] ?? new Uint8Array()))?.[1];
29
+ for (const master of Object.keys(output).filter(path => SLIDE_MASTER.test(path))) {
30
+ const theme = themeOf(master);
31
+ if (theme) used.add(theme);
32
+ }
33
+ let next = Math.max(0, ...Object.keys(output).map(path => Number(/^ppt\/theme\/theme(\d+)\.xml$/.exec(path)?.[1] ?? 0))) + 1;
34
+ let overrides = '';
35
+ for (const master of Object.keys(output).filter(path => NOTES_MASTER.test(path)).sort()) {
36
+ const theme = themeOf(master);
37
+ if (!theme) continue;
38
+ if (!used.has(theme)) { used.add(theme); continue; }
39
+ const source = output[`ppt/theme/${theme}`];
40
+ if (!source) continue;
41
+ const name = `theme${next++}.xml`;
42
+ output[`ppt/theme/${name}`] = [source[0], source[1]];
43
+ const rels = relsOf(master);
44
+ output[rels] = [encoder.encode(text(output[rels][0]).replace(THEME_TARGET, `Target="../theme/${name}"`)), output[rels][1]];
45
+ overrides += `<Override PartName="/ppt/theme/${name}" ContentType="${THEME_TYPE}"/>`;
46
+ used.add(name);
47
+ }
48
+ if (overrides) output['[Content_Types].xml'] = [encoder.encode(text(types[0]).replace('</Types>', `${overrides}</Types>`)), types[1]];
49
+ }
@@ -122,7 +122,7 @@ export function applyPitchFamilies(entries, pitch) {
122
122
  // docProps/app.xml: regenerate the "Fonts Used" group of HeadingPairs and
123
123
  // TitlesOfParts from the fonts the finished package uses, and name the theme
124
124
  // the way the theme part does. Other groups (slide titles) are kept.
125
- export function writeFontsUsed(appXml, fonts, themeName) {
125
+ export function writeFontsUsed(appXml, fonts, themeNames) {
126
126
  const pairs = headingPairs(appXml), titles = titlesOfParts(appXml);
127
127
  if (!pairs || !titles) return appXml;
128
128
  const groups = [];
@@ -135,7 +135,8 @@ export function writeFontsUsed(appXml, fonts, themeName) {
135
135
  if (fontsGroup) fontsGroup[1] = fonts;
136
136
  else groups.unshift(['Fonts Used', fonts]);
137
137
  const themeGroup = groups.find(([name]) => name === 'Theme');
138
- if (themeGroup && themeName && themeGroup[1].length === 1) themeGroup[1] = [themeName];
138
+ const names = [themeNames ?? []].flat();
139
+ if (themeGroup && names.length) themeGroup[1] = names;
139
140
  const kept = groups.filter(([, items]) => items.length);
140
141
  const variants = kept.map(([name, items]) => `<vt:variant><vt:lpstr>${escapeXml(name)}</vt:lpstr></vt:variant><vt:variant><vt:i4>${items.length}</vt:i4></vt:variant>`).join('');
141
142
  const items = kept.flatMap(([, list]) => list).map(item => `<vt:lpstr>${escapeXml(item)}</vt:lpstr>`).join('');
@@ -152,8 +153,9 @@ export function finalizeFontsUsed(output) {
152
153
  const parts = {};
153
154
  for (const [path, [bytes]] of Object.entries(output)) if (/^ppt\/.*\.xml$/.test(path)) parts[path] = bytes;
154
155
  const fonts = packageFontsUsed(inventoryPptxTypefaces(parts, {nested: false}));
155
- const themeName = /<a:theme\b[^>]*\bname="([^"]*)"/.exec(parts['ppt/theme/theme1.xml'] ? text(parts['ppt/theme/theme1.xml']) : '')?.[1];
156
- output['docProps/app.xml'] = [encoder.encode(writeFontsUsed(text(app[0]), fonts, themeName === undefined ? undefined : unescapeFace(themeName))), app[1]];
156
+ const themeNames = Object.keys(parts).filter(path => /^ppt\/theme\/theme\d+\.xml$/.test(path)).sort((a, b) => Number(a.match(/\d+/)[0]) - Number(b.match(/\d+/)[0]))
157
+ .map(path => /<a:theme\b[^>]*\bname="([^"]*)"/.exec(text(parts[path]))?.[1]).filter(name => name !== undefined).map(unescapeFace);
158
+ output['docProps/app.xml'] = [encoder.encode(writeFontsUsed(text(app[0]), fonts, themeNames)), app[1]];
157
159
  }
158
160
 
159
161
  function unescapeFace(value) {
@@ -57,7 +57,37 @@ export function planScriptFonts(presentation, report) {
57
57
  report?.({code: "paragraph-direction-unavailable", path: "language",
58
58
  message: "The installed @openpresentation/opf has no paragraphDirection, so right-to-left paragraphs are not marked; the preview and export must share that rule. Use a core release that exports it."});
59
59
  }
60
- return {deck, slides, lang: deck.lang, rtl};
60
+ return {deck, slides, lang: deck.lang, rtl, contentEastAsian: contentEastAsianFonts(presentation, deck, report)};
61
+ }
62
+
63
+ // FF-05: East Asian characters in a deck whose own language has no East Asian font (an English deck with Japanese
64
+ // text) still need a real East Asian theme font; an empty slot reads as an unresolved +mn-ea in PowerPoint. The
65
+ // script of the text picks the language core resolves it for: kana is Japanese, hangul Korean, Han alone Simplified
66
+ // Chinese. Returns {heading, body} families, or null when the deck language already selects one or the text has none.
67
+ const KANA = /[\p{Script=Hiragana}\p{Script=Katakana}]/u, HANGUL = /\p{Script=Hangul}/u, HAN = /\p{Script=Han}/u;
68
+ function contentEastAsianFonts(presentation, deck, report) {
69
+ if (deck.sources.eastAsian !== "latin") return null;
70
+ let kana = false, hangul = false, han = false;
71
+ const seen = new Set();
72
+ const walk = value => {
73
+ if (typeof value === "string") {
74
+ kana ||= KANA.test(value); hangul ||= HANGUL.test(value); han ||= HAN.test(value);
75
+ } else if (value && typeof value === "object" && !seen.has(value)) {
76
+ seen.add(value);
77
+ for (const item of Object.values(value)) walk(item);
78
+ }
79
+ };
80
+ walk(presentation.slides);
81
+ const language = kana ? "japanese" : hangul ? "korean" : han ? "chinese-simplified" : null;
82
+ if (!language) return null;
83
+ try {
84
+ const resolved = resolver({...presentation, language}, {slideIndex: 0});
85
+ return resolved.sources.eastAsian === "latin" ? null : {heading: resolved.heading.eastAsian, body: resolved.body.eastAsian};
86
+ } catch (error) {
87
+ report?.({code: "language-export-unavailable", path: "language",
88
+ message: `East Asian theme fonts for the East Asian text could not be resolved (${error instanceof Error ? error.message : String(error)}); the theme names the latin family for them.`});
89
+ return null;
90
+ }
61
91
  }
62
92
 
63
93
  const escapeAttribute = value => String(value).replace(/[&<>"']/g, char => ({"&": "&amp;", "<": "&lt;", ">": "&gt;", "\"": "&quot;", "'": "&apos;"}[char]));
@@ -82,8 +112,12 @@ export function themeScriptFonts(xml, plan) {
82
112
  for (const [tag, slots, family] of [["majorFont", heading, supplement?.heading], ["minorFont", body, supplement?.body]]) {
83
113
  xml = xml.replace(new RegExp(`<a:${tag}>[\\s\\S]*?</a:${tag}>`), block => {
84
114
  for (const [element, slot] of SCRIPT_SLOTS) {
85
- if (plan.deck.sources[slot] === "latin") continue;
86
- const face = escapeAttribute(slots[slot]);
115
+ // FF-05: the East Asian slot always names a font (a script font, a font for the East Asian text, else the latin
116
+ // family): PowerPoint lists an empty slot as an empty-name font through every paragraph end mark. Complex script
117
+ // stays empty unless a script font is selected.
118
+ const content = element === "ea" && plan.deck.sources[slot] === "latin" ? plan.contentEastAsian?.[tag === "majorFont" ? "heading" : "body"] : undefined;
119
+ if (plan.deck.sources[slot] === "latin" && element !== "ea") continue;
120
+ const face = escapeAttribute(content ?? slots[slot]);
87
121
  block = block.replace(new RegExp(`<a:${element}\\b[^>]*/>`), `<a:${element} typeface="${face}"/>`);
88
122
  }
89
123
  if (supplement && family) {
@@ -364,3 +398,26 @@ export function languageDiagnostics(imported, observed, report) {
364
398
  }
365
399
  }
366
400
  }
401
+
402
+ /**
403
+ * FF-05: PptxGenJS writes every run's East Asian and complex-script font as a copy of the Latin face, with the
404
+ * charsets of other scripts (-122, -120). PowerPoint lists such a run font as an empty-name font in
405
+ * Presentation.Fonts and hides the real one (the charset is not the cause: a bare typeface does the same).
406
+ * PowerPoint's own runs name only the Latin face; East Asian and complex-script faces come from the paragraph or
407
+ * master style and the theme slots (+mn-ea, +mn-cs), which the exporter writes. A run keeps no explicit ea/cs
408
+ * typeface; theme references stay.
409
+ */
410
+ export function stripRunScriptFonts(xml) {
411
+ return xml.replace(/<a:(?:ea|cs)\s+typeface="(?!\+)[^"]*"[^>]*\/>/g, "");
412
+ }
413
+
414
+ /**
415
+ * Without core's resolver the theme's East Asian slot still repeats the latin family of its font group, which keeps
416
+ * the paragraph end marks from reading an empty font (see themeScriptFonts).
417
+ */
418
+ export function themeEastAsianFromLatin(xml) {
419
+ return xml.replace(/<a:(majorFont|minorFont)>[\s\S]*?<\/a:\1>/g, block => {
420
+ const latin = /<a:latin\b[^>]*\btypeface="([^"]+)"/.exec(block)?.[1];
421
+ return latin ? block.replace(/<a:ea typeface=""\/>/, `<a:ea typeface="${latin}"/>`) : block;
422
+ });
423
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openpresentation/opf-pptx",
3
- "version": "0.12.0",
3
+ "version": "0.12.1",
4
4
  "description": "Pure local OPF to PPTX export and PPTX to OPF import tooling.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -37,8 +37,8 @@
37
37
  "sideEffects": false,
38
38
  "scripts": {
39
39
  "build": "node scripts/build.mjs",
40
- "typecheck": "node --check src/index.js && node --check src/annotation-export.js && node --check src/annotation-provenance.js && node --check test/annotations.mjs && node --check test/region-reading-order.mjs && node --check test/symbol-fonts.mjs && node --check test/math-emoji.mjs && node --check src/svg-image.js && node --check test/svg-image.mjs && node --check test/native-svg-decks.mjs && node --check src/chart-types.js && node --check src/chartex.js && node --check src/chart-options.js && node --check test/chart-options.mjs && node --check test/chartex.mjs && node --check test/chart-text-size.mjs && node --check test/chart-types.mjs && node --check test/design-alignment.mjs && node --check src/color-ref.js && node --check src/theme-colors.js && node --check test/theme-colors.mjs && node --check test/tag-colour.mjs && node --check test/table-theme-colors.mjs && node --check scripts/build.mjs && node --check scripts/validate-package.mjs && node --check test/smoke.mjs && node --check src/image-geometry.js && node --check src/slide-image-provenance.js && node --check test/slide-image.mjs && node --check test/slide-image-treatments.mjs && node --check src/watermark-provenance.js && node --check src/logo-provenance.js && node --check test/design-fields.mjs && node --check src/media-dedupe.js && node --check test/media-dedupe.mjs && node --check test/watermark.mjs && node --check test/image-fit.mjs && node --check test/image-orientation.mjs && node --check test/table-layout.mjs && node --check test/table-import.mjs && node --check test/object-ids.mjs && node --check test/export-corpus.mjs && node --check test/image-media-type.mjs && node --check src/image-fallback-node.js && node --check src/image-fallback-browser.js && node --check test/webp-fallback.mjs && node --check scripts/build-browser-check.mjs && node --check test/webp-fallback-browser.js && node --check test/image-fallback-boundary.mjs && node --check src/background.js && node --check test/background.mjs && node --check src/image-import.js && node --check test/image-import.mjs && node --check test/native-background.mjs && node --check src/background-import.js && node --check test/background-inheritance.mjs && node --check test/rich-table.mjs && node --check src/table-import.js && node --check test/rich-table-import.mjs && node --check test/rich-table-import-browser.js && node --check test/table-row-sizing.mjs && node --check test/table-styles.mjs && node --check test/table-styles-browser.js && node --check test/styled-table.mjs && node --check src/table-cell-import.js && node --check test/styled-table-import.mjs && node --check test/styled-table-import-browser.js && node --check src/table-border-import.js && node --check test/table-border-styles.mjs && node --check src/card-provenance.js && node --check test/content-cards.mjs && node --check src/chart-workbook.js && node --check test/chart-colors.mjs && node --check test/single-series-colour.mjs && node --check test/chart-workbook.mjs && node --check test/single-column-chart.mjs && node --check test/native-chart-colors.mjs && node --check src/heading-provenance.js && node --check test/accepted-text.mjs && node --check test/heading-provenance.mjs && node --check src/text-provenance.js && node --check test/plain-whitespace.mjs && node --check src/timeline-provenance.js && node --check src/quote-provenance.js && node --check test/quote-provenance.mjs && node --check test/shared-timeline.mjs && node --check test/shared-furniture.mjs && node --check src/furniture-provenance.js && node --check src/document-provenance.js && node --check src/content-topology.js && node --check src/sections.js && node --check test/document-provenance.mjs && node --check test/content-topology.mjs && node --check test/sections.mjs && node --check test/provenance-interop.mjs && node --check test/furniture-provenance.mjs && node --check src/furniture-fields.js && node --check test/furniture-fields.mjs && node --check src/native-furniture.js && node --check test/native-furniture.mjs && node --check test/furniture-wrapped-date.mjs && node --check test/native-picture.mjs && node --check test/compare-native-picture.mjs && node --check test/compare-native-picture-edit.mjs && node --check test/compare-native-picture-edit-controls.mjs && node --check test/native-font-embed-audit.mjs && node --check test/native-font-embed-controls.mjs && node --check test/native-font-embed-fixture-source.mjs && node --check test/native-font-edit-fixture.mjs && node --check test/native-mixed-edit-audit.mjs && node --check test/native-mixed-edit-controls.mjs && node --check test/master-bullet-font.mjs && node --check test/native-font-inventory-audit.mjs && node --check test/native-font-inventory-controls.mjs && node --check test/default-font-scheme.mjs && node --check test/export-chosen-fonts.mjs && node --check test/background-fills.mjs && node --check src/typeface-inventory.js && node --check src/package-fonts.js && node --check test/typeface-inventory.mjs && node --check src/script-fonts.js && node --check test/script-fonts.mjs && node --check test/rtl-layout.mjs && node --check test/theme-script-slots.mjs && node --check test/powershell-scan.mjs && node --check test/powershell-scan-probes.mjs && node --check test/socials-furniture.mjs && node --check src/media-provenance.js && node --check test/layout-parity.mjs && node --check test/cover-centering.mjs && node --check test/media-placeholder.mjs && node --check test/image-placeholder.mjs && node --check test/svg-image-degrade.mjs && node --check test/background-colorref.mjs && node --check test/import-fabrications.mjs && node --check src/run-colors.js && node --check test/hard-line-breaks.mjs && node --check test/import-details.mjs && node --check test/list-shape-names.mjs && node --check src/numbered-list.js && node --check test/numbered-lists.mjs && node --check test/layout-intent.mjs && node --check test/chart-cache-import.mjs && node --check test/zip-date.mjs && node --check test/native-text-whitespace.mjs && node --check test/native-underline.mjs && node --check src/native-text-style.js && node --check src/font-weights.js && node --check test/font-weight-import.mjs && node --check src/body-text-import.js && node --check test/native-body-rich-text.mjs && node --check test/native-body-rich-text-browser.js && node --check test/native-quote-import-contract.mjs && node --check test/native-quote-import-controls.mjs && node --check test/native-quote.mjs && node --check test/determinism.mjs && node --check test/determinism-fixtures.mjs && node --check test/table-frame-furniture-order.mjs && node --check test/shape-border-widths.mjs && node --check test/gallery-layout-alignment.mjs && node --check src/import-signals.js && node --check test/import-signals.mjs && node --check test/signals-deck.mjs && node --check test/rr-07-preview-polish.mjs && node --check scripts/rr07-native-set.mjs",
41
- "test": "npm run build && node test/annotations.mjs && node test/region-reading-order.mjs && node test/symbol-fonts.mjs && node test/math-emoji.mjs && node test/chart-types.mjs && node test/chartex.mjs && node test/chart-options.mjs && node test/chart-text-size.mjs && node test/zip-date.mjs && node test/background-fills.mjs && node test/rr-07-preview-polish.mjs && npm run test:furniture && node test/slide-image.mjs && node test/slide-image-treatments.mjs && node test/watermark.mjs && node test/design-fields.mjs && node test/media-dedupe.mjs && node test/document-provenance.mjs && node test/content-topology.mjs && node test/sections.mjs && node test/layout-intent.mjs && node test/master-bullet-font.mjs && node test/default-font-scheme.mjs && node test/script-fonts.mjs && node test/rtl-layout.mjs && node test/theme-script-slots.mjs && node test/export-chosen-fonts.mjs && node test/typeface-inventory.mjs && node test/color-ref-export.mjs && node test/theme-colors.mjs && node test/tag-colour.mjs && node test/table-theme-colors.mjs && node test/plain-whitespace.mjs && node test/native-text-whitespace.mjs && node test/native-body-rich-text.mjs && node test/native-underline.mjs && node test/font-weight-import.mjs && node test/design-alignment.mjs && node test/accepted-text.mjs && node test/heading-provenance.mjs && node test/native-quote-import-controls.mjs && node test/chart-colors.mjs && node test/single-series-colour.mjs && node test/content-cards.mjs && node test/chart-workbook.mjs && node test/single-column-chart.mjs && node --test test/chart-cache-import.mjs && node test/dependency-boundary.mjs && npm run build:browser-check && npm run test:styled-table && node test/content-layout.mjs && node test/layout-parity.mjs && node test/gallery-layout-alignment.mjs && node test/cover-centering.mjs && node test/table-frame-furniture-order.mjs && node test/media-placeholder.mjs && node test/shape-border-widths.mjs && node test/image-placeholder.mjs && node test/svg-image-degrade.mjs && node test/svg-image.mjs && node test/background-colorref.mjs && node test/import-fabrications.mjs && node test/hard-line-breaks.mjs && node test/import-details.mjs && node test/list-shape-names.mjs && node test/numbered-lists.mjs && node test/shared-quote.mjs && node test/quote-provenance.mjs && node test/import-signals.mjs && npm run test:timeline && npm run test:determinism && node test/template-variables.mjs",
40
+ "typecheck": "node --check src/index.js && node --check src/annotation-export.js && node --check src/annotation-provenance.js && node --check test/annotations.mjs && node --check test/region-reading-order.mjs && node --check test/symbol-fonts.mjs && node --check test/math-emoji.mjs && node --check src/svg-image.js && node --check test/svg-image.mjs && node --check test/native-svg-decks.mjs && node --check src/chart-types.js && node --check src/chartex.js && node --check src/chart-options.js && node --check test/chart-options.mjs && node --check test/chartex.mjs && node --check test/chart-text-size.mjs && node --check test/chart-types.mjs && node --check test/design-alignment.mjs && node --check src/color-ref.js && node --check src/theme-colors.js && node --check test/theme-colors.mjs && node --check test/tag-colour.mjs && node --check test/table-theme-colors.mjs && node --check scripts/build.mjs && node --check scripts/validate-package.mjs && node --check test/smoke.mjs && node --check src/image-geometry.js && node --check src/slide-image-provenance.js && node --check test/slide-image.mjs && node --check test/slide-image-treatments.mjs && node --check src/watermark-provenance.js && node --check src/logo-provenance.js && node --check test/design-fields.mjs && node --check src/media-dedupe.js && node --check test/media-dedupe.mjs && node --check test/watermark.mjs && node --check test/image-fit.mjs && node --check test/image-orientation.mjs && node --check test/table-layout.mjs && node --check test/table-import.mjs && node --check test/object-ids.mjs && node --check test/export-corpus.mjs && node --check test/image-media-type.mjs && node --check src/image-fallback-node.js && node --check src/image-fallback-browser.js && node --check test/webp-fallback.mjs && node --check scripts/build-browser-check.mjs && node --check test/webp-fallback-browser.js && node --check test/image-fallback-boundary.mjs && node --check src/background.js && node --check test/background.mjs && node --check src/image-import.js && node --check test/image-import.mjs && node --check test/native-background.mjs && node --check src/background-import.js && node --check test/background-inheritance.mjs && node --check test/rich-table.mjs && node --check src/table-import.js && node --check test/rich-table-import.mjs && node --check test/rich-table-import-browser.js && node --check test/table-row-sizing.mjs && node --check test/table-styles.mjs && node --check test/table-styles-browser.js && node --check test/styled-table.mjs && node --check src/table-cell-import.js && node --check test/styled-table-import.mjs && node --check test/styled-table-import-browser.js && node --check src/table-border-import.js && node --check test/table-border-styles.mjs && node --check src/card-provenance.js && node --check test/content-cards.mjs && node --check src/chart-workbook.js && node --check test/chart-colors.mjs && node --check test/single-series-colour.mjs && node --check test/chart-workbook.mjs && node --check test/single-column-chart.mjs && node --check test/native-chart-colors.mjs && node --check src/heading-provenance.js && node --check test/accepted-text.mjs && node --check test/heading-provenance.mjs && node --check src/text-provenance.js && node --check test/plain-whitespace.mjs && node --check src/timeline-provenance.js && node --check src/quote-provenance.js && node --check test/quote-provenance.mjs && node --check test/shared-timeline.mjs && node --check test/shared-furniture.mjs && node --check src/furniture-provenance.js && node --check src/document-provenance.js && node --check src/content-topology.js && node --check src/sections.js && node --check test/document-provenance.mjs && node --check test/content-topology.mjs && node --check test/sections.mjs && node --check test/provenance-interop.mjs && node --check test/furniture-provenance.mjs && node --check src/furniture-fields.js && node --check test/furniture-fields.mjs && node --check src/native-furniture.js && node --check test/native-furniture.mjs && node --check test/furniture-wrapped-date.mjs && node --check test/native-picture.mjs && node --check test/compare-native-picture.mjs && node --check test/compare-native-picture-edit.mjs && node --check test/compare-native-picture-edit-controls.mjs && node --check test/native-font-embed-audit.mjs && node --check test/native-font-embed-controls.mjs && node --check test/native-font-embed-fixture-source.mjs && node --check test/native-font-edit-fixture.mjs && node --check test/native-mixed-edit-audit.mjs && node --check test/native-mixed-edit-controls.mjs && node --check test/master-bullet-font.mjs && node --check test/native-font-inventory-audit.mjs && node --check test/native-font-inventory-controls.mjs && node --check test/default-font-scheme.mjs && node --check test/export-chosen-fonts.mjs && node --check test/background-fills.mjs && node --check src/typeface-inventory.js && node --check src/package-fonts.js && node --check test/typeface-inventory.mjs && node --check src/script-fonts.js && node --check test/script-fonts.mjs && node --check test/rtl-layout.mjs && node --check test/theme-script-slots.mjs && node --check src/master-themes.js && node --check test/presentation-order.mjs && node --check test/notes-master-theme.mjs && node --check test/powershell-scan.mjs && node --check test/powershell-scan-probes.mjs && node --check test/socials-furniture.mjs && node --check src/media-provenance.js && node --check test/layout-parity.mjs && node --check test/cover-centering.mjs && node --check test/media-placeholder.mjs && node --check test/image-placeholder.mjs && node --check test/svg-image-degrade.mjs && node --check test/background-colorref.mjs && node --check test/import-fabrications.mjs && node --check src/run-colors.js && node --check test/hard-line-breaks.mjs && node --check test/import-details.mjs && node --check test/list-shape-names.mjs && node --check src/numbered-list.js && node --check test/numbered-lists.mjs && node --check test/layout-intent.mjs && node --check test/chart-cache-import.mjs && node --check test/zip-date.mjs && node --check test/native-text-whitespace.mjs && node --check test/native-underline.mjs && node --check src/native-text-style.js && node --check src/font-weights.js && node --check test/font-weight-import.mjs && node --check src/body-text-import.js && node --check test/native-body-rich-text.mjs && node --check test/native-body-rich-text-browser.js && node --check test/native-quote-import-contract.mjs && node --check test/native-quote-import-controls.mjs && node --check test/native-quote.mjs && node --check test/determinism.mjs && node --check test/determinism-fixtures.mjs && node --check test/table-frame-furniture-order.mjs && node --check test/shape-border-widths.mjs && node --check test/gallery-layout-alignment.mjs && node --check src/import-signals.js && node --check test/import-signals.mjs && node --check test/signals-deck.mjs && node --check test/rr-07-preview-polish.mjs && node --check scripts/rr07-native-set.mjs",
41
+ "test": "npm run build && node test/annotations.mjs && node test/region-reading-order.mjs && node test/symbol-fonts.mjs && node test/math-emoji.mjs && node test/chart-types.mjs && node test/chartex.mjs && node test/chart-options.mjs && node test/chart-text-size.mjs && node test/zip-date.mjs && node test/background-fills.mjs && node test/rr-07-preview-polish.mjs && npm run test:furniture && node test/slide-image.mjs && node test/slide-image-treatments.mjs && node test/watermark.mjs && node test/design-fields.mjs && node test/media-dedupe.mjs && node test/document-provenance.mjs && node test/content-topology.mjs && node test/sections.mjs && node test/layout-intent.mjs && node test/master-bullet-font.mjs && node test/default-font-scheme.mjs && node test/script-fonts.mjs && node test/rtl-layout.mjs && node test/theme-script-slots.mjs && node test/presentation-order.mjs && node test/notes-master-theme.mjs && node test/export-chosen-fonts.mjs && node test/typeface-inventory.mjs && node test/color-ref-export.mjs && node test/theme-colors.mjs && node test/tag-colour.mjs && node test/table-theme-colors.mjs && node test/plain-whitespace.mjs && node test/native-text-whitespace.mjs && node test/native-body-rich-text.mjs && node test/native-underline.mjs && node test/font-weight-import.mjs && node test/design-alignment.mjs && node test/accepted-text.mjs && node test/heading-provenance.mjs && node test/native-quote-import-controls.mjs && node test/chart-colors.mjs && node test/single-series-colour.mjs && node test/content-cards.mjs && node test/chart-workbook.mjs && node test/single-column-chart.mjs && node --test test/chart-cache-import.mjs && node test/dependency-boundary.mjs && npm run build:browser-check && npm run test:styled-table && node test/content-layout.mjs && node test/layout-parity.mjs && node test/gallery-layout-alignment.mjs && node test/cover-centering.mjs && node test/table-frame-furniture-order.mjs && node test/media-placeholder.mjs && node test/shape-border-widths.mjs && node test/image-placeholder.mjs && node test/svg-image-degrade.mjs && node test/svg-image.mjs && node test/background-colorref.mjs && node test/import-fabrications.mjs && node test/hard-line-breaks.mjs && node test/import-details.mjs && node test/list-shape-names.mjs && node test/numbered-lists.mjs && node test/shared-quote.mjs && node test/quote-provenance.mjs && node test/import-signals.mjs && npm run test:timeline && npm run test:determinism && node test/template-variables.mjs",
42
42
  "validate": "node scripts/validate-package.mjs",
43
43
  "test:packed": "node test/packed-install.mjs && node test/provenance-interop.mjs",
44
44
  "test:compare-published": "node test/compare-published.mjs",