@openpresentation/opf-render 0.2.0 → 0.4.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.
Files changed (3) hide show
  1. package/README.md +2 -0
  2. package/dist/svg.js +19 -44
  3. package/package.json +4 -4
package/README.md CHANGED
@@ -104,6 +104,8 @@ This repo does not require an npm automation token when Trusted Publishing is co
104
104
 
105
105
  The current checkout uses `@openpresentation/opf/composition` for portable geometry. Slides can select `auto`, `row`, `column`, or `grid`, set weighted tracks, and request path-specific overflow diagnostics. See the sibling OPF repo's `docs/dynamic-composition.md` for the complete contract.
106
106
 
107
+ Version 0.3.0 requires core 0.5.0 and renders rich table cells and headers using canonical `TextRun[]`, including editor trace geometry.
108
+
107
109
  Version 0.2.x requires the published `@openpresentation/opf@^0.4.1` for composition, pagination and measured text. A clean `npm ci && npm test` uses registry packages and runs the full raster corpus without sibling checkouts. For coordinated source development, build OPF and run `node scripts/link-ecosystem.mjs` there; `pnpm test:ecosystem` checks editing, rendering, editable PowerPoint geometry and import together.
108
110
 
109
111
  For actual font measurement, load `loadBundledFontRegistry()` from `@openpresentation/opf-render/fonts-node`, then pass its `textMeasurement` to rendering and its `embeddedFonts` to SVG export. Pass its `fontFiles` to PNG/PDF conversion. The browser-safe `@openpresentation/opf-render/fonts` entry accepts local font bytes. Missing fonts/glyphs fail explicitly; aliases and fallback are opt-in and recorded in `registry.substitutions`. Bundled font license notices travel with embedded SVG fonts.
package/dist/svg.js CHANGED
@@ -1,4 +1,4 @@
1
- import { fitList, fitRichText, composeSlide, resolveCanvasDimensions, resolveFontFamilies, resolveTextStyle, textWidthMeasurer, fitText } from "@openpresentation/opf/composition";
1
+ import { layoutTable, fitList, fitRichText, composeSlide, resolveCanvasDimensions, resolveFontFamilies, resolveTextStyle, textWidthMeasurer, fitText } from "@openpresentation/opf/composition";
2
2
  import {
3
3
  catalogs as bundledCatalogs,
4
4
  validatePresentation
@@ -911,47 +911,22 @@ function renderTimeline(item, box, bound, options) {
911
911
 
912
912
  function renderTable(item, box, bound, options) {
913
913
  const scale = Math.min(bound.design.dimensions.width, bound.design.dimensions.height) / 720;
914
- const table = item.value ?? {};
915
- const columns = Array.isArray(table.columns) ? table.columns : [];
916
- const rows = Array.isArray(table.rows) ? table.rows : [];
917
- const allRows = columns.length ? [columns, ...rows] : rows;
918
- const columnCount = Math.max(1, ...allRows.map((row) => Array.isArray(row) ? row.length : 1));
919
- const rowCount = Math.max(1, allRows.length);
920
- const cellWidth = box.width / columnCount;
921
- const cellHeight = Math.min(54 * scale, box.height / rowCount);
914
+ const layout = layoutTable(item.value, box, {scale, minFontSize:(bound.composition ?? bound.geometry.composition).minFontSize, fontFamily:bound.design.fonts.body, textMeasurement:options.textMeasurement, path:item.path});
922
915
  const children = [];
923
-
924
- allRows.forEach((row, rowIndex) => {
925
- const cells = Array.isArray(row) ? row : [row];
926
- for (let columnIndex = 0; columnIndex < columnCount; columnIndex += 1) {
927
- const x = box.x + columnIndex * cellWidth;
928
- const y = box.y + rowIndex * cellHeight;
929
- const cellPath = rowIndex === 0 && columns.length ? `${item.path}.columns.${columnIndex}` : `${item.path}.rows.${rowIndex - (columns.length ? 1 : 0)}.${columnIndex}`;
930
- children.push(tag("rect", {
931
- x: stableNumber(x),
932
- y: stableNumber(y),
933
- width: stableNumber(cellWidth),
934
- height: stableNumber(cellHeight),
935
- fill: rowIndex === 0 && columns.length ? bound.design.colors.primary : bound.design.colors.surface,
936
- stroke: bound.design.colors.border,
937
- "stroke-width": 1,
938
- ...traceAttrs(options, cellPath)
939
- }));
940
- children.push(renderTextBox(flattenText(cells[columnIndex] ?? ""), {
941
- x: x + 10 * scale,
942
- y: y + 8 * scale,
943
- width: Math.max(scale, cellWidth - 20 * scale),
944
- height: Math.max(scale, cellHeight - 12 * scale)
945
- }, bound, {
946
- path: cellPath,
947
- fontSize: 15,
948
- fontFamily: bound.design.fonts.body,
949
- fontWeight: rowIndex === 0 && columns.length ? 700 : 400,
950
- fill: rowIndex === 0 && columns.length ? "#FFFFFF" : bound.design.colors.text,
951
- options
952
- }));
953
- }
954
- });
916
+ for (const row of layout.rows) for (const cell of row.cells) {
917
+ children.push(tag("rect", {
918
+ x: stableNumber(cell.box.x), y: stableNumber(cell.box.y),
919
+ width: stableNumber(cell.box.width), height: stableNumber(cell.box.height),
920
+ fill: cell.header ? bound.design.colors.primary : bound.design.colors.surface,
921
+ stroke: bound.design.colors.border, "stroke-width":1,
922
+ ...traceAttrs(options, cell.path)
923
+ }));
924
+ children.push((cell.rich ? renderRichTextBox : renderTextBox)(cell.rich ? cell.value : flattenText(cell.value ?? ""), cell.textBox, bound, {
925
+ path:cell.path, fontSize:15, fontFamily:bound.design.fonts.body,
926
+ fontWeight:cell.header ? 700 : 400, textStyle:cell.textStyle, fit:cell.fit,
927
+ fill:cell.header ? "#FFFFFF" : bound.design.colors.text, options
928
+ }));
929
+ }
955
930
 
956
931
  return tag("g", traceAttrs(options, item.path), children.join("\n"));
957
932
  }
@@ -1167,7 +1142,7 @@ function renderEmbeddedFonts(fonts = []) {
1167
1142
 
1168
1143
  function renderRichTextBox(value, box, bound, config) {
1169
1144
  const scale=Math.min(bound.design.dimensions.width,bound.design.dimensions.height)/720;
1170
- const fit=fitRichText(value,box,config.fontSize*scale,((bound.composition??bound.geometry.composition).minFontSize??16)*scale,{style:{fontFamily:config.fontFamily,fontWeight:config.fontWeight??400,path:config.path},textMeasurement:config.options.textMeasurement});
1145
+ const fit=config.fit??fitRichText(value,box,config.fontSize*scale,((bound.composition??bound.geometry.composition).minFontSize??16)*scale,{style:{fontFamily:config.fontFamily,fontWeight:config.fontWeight??400,path:config.path},textMeasurement:config.options.textMeasurement});
1171
1146
  if(fit.overflow){const diagnostic={code:'text-overflow',path:config.path,message:'Mixed-style text exceeds its cell at the minimum font size.'};reportDiagnostic(diagnostic,config.options);if((bound.composition??bound.geometry.composition).overflow==='error')throw new OPFRenderError('layout-overflow',diagnostic.message,{issues:[diagnostic]});}
1172
1147
  return renderRichLines(value,fit,box,bound,config);
1173
1148
  }
@@ -1195,8 +1170,8 @@ function renderRichLines(value,fit,box,bound,config) {
1195
1170
 
1196
1171
  function renderTextBox(text, box, bound, config) {
1197
1172
  const scale = Math.min(bound.design.dimensions.width, bound.design.dimensions.height) / 720;
1198
- const style = resolveTextStyle({fontFamily:config.fontFamily,fontWeight:config.fontWeight ?? 400,italic:config.italic ?? false,path:config.path},config.options.textMeasurement);
1199
- const fit = fitText(String(text ?? ""), box, config.fontSize * scale,
1173
+ const style = config.textStyle ?? resolveTextStyle({fontFamily:config.fontFamily,fontWeight:config.fontWeight ?? 400,italic:config.italic ?? false,path:config.path},config.options.textMeasurement);
1174
+ const fit = config.fit ?? fitText(String(text ?? ""), box, config.fontSize * scale,
1200
1175
  ((bound.composition ?? bound.geometry.composition).minFontSize ?? 16) * scale, textWidthMeasurer(style,config.options.textMeasurement));
1201
1176
  if (fit.overflow) {
1202
1177
  const diagnostic = { code: "text-overflow", path: config.path,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openpresentation/opf-render",
3
- "version": "0.2.0",
3
+ "version": "0.4.0",
4
4
  "description": "Deterministic local OPF renderer for SVG, PNG, and PDF output.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -55,9 +55,9 @@
55
55
  "sideEffects": false,
56
56
  "scripts": {
57
57
  "build": "node scripts/build.mjs",
58
- "typecheck": "node --check src/index.js && node --check scripts/build.mjs && node --check scripts/validate-package.mjs && node --check test/smoke.mjs && node --check test/golden.mjs && node --check src/fonts.js && node --check src/fonts-node.js && node --check src/font-compatibility.js && node --check test/office-fonts.mjs && node --check src/fonts-browser.js && node --check test/fonts-browser.mjs && node --check src/svg.js && node --check src/raster.js && node --check src/raster-images.js && node --check test/webp.mjs && node --check test/jpeg-orientation.mjs && node --check scripts/build-browser-check.mjs && node --check test/jpeg-browser.js",
58
+ "typecheck": "node --check src/index.js && node --check scripts/build.mjs && node --check scripts/validate-package.mjs && node --check test/smoke.mjs && node --check test/golden.mjs && node --check src/fonts.js && node --check src/fonts-node.js && node --check src/font-compatibility.js && node --check test/office-fonts.mjs && node --check src/fonts-browser.js && node --check test/fonts-browser.mjs && node --check src/svg.js && node --check src/raster.js && node --check src/raster-images.js && node --check test/webp.mjs && node --check test/jpeg-orientation.mjs && node --check scripts/build-browser-check.mjs && node --check test/jpeg-browser.js && node --check test/rich-table.mjs",
59
59
  "validate": "node scripts/validate-package.mjs",
60
- "test": "npm run build && node test/smoke.mjs && node test/webp.mjs && node test/jpeg-orientation.mjs && node test/fonts.mjs && node test/office-fonts.mjs && node test/timeline.mjs && node test/rich-trace.mjs && node test/golden-gate.mjs && node test/golden.mjs && node test/fonts-browser.mjs && node test/design-preview.mjs && npm run build:browser-check",
60
+ "test": "npm run build && node test/smoke.mjs && node test/webp.mjs && node test/jpeg-orientation.mjs && node test/fonts.mjs && node test/office-fonts.mjs && node test/timeline.mjs && node test/rich-trace.mjs && node test/rich-table.mjs && node test/golden-gate.mjs && node test/golden.mjs && node test/fonts-browser.mjs && node test/design-preview.mjs && npm run build:browser-check",
61
61
  "test:golden": "npm run build && node test/golden.mjs",
62
62
  "golden:update": "npm run build && node test/golden.mjs --update",
63
63
  "prepack": "npm run build",
@@ -72,7 +72,7 @@
72
72
  "@expo-google-fonts/roboto": "^0.4.3",
73
73
  "@expo-google-fonts/roboto-mono": "^0.4.2",
74
74
  "@expo-google-fonts/tinos": "0.4.2",
75
- "@openpresentation/opf": "^0.4.1",
75
+ "@openpresentation/opf": "^0.6.0",
76
76
  "@resvg/resvg-js": "^2.6.2",
77
77
  "fontkit": "^2.0.4",
78
78
  "pdf-lib": "^1.17.1",