@openpresentation/opf-render 0.5.0 → 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.
Files changed (3) hide show
  1. package/README.md +4 -2
  2. package/dist/svg.js +12 -26
  3. package/package.json +7 -4
package/README.md CHANGED
@@ -1,5 +1,7 @@
1
1
  # OPF Render
2
2
 
3
+ Version 0.6.0 requires core 0.8.0 and renders its accepted quote body/source geometry without fitting it again. Adaptive footer allocation, readability floors and strict failures are shared with the coordinated PPTX 0.6.0 exporter. The 41 reviewed quote-footer raster changes keep their outer content boxes; see [the raster review](docs/evidence/shared-quote-raster-review.json). Glyph containment and separation do not establish native pixel equivalence.
4
+
3
5
  Deterministic local renderer for Open Presentation Format documents. The shared SVG core implements validation, catalog resolution, placeholder binding and text layout. Node APIs additionally convert SVG to PNG and raster-backed PDF.
4
6
 
5
7
  ## Scope
@@ -32,7 +34,7 @@ const pdf = await svgToPdf(svgs, { scale: 1 });
32
34
 
33
35
  `svgToPng` returns PNG bytes for one SVG. `svgToPdf` accepts one SVG or an array of SVGs and returns PDF bytes with one slide per page. The SVG page `width`/`height` or `viewBox` determines the PDF page size; `scale` controls raster density only.
34
36
 
35
- ## Browser preview and fonts (preview APIs)
37
+ ## Browser preview and fonts
36
38
 
37
39
  Use `@openpresentation/opf-render/svg` for browser rendering without Node dependencies. Browser-aware bundlers also select this shared SVG implementation for the root import; Node's root import retains PNG/PDF conversion.
38
40
 
@@ -49,7 +51,7 @@ container.innerHTML = renderSvg(presentation, {
49
51
 
50
52
  Each entry contains `url` or `data: Uint8Array`, with optional `family`, `weight`, `italic` and `license`. The loader registers browser FontFaces using the same bytes used for measurement. It fetches only URLs supplied by the host, supports an AbortSignal and custom fetch, and awaits font loading. Use pinned static faces and retain their licenses. For standalone SVG export also pass `embeddedFonts: fonts.embeddedFonts`; embedding is unnecessary for each live draft after browser fonts are loaded.
51
53
 
52
- The new entries require coordinated preview builds of OPF and the toolkit. The sibling OPF repository's `pnpm pack:ecosystem` prepares local npm tarballs; older registry releases do not include these APIs. See `opf/docs/live-editor.md` for installation and the fidelity contract. Identical SVG geometry does not guarantee identical raster pixels across browser engines or PowerPoint.
54
+ These browser entrypoints are included in the published package. For coordinated development, the sibling OPF repository's `pnpm pack:ecosystem` prepares local npm tarballs. See the core repository's [live editor guide](https://github.com/OpenPresentation/opf/blob/main/docs/live-editor.md) for installation and the fidelity contract. Identical SVG geometry does not guarantee identical raster pixels across browser engines or PowerPoint.
53
55
 
54
56
  ## Runtime Policy
55
57
 
package/dist/svg.js CHANGED
@@ -626,7 +626,7 @@ function renderSlideContent(bound, width, height, options) {
626
626
  }
627
627
 
628
628
  function reportDiagnostic(diagnostic, options) {
629
- const key = `${diagnostic.code}:${diagnostic.path}`;
629
+ const key = `${diagnostic.code}:${diagnostic.path}:${diagnostic.reason ?? ''}`;
630
630
  if (options._diagnosticPaths.has(key)) return;
631
631
  options._diagnosticPaths.add(key);
632
632
  options.onDiagnostic?.(diagnostic);
@@ -832,30 +832,16 @@ function renderMetric(item, box, bound, options) {
832
832
  }
833
833
 
834
834
  function renderQuote(item, box, bound, options) {
835
- const quote = isPlainObject(item.value) ? item.value : { text: item.value };
836
- const children = [
837
- renderTextBox(`"${quote.text ?? ""}"`, inset(box, 18), bound, {
838
- path: `${item.path}.text`,
839
- fontSize: 28,
840
- fontFamily: bound.design.fonts.heading,
841
- fontWeight: 600,
842
- fill: bound.design.colors.text,
843
- options
844
- }),
845
- renderTextBox([quote.attribution, quote.source].filter(Boolean).join(" - "), {
846
- x: box.x + 18,
847
- y: box.y + box.height - 58,
848
- width: box.width - 36,
849
- height: 40
850
- }, bound, {
851
- path: item.path,
852
- fontSize: 17,
853
- fontFamily: bound.design.fonts.body,
854
- fontWeight: 500,
855
- fill: bound.design.colors.mutedText,
856
- options
857
- })
858
- ];
835
+ const layout = item.quoteLayout;
836
+ if (!layout) throw new OPFRenderError('missing-quote-layout', 'Quote rendering requires a coordinated core build with shared quote geometry.', {path:item.path});
837
+ const children = layout.parts.map(part => {
838
+ if (!part.fit) throw new OPFRenderError('layout-overflow', 'Quote content has no usable internal space; increase its cell size before rendering.', {path:part.path,issues:layout.diagnostics});
839
+ return renderTextBox(part.text,part.box,bound,{
840
+ path:part.path,fit:part.fit,textStyle:part.style,fontFamily:part.requestedStyle.fontFamily,
841
+ fill:part.role==='footer'?bound.design.colors.mutedText:bound.design.colors.text,
842
+ diagnosticsHandled:true,options,
843
+ });
844
+ });
859
845
  return tag("g", traceAttrs(options, item.path), children.join("\n"));
860
846
  }
861
847
 
@@ -1219,7 +1205,7 @@ function renderTextBox(text, box, bound, config) {
1219
1205
  const style = config.textStyle ?? resolveTextStyle({fontFamily:config.fontFamily,fontWeight:config.fontWeight ?? 400,italic:config.italic ?? false,path:config.path},config.options.textMeasurement);
1220
1206
  const fit = config.fit ?? fitText(String(text ?? ""), box, config.fontSize * scale,
1221
1207
  ((bound.composition ?? bound.geometry.composition).minFontSize ?? 16) * scale, textWidthMeasurer(style,config.options.textMeasurement));
1222
- if (fit.overflow) {
1208
+ if (fit.overflow && !config.diagnosticsHandled) {
1223
1209
  const diagnostic = { code: "text-overflow", path: config.path,
1224
1210
  message: "Text exceeds its cell at the minimum font size; shorten it, increase its space, or split the slide." };
1225
1211
  reportDiagnostic(diagnostic, config.options);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openpresentation/opf-render",
3
- "version": "0.5.0",
3
+ "version": "0.6.0",
4
4
  "description": "Deterministic local OPF renderer for SVG, PNG, and PDF output.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -57,8 +57,10 @@
57
57
  "build": "node scripts/build.mjs",
58
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 && node --check test/styled-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/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 && npm run test:styled-table",
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/quote-footer.mjs && node test/shared-quote.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 && npm run test:styled-table",
61
61
  "test:golden": "npm run build && node test/golden.mjs",
62
+ "test:packed": "node test/packed-install.mjs",
63
+ "test:browser": "npm run build:browser-check && node test/browser-check.mjs",
62
64
  "golden:update": "npm run build && node test/golden.mjs --update",
63
65
  "prepack": "npm run build",
64
66
  "build:browser-check": "node scripts/build-browser-check.mjs",
@@ -73,13 +75,14 @@
73
75
  "@expo-google-fonts/roboto": "^0.4.3",
74
76
  "@expo-google-fonts/roboto-mono": "^0.4.2",
75
77
  "@expo-google-fonts/tinos": "0.4.2",
76
- "@openpresentation/opf": "^0.7.0",
78
+ "@openpresentation/opf": "^0.8.0",
77
79
  "@resvg/resvg-js": "^2.6.2",
78
80
  "fontkit": "^2.0.4",
79
81
  "pdf-lib": "^1.17.1",
80
82
  "sharp": "0.35.4"
81
83
  },
82
84
  "devDependencies": {
83
- "esbuild": "0.28.2"
85
+ "esbuild": "0.28.2",
86
+ "playwright": "1.63.0"
84
87
  }
85
88
  }