@openpresentation/opf-pptx 0.10.0 → 0.11.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.
@@ -2,7 +2,7 @@
2
2
 
3
3
  ## Coordinated `@openpresentation/opf` pin
4
4
 
5
- PPTX 0.10.0 depends on published [`@openpresentation/opf@^0.11.1`](https://www.npmjs.com/package/@openpresentation/opf) (reference layer: ColorRef, document `variables`, exported `resolveColorRef()`). The optional renderer peer is [`@openpresentation/opf-render@^0.10.0`](https://www.npmjs.com/package/@openpresentation/opf-render) so editor 0.9.0 + render 0.10.0 + pptx 0.10.0 install together. CI checks out OPF [`b8a1faf`](https://github.com/OpenPresentation/opf/commit/b8a1faf24203635bd36b09ea19f44bcd53781f16) (`opf-v0.11.0`) for coordinated source linking. Named content colors resolve through core `resolveColorRef()`; eight-digit hex alpha stays a local export concern because core `normalizeHexColor` drops the alpha byte. Native `schemeClr` / theme `clrScheme` writes remain follow-up work.
5
+ PPTX 0.11.0 depends on published [`@openpresentation/opf@^0.11.2`](https://www.npmjs.com/package/@openpresentation/opf) (reference layer: ColorRef, document `variables`, exported `resolveColorRef()`). The optional renderer peer is [`@openpresentation/opf-render@^0.11.0`](https://www.npmjs.com/package/@openpresentation/opf-render) so editor 0.10.0 + render 0.11.0 + pptx 0.11.0 install together. CI checks out OPF [`b8a1faf`](https://github.com/OpenPresentation/opf/commit/b8a1faf24203635bd36b09ea19f44bcd53781f16) (`opf-v0.11.0`) for coordinated source linking. Named content colors resolve through core `resolveColorRef()`; eight-digit hex alpha stays a local export concern because core `normalizeHexColor` drops the alpha byte. Native `schemeClr` / theme `clrScheme` writes remain follow-up work.
6
6
 
7
7
  OPF PPTX 0.5.1 ships the exact, unmodified PptxGenJS 4.0.1 ESM distribution in `vendor/pptxgenjs`, with its MIT license, upstream archive integrity and per-file SHA-256 hashes. Its actual JSZip dependency is declared directly. Ordinary npm installations therefore omit the unused image-size parser without requiring consumer overrides. This removes the affected dependency; it does not patch the parser. Published OPF PPTX 0.5.0 retains the older graph.
8
8
 
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # OPF PPTX
2
2
 
3
- Version 0.10.0 requires core `^0.11.1` and the optional `@openpresentation/opf-render` peer `^0.10.0`. It adds native slide-number and date fields, socials, slide-image treatments, theme color schemes, script-slot fonts and RTL, re-import of design and metadata references, native underline and current-body formatting on import, and UTC-canonical `zipDate` (see the changelog for the intentional contract changes).
3
+ Version 0.11.0 requires core `^0.11.2` (cover slides are centered between header and footer furniture) and the optional `@openpresentation/opf-render` peer `^0.11.0`; install them together so export and preview resolve the same core. Version 0.10.0 required core `^0.11.1` and the peer `^0.10.0`. It adds native slide-number and date fields, socials, slide-image treatments, theme color schemes, script-slot fonts and RTL, re-import of design and metadata references, native underline and current-body formatting on import, and UTC-canonical `zipDate` (see the changelog for the intentional contract changes).
4
4
 
5
5
  Version 0.9.1 kept core `^0.11.0` and raised the optional `@openpresentation/opf-render` peer to `^0.9.0` so it coexists with editor 0.8.0. ColorRef / `variables` on content colors still hex-resolve through core `resolveColorRef()` before PptxGenJS `srgbClr` export. Unrecognized run colors such as `color:'invalid'` still validate and fall back to the theme text color. Native DrawingML `schemeClr` and theme `clrScheme` writes, and native `p:hf` headers/footers, are not in this release. Import still flattens theme colors to hex. Metric, quote and timeline layout placeholders and the corrected text-bullet contract from 0.8.1 are retained.
6
6
 
@@ -103,7 +103,7 @@ This intentionally tightens the previous host-dependent `Date` parsing contract.
103
103
 
104
104
  ### New in 0.10.0: current native body formatting
105
105
 
106
- Current source imports supported formatting from ordinary untagged native body text and list items. A value that previously imported as a string can now be a rich-run array containing the same current characters with explicit native properties. Unstyled values remain strings; title/subtitle selection and tagged recovery stay separate. Run, field and break order, blank paragraphs, significant whitespace, explicit normal overrides, paragraph/list defaults, point sizes, Latin font families, supported colors/alpha, hyperlinks and script direction come from the current PPTX. Edits, clears and deletion remain authoritative in every provenance mode.
106
+ Current source imports supported formatting from ordinary untagged native body text and list items. A value that previously imported as a string can now be a rich-run array containing the same current characters with explicit native properties. Unstyled values remain strings; title/subtitle selection and tagged recovery stay separate. Run, field and break order, blank paragraphs, significant whitespace, explicit normal overrides, paragraph/list defaults, point sizes, Latin font families, supported colors/alpha, hyperlinks and script direction come from the current PPTX. Edits, clears and deletion remain authoritative in every provenance mode. Native weight faces of the bundled Roboto family (for example `Roboto SemiBold`) import as `Roboto` plus `bold` for weights of 600 and above. Medium (500) and lighter weights import as regular, because OPF runs have no numeric weight, so a round trip re-exports Medium as Regular; `approximate-body-font-weight` reports this. Authored families such as `Arial Black` or `Aptos Light` keep their names.
107
107
 
108
108
  This does not reconstruct original source run identities or boundaries between native shapes, infer master/layout text styles, or recover cached authored content. Native paragraph/break boundaries become LF; character-reference CR remains explicit. Unsupported properties report body-path diagnostics rather than claim exact formatting. Reading order still uses current native positions. Published npm `0.9.1` does not contain this representation change; portable conversion checks do not establish Office rendering or general rich-text round-trip fidelity.
109
109
 
@@ -148,7 +148,7 @@ When the package carries an FF-32 stored `language` ([document round trip](docs/
148
148
 
149
149
  Exports report `language-unresolved` when the document's language cannot be resolved locally (a URL, `pkg:` reference or unknown id) and `en-US` is used.
150
150
 
151
- **Core without the resolver.** Core `@openpresentation/opf` 0.11.0 and earlier have no `resolveScriptFonts` (this release requires ^0.11.1, so this only applies to a forced older core). Export with it is byte-identical to the output before FF-07 (`lang="en-US"`, empty theme `ea`/`cs`, no `rtl`), and a document that names a language gets a `language-export-unavailable` diagnostic. A core with the resolver but without `paragraphDirection` marks no paragraph direction and reports `paragraph-direction-unavailable` for a right-to-left deck. Import then matches run tags against the installed catalog's `bcp47` and primary language. `npm run test:packed` exercises this path against the registry release. CI links core at a pinned commit that has the resolver.
151
+ **Core without the resolver.** Core `@openpresentation/opf` 0.11.0 and earlier have no `resolveScriptFonts` (this release requires ^0.11.2, so this only applies to a forced older core). Export with it is byte-identical to the output before FF-07 (`lang="en-US"`, empty theme `ea`/`cs`, no `rtl`), and a document that names a language gets a `language-export-unavailable` diagnostic. A core with the resolver but without `paragraphDirection` marks no paragraph direction and reports `paragraph-direction-unavailable` for a right-to-left deck. Import then matches run tags against the installed catalog's `bcp47` and primary language. `npm run test:packed` exercises this path against the registry release. CI links core at a pinned commit that has the resolver.
152
152
 
153
153
  ## Runtime Policy
154
154
 
@@ -0,0 +1,63 @@
1
+ // One table of the weight and posture words that native legacy font families use
2
+ // as style-link names ("Roboto SemiBold"). The exporter recognizes them to keep
3
+ // a chosen family's own faces (sameTypeface) and the importer uses the same
4
+ // table to map such a face back to family plus OPF weight (splitWeightFace).
5
+ // Weights are CSS numeric weights. Words are compact; a two-word spelling
6
+ // ("semi bold") is the same word split by one space.
7
+ export const FONT_WEIGHT_WORDS = Object.freeze([
8
+ ['thin', 100], ['hairline', 100],
9
+ ['extralight', 200], ['ultralight', 200], ['light', 300], ['semilight', 350], ['demilight', 350],
10
+ ['book', 400], ['regular', 400], ['normal', 400], ['medium', 500],
11
+ ['semibold', 600], ['demibold', 600], ['bold', 700], ['extrabold', 800], ['ultrabold', 800],
12
+ ['black', 900], ['heavy', 900], ['extrablack', 950], ['ultrablack', 950],
13
+ ]);
14
+ export const FONT_POSTURE_WORDS = Object.freeze(['italic', 'oblique']);
15
+
16
+ const weights = new Map(FONT_WEIGHT_WORDS);
17
+ const postures = new Set(FONT_POSTURE_WORDS);
18
+ const isWord = word => weights.has(word) || postures.has(word);
19
+
20
+ // Tokenize a style suffix in one linear pass, without regular expressions:
21
+ // this runs on typeface names from untrusted PPTX files. A word is one
22
+ // whitespace-separated part ("SemiBold"), or two adjacent parts that join into a
23
+ // word ("Semi Bold"). Returns the lower-case words, or undefined when any part
24
+ // is not a style word.
25
+ function styleWords(suffix) {
26
+ const parts = suffix.toLowerCase().split(/[ \t]+/).filter(Boolean);
27
+ if (!parts.length) return undefined;
28
+ const words = [];
29
+ for (let index = 0; index < parts.length; index++) {
30
+ if (isWord(parts[index])) words.push(parts[index]);
31
+ else if (index + 1 < parts.length && isWord(parts[index] + parts[index + 1])) words.push(parts[index] + parts[++index]);
32
+ else return undefined;
33
+ }
34
+ return words;
35
+ }
36
+ // True when the whole suffix is made only of style words, e.g. "SemiBold", "Extra Bold Italic".
37
+ export const isFaceStyleSuffix = suffix => styleWords(suffix) !== undefined;
38
+
39
+ // Families whose weight faces the exporter itself writes under native style-link
40
+ // names: the bundled families a provider resolves by legacy weight name. Other
41
+ // families ("Aptos Light", a theme font that is itself "Roboto Light") are
42
+ // authored typefaces and are never split.
43
+ export const KNOWN_WEIGHT_FACE_FAMILIES = Object.freeze(['Roboto']);
44
+
45
+ // Split "<family> <weight words>" into the family and its CSS weight. Returns
46
+ // undefined for any other name, so authored families are never altered.
47
+ export function splitWeightFace(typeface, families = KNOWN_WEIGHT_FACE_FAMILIES) {
48
+ if (typeof typeface !== 'string') return undefined;
49
+ const name = typeface.trim();
50
+ for (const family of families) {
51
+ const base = family.trim();
52
+ if (name.length <= base.length + 1 || !name.toLowerCase().startsWith(base.toLowerCase()) || !/[ \t]/.test(name[base.length])) continue;
53
+ const words = styleWords(name.slice(base.length + 1));
54
+ if (!words) continue;
55
+ let weight, italic = false;
56
+ for (const word of words) {
57
+ if (postures.has(word)) italic = true;
58
+ else weight = weights.get(word);
59
+ }
60
+ return {family: base, weight: weight ?? 400, italic};
61
+ }
62
+ return undefined;
63
+ }
package/dist/index.js CHANGED
@@ -1,4 +1,5 @@
1
1
  import {nativeBodyReader, joinNativeParagraphs} from './body-text-import.js';
2
+ import {isFaceStyleSuffix} from './font-weights.js';
2
3
  import {importTableFrames} from './table-import.js';
3
4
  import {applyChartFonts, applyPitchFamilies, finalizeFontsUsed, fontPitchFamilies} from './package-fonts.js';
4
5
  import {readChartCategoryHeading,writeChartCategoryHeading} from './chart-workbook.js';
@@ -1234,7 +1235,7 @@ async function addSlide(pptx, presentation, opfSlide, slideIndex, context, optio
1234
1235
  const surface=slideContext.colorScheme[isDarkHex(slideContext.colors.background)?'dark2':'light2']??`#${slideContext.colors.surface}`;
1235
1236
  slide.addShape('roundRect',{x:frame.x/96,y:frame.y/96,w:frame.width/96,h:frame.height/96,
1236
1237
  rectRadius:8*Math.min(widthInches,heightInches)/720,
1237
- fill:paint(surface),line:{...paint(slideContext.colorScheme.accent5??`#${slideContext.colors.border}`),pt:.75},objectName:`OPF card ${item.path}`});
1238
+ fill:paint(surface),line:{...paint(slideContext.colorScheme.accent5??`#${slideContext.colors.border}`),width:.75},objectName:`OPF card ${item.path}`});
1238
1239
  }
1239
1240
  if(['text','title','subtitle','tag'].includes(item.field)&&(item.text?.placement||item.text?.sourceLines)&&!item.text.richLines) {
1240
1241
  addMeasuredPayloadText(slide,item.value,item.box,slideContext,options,{path:item.path,fit:item.text,textStyle:item.textStyle,sourceText:item.field==='text'&&!!item.text.sourceLines,align:alignmentFor(item),diagnosticsHandled:true,heading:['title','subtitle','tag'].includes(item.field)?item.field:undefined,color:item.field==='tag'?slideContext.colors.accent:slideContext.colors.text});
@@ -1738,7 +1739,7 @@ function addCodePayload(slide, value, layout, region, context, path) {
1738
1739
  const group = String(context.codeTags.size + 1), panelName = `OPF code ${group} panel`;
1739
1740
  for (const part of layout.parts) if (!part.fit) throw new OPFPptxError('layout-overflow', 'Code content has no usable internal space; increase its cell size before exporting.', {path:part.path,issues:layout.diagnostics});
1740
1741
  context.codeTags.set(panelName,codeManifest(value,layout,group));
1741
- slide.addShape('rect', {...region, fill: {color: '111827'}, line: {color: '334155', pt: .75}, objectName:panelName});
1742
+ slide.addShape('rect', {...region, fill: {color: '111827'}, line: {color: '334155', width: .75}, objectName:panelName});
1742
1743
  for (const [partIndex,part] of layout.parts.entries()) {
1743
1744
  if (!part.fit) throw new OPFPptxError('layout-overflow', 'Code content has no usable internal space; increase its cell size before exporting.', {path:part.path,issues:layout.diagnostics});
1744
1745
  for (const [index,line] of part.fit.sourceLines.entries()) {
@@ -1801,7 +1802,7 @@ function addTimelinePayload(slide, value, layout, context, options, path) {
1801
1802
  const scale=Math.min(context.dimensions.widthInches,context.dimensions.heightInches)*96/720;
1802
1803
  const group=String(context.timelineTags.size),anchor=timelineManifest(value,layout),connectorName=`OPF timeline ${group} connector`;
1803
1804
  const {x1,y1,x2,y2}=layout.connector;
1804
- slide.addShape('line',{objectName:connectorName,x:x1/96,y:y1/96,w:(x2-x1)/96,h:(y2-y1)/96,line:{color:context.colors.border,pt:3*scale*.75}});
1805
+ slide.addShape('line',{objectName:connectorName,x:x1/96,y:y1/96,w:(x2-x1)/96,h:(y2-y1)/96,line:{color:context.colors.border,width:3*scale*.75}});
1805
1806
  context.timelineTags.set(connectorName,{v:1,group,role:'connector'});
1806
1807
  for(const marker of layout.markers){
1807
1808
  const objectName=`OPF timeline ${group} marker ${marker.eventIndex}`;
@@ -1829,7 +1830,7 @@ function addMediaPayload(slide, presentation, value, region, path, context, opti
1829
1830
  context.mediaTags.set(names.badge, {v: 1, role: "badge", path});
1830
1831
  context.mediaTags.set(names.play, {v: 1, role: "play", path});
1831
1832
  }
1832
- slide.addShape("rect", {x: region.x, y: region.y, w: region.w, h: region.h, fill: {color: context.colors.surface}, line: {color: context.colors.border, pt: 0.75}, hyperlink, objectName: names.frame});
1833
+ slide.addShape("rect", {x: region.x, y: region.y, w: region.w, h: region.h, fill: {color: context.colors.surface}, line: {color: context.colors.border, width: 0.75}, hyperlink, objectName: names.frame});
1833
1834
  slide.addShape("ellipse", {x: icon.x / 96, y: icon.y / 96, w: 72 / 96, h: 72 / 96, fill: {color: context.colors.accent}, line: {transparency: 100}, hyperlink: hyperlink && {url: source}, objectName: names.badge});
1834
1835
  slide.addShape("triangle", {x: (icon.x + 28) / 96, y: (icon.y + 22) / 96, w: 28 / 96, h: 28 / 96, rotate: 90, fill: {color: "FFFFFF"}, line: {transparency: 100}, hyperlink: hyperlink && {url: source}, objectName: names.play});
1835
1836
  addMeasuredPayloadText(slide, mediaCaption(value), {...box, y: icon.y + 72 + 20, height: 50}, context, options,
@@ -1914,7 +1915,7 @@ function addPlaceholderPayload(slide, label, description, region, context) {
1914
1915
  w: region.w,
1915
1916
  h: region.h,
1916
1917
  fill: { color: context.colors.surface, transparency: 10 },
1917
- line: { color: context.colors.border, pt: 0.75 }
1918
+ line: { color: context.colors.border, width: 0.75 }
1918
1919
  });
1919
1920
  slide.addText(`${label}\n${description}`, {
1920
1921
  x: region.x + 0.12,
@@ -1937,12 +1938,11 @@ function addPlaceholderPayload(slide, label, description, region, context) {
1937
1938
  // always names the developer's chosen family. Measurement still uses the
1938
1939
  // substitute, because the provider resolves the chosen family again on every
1939
1940
  // measure/outline call, so layout is unchanged.
1940
- const FACE_STYLE_WORDS = /^(?:(?:thin|hairline|extra ?light|ultra ?light|light|semi ?light|demi ?light|book|regular|normal|medium|semi ?bold|demi ?bold|bold|extra ?bold|ultra ?bold|black|heavy|extra ?black|ultra ?black|italic|oblique)\s*)+$/i;
1941
1941
  function sameTypeface(requested, resolved) {
1942
1942
  if (typeof resolved !== 'string') return false;
1943
1943
  const want = requested.trim().toLowerCase(), got = resolved.trim().toLowerCase();
1944
1944
  // A legacy four-style family such as "Roboto Medium" is the chosen typeface.
1945
- return got === want || got.startsWith(`${want} `) && FACE_STYLE_WORDS.test(got.slice(want.length + 1));
1945
+ return got === want || got.startsWith(`${want} `) && isFaceStyleSuffix(got.slice(want.length + 1));
1946
1946
  }
1947
1947
  function chosenFamilyMeasurement(measurement) {
1948
1948
  if (!measurement || typeof measurement.resolveStyle !== 'function') return measurement;
@@ -1,7 +1,10 @@
1
1
  import {readBackgroundColor} from './background.js';
2
+ import {splitWeightFace} from './font-weights.js';
2
3
 
3
4
  const boolean = value => ['1','true','on'].includes(value) ? true : ['0','false','off'].includes(value) ? false : undefined;
4
5
 
6
+ const themeFamilies = context => ['a:majorFont', 'a:minorFont'].map(key => context.fonts?.[key]?.['a:latin']?.typeface?.trim().toLowerCase()).filter(Boolean);
7
+
5
8
  export function nativeRunStyle(properties, context, relationships, report, kind = 'table') {
6
9
  const result = {};
7
10
  for (const [native, opf] of [['b','bold'], ['i','italic']]) {
@@ -23,8 +26,19 @@ export function nativeRunStyle(properties, context, relationships, report, kind
23
26
  if (font) {
24
27
  const match = font.match(/^\+(mj|mn)-(lt|ea|cs)$/);
25
28
  const family = match ? context.fonts?.[match[1] === 'mj' ? 'a:majorFont' : 'a:minorFont']?.[{lt:'a:latin',ea:'a:ea',cs:'a:cs'}[match[2]]]?.typeface : font;
26
- if (family) result.fontFamily = family;
27
- else report(`unsupported-${kind}-font`, 'The theme font reference could not be resolved from this archive.');
29
+ if (family) {
30
+ result.fontFamily = family;
31
+ // The exporter writes a bundled family's weight faces (Roboto SemiBold) under their
32
+ // native style-link names. OPF runs carry the family plus bold, never the face name.
33
+ // A typeface that is itself a theme font was authored as a family; leave it as is.
34
+ const face = themeFamilies(context).includes(family.trim().toLowerCase()) ? undefined : splitWeightFace(family);
35
+ if (face) {
36
+ result.fontFamily = face.family;
37
+ if (face.weight >= 600) result.bold = true;
38
+ if (face.italic) result.italic = true;
39
+ if (face.weight !== 400 && face.weight !== 700) report(`approximate-${kind}-font-weight`, 'OPF text runs represent regular or bold only; the native weight face was imported as its family, with bold for weights of 600 and above.');
40
+ }
41
+ } else report(`unsupported-${kind}-font`, 'The theme font reference could not be resolved from this archive.');
28
42
  }
29
43
  if (properties['a:solidFill']) {
30
44
  const color = readBackgroundColor(properties['a:solidFill'], context);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openpresentation/opf-pptx",
3
- "version": "0.10.0",
3
+ "version": "0.11.0",
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 test/design-alignment.mjs && node --check src/color-ref.js && node --check src/theme-colors.js && node --check test/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 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/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 test/shared-timeline.mjs && node --check test/shared-furniture.mjs && node --check src/furniture-provenance.js && node --check src/document-provenance.js && node --check test/document-provenance.mjs && node --check test/furniture-provenance.mjs && node --check src/furniture-fields.js && node --check test/furniture-fields.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/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/media-placeholder.mjs && node --check test/image-placeholder.mjs && node --check test/list-shape-names.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/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",
41
- "test": "npm run build && node test/zip-date.mjs && node test/background-fills.mjs && npm run test:furniture && node test/slide-image.mjs && node test/slide-image-treatments.mjs && node test/document-provenance.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/export-chosen-fonts.mjs && node test/typeface-inventory.mjs && node test/color-ref-export.mjs && node test/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/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/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/table-frame-furniture-order.mjs && node test/media-placeholder.mjs && node test/image-placeholder.mjs && node test/list-shape-names.mjs && node test/shared-quote.mjs && npm run test:timeline && npm run test:determinism",
40
+ "typecheck": "node --check src/index.js && 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 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 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/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 test/shared-timeline.mjs && node --check test/shared-furniture.mjs && node --check src/furniture-provenance.js && node --check src/document-provenance.js && node --check test/document-provenance.mjs && node --check test/furniture-provenance.mjs && node --check src/furniture-fields.js && node --check test/furniture-fields.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/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/list-shape-names.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",
41
+ "test": "npm run build && node test/zip-date.mjs && node test/background-fills.mjs && npm run test:furniture && node test/slide-image.mjs && node test/slide-image-treatments.mjs && node test/document-provenance.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/export-chosen-fonts.mjs && node test/typeface-inventory.mjs && node test/color-ref-export.mjs && node test/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/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/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/list-shape-names.mjs && node test/shared-quote.mjs && npm run test:timeline && npm run test:determinism",
42
42
  "validate": "node scripts/validate-package.mjs",
43
43
  "test:packed": "node test/packed-install.mjs",
44
44
  "test:compare-published": "node test/compare-published.mjs",
@@ -60,14 +60,14 @@
60
60
  "test:determinism": "node test/determinism.mjs"
61
61
  },
62
62
  "dependencies": {
63
- "@openpresentation/opf": "^0.11.1",
63
+ "@openpresentation/opf": "^0.11.2",
64
64
  "fast-xml-parser": "^5.8.0",
65
65
  "fflate": "^0.8.3",
66
66
  "jszip": "3.10.2",
67
67
  "sharp": "0.35.4"
68
68
  },
69
69
  "peerDependencies": {
70
- "@openpresentation/opf-render": "^0.10.0"
70
+ "@openpresentation/opf-render": "^0.11.0"
71
71
  },
72
72
  "peerDependenciesMeta": {
73
73
  "@openpresentation/opf-render": {
@@ -75,7 +75,8 @@
75
75
  }
76
76
  },
77
77
  "devDependencies": {
78
- "@openpresentation/opf-render": "0.10.0",
78
+ "@expo-google-fonts/carlito": "0.4.1",
79
+ "@openpresentation/opf-render": "0.11.0",
79
80
  "esbuild": "0.28.2",
80
81
  "playwright": "1.63.0"
81
82
  },