@openpresentation/opf-pptx 0.24.0 → 0.25.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -3
- package/dist/index.d.ts +3 -2
- package/dist/index.js +46 -7
- package/dist/template-layouts.js +5 -5
- package/package.json +5 -5
package/README.md
CHANGED
|
@@ -74,7 +74,7 @@ const bytes = await toPptx({
|
|
|
74
74
|
await fs.promises.writeFile("quarterly-review.pptx", bytes);
|
|
75
75
|
```
|
|
76
76
|
|
|
77
|
-
`toPptx` returns a `Uint8Array` containing a PowerPoint-openable `.pptx`. It does not fetch remote assets. Data URI images and local paths can be embedded directly; hosts that need private asset loading should pass `imageResolver(src, context)`. Every picture path (image blocks and region images, quote photos, slide and deck background pictures, watermarks, cover logos, header and footer images and logos, picture bullets) reports an unresolved source with exactly one `unresolved-asset` diagnostic at its OPF path and draws the preview's fallback (the placeholder, the background colour, no watermark, or the character bullets), so strict CLI export (`--fail-on warning`) gates it. A local file path that cannot be read is unresolved like any other source (`reason: 'file-unreadable'`), instead of failing the export. Set `strictAssets: true` to turn unresolved or remote image assets into structured `OPFPptxError` failures instead of editable placeholder boxes. The "Image unavailable" placeholder (a dashed panel `OPF image placeholder N` with its label lines, or a cross when no label fits) is one native group `OPF image placeholder N group` (opf-pptx#221) that carries the accessible name (`Image unavailable: <alt>`) as its alt text, so PowerPoint moves or deletes it as one object while each member stays an editable shape; a logo and a header or footer image keep their ungrouped panel (generated furniture is never grouped). In `full` provenance the panel of an image block carries an `OPF_IMAGE_PLACEHOLDER_V1` tag (the slide, the block path, the authored image value and the block's own fields), and `fromPptx` turns it back into that image block; without the tag (`references-only`, `provenance: false`) or with a damaged one (`invalid-image-provenance`) the shapes import as ordinary content.
|
|
77
|
+
`toPptx` returns a `Uint8Array` containing a PowerPoint-openable `.pptx`. It does not fetch remote assets. Data URI images and local paths can be embedded directly; hosts that need private asset loading should pass `imageResolver(src, context)`. Every picture path (image blocks and region images, quote photos, slide and deck background pictures, watermarks, cover logos, header and footer images and logos, picture bullets) reports an unresolved source with exactly one `unresolved-asset` diagnostic at its OPF path and draws the preview's fallback (the placeholder, the background colour, no watermark, or the character bullets), so strict CLI export (`--fail-on warning`) gates it. A local file path that cannot be read is unresolved like any other source (`reason: 'file-unreadable'`), instead of failing the export. An `imageResolver` that throws (the CLI's does for a local image it cannot read) is unresolved the same way (`reason: 'resolver-failed'`): the export continues without that image, with its fallback and one `unresolved-asset` warning that carries the resolver's message and says the image was left out (a background keeps the slide's resolved background color, with no picture and no relationship written). `strictAssets: true` turns that failure into a `missing-asset` `OPFPptxError` (`details.reason: 'resolver-failed'`), like every other unresolved source; the CLI's `--fail-on warning` gates the warning. A cancelled export (`AbortError`) and an `OPFPptxError` the resolver throws still propagate. Set `strictAssets: true` to turn unresolved or remote image assets into structured `OPFPptxError` failures instead of editable placeholder boxes. The "Image unavailable" placeholder (a dashed panel `OPF image placeholder N` with its label lines, or a cross when no label fits) is one native group `OPF image placeholder N group` (opf-pptx#221) that carries the accessible name (`Image unavailable: <alt>`) as its alt text, so PowerPoint moves or deletes it as one object while each member stays an editable shape; a logo and a header or footer image keep their ungrouped panel (generated furniture is never grouped). In `full` provenance the panel of an image block carries an `OPF_IMAGE_PLACEHOLDER_V1` tag (the slide, the block path, the authored image value and the block's own fields), and `fromPptx` turns it back into that image block; without the tag (`references-only`, `provenance: false`) or with a damaged one (`invalid-image-provenance`) the shapes import as ordinary content.
|
|
78
78
|
|
|
79
79
|
Embedded picture bytes are a PNG, JPEG, GIF, WebP or SVG. Bytes that are no readable image (or an SVG that cannot be exported: malformed XML, no `xmlns`, no width and height and no viewBox, or no rasterizer, see below) export what the preview shows, like an unresolved asset: the "Image unavailable" placeholder (nothing for a watermark, the background colour for a background image) and one `unresolved-asset` diagnostic with a `reason` (`unsupported-format`, `svg-malformed`, `svg-no-size`, `svg-too-large`, `svg-unsafe`, `svg-unreadable`, `svg-rasterizer-unavailable`, `svg-render-failed`); `strictAssets` throws `unsupported-image-dimensions` for an unreadable raster, `invalid-svg-image` for an unusable SVG, `svg-rasterizer-unavailable` or `svg-render-failed`.
|
|
80
80
|
|
|
@@ -322,7 +322,7 @@ LibreOffice is not a runtime dependency. When it is installed in CI or a local v
|
|
|
322
322
|
Public npm package publication is handled by `.github/workflows/release.yml` through npm Trusted Publishing (GitHub Actions OIDC) with npm provenance; no npm token is stored. The owner authorized agents to prepare and publish npm releases whenever a release is required (2026-09-29). This authorization does not waive any gate.
|
|
323
323
|
|
|
324
324
|
1. Open a release-prep PR containing only the version bump, `CHANGELOG.md` (assembled from `changes/` with `node scripts/changelog-fragments.mjs assemble --version X.Y.Z`), dependency ranges, lockfile and current-instruction docs. Publish in dependency order (core, then renderer, then PPTX, then editor): refresh this repo's lockfile only after the required `@openpresentation/opf` and `@openpresentation/opf-render` versions are on the registry (`npm install --package-lock-only`), then run `npm run test:packed` against them.
|
|
325
|
-
2. Merge after CI is green, then publish by pushing the git tag `opf-pptx-v<version>` (or `@openpresentation/opf-pptx@v<version>`) at the merge commit. The workflow verifies that the tag matches `package.json` and
|
|
325
|
+
2. Merge after CI is green, then publish by pushing the git tag `opf-pptx-v<version>` (or `@openpresentation/opf-pptx@v<version>`) at the merge commit. The workflow verifies that the tag matches `package.json`, that the push-to-`main` run of `ci.yml` on exactly the tagged commit succeeded (`scripts/ci-evidence.mjs`; it fails closed and re-runs no test, RR-100), and the artifact itself (audit, the packed install, `npm pack --dry-run`) before `npm publish --access public --provenance`. A tag pushed while CI still runs fails at the evidence step; re-run the failed job once CI is green. A manual `workflow_dispatch` runs the same job without the tag check and is a fallback only.
|
|
326
326
|
3. Verify with `npm view @openpresentation/opf-pptx@<version> version gitHead dist.attestations` and, from the core repo, `node scripts/test-pptx-publication.mjs <version> <release-commit> <this-checkout>`. Never republish an existing version.
|
|
327
327
|
|
|
328
328
|
## Shared dynamic composition
|
|
@@ -414,7 +414,7 @@ Pattern backgrounds export as a native `<a:pattFill>` with explicit foreground a
|
|
|
414
414
|
|
|
415
415
|
`code.language` and `metric.trend` export natively from the same core tables the preview reads (RR-07). Code keeps one text box per source line; a language adds coloured runs (theme-derived colours kept at 4.5:1 or more on the #111827 panel) whose text concatenates to the exact line, and an unknown language is exported plain. A trend adds one arrow shape (`upArrow`, `downArrow` or `rightArrow`, alt text "Trend: up") beside the trend word and colours the delta and trend text green, red or neutral; the arrow is part of the metric's provenance group, so the metric re-imports as the one authored payload. `scripts/rr07-native-set.mjs <dir>` builds the native-check decks, preview PNGs and a manifest.
|
|
416
416
|
|
|
417
|
-
An image background (`{type: "image", src, alt, fit, focus, opacity, recolor, overlay}`, or an image source string, a cover image; core composes it as `backgroundImage` from the slide, deck or theme background) never moves content. Without alt text it exports as the native slide background, an `<a:blipFill>` for the embedded raster (`data:` sources, declared `asset:` ids and `imageResolver` results): `cover` (the default) crops the picture to the slide with `a:srcRect` around the `focus` point (core's `fitImage`), `contain` centers the whole picture with `a:fillRect` insets, `stretch` fills the slide, and `tile` repeats the picture at its intrinsic size (1 picture pixel = 1 reference pixel, 1/96 in) from the top-left corner. With `dpi="0"`, a native tile is sized from the raster's own resolution: PNG `pHYs` in metres; JPEG JFIF density in inches or centimetres, otherwise EXIF `XResolution`/`YResolution`; 96 dpi when absent. The tile scale is that resolution over 96 on each axis. `recolor` becomes `a:grayscl` or `a:duotone` and opacity `a:alphaModFix` on the blip, as for image blocks. With alt text (the background's own only: a referenced asset's alt does not make a background meaningful) the picture is a full-slide `p:pic` at the back of the slide, named `OPF background slides.N`, carrying the text as its `descr` so screen readers read it; the slide background is then the colour scheme's default background colour, which shows around a contained picture and through a translucent one. The overlay is one shape directly above the picture (`OPF background overlay slides.N` for a native fill, `OPF background slides.N overlay` for the picture): the whole slide, or a band along one edge. WebP sources become PNG parts as for pictures. An unresolved image keeps the background
|
|
417
|
+
An image background (`{type: "image", src, alt, fit, focus, opacity, recolor, overlay}`, or an image source string, a cover image; core composes it as `backgroundImage` from the slide, deck or theme background) never moves content. Without alt text it exports as the native slide background, an `<a:blipFill>` for the embedded raster (`data:` sources, declared `asset:` ids and `imageResolver` results): `cover` (the default) crops the picture to the slide with `a:srcRect` around the `focus` point (core's `fitImage`), `contain` centers the whole picture with `a:fillRect` insets, `stretch` fills the slide, and `tile` repeats the picture at its intrinsic size (1 picture pixel = 1 reference pixel, 1/96 in) from the top-left corner. With `dpi="0"`, a native tile is sized from the raster's own resolution: PNG `pHYs` in metres; JPEG JFIF density in inches or centimetres, otherwise EXIF `XResolution`/`YResolution`; 96 dpi when absent. The tile scale is that resolution over 96 on each axis. `recolor` becomes `a:grayscl` or `a:duotone` and opacity `a:alphaModFix` on the blip, as for image blocks. With alt text (the background's own only: a referenced asset's alt does not make a background meaningful) the picture is a full-slide `p:pic` at the back of the slide, named `OPF background slides.N`, carrying the text as its `descr` so screen readers read it; the slide background is then the colour scheme's default background colour, which shows around a contained picture and through a translucent one. The overlay is one shape directly above the picture (`OPF background overlay slides.N` for a native fill, `OPF background slides.N overlay` for the picture): the whole slide, or a band along one edge. WebP sources become PNG parts as for pictures. An unresolved image (no source, an unreadable file, or an `imageResolver` that threw) keeps the background the slide would have without the picture and reports `unresolved-asset` at the background's path (`strictAssets` throws): the deck's own background when the slide's was the picture, otherwise the slide's `bg1` through its variant (a dark variant stays dark), written as the native fill such a slide has; text colors are unchanged, and a picture that comes from a theme's own background keeps the canvas color. A JPEG EXIF orientation cannot rotate a background fill, so it reports `unsupported-background-image-orientation`.
|
|
418
418
|
|
|
419
419
|
Import reads `a:pattFill` with a DrawingML preset and resolvable foreground/background colors, including slide/layout/master inheritance and theme references, as an OPF pattern background. Import does not guess undefined default colors. Import also reads embedded `a:blipFill` pictures as an image background with the exact image bytes as a `data:` URI. Tiles become `tile`. If the tile scale, offset, alignment, flip or source insets differ from the geometry OPF `tile` exports (the picture at its intrinsic size), the import reports `approximate-background-image`. A crop matching the slide aspect becomes `cover`, with the `focus` the crop centers (a crop pinned to an edge reads as the edge-most focus with that crop); centered fill insets matching the image aspect become `contain`; an uncropped fill of another aspect becomes `stretch`. Padded, offset or effect-bearing fills become the closest fit and report `approximate-background-image`. `a:grayscl` and an `a:duotone` of two literal colours become `recolor`. An unchanged tagged background picture (`OPF_IMAGE_V1`) becomes the image background with its alt text, fit, focus, opacity and recolor, and an unchanged tagged overlay (`OPF_IMAGE_OVERLAY_V1`) its overlay. Linked or unreadable pictures report `unsupported-background-image`. `test/background-fills.mjs` and `test/background-images.mjs` cover export, import, native edits, inheritance and diagnostics. Microsoft PowerPoint rendering of these fills has not been verified natively: the resolution-based tile scale, a tiled `p:pic` fill, what PowerPoint shows around a contained or through a translucent native background fill (it has nothing behind the slide background), and negative `a:srcRect` insets.
|
|
420
420
|
|
package/dist/index.d.ts
CHANGED
|
@@ -53,11 +53,12 @@ export interface ContentPlaceholderDiagnostic { code: "content-placeholder"; pat
|
|
|
53
53
|
* `"svg-unsafe"` (a DOCTYPE with external or markup entities), `"svg-rasterizer-unavailable"` (no `options.svgRasterizer` and `@openpresentation/opf-render`
|
|
54
54
|
* is not installed; always the case in a browser build without `svgRasterizer`) `"svg-render-failed"` (the rasterizer threw or returned no PNG) or `"svg-unreadable"` (a local `.svg` path that could not be read).
|
|
55
55
|
* `reason: "file-unreadable"`: a local raster path that is not a readable file (Node; a browser build leaves local paths to the engine).
|
|
56
|
-
* `
|
|
56
|
+
* `reason: "resolver-failed"`: `options.imageResolver` threw for the source (the CLI's does for a local image it cannot read: a missing file, a path outside the asset directory, a file that is no image). The export continues without the image, and `message` is the resolver's own message followed by a note that the image was left out; the fallback is the one of the picture's use (a background keeps the slide's resolved background color).
|
|
57
|
+
* `strictAssets` throws `missing-asset` (`details.reason: "resolver-failed"`) for such a resolver failure, `unsupported-image-dimensions` for an unreadable raster, `invalid-svg-image` for the SVG content reasons above (and `svg-unreadable`), `svg-rasterizer-unavailable` for a
|
|
57
58
|
* missing rasterizer, `svg-render-failed` for a failed one and `missing-asset` for an unreadable local file instead.
|
|
58
59
|
* Every picture path reports this (image blocks and region images, quote photos, background pictures, watermarks, logos, header/footer images, picture bullets), once per picture.
|
|
59
60
|
*/
|
|
60
|
-
export interface UnresolvedAssetDiagnostic { code: "unresolved-asset"; path: string; message: string; reason?: "unresolved-source" | "unsupported-format" | "svg-malformed" | "svg-no-size" | "svg-too-large" | "svg-unsafe" | "svg-rasterizer-unavailable" | "svg-render-failed" | "svg-unreadable" | "file-unreadable" }
|
|
61
|
+
export interface UnresolvedAssetDiagnostic { code: "unresolved-asset"; path: string; message: string; reason?: "unresolved-source" | "unsupported-format" | "svg-malformed" | "svg-no-size" | "svg-too-large" | "svg-unsafe" | "svg-rasterizer-unavailable" | "svg-render-failed" | "svg-unreadable" | "file-unreadable" | "resolver-failed" }
|
|
61
62
|
/** An SVG picture had scripts, `foreignObject`, event handlers, references outside the file, `@import` rules or a DOCTYPE; they were removed from the embedded SVG (on import too). Nothing in an SVG is run or fetched. */
|
|
62
63
|
export interface SvgSanitizedDiagnostic { code: "svg-sanitized"; path: string; message: string }
|
|
63
64
|
/** An SVG image block with a duotone recolor or a non-rectangular shape (or an SVG image background with the tile fit) exports as its PNG raster, not as a native SVG picture, so the effect applies as in the preview (PowerPoint applies opacity, grayscale and a border to an SVG picture, and those stay native). */
|
package/dist/index.js
CHANGED
|
@@ -2403,6 +2403,9 @@ function placedImageBands(slide) {
|
|
|
2403
2403
|
|
|
2404
2404
|
// One slide's export context: core's resolveSlideContext (`core`: composeSlide's options, the records and the diagnostics) and
|
|
2405
2405
|
// the exporter's colours and measurement-resolved families derived from it.
|
|
2406
|
+
// RR-97: the deck's own background stays literal on a slide whose picture background is unreadable and falls back to it (useFallbackBackground).
|
|
2407
|
+
const geometryFallbackBackground = presentation => presentation.design?.background;
|
|
2408
|
+
|
|
2406
2409
|
function exportSlideContext(presentation, slideIndex, baseContext, options) {
|
|
2407
2410
|
const slide = presentation.slides[slideIndex];
|
|
2408
2411
|
const effective = { ...presentation, design: { ...presentation.design, ...slide.design } };
|
|
@@ -2433,7 +2436,7 @@ function exportSlideContext(presentation, slideIndex, baseContext, options) {
|
|
|
2433
2436
|
const text = defaultTextSchemeValues(slideContext);
|
|
2434
2437
|
slideContext.textColor = text.text ?? resolved.colors.text;
|
|
2435
2438
|
slideContext.mutedColor = text.muted ?? resolved.colors.mutedText;
|
|
2436
|
-
baseContext.slideSchemeColors[slideIndex] = {table: schemeColorTable(slideContext), keep: authoredLiteralColors(slide, presentation.variables, {...effective.design, colorScheme: undefined, theme: undefined})};
|
|
2439
|
+
baseContext.slideSchemeColors[slideIndex] = {table: schemeColorTable(slideContext), keep: authoredLiteralColors(slide, presentation.variables, {...effective.design, colorScheme: undefined, theme: undefined}, geometryFallbackBackground(presentation))};
|
|
2437
2440
|
return slideContext;
|
|
2438
2441
|
}
|
|
2439
2442
|
|
|
@@ -2733,7 +2736,7 @@ async function addBackgroundImage(slide, presentation, background, slideIndex, s
|
|
|
2733
2736
|
treatment, alt, role: 'background', objectName: backgroundImageName(slidePath)
|
|
2734
2737
|
});
|
|
2735
2738
|
// addImagePicture reported the unresolved source once (opf-pptx#210).
|
|
2736
|
-
if (!name) return;
|
|
2739
|
+
if (!name) { useFallbackBackground(slide, presentation, background, slideIndex, slideContext, context, options); return; }
|
|
2737
2740
|
owner = name;
|
|
2738
2741
|
overlayName = imageOverlayName(name);
|
|
2739
2742
|
} else {
|
|
@@ -2742,6 +2745,7 @@ async function addBackgroundImage(slide, presentation, background, slideIndex, s
|
|
|
2742
2745
|
const resolved = await resolveImage(background.src, presentation, options, background.path, outcome);
|
|
2743
2746
|
if (!resolved) {
|
|
2744
2747
|
if (!outcome.reported) options.onDiagnostic?.(backgroundUnresolved(background.path));
|
|
2748
|
+
useFallbackBackground(slide, presentation, background, slideIndex, slideContext, context, options);
|
|
2745
2749
|
return;
|
|
2746
2750
|
}
|
|
2747
2751
|
// PptxGenJS embeds the raster and its relationship; packaging replaces its stretched fill with the fitted native fill.
|
|
@@ -2760,6 +2764,29 @@ async function addBackgroundImage(slide, presentation, background, slideIndex, s
|
|
|
2760
2764
|
if (background.overlay) addImageOverlay(slide, background.overlay, structuredClone(isPlainObject(authored) ? authored.overlay : undefined) ?? {color: background.overlay.color, opacity: background.overlay.opacity}, overlayName, owner, { ...slideContext, slidePath }, effects);
|
|
2761
2765
|
}
|
|
2762
2766
|
|
|
2767
|
+
// RR-97: a background picture that cannot be read leaves the background the slide would have without it: the deck's own
|
|
2768
|
+
// background (or the theme's) when the slide's was the picture, and the variant's bg1 when the picture is the only background,
|
|
2769
|
+
// written as the same native fill (a scheme color through the slide's map, a solid color, a gradient or a pattern) a slide with
|
|
2770
|
+
// that background has. It is not the literal canvas color core falls back to for a picture, which is the light default and so
|
|
2771
|
+
// white behind the light text of a dark variant. A picture that comes from the theme's own background keeps that canvas color.
|
|
2772
|
+
// Only the fill is replaced: the text color was chosen for the picture and is unchanged.
|
|
2773
|
+
function useFallbackBackground(slide, presentation, background, slideIndex, slideContext, context, options) {
|
|
2774
|
+
const without = design => Object.fromEntries(Object.entries(design ?? {}).filter(([key]) => key !== 'background'));
|
|
2775
|
+
let twin;
|
|
2776
|
+
if (/^slides\.\d+\.design\.background(\.|$)/.test(background.path ?? '')) twin = {...presentation, slides: presentation.slides.map((entry, index) => index === slideIndex ? {...entry, design: without(entry.design)} : entry)};
|
|
2777
|
+
else if (/^design\.background(\.|$)/.test(background.path ?? '')) twin = {...presentation, design: without(presentation.design)};
|
|
2778
|
+
if (!twin) return;
|
|
2779
|
+
const effective = {...twin, design: {...twin.design, ...twin.slides[slideIndex].design}};
|
|
2780
|
+
const resolved = resolvePresentationContext(effective, options, coreContext(twin, slideIndex, options));
|
|
2781
|
+
const fallback = {...slideContext, backgroundDefinition: resolved.backgroundDefinition, colors: resolved.colors, roles: resolved.roles, colorScheme: resolved.colorScheme, variables: resolved.variables};
|
|
2782
|
+
slide.background = {color: resolved.colors.background};
|
|
2783
|
+
const definition = resolved.backgroundDefinition;
|
|
2784
|
+
const fill = definition === undefined ? null : schemeBackgroundFill(definition, fallback) ?? nativeBackgroundFill(definition, {
|
|
2785
|
+
width: slideContext.dimensions.widthInches, height: slideContext.dimensions.heightInches
|
|
2786
|
+
}, resolved.colors.background, resolved.colors.text, (reference, hex) => schemeColorValue(reference, hex, fallback), reference => resolveBackgroundColorRef(reference, resolved.colorScheme, resolved.variables));
|
|
2787
|
+
if (fill) context.backgroundFills.set(`ppt/slides/slide${slideIndex + 1}.xml`, fill);
|
|
2788
|
+
}
|
|
2789
|
+
|
|
2763
2790
|
const backgroundUnresolved = path => ({code: 'unresolved-asset', path, reason: 'unresolved-source', message: 'The background image needs an embedded raster, a declared asset or a host imageResolver; the slide background color was exported instead.'});
|
|
2764
2791
|
|
|
2765
2792
|
// design.watermark: one native picture per slide, added after the slide image
|
|
@@ -4015,7 +4042,7 @@ function histogramBins(values) {
|
|
|
4015
4042
|
// was already emitted. `outcome.box` (inches, in) sizes an SVG's fallback raster; `outcome.svg` (out) carries the SVG to
|
|
4016
4043
|
// attach to the picture the caller adds. Local paths are read by PptxGenJS and checked after embedding.
|
|
4017
4044
|
async function resolveImage(asset, presentation, options, path, outcome = {}) {
|
|
4018
|
-
const resolved = await resolveImageSource(asset, presentation, options, path);
|
|
4045
|
+
const resolved = await resolveImageSource(asset, presentation, options, path, outcome);
|
|
4019
4046
|
// A local SVG file is read here (PptxGenJS cannot size it); a raster path is read by PptxGenJS itself.
|
|
4020
4047
|
if (resolved?.path && /\.svg$/i.test(resolved.path)) {
|
|
4021
4048
|
let file;
|
|
@@ -4107,7 +4134,7 @@ function dataUriBytes(uri) {
|
|
|
4107
4134
|
}
|
|
4108
4135
|
}
|
|
4109
4136
|
|
|
4110
|
-
async function resolveImageSource(asset, presentation, options, path) {
|
|
4137
|
+
async function resolveImageSource(asset, presentation, options, path, outcome = {}) {
|
|
4111
4138
|
const assetObject = dereferenceAsset(asset, presentation, path);
|
|
4112
4139
|
const src = typeof assetObject === "string" ? assetObject : assetObject?.src;
|
|
4113
4140
|
if (!src) {
|
|
@@ -4117,9 +4144,21 @@ async function resolveImageSource(asset, presentation, options, path) {
|
|
|
4117
4144
|
return null;
|
|
4118
4145
|
}
|
|
4119
4146
|
|
|
4120
|
-
|
|
4121
|
-
|
|
4122
|
-
:
|
|
4147
|
+
let resolvedByHost = null;
|
|
4148
|
+
if (options.imageResolver) {
|
|
4149
|
+
try { resolvedByHost = await options.imageResolver(src, { asset: assetObject, presentation, path }); }
|
|
4150
|
+
catch (error) {
|
|
4151
|
+
// RR-97: a resolver that throws (the CLI's does for an image it cannot read) is an unresolved image, not a failed export: the
|
|
4152
|
+
// picture's own fallback and one `unresolved-asset` warning that carries the resolver's message. A cancelled export and an
|
|
4153
|
+
// exporter error still propagate, and `strictAssets` turns the failure into a `missing-asset` error.
|
|
4154
|
+
if (error instanceof OPFPptxError || error?.name === "AbortError") throw error;
|
|
4155
|
+
const cause = errorMessage(error).trim();
|
|
4156
|
+
if (options.strictAssets) throw new OPFPptxError("missing-asset", cause || "The imageResolver could not read the image.", { path, src, reason: "resolver-failed" });
|
|
4157
|
+
options.onDiagnostic?.({ code: "unresolved-asset", path, reason: "resolver-failed", message: `${cause ? `${/[.!?]$/.test(cause) ? cause : `${cause}.`} ` : "The imageResolver could not read the image. "}The image was left out of the export; the preview's fallback is exported instead (the unavailable-image placeholder, the slide background color, the character bullets, or no watermark).` });
|
|
4158
|
+
outcome.reported = true;
|
|
4159
|
+
return null;
|
|
4160
|
+
}
|
|
4161
|
+
}
|
|
4123
4162
|
if (resolvedByHost) return normalizeResolvedImage(resolvedByHost, assetObject);
|
|
4124
4163
|
|
|
4125
4164
|
if (src.startsWith("data:")) return { data: src };
|
package/dist/template-layouts.js
CHANGED
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
// `layout-changed`). A block read from a placeholder of a region gets a `region` pin when plain binding would put it
|
|
26
26
|
// elsewhere; content that document provenance restored is never pinned.
|
|
27
27
|
|
|
28
|
-
import {composeLayoutAreas, composeSlide, layoutTemplate, bindRegions, regionAccepts} from '@openpresentation/opf/composition';
|
|
28
|
+
import {composeLayoutAreas, composeSlide, isLayoutTemplate, layoutTemplate, bindRegions, regionAccepts} from '@openpresentation/opf/composition';
|
|
29
29
|
import {validateCatalogRecord, catalogRecords, resolveReference} from '@openpresentation/opf';
|
|
30
30
|
import {gallery} from '@openpresentation/gallery';
|
|
31
31
|
import {decodeTextTag, encodeTextTag} from './code-provenance.js';
|
|
@@ -38,7 +38,7 @@ const LAYOUT_TYPE = 'application/vnd.openxmlformats-officedocument.presentationm
|
|
|
38
38
|
const TAGS_TYPE = 'application/vnd.openxmlformats-officedocument.presentationml.tags+xml';
|
|
39
39
|
const EMU = 9525;
|
|
40
40
|
// Region placeholder indexes (coordinator decisions on RR-81): a base per placeholder kind plus the region's position among the
|
|
41
|
-
// layout's regions of that kind, in the record's `regions` order: `obj` 100, `body` 300, `pic` 500, `chart` 700, `tbl` 900,
|
|
41
|
+
// layout's regions of that kind, in the record's `regions` order (core's reading order of the region list): `obj` 100, `body` 300, `pic` 500, `chart` 700, `tbl` 900,
|
|
42
42
|
// `media` 1100 (so the first chart region of every layout is 700, its second 701). PowerPoint's Change Layout pairs a slide's
|
|
43
43
|
// placeholders with the new layout's by `idx`, so only regions of one kind pair (Pillars to Text keeps `obj` with `obj`; a chart
|
|
44
44
|
// bound to Chart beside's `chart` region never lands in Table beside's `tbl` region, as it did with one sequence for every kind).
|
|
@@ -85,7 +85,7 @@ export const bindsObject = (type, object) => (BINDS[type] ?? []).includes(object
|
|
|
85
85
|
export function regionIndexes(record, nativeBodyRegion) {
|
|
86
86
|
const regions = new Map(layoutTemplate(record).regions.map(region => [region.name, region]));
|
|
87
87
|
const counts = {};
|
|
88
|
-
const indexes = Object.fromEntries(
|
|
88
|
+
const indexes = Object.fromEntries([...regions.keys()].map(name => {
|
|
89
89
|
const type = placeholderType(regions.get(name));
|
|
90
90
|
counts[type] = (counts[type] ?? -1) + 1;
|
|
91
91
|
return [name, REGION_IDX_BASE[type] + counts[type]];
|
|
@@ -119,7 +119,7 @@ export function recordHash(record) {
|
|
|
119
119
|
*/
|
|
120
120
|
export function slideTemplate(core) {
|
|
121
121
|
const record = core?.resolved?.layout, provenance = core?.resolved?.provenance?.layout;
|
|
122
|
-
if (!record || !provenance || !
|
|
122
|
+
if (!record || !provenance || !isLayoutTemplate(record)) return undefined;
|
|
123
123
|
try { layoutTemplate(record); } catch { return undefined; }
|
|
124
124
|
return {record, id: provenance.id, group: provenance.group, ...(provenance.source !== undefined ? {source: provenance.source} : {}), reference: provenance.reference, origin: provenance.origin,
|
|
125
125
|
key: templateKey(provenance)};
|
|
@@ -127,7 +127,7 @@ export function slideTemplate(core) {
|
|
|
127
127
|
|
|
128
128
|
const templateKey = ({group, id, source}) => `${group}\u0000${id}\u0000${source ?? ''}`;
|
|
129
129
|
function isTemplate(record) {
|
|
130
|
-
if (record === null || typeof record !== 'object' || !
|
|
130
|
+
if (record === null || typeof record !== 'object' || !isLayoutTemplate(record)) return false;
|
|
131
131
|
try { layoutTemplate(record); return true; } catch { return false; }
|
|
132
132
|
}
|
|
133
133
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@openpresentation/opf-pptx",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.25.0",
|
|
4
4
|
"description": "Pure local OPF to PPTX export and PPTX to OPF import tooling.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -65,8 +65,8 @@
|
|
|
65
65
|
},
|
|
66
66
|
"dependencies": {
|
|
67
67
|
"@node-projects/jszip": "4.3.0",
|
|
68
|
-
"@openpresentation/gallery": "^
|
|
69
|
-
"@openpresentation/opf": "^0.
|
|
68
|
+
"@openpresentation/gallery": "^5.0.0",
|
|
69
|
+
"@openpresentation/opf": "^0.25.0",
|
|
70
70
|
"@rgrove/parse-xml": "4.2.3",
|
|
71
71
|
"fast-xml-parser": "^5.8.0",
|
|
72
72
|
"fflate": "^0.8.3",
|
|
@@ -74,7 +74,7 @@
|
|
|
74
74
|
"sharp": "0.35.5"
|
|
75
75
|
},
|
|
76
76
|
"peerDependencies": {
|
|
77
|
-
"@openpresentation/opf-render": "^0.
|
|
77
|
+
"@openpresentation/opf-render": "^0.25.0"
|
|
78
78
|
},
|
|
79
79
|
"peerDependenciesMeta": {
|
|
80
80
|
"@openpresentation/opf-render": {
|
|
@@ -98,7 +98,7 @@
|
|
|
98
98
|
"@expo-google-fonts/roboto": "0.4.3",
|
|
99
99
|
"@expo-google-fonts/roboto-mono": "0.4.2",
|
|
100
100
|
"@expo-google-fonts/tinos": "0.4.2",
|
|
101
|
-
"@openpresentation/opf-render": "0.
|
|
101
|
+
"@openpresentation/opf-render": "0.25.0",
|
|
102
102
|
"@resvg/resvg-js": "2.6.2",
|
|
103
103
|
"esbuild": "0.28.2",
|
|
104
104
|
"playwright": "1.64.0"
|