@openpresentation/opf-pptx 0.5.1 → 0.5.2
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 +66 -57
- package/package.json +4 -4
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.5.2 improves native metrics, quotes, code panels, timelines and chart-label contrast, with renderer 0.5.1 compatibility and fitted quote footer separation. See [native content evidence and limitations](docs/native-content-candidate.md). Native chart geometry and general scalar-text wrapping 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
|
@@ -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, payload.quote, region, context);
|
|
959
|
+
addQuotePayload(slide, payload.quote, region, 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,73 @@ 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 = resolveTextStyle({fontFamily: config.fontFamily ?? context.fonts.body, fontWeight: config.fontWeight ?? 400, path: config.path}, options.textMeasurement);
|
|
1199
|
+
const fit = fitText(String(text ?? ''), box, config.fontSize * scale, (context.composition?.minFontSize ?? 16) * scale, textWidthMeasurer(style, options.textMeasurement));
|
|
1200
|
+
if (fit.overflow) {
|
|
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
|
-
...textBoxOptions(region, context, 17),
|
|
1233
|
-
italic: true,
|
|
1234
|
-
color: context.colors.text,
|
|
1235
|
-
fit: "shrink"
|
|
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});
|
|
1237
1232
|
}
|
|
1238
1233
|
|
|
1239
|
-
function
|
|
1240
|
-
const
|
|
1234
|
+
function addQuotePayload(slide, value, region, context, options, path) {
|
|
1235
|
+
const quote = isPlainObject(value) ? value : {text: value}, box = pixelBox(region);
|
|
1236
|
+
const attribution = [quote.attribution, quote.source].filter(Boolean).join(' - ');
|
|
1237
|
+
// Keep the footer and an 18px gap outside the body's text fitting area.
|
|
1238
|
+
addMeasuredPayloadText(slide, `"${quote.text ?? ''}"`, {x: box.x + 18, y: box.y + 18, width: Math.max(1, box.width - 36), height: Math.max(1, box.height - (attribution ? 94 : 36))}, context, options, {path: path + '.text', fontSize: 28, fontFamily: context.fonts.heading, fontWeight: 600});
|
|
1239
|
+
addMeasuredPayloadText(slide, attribution, {x: box.x + 18, y: box.y + box.height - 58, width: box.width - 36, height: 40}, context, options, {path, fontSize: 17, fontWeight: 500, color: context.colors.mutedText});
|
|
1240
|
+
}
|
|
1241
|
+
|
|
1242
|
+
function addTimelinePayload(slide, value, region, context, options, path) {
|
|
1243
|
+
const timeline = Array.isArray(value) ? {events: value} : value;
|
|
1241
1244
|
const events = Array.isArray(timeline?.events) ? timeline.events : [];
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1245
|
+
if (!events.length) return;
|
|
1246
|
+
const box = pixelBox(region), labelWidth = box.width / Math.max(2, events.length);
|
|
1247
|
+
const gap = (box.width - labelWidth) / Math.max(1, events.length - 1);
|
|
1248
|
+
const start = events.length === 1 ? box.x + box.width / 2 : box.x + labelWidth / 2;
|
|
1249
|
+
const end = events.length === 1 ? start : box.x + box.width - labelWidth / 2;
|
|
1250
|
+
const y = box.y + box.height * .46, radius = Math.min(9, labelWidth / 5, box.height * .04);
|
|
1251
|
+
slide.addShape('line', {x: start / 96, y: y / 96, w: (end - start) / 96, h: 0, line: {color: context.colors.border, pt: 3 * .75}});
|
|
1252
|
+
for (const [index, event] of events.entries()) {
|
|
1253
|
+
const x = start + index * gap;
|
|
1254
|
+
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}});
|
|
1255
|
+
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'});
|
|
1256
|
+
}
|
|
1248
1257
|
}
|
|
1249
1258
|
|
|
1250
1259
|
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.5.
|
|
3
|
+
"version": "0.5.2",
|
|
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",
|
|
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",
|
|
@@ -55,7 +55,7 @@
|
|
|
55
55
|
"sharp": "0.35.4"
|
|
56
56
|
},
|
|
57
57
|
"peerDependencies": {
|
|
58
|
-
"@openpresentation/opf-render": "^0.5.
|
|
58
|
+
"@openpresentation/opf-render": "^0.5.1"
|
|
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.5.
|
|
66
|
+
"@openpresentation/opf-render": "0.5.1",
|
|
67
67
|
"esbuild": "0.28.2",
|
|
68
68
|
"playwright": "1.63.0"
|
|
69
69
|
},
|