@openpresentation/opf-pptx 0.11.1 → 0.11.3

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.
@@ -158,7 +158,7 @@ export function partScriptFonts(path, xml, plan, slideIndex) {
158
158
  xml = xml.replace(/<p:(sp|graphicFrame)>[\s\S]*?<\/p:\1>/g, shape =>
159
159
  runScriptFonts(shape, resolved, /<p:cNvPr\b[^>]*\bname="OPF heading /.test(shape)));
160
160
  if (plan.rtl) xml = paragraphRtl(xml, plan.deck.direction);
161
- } else if (/^ppt\/(?:charts\/chart|notesSlides\/notesSlide)\d+\.xml$/.test(path)) {
161
+ } else if (/^ppt\/(?:charts\/chart(?:Ex)?|notesSlides\/notesSlide)\d+\.xml$/.test(path)) {
162
162
  xml = runScriptFonts(xml, resolved, false);
163
163
  if (plan.rtl && path.startsWith("ppt/notesSlides/")) xml = paragraphRtl(xml, plan.deck.direction);
164
164
  } else if (plan.rtl && /^ppt\/(?:slideMasters\/slideMaster\d+|slideLayouts\/slideLayout\d+|notesMasters\/notesMaster\d+|presentation)\.xml$/.test(path)) {
@@ -102,7 +102,9 @@ function backgroundSlot(definition) {
102
102
  * `vendor` limits the result to scheme values PptxGenJS 4.0.1 can emit.
103
103
  */
104
104
  export function schemeColorValue(reference, hex, context, {vendor = false} = {}) {
105
- if (typeof reference !== 'string') return undefined;
105
+ // A slide with its own color scheme is pinned to literal colors: a theme edit
106
+ // in PowerPoint must not partially recolor it, even where a value coincides.
107
+ if (typeof reference !== 'string' || context.schemeOverride) return undefined;
106
108
  const name = reference.trim();
107
109
  const resolved = sixDigitHex(hex);
108
110
  const deck = context.themeColors;
@@ -120,17 +122,73 @@ export function schemeColorValue(reference, hex, context, {vendor = false} = {})
120
122
  return undefined;
121
123
  }
122
124
 
125
+ function backgroundOpacity(definition) {
126
+ const opacity = typeof definition === 'object' && Number.isFinite(definition?.opacity) ? definition.opacity : 1;
127
+ return Math.max(0, Math.min(1, opacity));
128
+ }
129
+
130
+ /** Scheme value of a theme-slot slide background, or undefined when the background stays literal. */
131
+ export function schemeBackgroundValue(definition, context) {
132
+ const slot = backgroundSlot(definition);
133
+ return slot ? schemeColorValue(slot, context.colors?.background, context) : undefined;
134
+ }
135
+
123
136
  /** Native slide background fill for a theme-slot background, or null when it must stay literal. */
124
137
  export function schemeBackgroundFill(definition, context) {
125
- const slot = backgroundSlot(definition);
126
- if (!slot) return null;
127
- const value = schemeColorValue(slot, context.colors?.background, context);
138
+ const value = schemeBackgroundValue(definition, context);
128
139
  if (!value) return null;
129
- const opacity = typeof definition === 'object' && Number.isFinite(definition.opacity) ? definition.opacity : 1;
130
- const alpha = Math.round(Math.max(0, Math.min(1, opacity)) * 100000);
140
+ const alpha = Math.round(backgroundOpacity(definition) * 100000);
131
141
  return `<a:solidFill>${alpha === 100000 ? `<a:schemeClr val="${value}"/>` : `<a:schemeClr val="${value}"><a:alpha val="${alpha}"/></a:schemeClr>`}</a:solidFill>`;
132
142
  }
133
143
 
144
+ // The exporter picks default text for contrast with the slide background:
145
+ // light1 on a dark background, else the scheme text (dark1); muted text is
146
+ // dark2/light2. When the background itself is a theme reference, the matching
147
+ // text slot is the semantic pair, so a PowerPoint theme switch moves both.
148
+ const TEXT_PAIRS = Object.freeze({
149
+ light1: {text: 'dark1', muted: 'dark2'}, light2: {text: 'dark1', muted: 'dark2'},
150
+ dark1: {text: 'light1', muted: 'light2'}, dark2: {text: 'light1', muted: 'light2'},
151
+ });
152
+
153
+ /**
154
+ * Scheme values for the exporter's default text and muted text on this slide.
155
+ * A role gets a scheme value only when the slide background is an opaque theme
156
+ * reference to a light or dark slot, the slide has no scheme override, and the
157
+ * resolved default color is exactly the paired slot in the deck theme.
158
+ */
159
+ export function defaultTextSchemeValues(context) {
160
+ const background = schemeBackgroundValue(context.backgroundDefinition, context);
161
+ if (!background || backgroundOpacity(context.backgroundDefinition) !== 1) return {};
162
+ const pair = TEXT_PAIRS[backgroundSlot(context.backgroundDefinition)];
163
+ if (!pair) return {};
164
+ const values = {};
165
+ for (const role of ['text', 'muted']) {
166
+ const hex = role === 'text' ? context.colors?.text : context.colors?.mutedText;
167
+ const value = schemeColorValue(pair[role], hex, context);
168
+ if (value) values[role] = value;
169
+ }
170
+ return values;
171
+ }
172
+
173
+ /**
174
+ * Scheme value for the exporter's default text in a table cell whose fill is a
175
+ * theme reference (`fillValue`, from schemeColorValue). A light or dark fill (bg1,
176
+ * bg2, tx1, tx2) pairs with the opposite text slot (dark1 on bg, light1 on tx).
177
+ * An accent fill has contrast-selected text, which stays a theme reference only
178
+ * when the selection is exactly the deck light1 or dark1. Anything else, and any
179
+ * fill that is not a theme reference, keeps literal text.
180
+ */
181
+ export function tableTextSchemeValue(fillValue, hex, context) {
182
+ if (!fillValue) return undefined;
183
+ const light = /^bg[12]$/.test(fillValue), dark = /^tx[12]$/.test(fillValue);
184
+ const candidates = light ? ['dark1'] : dark ? ['light1'] : /^accent[1-6]$/.test(fillValue) ? ['light1', 'dark1'] : [];
185
+ for (const slot of candidates) {
186
+ const value = schemeColorValue(slot, hex, context, {vendor: true});
187
+ if (value) return value;
188
+ }
189
+ return undefined;
190
+ }
191
+
134
192
  // ---------------------------------------------------------------------------
135
193
  // Import
136
194
 
@@ -202,3 +260,31 @@ export function recoverTheme({themeName, colors, majorFont, minorFont}, {themes,
202
260
  const fontMatch = !!fonts && fonts.heading === majorFont && fonts.body === minorFont;
203
261
  return colorMatch || fontMatch ? {id: theme.id} : {unverified: theme.id};
204
262
  }
263
+
264
+ // ---------------------------------------------------------------------------
265
+ // Slide master
266
+
267
+ /** a:solidFill child for a pptx color value: a scheme value or RRGGBB. */
268
+ export function solidColorXml(value) {
269
+ return /^(?:tx[12]|bg[12]|accent[1-6]|hlink|folHlink)$/.test(value) ? `<a:schemeClr val="${value}"/>` : `<a:srgbClr val="${value}"/>`;
270
+ }
271
+
272
+ /**
273
+ * Give the slide master the deck's theme background and matching default text
274
+ * so slides added in PowerPoint follow the chosen theme. The vendored master
275
+ * has no background (its layout uses the bg1 background style) and tx1 text.
276
+ */
277
+ export function writeMasterBackground(xml, {fill, text}) {
278
+ let result = xml.replace(/<p:bg>[\s\S]*?<\/p:bg>/, '');
279
+ result = result.replace(/<p:cSld\b([^>]*)>/, `<p:cSld$1><p:bg><p:bgPr>${fill}<a:effectLst/></p:bgPr></p:bg>`);
280
+ if (text && text !== 'tx1') {
281
+ result = result.replace(/<p:txStyles>[\s\S]*?<\/p:txStyles>/, styles =>
282
+ styles.split('<a:solidFill><a:schemeClr val="tx1"/></a:solidFill>').join(`<a:solidFill>${solidColorXml(text)}</a:solidFill>`));
283
+ }
284
+ return result;
285
+ }
286
+
287
+ /** The vendored layout's own bg1 background would hide the master background. */
288
+ export function inheritLayoutBackground(xml) {
289
+ return xml.replace(/<p:bg>[\s\S]*?<\/p:bg>/, '');
290
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openpresentation/opf-pptx",
3
- "version": "0.11.1",
3
+ "version": "0.11.3",
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 src/watermark-provenance.js && 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/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/watermark.mjs && node test/media-dedupe.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",
40
+ "typecheck": "node --check src/index.js && node --check src/chart-types.js && node --check src/chartex.js && node --check test/chartex.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/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/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/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/chart-types.mjs && node test/chartex.mjs && 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/watermark.mjs && node test/media-dedupe.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/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/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",