@openpresentation/opf-pptx 0.5.1 → 0.6.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 +2 -0
- package/dist/index.js +71 -59
- package/package.json +5 -5
package/README.md
CHANGED
|
@@ -4,6 +4,8 @@ Pure local PowerPoint conversion tooling for Open Presentation Format documents.
|
|
|
4
4
|
|
|
5
5
|
## Scope
|
|
6
6
|
|
|
7
|
+
Version 0.6.0 requires core 0.8.0 and uses renderer 0.6.0 for coordinated preview/font measurement. Quotes export their accepted body/source lines and styles without another fitting pass. [Controlled Windows PowerPoint evidence](docs/evidence/shared-quote-integration/comparison.json) records glyph containment, separation, save/reopen and text reimport against its exact source/font hashes. Native quote import returns editable text blocks and does not restore the original OPF quote structure, typography or readability policy. Native chart geometry and general scalar-text wrapping also remain different from preview; editability and valid reimport do not establish raster equivalence.
|
|
8
|
+
|
|
7
9
|
- Package: `@openpresentation/opf-pptx`
|
|
8
10
|
- Repository: `OpenPresentation/opf-pptx`
|
|
9
11
|
- License: MIT
|
package/dist/index.js
CHANGED
|
@@ -907,7 +907,7 @@ async function addSlide(pptx, presentation, opfSlide, slideIndex, context, optio
|
|
|
907
907
|
} else if (item.field === "text" && typeof item.value === "string") {
|
|
908
908
|
slide.addText(item.text.lines.join("\n"), {...textBoxOptions(region, slideContext, item.text.fontSize * 0.75),fontFace:item.textStyle.fontFamily,bold:item.textStyle.fontWeight>=600,italic:item.textStyle.italic});
|
|
909
909
|
} else {
|
|
910
|
-
await addPayload(slide, presentation, item.payload, region, item.path, { ...slideContext, composition: item.composition, contentAlignment: opfSlide.design?.contentAlignment ?? presentation.design?.contentAlignment ?? "left" }, options);
|
|
910
|
+
await addPayload(slide, presentation, item.payload, region, item.path, { ...slideContext, composition: item.composition, contentAlignment: opfSlide.design?.contentAlignment ?? presentation.design?.contentAlignment ?? "left" }, options, item.quoteLayout);
|
|
911
911
|
}
|
|
912
912
|
}
|
|
913
913
|
|
|
@@ -928,7 +928,7 @@ function fieldToType(field) {
|
|
|
928
928
|
return field === "items" || field === "bullets" ? "list" : field;
|
|
929
929
|
}
|
|
930
930
|
|
|
931
|
-
async function addPayload(slide, presentation, payload, region, path, context, options) {
|
|
931
|
+
async function addPayload(slide, presentation, payload, region, path, context, options, quoteLayout) {
|
|
932
932
|
const kind = inferPayloadKind(payload);
|
|
933
933
|
switch (kind) {
|
|
934
934
|
case "text":
|
|
@@ -950,16 +950,16 @@ async function addPayload(slide, presentation, payload, region, path, context, o
|
|
|
950
950
|
addTablePayload(slide, payload.table, region, context, options, path);
|
|
951
951
|
break;
|
|
952
952
|
case "code":
|
|
953
|
-
addCodePayload(slide, payload.code, region, context);
|
|
953
|
+
addCodePayload(slide, payload.code, region, context, options, path);
|
|
954
954
|
break;
|
|
955
955
|
case "metric":
|
|
956
|
-
addMetricPayload(slide, payload.metric, region, context);
|
|
956
|
+
addMetricPayload(slide, payload.metric, region, context, options, path);
|
|
957
957
|
break;
|
|
958
958
|
case "quote":
|
|
959
|
-
addQuotePayload(slide,
|
|
959
|
+
addQuotePayload(slide, quoteLayout, context, options, path);
|
|
960
960
|
break;
|
|
961
961
|
case "timeline":
|
|
962
|
-
addTimelinePayload(slide, payload.timeline, region, context);
|
|
962
|
+
addTimelinePayload(slide, payload.timeline, region, context, options, path);
|
|
963
963
|
break;
|
|
964
964
|
default:
|
|
965
965
|
addPlaceholderPayload(slide, "Unsupported OPF payload", payload, region, context);
|
|
@@ -1088,8 +1088,11 @@ function addChartPayload(slide, chart, region, context) {
|
|
|
1088
1088
|
chartColors: CHART_COLORS,
|
|
1089
1089
|
catAxisLabelFontFace: context.fonts.body,
|
|
1090
1090
|
catAxisLabelFontSize: 9,
|
|
1091
|
+
catAxisLabelColor: context.colors.text,
|
|
1091
1092
|
valAxisLabelFontFace: context.fonts.body,
|
|
1092
1093
|
valAxisLabelFontSize: 9,
|
|
1094
|
+
valAxisLabelColor: context.colors.text,
|
|
1095
|
+
legendColor: context.colors.text,
|
|
1093
1096
|
showValue: false,
|
|
1094
1097
|
valGridLine: { color: context.colors.border, transparency: 30, size: 1 },
|
|
1095
1098
|
barDir: chartData.barDir,
|
|
@@ -1184,67 +1187,76 @@ function addTablePayload(slide, table, region, context, options, path) {
|
|
|
1184
1187
|
});
|
|
1185
1188
|
}
|
|
1186
1189
|
|
|
1187
|
-
function
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1190
|
+
function pixelBox(region) {
|
|
1191
|
+
return {x: region.x * 96, y: region.y * 96, width: region.w * 96, height: region.h * 96};
|
|
1192
|
+
}
|
|
1193
|
+
|
|
1194
|
+
// Match the published renderer's payload geometry and shared core text fitting.
|
|
1195
|
+
// Each fitted line remains native editable text, without PowerPoint rewrapping it.
|
|
1196
|
+
function addMeasuredPayloadText(slide, text, box, context, options, config) {
|
|
1197
|
+
const scale = Math.min(context.dimensions.widthInches, context.dimensions.heightInches) * 96 / 720;
|
|
1198
|
+
const style = config.textStyle ?? resolveTextStyle({fontFamily: config.fontFamily ?? context.fonts.body, fontWeight: config.fontWeight ?? 400, path: config.path}, options.textMeasurement);
|
|
1199
|
+
const fit = config.fit ?? fitText(String(text ?? ''), box, config.fontSize * scale, (context.composition?.minFontSize ?? 16) * scale, textWidthMeasurer(style, options.textMeasurement));
|
|
1200
|
+
if (fit.overflow && !config.diagnosticsHandled) {
|
|
1201
|
+
const diagnostic = {code: 'text-overflow', path: config.path, message: 'Text exceeds its cell at the minimum font size; shorten it, increase its space, or split the slide.'};
|
|
1202
|
+
options.onDiagnostic?.(diagnostic);
|
|
1203
|
+
if (context.composition?.overflow === 'error') throw new OPFPptxError('layout-overflow', diagnostic.message, {path: config.path, issues: [diagnostic]});
|
|
1204
|
+
}
|
|
1205
|
+
for (const [index, line] of fit.lines.entries()) {
|
|
1206
|
+
if (!line) continue;
|
|
1207
|
+
slide.addText(line, {
|
|
1208
|
+
...textBoxOptions({x: box.x / 96, y: (box.y + index * fit.lineHeight) / 96, w: box.width / 96, h: fit.lineHeight / 96}, context, fit.fontSize * .75),
|
|
1209
|
+
fontFace: style.fontFamily, bold: style.fontWeight >= 600, italic: style.italic,
|
|
1210
|
+
color: normalizeHex(config.color ?? context.colors.text), align: config.align ?? context.contentAlignment ?? 'left',
|
|
1211
|
+
fit: 'none', wrap: false, lineSpacingMultiple: 1,
|
|
1212
|
+
});
|
|
1213
|
+
}
|
|
1199
1214
|
}
|
|
1200
1215
|
|
|
1201
|
-
function
|
|
1202
|
-
const
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
fontFace: context.fonts.heading,
|
|
1210
|
-
fontSize: 30,
|
|
1211
|
-
bold: true,
|
|
1212
|
-
color: context.colors.accent,
|
|
1213
|
-
fit: "shrink"
|
|
1214
|
-
});
|
|
1215
|
-
slide.addText([metric.label, metric.description, metric.delta].filter(Boolean).join("\n"), {
|
|
1216
|
-
x: region.x,
|
|
1217
|
-
y: region.y + 0.76,
|
|
1218
|
-
w: region.w,
|
|
1219
|
-
h: Math.max(0.3, region.h - 0.78),
|
|
1220
|
-
margin: 0,
|
|
1221
|
-
fontFace: context.fonts.body,
|
|
1222
|
-
fontSize: 12,
|
|
1223
|
-
color: context.colors.text,
|
|
1224
|
-
fit: "shrink"
|
|
1216
|
+
function addCodePayload(slide, value, region, context, options, path) {
|
|
1217
|
+
const box = pixelBox(region), code = typeof value === 'string' ? value : value?.source ?? JSON.stringify(value);
|
|
1218
|
+
const label = typeof value === 'object' && value?.language ? String(value.language) : 'code';
|
|
1219
|
+
slide.addShape('rect', {...region, fill: {color: '111827'}, line: {color: '334155', pt: .75}});
|
|
1220
|
+
// The label has a fixed baseline in the SVG renderer; it does not participate in fitting.
|
|
1221
|
+
slide.addText(label.toUpperCase(), {
|
|
1222
|
+
...textBoxOptions({x: (box.x + 18) / 96, y: (box.y + 14) / 96, w: Math.max(1, box.width - 36) / 96, h: 20 / 96}, context, 14 * .75),
|
|
1223
|
+
fontFace: context.fonts.code, bold: true, color: '93C5FD', fit: 'none', wrap: false,
|
|
1225
1224
|
});
|
|
1225
|
+
addMeasuredPayloadText(slide, code, {x: box.x + 18, y: box.y + 46, width: box.width - 36, height: box.height - 64}, context, options, {path, fontSize: 18, fontFamily: context.fonts.code, color: 'E5E7EB'});
|
|
1226
1226
|
}
|
|
1227
1227
|
|
|
1228
|
-
function
|
|
1229
|
-
const
|
|
1230
|
-
|
|
1231
|
-
slide.
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1228
|
+
function addMetricPayload(slide, value, region, context, options, path) {
|
|
1229
|
+
const metric = isPlainObject(value) ? value : {value}, box = pixelBox(region);
|
|
1230
|
+
addMeasuredPayloadText(slide, metric.value, {...box, height: box.height * .45}, context, options, {path: path + '.value', fontSize: Math.min(76, box.height * .28), fontFamily: context.fonts.heading, fontWeight: 800, color: context.colors.accent});
|
|
1231
|
+
addMeasuredPayloadText(slide, [metric.label, metric.description, metric.delta].filter(Boolean).join('\n'), {x: box.x, y: box.y + box.height * .45, width: box.width, height: box.height * .55}, context, options, {path, fontSize: 23, fontWeight: 500});
|
|
1232
|
+
}
|
|
1233
|
+
|
|
1234
|
+
function addQuotePayload(slide, layout, context, options, path) {
|
|
1235
|
+
if (!layout) throw new OPFPptxError('missing-quote-layout', 'Quote export requires a coordinated core build with shared quote geometry.', {path});
|
|
1236
|
+
for (const part of layout.parts) {
|
|
1237
|
+
if (!part.fit) throw new OPFPptxError('layout-overflow', 'Quote content has no usable internal space; increase its cell size before exporting.', {path:part.path,issues:layout.diagnostics});
|
|
1238
|
+
addMeasuredPayloadText(slide,part.text,part.box,context,options,{
|
|
1239
|
+
path:part.path,fit:part.fit,textStyle:part.style,diagnosticsHandled:true,
|
|
1240
|
+
color:part.role==='footer'?context.colors.mutedText:context.colors.text,
|
|
1241
|
+
});
|
|
1242
|
+
}
|
|
1237
1243
|
}
|
|
1238
1244
|
|
|
1239
|
-
function addTimelinePayload(slide, value, region, context) {
|
|
1240
|
-
const timeline = Array.isArray(value) ? {
|
|
1245
|
+
function addTimelinePayload(slide, value, region, context, options, path) {
|
|
1246
|
+
const timeline = Array.isArray(value) ? {events: value} : value;
|
|
1241
1247
|
const events = Array.isArray(timeline?.events) ? timeline.events : [];
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
+
if (!events.length) return;
|
|
1249
|
+
const box = pixelBox(region), labelWidth = box.width / Math.max(2, events.length);
|
|
1250
|
+
const gap = (box.width - labelWidth) / Math.max(1, events.length - 1);
|
|
1251
|
+
const start = events.length === 1 ? box.x + box.width / 2 : box.x + labelWidth / 2;
|
|
1252
|
+
const end = events.length === 1 ? start : box.x + box.width - labelWidth / 2;
|
|
1253
|
+
const y = box.y + box.height * .46, radius = Math.min(9, labelWidth / 5, box.height * .04);
|
|
1254
|
+
slide.addShape('line', {x: start / 96, y: y / 96, w: (end - start) / 96, h: 0, line: {color: context.colors.border, pt: 3 * .75}});
|
|
1255
|
+
for (const [index, event] of events.entries()) {
|
|
1256
|
+
const x = start + index * gap;
|
|
1257
|
+
slide.addShape('ellipse', {x: (x - radius) / 96, y: (y - radius) / 96, w: radius * 2 / 96, h: radius * 2 / 96, fill: {color: context.colors.accent}, line: {transparency: 100}});
|
|
1258
|
+
addMeasuredPayloadText(slide, [event.when, event.what, event.description].filter(Boolean).join('\n'), {x: x - labelWidth / 2, y: index % 2 === 0 ? box.y : y + 24, width: labelWidth, height: box.height * .38}, context, options, {path: `${path}.events.${index}`, fontSize: 16, fontWeight: 500, align: 'center'});
|
|
1259
|
+
}
|
|
1248
1260
|
}
|
|
1249
1261
|
|
|
1250
1262
|
function addPlaceholderPayload(slide, label, value, region, context) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@openpresentation/opf-pptx",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.0",
|
|
4
4
|
"description": "Pure local OPF to PPTX export and PPTX to OPF import tooling.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -38,7 +38,7 @@
|
|
|
38
38
|
"scripts": {
|
|
39
39
|
"build": "node scripts/build.mjs",
|
|
40
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",
|
|
41
|
-
"test": "npm run build && node test/dependency-boundary.mjs && npm run build:browser-check && npm run test:styled-table",
|
|
41
|
+
"test": "npm run build && 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",
|
|
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",
|
|
@@ -48,14 +48,14 @@
|
|
|
48
48
|
"test:styled-table": "node test/styled-table.mjs && node test/styled-table-import.mjs"
|
|
49
49
|
},
|
|
50
50
|
"dependencies": {
|
|
51
|
-
"@openpresentation/opf": "^0.
|
|
51
|
+
"@openpresentation/opf": "^0.8.0",
|
|
52
52
|
"fast-xml-parser": "^5.8.0",
|
|
53
53
|
"fflate": "^0.8.3",
|
|
54
54
|
"jszip": "3.10.1",
|
|
55
55
|
"sharp": "0.35.4"
|
|
56
56
|
},
|
|
57
57
|
"peerDependencies": {
|
|
58
|
-
"@openpresentation/opf-render": "^0.
|
|
58
|
+
"@openpresentation/opf-render": "^0.6.0"
|
|
59
59
|
},
|
|
60
60
|
"peerDependenciesMeta": {
|
|
61
61
|
"@openpresentation/opf-render": {
|
|
@@ -63,7 +63,7 @@
|
|
|
63
63
|
}
|
|
64
64
|
},
|
|
65
65
|
"devDependencies": {
|
|
66
|
-
"@openpresentation/opf-render": "0.
|
|
66
|
+
"@openpresentation/opf-render": "0.6.0",
|
|
67
67
|
"esbuild": "0.28.2",
|
|
68
68
|
"playwright": "1.63.0"
|
|
69
69
|
},
|