@openpresentation/opf-render 0.6.0 → 0.7.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 +3 -1
- package/dist/svg.js +31 -22
- package/package.json +5 -3
package/README.md
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
# OPF Render
|
|
2
2
|
|
|
3
|
-
Version 0.
|
|
3
|
+
Version 0.7.0 requires core 0.9.0 and renders accepted quote and code geometry without fitting it again. Code filename/language/body parts preserve source whitespace, literal tabs and metadata case and expose trace targets for editing. The [43 reviewed code raster changes](docs/evidence/shared-code/raster-review.json) retain the other 762 corpus hashes. Planned coordinated releases PPTX 0.7.0 and editor 0.6.0 will add native source recovery and editing. Glyph containment and separation do not establish native pixel equivalence.
|
|
4
4
|
|
|
5
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.
|
|
6
6
|
|
|
7
|
+
Code strings that [XML 1.0 cannot represent](https://www.w3.org/TR/xml/#charsets) reject rendering with `invalid-code-text`, the OPF field path and UTF-16 offset. Input JSON stays unchanged. Tabs, line endings and valid supplementary Unicode remain accepted; schema validity and XML serialization do not certify font coverage or native fidelity.
|
|
8
|
+
|
|
7
9
|
## Scope
|
|
8
10
|
|
|
9
11
|
- Package: `@openpresentation/opf-render`
|
package/dist/svg.js
CHANGED
|
@@ -370,7 +370,9 @@ function inferLayoutId(slide) {
|
|
|
370
370
|
if (slide.table) return "table-1x";
|
|
371
371
|
if (slide.image) return "image-1x";
|
|
372
372
|
if (slide.video) return "media-1x";
|
|
373
|
-
|
|
373
|
+
// Unspecified code layout uses the same automatic flow as core pagination
|
|
374
|
+
// and PPTX export. An explicit code-1x preset still reserves its own slots.
|
|
375
|
+
if (slide.code !== undefined) return "blank";
|
|
374
376
|
if (slide.items || slide.bullets) return "list-1x";
|
|
375
377
|
if (slide.text || slide.metric || slide.quote || slide.timeline) return "text-1x";
|
|
376
378
|
if (slide.subtitle) return "title-subtitle";
|
|
@@ -768,8 +770,14 @@ function renderMedia(item, box, bound, options) {
|
|
|
768
770
|
}
|
|
769
771
|
|
|
770
772
|
function renderCode(item, box, bound, options) {
|
|
771
|
-
const
|
|
772
|
-
|
|
773
|
+
const layout = item.codeLayout;
|
|
774
|
+
if (!layout) throw new OPFRenderError('missing-code-layout', 'Code rendering requires a coordinated core build with shared code geometry.', {path:item.path});
|
|
775
|
+
for (const part of layout.parts) {
|
|
776
|
+
// XML 1.0 Char excludes controls and unpaired UTF-16 surrogates. The u flag
|
|
777
|
+
// keeps valid supplementary characters (surrogate pairs) accepted.
|
|
778
|
+
const invalid = /[\u0000-\u0008\u000B\u000C\u000E-\u001F\uD800-\uDFFF\uFFFE\uFFFF]/u.exec(part.text);
|
|
779
|
+
if (invalid) throw new OPFRenderError('invalid-code-text', `Code text contains U+${invalid[0].codePointAt(0).toString(16).toUpperCase().padStart(4,'0')} at UTF-16 offset ${invalid.index}, which XML cannot represent; edit that character before rendering.`, {path:part.path});
|
|
780
|
+
}
|
|
773
781
|
const children = [
|
|
774
782
|
tag("rect", {
|
|
775
783
|
x: box.x,
|
|
@@ -780,27 +788,28 @@ function renderCode(item, box, bound, options) {
|
|
|
780
788
|
stroke: "#334155",
|
|
781
789
|
"stroke-width": 1,
|
|
782
790
|
...traceAttrs(options, item.path)
|
|
783
|
-
}),
|
|
784
|
-
tag("text", {
|
|
785
|
-
x: box.x + 18,
|
|
786
|
-
y: box.y + 28,
|
|
787
|
-
fill: "#93C5FD",
|
|
788
|
-
"font-family": fontStack(bound.design.fonts.code, "monospace"),
|
|
789
|
-
"font-size": 14,
|
|
790
|
-
"font-weight": 700,
|
|
791
|
-
...traceAttrs(options, item.path)
|
|
792
|
-
}, escapeText(label.toUpperCase())),
|
|
793
|
-
renderTextBox(code, { x: box.x + 18, y: box.y + 46, width: box.width - 36, height: box.height - 64 }, bound, {
|
|
794
|
-
path: item.path,
|
|
795
|
-
fontSize: 18,
|
|
796
|
-
fontFamily: bound.design.fonts.code,
|
|
797
|
-
fontWeight: 400,
|
|
798
|
-
fill: "#E5E7EB",
|
|
799
|
-
options,
|
|
800
|
-
preserveNewlines: true
|
|
801
791
|
})
|
|
802
792
|
];
|
|
803
|
-
|
|
793
|
+
for (const part of layout.parts) {
|
|
794
|
+
if (!part.fit) throw new OPFRenderError('layout-overflow', 'Code content has no usable internal space; increase its cell size before rendering.', {path:part.path,issues:layout.diagnostics});
|
|
795
|
+
const lines=part.fit.sourceLines.map((line,index)=>tag('text',{
|
|
796
|
+
x:stableNumber(part.box.x),y:stableNumber(part.box.y+part.fit.fontSize+index*part.fit.lineHeight),
|
|
797
|
+
'text-anchor':'start','font-family':fontStack(part.style.fontFamily,'monospace'),
|
|
798
|
+
'font-size':stableNumber(part.fit.fontSize),'font-weight':part.style.fontWeight,'font-style':part.style.italic?'italic':undefined,
|
|
799
|
+
'xml:space':'preserve',style:'white-space:pre','text-rendering':'geometricPrecision',fill:part.role==='body'?'#E5E7EB':'#93C5FD',
|
|
800
|
+
...traceAttrs(options,part.path),...(options.trace?{'data-opf-code-role':part.role,'data-opf-generated':part.generated?'true':undefined,
|
|
801
|
+
'data-opf-text-start':line.start,'data-opf-text-end':line.end,'data-opf-text-next-start':line.nextStart,'data-opf-line-boundary':line.boundary}:{}),
|
|
802
|
+
},line.segments.map(segment=>tag('tspan',{
|
|
803
|
+
x:stableNumber(part.box.x+segment.x),
|
|
804
|
+
// SVG's CSS tab-size does not place literal tabs at their measured stops.
|
|
805
|
+
...(segment.kind==='tab'?{textLength:stableNumber(segment.width),lengthAdjust:'spacingAndGlyphs'}:{}),
|
|
806
|
+
...(options.trace?{'data-opf-segment':segment.kind,'data-opf-text-start':segment.start,'data-opf-text-end':segment.end}:{}),
|
|
807
|
+
},escapeText(part.text.slice(segment.start,segment.end)))).join('')));
|
|
808
|
+
children.push(tag('g',{...traceAttrs(options,part.path),...(options.trace?{'data-opf-code-role':part.role,'data-opf-generated':part.generated?'true':undefined,
|
|
809
|
+
'data-opf-box-x':part.box.x,'data-opf-box-y':part.box.y,'data-opf-box-width':part.box.width,'data-opf-box-height':part.box.height}:{}),
|
|
810
|
+
...(part.fit.overflow?{'data-opf-overflow':'true'}:{})},lines.join('\n')));
|
|
811
|
+
}
|
|
812
|
+
return tag("g", {...traceAttrs(options,item.path),...(options.trace?{'data-opf-code-container':'true'}:{})}, children.join("\n"));
|
|
804
813
|
}
|
|
805
814
|
|
|
806
815
|
function renderMetric(item, box, bound, options) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@openpresentation/opf-render",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.0",
|
|
4
4
|
"description": "Deterministic local OPF renderer for SVG, PNG, and PDF output.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -64,7 +64,9 @@
|
|
|
64
64
|
"golden:update": "npm run build && node test/golden.mjs --update",
|
|
65
65
|
"prepack": "npm run build",
|
|
66
66
|
"build:browser-check": "node scripts/build-browser-check.mjs",
|
|
67
|
-
"test:styled-table": "node test/styled-table.mjs"
|
|
67
|
+
"test:styled-table": "node test/styled-table.mjs",
|
|
68
|
+
"test:code": "node test/shared-code.mjs",
|
|
69
|
+
"test:code-browser": "node test/shared-code-browser.mjs"
|
|
68
70
|
},
|
|
69
71
|
"dependencies": {
|
|
70
72
|
"@expo-google-fonts/arimo": "0.4.3",
|
|
@@ -75,7 +77,7 @@
|
|
|
75
77
|
"@expo-google-fonts/roboto": "^0.4.3",
|
|
76
78
|
"@expo-google-fonts/roboto-mono": "^0.4.2",
|
|
77
79
|
"@expo-google-fonts/tinos": "0.4.2",
|
|
78
|
-
"@openpresentation/opf": "^0.
|
|
80
|
+
"@openpresentation/opf": "^0.9.0",
|
|
79
81
|
"@resvg/resvg-js": "^2.6.2",
|
|
80
82
|
"fontkit": "^2.0.4",
|
|
81
83
|
"pdf-lib": "^1.17.1",
|