@openpresentation/opf-pptx 0.12.2 → 0.12.4
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/chart-workbook.js +41 -0
- package/dist/index.js +3 -2
- package/package.json +10 -7
package/README.md
CHANGED
|
@@ -8,7 +8,7 @@ Version 0.9.1 kept core `^0.11.0` and raised the optional `@openpresentation/opf
|
|
|
8
8
|
|
|
9
9
|
Unfinished prepared shaping work is preserved in the [September 15 roadmap](docs/roadmap-shaping-20260915.md); it is not part of the published runtime.
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
This checkout supports Node 22 or later (`engines.node` `>=22`; CI tests Node 22, 24 and 26). Versions 0.8.0 to 0.12.3 declared `24.x`, so npm on Node 22 or 26 silently installed an older release instead (RR-20); upgrade past 0.12.3. Use `.nvmrc` (Node 24) for local development. Earlier published versions retain their original engine declarations. Browser entrypoints remain browser-safe; native application compatibility is verified separately.
|
|
12
12
|
|
|
13
13
|
Version 0.8.0 consumes shared heading/scalar/rich line placement, including `textRasterPadding`, through editable native line boxes without autofit. Complete heading tags retain title/subtitle/tag roles, line ordering and current edited text during import. Source boundary metadata also records hard-line separators, so complete scalar/heading groups recover current native text with spaces, tabs and exact authored line endings. Incomplete, duplicate, ambiguous or bulleted groups fall back to ordinary import with diagnostics. Legacy heading tags retain their prior native-line behavior; arbitrary formatting, nesting and geometry are not reconstructed. See the [source contract and limits](https://github.com/OpenPresentation/opf/blob/f94125a1ff95bf0974a055fe1c081d348dad54f2/docs/plans/text-placement.md). Native raster certification remains separate.
|
|
14
14
|
|
|
@@ -264,7 +264,7 @@ LibreOffice is not a runtime dependency. When it is installed in CI or a local v
|
|
|
264
264
|
|
|
265
265
|
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.
|
|
266
266
|
|
|
267
|
-
1. Open a release-prep PR containing only the version bump, `CHANGELOG.md
|
|
267
|
+
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.
|
|
268
268
|
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 reruns audit, typecheck, validate, tests, packed and browser checks before `npm publish --access public --provenance`. A manual `workflow_dispatch` runs the same job without the tag check and is a fallback only.
|
|
269
269
|
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.
|
|
270
270
|
|
|
@@ -334,7 +334,7 @@ Native viewer check: Keynote 14.4 displays the PNG/JPEG/GIF media-type specimens
|
|
|
334
334
|
|
|
335
335
|
Set `imageFormat: "preserve"` to embed WebP unchanged when the receiving application supports it. Other image formats keep their existing export behavior. Conversion errors include the OPF image path; images above 40 megapixels are rejected before compatible conversion. Decoder differences can affect color/alpha rounding, so byte identity across platforms is not promised.
|
|
336
336
|
|
|
337
|
-
Node conversion lazily loads the pinned open-source Sharp dependency and requires Node
|
|
337
|
+
Node conversion lazily loads the pinned open-source Sharp dependency and requires Node 22 or later. Normal package installation must include platform optional dependencies for its native binaries. Browser bundles select a separate browser decoder using local Blob/image/canvas APIs; Sharp and Node code are excluded. Neither path uploads images or fetches asset URLs. Source-preserving export and ordinary PNG/JPEG/GIF operations do not load Sharp.
|
|
338
338
|
|
|
339
339
|
`npm test` includes the Node pixel-reference cases and verifies browser bundling. To run the browser pixel checks, run `npm run build:browser-check`, serve this repository locally, and open `/artifacts/webp-fallback/browser/index.html`. The page reports 13 checks covering embedded PNG pixels, alpha, EXIF, the first animation frame, fit/crop, resolver calls and DOM canvas fallback. These are browser export checks, separate from the recorded Keynote viewing evidence.
|
|
340
340
|
|
package/dist/chart-workbook.js
CHANGED
|
@@ -81,3 +81,44 @@ export function writeChartCategoryHeading(entries,chartPart,heading){
|
|
|
81
81
|
}
|
|
82
82
|
entries[context.workbookPart]=zipSync(workbook);
|
|
83
83
|
}
|
|
84
|
+
|
|
85
|
+
// PptxGenJS 4.0.1 writes broken range metadata into chart workbooks (upstream gitbrent/PptxGenJS#1531): the
|
|
86
|
+
// category-chart branch (bar, line, area, pie, doughnut, radar, and the classic fallback of chartex charts) ends the
|
|
87
|
+
// table ref with a stray apostrophe (ref="A1:C7'"), the bubble branch takes the table's last row from its column
|
|
88
|
+
// count, and the category sheet dimension counts one label column (too narrow for multi-level categories). Keynote
|
|
89
|
+
// drops every chart whose embedded table ref does not parse (opf-pptx#162); Excel repairs the workbook when the chart
|
|
90
|
+
// data is opened. Each worksheet dimension and each table (and table autoFilter) ref that starts at the sheet's first
|
|
91
|
+
// data cell is set to the cell range the sheet actually holds (a correct range is left as it is).
|
|
92
|
+
// `workbook` maps the unzipped workbook part names to bytes and is changed in place.
|
|
93
|
+
const columnNumber=letters=>[...letters].reduce((value,letter)=>value*26+letter.charCodeAt(0)-64,0);
|
|
94
|
+
const columnName=number=>{let name='';for(let n=number;n>0;n=Math.floor((n-1)/26))name=String.fromCharCode(65+(n-1)%26)+name;return name;};
|
|
95
|
+
export function usedRange(sheetXml){
|
|
96
|
+
let minColumn=Infinity,minRow=Infinity,maxColumn=0,maxRow=0;
|
|
97
|
+
for(const [,letters,digits]of sheetXml.matchAll(/<c\b[^>]*?\br="([A-Z]{1,3})(\d{1,7})"/g)){
|
|
98
|
+
const column=columnNumber(letters),row=Number(digits);
|
|
99
|
+
minColumn=Math.min(minColumn,column);minRow=Math.min(minRow,row);maxColumn=Math.max(maxColumn,column);maxRow=Math.max(maxRow,row);
|
|
100
|
+
}
|
|
101
|
+
return maxRow?{start:`${columnName(minColumn)}${minRow}`,end:`${columnName(maxColumn)}${maxRow}`,ref:`${columnName(minColumn)}${minRow}:${columnName(maxColumn)}${maxRow}`,columns:maxColumn-minColumn+1}:undefined;
|
|
102
|
+
}
|
|
103
|
+
export function repairChartWorkbookRanges(workbook){
|
|
104
|
+
for(const sheetPart of Object.keys(workbook)){
|
|
105
|
+
if(!/^xl\/worksheets\/[^/]+\.xml$/.test(sheetPart))continue;
|
|
106
|
+
const sheet=decoder.decode(workbook[sheetPart]),range=usedRange(sheet);
|
|
107
|
+
if(!range)continue;
|
|
108
|
+
const fixedSheet=sheet.replace(/(<dimension\b[^>]*\bref=")[^"]*(")/,(_match,prefix,suffix)=>`${prefix}${range.ref}${suffix}`);
|
|
109
|
+
if(fixedSheet!==sheet)workbook[sheetPart]=encoder.encode(fixedSheet);
|
|
110
|
+
const slash=sheetPart.lastIndexOf('/');
|
|
111
|
+
const relationships=xml(workbook,`${sheetPart.slice(0,slash+1)}_rels/${sheetPart.slice(slash+1)}.rels`);
|
|
112
|
+
for(const rel of array(relationships?.Relationships?.Relationship)){
|
|
113
|
+
const ref=relationship(workbook,sheetPart,rel.Id);
|
|
114
|
+
if(!ref?.type?.endsWith('/table')||!workbook[ref.path])continue;
|
|
115
|
+
const table=decoder.decode(workbook[ref.path]);
|
|
116
|
+
// Only a table anchored at the sheet's first data cell (every PptxGenJS chart table) is resized.
|
|
117
|
+
const start=/<table\b[^>]*\bref="\$?([A-Z]+)\$?(\d+)/.exec(table);
|
|
118
|
+
if(!start||`${start[1]}${start[2]}`!==range.start)continue;
|
|
119
|
+
const fixed=table.replace(/(<(?:table|autoFilter)\b[^>]*\bref=")[^"]*(")/g,(_match,prefix,suffix)=>`${prefix}${range.ref}${suffix}`);
|
|
120
|
+
if(fixed!==table)workbook[ref.path]=encoder.encode(fixed);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
return workbook;
|
|
124
|
+
}
|
package/dist/index.js
CHANGED
|
@@ -4,7 +4,7 @@ import {isFaceStyleSuffix} from './font-weights.js';
|
|
|
4
4
|
import {importTableFrames} from './table-import.js';
|
|
5
5
|
import {applyChartFonts, applyPitchFamilies, finalizeFontsUsed, fontPitchFamilies} from './package-fonts.js';
|
|
6
6
|
import {giveNotesMastersOwnThemes} from './master-themes.js';
|
|
7
|
-
import {readChartCategoryHeading,writeChartCategoryHeading} from './chart-workbook.js';
|
|
7
|
+
import {readChartCategoryHeading,writeChartCategoryHeading,repairChartWorkbookRanges} from './chart-workbook.js';
|
|
8
8
|
import {CHARTEX_FALLBACK,resolveChartType,chartTypeFromNative,applyChartConstruct,NATIVE_CHART_ELEMENTS} from './chart-types.js';
|
|
9
9
|
import {attachChartexParts,chartFromChartex,CHARTEX_GRAPHIC_DATA_URI} from './chartex.js';
|
|
10
10
|
import {applyDataLabels,chartOptionsFromClassic,chartTargetFor,classicChartOptions,reportChartOptionDiagnostics,resolveChartOptionsFor} from './chart-options.js';
|
|
@@ -3623,7 +3623,8 @@ function normalizePartReferences(value, renameMaps) {
|
|
|
3623
3623
|
}
|
|
3624
3624
|
|
|
3625
3625
|
function normalizeNestedZip(bytes, context) {
|
|
3626
|
-
|
|
3626
|
+
// RR-17 (opf-pptx#162): valid table and dimension ranges, or Keynote drops the chart (see chart-workbook.js).
|
|
3627
|
+
const entries = repairChartWorkbookRanges(unzipSync(bytes));
|
|
3627
3628
|
const output = {};
|
|
3628
3629
|
for (const path of Object.keys(entries).sort()) {
|
|
3629
3630
|
// A gap in the chart data is a blank workbook cell, not a numeric cell with an empty value.
|
package/package.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@openpresentation/opf-pptx",
|
|
3
|
-
"version": "0.12.
|
|
3
|
+
"version": "0.12.4",
|
|
4
4
|
"description": "Pure local OPF to PPTX export and PPTX to OPF import tooling.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"private": false,
|
|
8
8
|
"engines": {
|
|
9
|
-
"node": "
|
|
9
|
+
"node": ">=22"
|
|
10
10
|
},
|
|
11
11
|
"repository": {
|
|
12
12
|
"type": "git",
|
|
@@ -37,8 +37,11 @@
|
|
|
37
37
|
"sideEffects": false,
|
|
38
38
|
"scripts": {
|
|
39
39
|
"build": "node scripts/build.mjs",
|
|
40
|
-
"typecheck": "node
|
|
41
|
-
"test": "npm run build && node
|
|
40
|
+
"typecheck": "node scripts/check-syntax.mjs src test scripts",
|
|
41
|
+
"test": "npm run build && node scripts/run-tests.mjs && npm run build:browser-check && npm run check:runners && npm run check:changes",
|
|
42
|
+
"test:contract": "npm run build && node scripts/run-tests.mjs --suite contract && npm run build:browser-check",
|
|
43
|
+
"check:runners": "node --test scripts/run-tests.test.mjs",
|
|
44
|
+
"check:changes": "node --test scripts/changelog-fragments.test.mjs && node scripts/changelog-fragments.mjs check",
|
|
42
45
|
"validate": "node scripts/validate-package.mjs",
|
|
43
46
|
"test:packed": "node test/packed-install.mjs && node test/provenance-interop.mjs",
|
|
44
47
|
"test:compare-published": "node test/compare-published.mjs",
|
|
@@ -61,14 +64,14 @@
|
|
|
61
64
|
"test:determinism": "node test/determinism.mjs"
|
|
62
65
|
},
|
|
63
66
|
"dependencies": {
|
|
64
|
-
"@openpresentation/opf": "^0.12.
|
|
67
|
+
"@openpresentation/opf": "^0.12.2",
|
|
65
68
|
"fast-xml-parser": "^5.8.0",
|
|
66
69
|
"fflate": "^0.8.3",
|
|
67
70
|
"jszip": "3.10.2",
|
|
68
71
|
"sharp": "0.35.4"
|
|
69
72
|
},
|
|
70
73
|
"peerDependencies": {
|
|
71
|
-
"@openpresentation/opf-render": "^0.12.
|
|
74
|
+
"@openpresentation/opf-render": "^0.12.2"
|
|
72
75
|
},
|
|
73
76
|
"peerDependenciesMeta": {
|
|
74
77
|
"@openpresentation/opf-render": {
|
|
@@ -77,7 +80,7 @@
|
|
|
77
80
|
},
|
|
78
81
|
"devDependencies": {
|
|
79
82
|
"@expo-google-fonts/carlito": "0.4.1",
|
|
80
|
-
"@openpresentation/opf-render": "0.12.
|
|
83
|
+
"@openpresentation/opf-render": "0.12.2",
|
|
81
84
|
"esbuild": "0.28.2",
|
|
82
85
|
"playwright": "1.63.0"
|
|
83
86
|
},
|