@openpresentation/opf-pptx 0.8.1 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,5 +1,9 @@
1
1
  # PptxGenJS runtime provenance
2
2
 
3
+ ## Coordinated `@openpresentation/opf` pin
4
+
5
+ PPTX 0.9.0 depends on published [`@openpresentation/opf@^0.11.0`](https://www.npmjs.com/package/@openpresentation/opf) (reference layer: ColorRef, document `variables`, exported `resolveColorRef()`). 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
+
3
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.
4
8
 
5
9
  The inspected PptxGenJS 4.0.1 ESM/CommonJS distributions do not import `image-size`. The only sizing-module reference is in a commented, unused helper. OPF supplies image geometry itself. The image-fit follow-up reads bounded PNG/JPEG/GIF/WebP dimension headers and inline JPEG EXIF orientation; it does not load image-size or decode pixels.
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # OPF PPTX
2
2
 
3
- Version 0.8.1 requires core 0.10.1, including metric, quote and timeline layout placeholders and the corrected text-bullet contract. It retains the existing PowerPoint export APIs.
3
+ Version 0.9.0 requires core 0.11.0, including ColorRef / `variables` on content colors. It hex-resolves named colors 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.
4
4
 
5
5
  Unfinished prepared shaping work is preserved in the [September 15 roadmap](docs/roadmap-shaping-20260915.md); it is not part of the published runtime.
6
6
 
@@ -72,7 +72,7 @@ The first exporter keeps the public API stable while using `pptxgenjs` internall
72
72
 
73
73
  - `Slide.title`, `Slide.subtitle`, and `Slide.tag` become editable text boxes, not PowerPoint master placeholders.
74
74
  - Root payloads, `blocks[]`, and promoted region keys become editable slide objects in deterministic regions. Promoted keys use the OPF 3x3 region vocabulary (`top`, `middle`, `bottom`, `left`, `center`, `right`).
75
- - Text, lists, metrics, quotes, timelines, code, tables, and inline-data charts are emitted as editable PowerPoint text, table, and chart objects.
75
+ - Text, lists, metrics, quotes, timelines, code, tables, and inline-data charts are emitted as editable PowerPoint text, table, and chart objects. Content ColorRef values (hex, scheme slots/roles, and `var:<id>`) flatten to `a:srgbClr` via core `resolveColorRef()`; this is hex-resolve, not native `schemeClr` / theme write.
76
76
  - Image assets are embedded only when supplied as data URIs, local paths, or host-resolved bytes/paths. Remote asset URLs are never fetched by the runtime path.
77
77
  - ZIP entries, generated chart/workbook part names, core-property timestamps, and nested chart workbook timestamps are normalized for reproducible bytes.
78
78
 
@@ -0,0 +1,97 @@
1
+ // Export ColorRef through published core resolveColorRef(). Keep 8-digit hex
2
+ // alpha locally: normalizeHexColor() strips the AA byte, which PptxGenJS needs.
3
+
4
+ import { resolveColorRef as resolveCoreColorRef } from "@openpresentation/opf";
5
+
6
+ const UNRESOLVED = "";
7
+
8
+ function isPlainObject(value) {
9
+ return value !== null && typeof value === "object" && !Array.isArray(value);
10
+ }
11
+
12
+ function withHash(value) {
13
+ if (typeof value !== "string") return undefined;
14
+ const trimmed = value.trim();
15
+ if (!trimmed) return undefined;
16
+ return trimmed.startsWith("#") ? trimmed : `#${trimmed}`;
17
+ }
18
+
19
+ function expandShorthand(hex) {
20
+ const raw = hex.replace(/^#/, "");
21
+ if (/^[0-9a-f]{3}$/i.test(raw)) return raw.split("").map((char) => char + char).join("");
22
+ return raw;
23
+ }
24
+
25
+ /** Literal #RGB / #RRGGBB / #RRGGBBAA, including pptx-internal hex without '#'. */
26
+ function literalExportHex(entry) {
27
+ if (typeof entry !== "string") return undefined;
28
+ const hashed = withHash(entry.trim());
29
+ if (!hashed) return undefined;
30
+ if (/^#(?:[0-9a-f]{3}|[0-9a-f]{6}|[0-9a-f]{8})$/i.test(hashed)) {
31
+ return `#${expandShorthand(hashed).toUpperCase()}`;
32
+ }
33
+ return undefined;
34
+ }
35
+
36
+ /** Pass document variables through; core accepts hex shorthand or `{ type: "color", value }`. */
37
+ export function resolveVariableColors(variables) {
38
+ return isPlainObject(variables) ? variables : {};
39
+ }
40
+
41
+ function coreRoles(ctx) {
42
+ const colors = ctx.colors ?? {};
43
+ return {
44
+ background: withHash(colors.background),
45
+ surface: withHash(colors.surface),
46
+ text: withHash(colors.text),
47
+ textSecondary: withHash(colors.mutedText),
48
+ accent: withHash(colors.accent),
49
+ };
50
+ }
51
+
52
+ function coreOptions(ctx, fallback = UNRESOLVED) {
53
+ return {
54
+ colorScheme: ctx.colorScheme ?? {},
55
+ roles: coreRoles(ctx),
56
+ variables: ctx.variables ?? {},
57
+ fallback,
58
+ };
59
+ }
60
+
61
+ /**
62
+ * Resolve a ColorRef or TextRun.color string to a #RRGGBB or #RRGGBBAA value.
63
+ * Unrecognized values return undefined so callers can apply the theme fallback.
64
+ */
65
+ export function resolveColorRefValue(entry, ctx) {
66
+ if (entry === undefined || entry === null || entry === "") return undefined;
67
+ if (typeof entry !== "string") return undefined;
68
+ const trimmed = entry.trim();
69
+ const literal = literalExportHex(trimmed);
70
+ if (literal) return literal;
71
+ const resolved = resolveCoreColorRef(trimmed, coreOptions(ctx, UNRESOLVED));
72
+ return resolved || undefined;
73
+ }
74
+
75
+ export function colorContext(context, fallback) {
76
+ return {
77
+ colorScheme: context.colorScheme,
78
+ colors: context.colors,
79
+ variables: context.variables ?? {},
80
+ fallback: fallback ?? context.colors?.text ?? "000000",
81
+ };
82
+ }
83
+
84
+ /** PptxGenJS export color: six or eight uppercase hex digits without '#'. */
85
+ export function resolveExportColor(entry, ctx) {
86
+ const fallback = ctx.fallback ?? ctx.colors?.text ?? "000000";
87
+ const resolved = resolveColorRefValue(entry, ctx);
88
+ const hex = resolved ?? withHash(fallback) ?? "#000000";
89
+ const raw = expandShorthand(hex.replace(/^#/, ""));
90
+ if (/^[0-9A-F]{6}([0-9A-F]{2})?$/i.test(raw)) return raw.toUpperCase();
91
+ return fallback.replace(/^#/, "").toUpperCase().slice(0, 6);
92
+ }
93
+
94
+ export function exportColorAlpha(entry, ctx) {
95
+ const raw = resolveExportColor(entry, ctx);
96
+ return raw.length === 8 ? (1 - parseInt(raw.slice(6), 16) / 255) * 100 : 0;
97
+ }
package/dist/index.js CHANGED
@@ -13,6 +13,7 @@ import {importBackground} from './background-import.js';
13
13
  import { webpToPng } from '#image-fallback';
14
14
  import { rasterMetadata, pictureTransform, normalizeImageOrientation } from './image-geometry.js';
15
15
  import { layoutTable, composeSlide, fitText, fitRichText, textWidthMeasurer, resolveCanvasDimensions, resolveFontFamilies, resolveTextStyle, textColorForFill, chartColorForFill } from "@openpresentation/opf/composition";
16
+ import { colorContext, resolveExportColor, resolveVariableColors } from "./color-ref.js";
16
17
  import PptxGenJS from "../vendor/pptxgenjs/pptxgen.es.js";
17
18
  import { unzipSync, zipSync } from "fflate";
18
19
  import { XMLParser } from "fast-xml-parser";
@@ -908,10 +909,15 @@ function resolvePresentationContext(presentation, options) {
908
909
  accent: normalizeHex(colorScheme.primary ?? colorScheme.accent1 ?? "#2874A6"),
909
910
  surface: normalizeHex(colorScheme.surface ?? (darkBackground ? colorScheme.dark2 : colorScheme.light2) ?? "#F8FAFC"),
910
911
  border: normalizeHex(colorScheme.accent5 ?? "#CBD5E1")
911
- }
912
+ },
913
+ variables: resolveVariableColors(presentation.variables)
912
914
  };
913
915
  }
914
916
 
917
+ function exportColor(entry, context, fallback) {
918
+ return resolveExportColor(entry, colorContext(context, fallback));
919
+ }
920
+
915
921
  function configurePresentation(pptx, presentation, context) {
916
922
  pptx.defineLayout({
917
923
  name: context.layoutName,
@@ -977,7 +983,10 @@ async function addSlide(pptx, presentation, opfSlide, slideIndex, context, optio
977
983
  } else if (item.field === "text" && item.text?.richLines) {
978
984
  const alignment=item.text.placement?.alignment??opfSlide.design?.contentAlignment??presentation.design?.contentAlignment??'left';
979
985
  for(const [index,line] of item.text.richLines.entries()){
980
- const runs=line.fragments.map(fragment=>({text:fragment.text,options:{...nativeFontOptions(fragment.style),fontSize:fragment.fontSize*.75,color:normalizeHex(fragment.run.color??slideContext.colors.text),underline:fragment.run.underline?{color:normalizeHex(fragment.run.color??slideContext.colors.text)}:undefined,strike:fragment.run.strikethrough?'sngStrike':undefined,baseline:fragment.baselineShift?-fragment.baselineShift/fragment.fontSize*2000:undefined,hyperlink:fragment.run.link&&/^(https?:|mailto:)/i.test(fragment.run.link)?{url:fragment.run.link}:undefined}}));
986
+ const runs=line.fragments.map(fragment=>{
987
+ const runColor=exportColor(fragment.run.color,slideContext,slideContext.colors.text);
988
+ return {text:fragment.text,options:{...nativeFontOptions(fragment.style),fontSize:fragment.fontSize*.75,color:normalizeHex(runColor),underline:fragment.run.underline?{color:normalizeHex(runColor)}:undefined,strike:fragment.run.strikethrough?'sngStrike':undefined,baseline:fragment.baselineShift?-fragment.baselineShift/fragment.fontSize*2000:undefined,hyperlink:fragment.run.link&&/^(https?:|mailto:)/i.test(fragment.run.link)?{url:fragment.run.link}:undefined}};
989
+ });
981
990
  const placed=item.text.placement?.lines[index],factor=alignment==='right'?1:alignment==='center'?.5:0;
982
991
  const area=placed?{...region,x:(placed.x+line.width*factor-item.box.width*factor)/96,y:placed.y/96,h:placed.height/96}:{...region,y:region.y+line.y/96,h:line.height/96};
983
992
  if(runs.length)slide.addText(runs,{...textBoxOptions(area,slideContext,item.text.fontSize*.75),align:alignment,fit:'none',wrap:false,lineSpacingMultiple:1});
@@ -999,7 +1008,7 @@ function resolveSlideContext(presentation, slide, baseContext, options) {
999
1008
  || Math.abs(resolved.dimensions.heightInches - baseContext.dimensions.heightInches) > 1e-6) {
1000
1009
  throw new OPFPptxError("mixed-slide-dimensions", "PowerPoint requires one canvas size per presentation. Set dimensions on the deck or export this slide separately.");
1001
1010
  }
1002
- return { ...baseContext, backgroundDefinition: resolved.backgroundDefinition, colorScheme: resolved.colorScheme, fonts: resolved.fonts, colors: resolved.colors, imageFill: effective.design.imageFill ?? "fit" };
1011
+ return { ...baseContext, backgroundDefinition: resolved.backgroundDefinition, colorScheme: resolved.colorScheme, fonts: resolved.fonts, colors: resolved.colors, variables: resolved.variables, imageFill: effective.design.imageFill ?? "fit" };
1003
1012
  }
1004
1013
 
1005
1014
  function fieldToType(field) {
@@ -1066,8 +1075,12 @@ function addTextPayload(slide, value, region, context) {
1066
1075
  slide.addText(stringifyText(value), textBoxOptions(region, context, 18));
1067
1076
  }
1068
1077
 
1069
- function richLineRuns(line,color) {
1070
- return line.fragments.map(fragment=>({text:fragment.text,options:{...nativeFontOptions(fragment.style),fontSize:fragment.fontSize*.75,color:normalizeHex(fragment.run.color??color),underline:fragment.run.underline?{color:normalizeHex(fragment.run.color??color)}:undefined,strike:fragment.run.strikethrough?'sngStrike':undefined,baseline:fragment.baselineShift?-fragment.baselineShift/fragment.fontSize*2000:undefined,hyperlink:fragment.run.link&&/^(https?:|mailto:)/i.test(fragment.run.link)?{url:fragment.run.link}:undefined}}));
1078
+ function richLineRuns(line,color,context) {
1079
+ const fallback=color.replace(/^#/,'');
1080
+ return line.fragments.map(fragment=>{
1081
+ const runColor=exportColor(fragment.run.color,context,fallback);
1082
+ return {text:fragment.text,options:{...nativeFontOptions(fragment.style),fontSize:fragment.fontSize*.75,color:normalizeHex(runColor),underline:fragment.run.underline?{color:normalizeHex(runColor)}:undefined,strike:fragment.run.strikethrough?'sngStrike':undefined,baseline:fragment.baselineShift?-fragment.baselineShift/fragment.fontSize*2000:undefined,hyperlink:fragment.run.link&&/^(https?:|mailto:)/i.test(fragment.run.link)?{url:fragment.run.link}:undefined}};
1083
+ });
1071
1084
  }
1072
1085
  function addMeasuredList(slide,fit,context) {
1073
1086
  for(const entry of fit.listEntries){
@@ -1078,7 +1091,7 @@ function addMeasuredList(slide,fit,context) {
1078
1091
  const objectName=first?`OPF list paragraph ${context.listMarkers.size+1}`:undefined;
1079
1092
  if(first)context.listMarkers.set(objectName,{fontFamily:entry.marker.style.fontFamily,fontSize:entry.marker.fontSize*.75,color:normalizeHex(context.colors.text)});
1080
1093
  const paragraph=first?{bullet:{characterCode:entry.marker.text.codePointAt(0).toString(16).padStart(4,'0'),indent:entry.marker.indent*.75},indentLevel:level}:{bullet:false};
1081
- const runs=richLineRuns(line,color);
1094
+ const runs=richLineRuns(line,color,context);
1082
1095
  if(!runs.length)runs.push({text:'',options:{}});
1083
1096
  // Keep paragraph intent identical across runs. ZIP normalization below
1084
1097
  // removes the duplicate paragraph-property nodes emitted by PptxGenJS.
@@ -1210,8 +1223,10 @@ function addTablePayload(slide, table, region, context, options, path) {
1210
1223
  const rows = layout.rows.map(row => row.cells.map(cell => {
1211
1224
  const {header,rich,fit} = cell;
1212
1225
  const cellStyle = cell.style ?? {};
1213
- const baseFill = (cellStyle.fill ?? (header ? '#' + context.colors.accent : '#' + context.colors.surface)).replace(/^#/, '');
1214
- const baseColor = (cellStyle.color ?? textColorForFill('#' + baseFill, header ? '#FFFFFF' : '#' + context.colors.text)).replace(/^#/, '');
1226
+ const defaultFillHex = header ? context.colors.accent : context.colors.surface;
1227
+ const baseFill = exportColor(cellStyle.fill ?? defaultFillHex, context, defaultFillHex);
1228
+ const inheritedText = textColorForFill(`#${baseFill}`, header ? "#FFFFFF" : `#${context.colors.text}`);
1229
+ const baseColor = exportColor(cellStyle.color ?? inheritedText.replace(/^#/, ""), context, inheritedText.replace(/^#/, ""));
1215
1230
  const alpha = value => value.length === 8 ? (1 - parseInt(value.slice(6), 16) / 255) * 100 : 0;
1216
1231
  const text = stringifyText(cell.value), style = cell.textStyle;
1217
1232
  const fragments = rich ? fit.richLines.flatMap(line => line.fragments) : [];
@@ -1219,7 +1234,7 @@ function addTablePayload(slide, table, region, context, options, path) {
1219
1234
  const run = typeof value === 'string' ? {text:value} : value;
1220
1235
  const fragment = fragments.find(item => item.runIndex === index);
1221
1236
  const runStyle = fragment?.style ?? resolveTextStyle({...style,fontFamily:run.fontFamily ?? style.fontFamily,fontWeight:run.bold === undefined ? style.fontWeight : run.bold ? 700 : 400,italic:run.italic ?? style.italic}, options.textMeasurement);
1222
- const rawColor = /^#(?:[0-9a-f]{3}|[0-9a-f]{6}|[0-9a-f]{8})$/i.test(run.color ?? '') ? run.color.slice(1) : baseColor;
1237
+ const rawColor = run.color !== undefined && run.color !== '' ? exportColor(run.color, context, baseColor) : baseColor;
1223
1238
  const color = normalizeHex(rawColor), transparency = rawColor.length === 8 ? (1 - parseInt(rawColor.slice(6), 16) / 255) * 100 : 0;
1224
1239
  const runOptions = {
1225
1240
  ...nativeFontOptions(runStyle),fontSize:fragment ? fragment.fontSize * .75 : fit.fontSize * .75,
@@ -1494,9 +1509,9 @@ function textRuns(value, context, fallbackFontSize) {
1494
1509
  options: {
1495
1510
  bold: run?.bold,
1496
1511
  italic: run?.italic,
1497
- underline: run?.underline ? { color: normalizeHex(run.color ?? context.colors.text) } : undefined,
1512
+ underline: run?.underline ? { color: normalizeHex(exportColor(run?.color, context, context.colors.text)) } : undefined,
1498
1513
  strike: run?.strikethrough ? "sngStrike" : undefined,
1499
- color: normalizeHex(run?.color ?? context.colors.text),
1514
+ color: normalizeHex(exportColor(run?.color, context, context.colors.text)),
1500
1515
  fontFace: run?.fontFamily ?? context.fonts.body,
1501
1516
  fontSize: run?.fontSize ?? fallbackFontSize,
1502
1517
  superscript: run?.superscript,
@@ -1672,18 +1687,19 @@ function resolveDimensions(value) {
1672
1687
  }
1673
1688
 
1674
1689
  function resolveBackground(value, colorScheme) {
1690
+ const fallback = "FFFFFF";
1675
1691
  if (typeof value === "string") {
1676
- if (value.startsWith("#")) return normalizeHex(value);
1677
- return normalizeHex(colorScheme[value] ?? colorScheme.background ?? colorScheme.light1 ?? "#FFFFFF");
1692
+ if (value.startsWith("#")) return normalizeHex(value, fallback);
1693
+ return normalizeHex(colorScheme[value] ?? colorScheme.background ?? colorScheme.light1 ?? "#FFFFFF", fallback);
1678
1694
  }
1679
1695
  if (isPlainObject(value)) {
1680
- if (value.type === "solid" && value.color) return normalizeHex(value.color);
1696
+ if (value.type === "solid" && value.color) return normalizeHex(value.color, fallback);
1681
1697
  if (value.type === "theme" && value.slot) {
1682
- return normalizeHex(colorScheme[value.slot] ?? colorScheme.light1 ?? "#FFFFFF");
1698
+ return normalizeHex(colorScheme[value.slot] ?? colorScheme.light1 ?? "#FFFFFF", fallback);
1683
1699
  }
1684
- if (value.backgroundColor) return normalizeHex(value.backgroundColor);
1700
+ if (value.backgroundColor) return normalizeHex(value.backgroundColor, fallback);
1685
1701
  }
1686
- return normalizeHex(colorScheme.background ?? colorScheme.light1 ?? "#FFFFFF");
1702
+ return normalizeHex(colorScheme.background ?? colorScheme.light1 ?? "#FFFFFF", fallback);
1687
1703
  }
1688
1704
 
1689
1705
  function resolveFonts(fontScheme) {
@@ -1697,8 +1713,8 @@ function readableTextColor(background, colorScheme) {
1697
1713
  : normalizeHex(colorScheme.text ?? colorScheme.dark1 ?? "#0F172A");
1698
1714
  }
1699
1715
 
1700
- function normalizeHex(value) {
1701
- if (typeof value !== "string") return "000000";
1716
+ function normalizeHex(value, fallback = "000000") {
1717
+ if (typeof value !== "string") return fallback.replace(/^#/, "").toUpperCase().slice(0, 6);
1702
1718
  const raw = value.trim().replace(/^#/, "");
1703
1719
  if (/^[0-9a-fA-F]{3}$/.test(raw)) {
1704
1720
  return raw.split("").map((char) => char + char).join("").toUpperCase();
@@ -1706,7 +1722,7 @@ function normalizeHex(value) {
1706
1722
  if (/^[0-9a-fA-F]{6,8}$/.test(raw)) {
1707
1723
  return raw.slice(0, 6).toUpperCase();
1708
1724
  }
1709
- return raw.toUpperCase();
1725
+ return fallback.replace(/^#/, "").toUpperCase().slice(0, 6);
1710
1726
  }
1711
1727
 
1712
1728
  function isDarkHex(value) {
@@ -1895,7 +1911,9 @@ function normalizePartBytes(path, bytes, context, renameMaps, entries, imageMeta
1895
1911
  for (const [edge, native] of [['left','lnL'],['right','lnR'],['top','lnT'],['bottom','lnB']]) {
1896
1912
  const border = style.borders?.[edge];
1897
1913
  if (!border) continue;
1898
- const fill = border.width === 0 ? '<a:noFill/>' : nativeBackgroundFill({type:'solid',color:border.color},{width:1,height:1});
1914
+ const borderHex = exportColor(border.color, context, context.colors.border);
1915
+ const borderOpacity = borderHex.length === 8 ? parseInt(borderHex.slice(6), 16) / 255 : 1;
1916
+ const fill = border.width === 0 ? '<a:noFill/>' : nativeBackgroundFill({type:'solid',color:'#' + borderHex.slice(0, 6), opacity: borderOpacity},{width:1,height:1}, context.colors.border);
1899
1917
  const dash = {solid:'solid',dash:'dash',dot:'sysDot'}[border.dash ?? 'solid'];
1900
1918
  const line = `<a:${native} w="${Math.round(border.width * table.scale * 9525)}" cap="flat" cmpd="sng" algn="ctr">${fill}<a:prstDash val="${dash}"/></a:${native}>`;
1901
1919
  const existing = new RegExp(`<a:${native}\\b[^>]*>[\\s\\S]*?<\\/a:${native}>`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openpresentation/opf-pptx",
3
- "version": "0.8.1",
3
+ "version": "0.9.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 scripts/build.mjs && node --check scripts/validate-package.mjs && node --check test/smoke.mjs && node --check src/image-geometry.js && 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/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 test/furniture-provenance.mjs",
41
- "test": "npm run build && npm run test:furniture && node test/plain-whitespace.mjs && node test/accepted-text.mjs && node test/heading-provenance.mjs && node test/chart-colors.mjs && node test/content-cards.mjs && node test/chart-workbook.mjs && node test/dependency-boundary.mjs && npm run build:browser-check && npm run test:styled-table && node test/content-layout.mjs && node test/shared-quote.mjs && npm run test:timeline",
40
+ "typecheck": "node --check src/index.js && node --check src/color-ref.js && node --check scripts/build.mjs && node --check scripts/validate-package.mjs && node --check test/smoke.mjs && node --check src/image-geometry.js && 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/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 test/furniture-provenance.mjs",
41
+ "test": "npm run build && npm run test:furniture && node test/color-ref-export.mjs && node test/plain-whitespace.mjs && node test/accepted-text.mjs && node test/heading-provenance.mjs && node test/chart-colors.mjs && node test/content-cards.mjs && node test/chart-workbook.mjs && node test/dependency-boundary.mjs && npm run build:browser-check && npm run test:styled-table && node test/content-layout.mjs && node test/shared-quote.mjs && npm run test:timeline",
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",
@@ -53,7 +53,7 @@
53
53
  "test:furniture": "node test/shared-furniture.mjs && node test/furniture-provenance.mjs"
54
54
  },
55
55
  "dependencies": {
56
- "@openpresentation/opf": "^0.10.1",
56
+ "@openpresentation/opf": "^0.11.0",
57
57
  "fast-xml-parser": "^5.8.0",
58
58
  "fflate": "^0.8.3",
59
59
  "jszip": "3.10.2",