@openpresentation/opf-render 0.6.0 → 0.8.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 +60 -6
- package/dist/font-manifest.js +326 -0
- package/dist/fonts-node.d.ts +23 -1
- package/dist/fonts-node.js +59 -29
- package/dist/fonts.d.ts +2 -2
- package/dist/fonts.js +46 -12
- package/dist/index.d.ts +16 -1
- package/dist/raster.js +6 -18
- package/dist/svg.d.ts +18 -1
- package/dist/svg.js +171 -133
- package/package.json +21 -8
package/dist/svg.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { layoutTable, fitList, fitRichText, composeSlide, resolveCanvasDimensions, resolveFontFamilies, resolveTextStyle, textWidthMeasurer, fitText } from "@openpresentation/opf/composition";
|
|
1
|
+
import { layoutTable, fitList, fitRichText, composeSlide, resolveCanvasDimensions, resolveFontFamilies, resolveTextStyle, textWidthMeasurer, fitText, textColorForFill, chartColorForFill } from "@openpresentation/opf/composition";
|
|
2
2
|
import {
|
|
3
3
|
catalogs as bundledCatalogs,
|
|
4
4
|
validatePresentation
|
|
@@ -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";
|
|
@@ -483,7 +485,7 @@ function bindSlide(presentation, slide, layout, index, context) {
|
|
|
483
485
|
|
|
484
486
|
const design = resolveDesign(presentation, slide, context);
|
|
485
487
|
for (const role of ["heading","body","code"]) design.fonts[role] = resolveTextStyle({fontFamily:design.fonts[role],fontWeight:role === "heading" ? 700 : 400},context.options.textMeasurement).fontFamily;
|
|
486
|
-
const geometry = composeSlide(slide, { ...design.dimensions, layout, slideIndex: index, fonts: design.fonts, textMeasurement: context.options.textMeasurement });
|
|
488
|
+
const geometry = composeSlide(slide, { ...design.dimensions, layout, slideIndex: index, fonts: design.fonts, contentAlignment:design.contentAlignment, titleAlignment:design.titleAlignment, textRasterPadding:context.options.textRasterPadding, contentBox:design.contentBox, textMeasurement: context.options.textMeasurement });
|
|
487
489
|
return {
|
|
488
490
|
geometry,
|
|
489
491
|
assets: presentation.assets ?? {},
|
|
@@ -620,7 +622,8 @@ function renderBackground(bound, width, height, options) {
|
|
|
620
622
|
function renderSlideContent(bound, width, height, options) {
|
|
621
623
|
for (const diagnostic of bound.geometry.diagnostics) reportDiagnostic(diagnostic, options);
|
|
622
624
|
return bound.geometry.items.map(item => {
|
|
623
|
-
const
|
|
625
|
+
const frame=item.frameBox;
|
|
626
|
+
const surface=frame ? tag('rect',{x:frame.x,y:frame.y,width:frame.width,height:frame.height,rx:8*Math.min(width,height)/720,fill:bound.design.colors.surface,stroke:bound.design.colors.border}) : '';
|
|
624
627
|
return surface+renderPayload(item, item.box, { ...bound, composition: item.composition }, options);
|
|
625
628
|
});
|
|
626
629
|
}
|
|
@@ -667,6 +670,9 @@ function renderTextPayload(item, box, bound, options) {
|
|
|
667
670
|
fontFamily: item.field === "title" ? bound.design.fonts.heading : bound.design.fonts.body,
|
|
668
671
|
fontWeight: item.field === "title" ? 700 : 400,
|
|
669
672
|
fill: bound.design.colors.text,
|
|
673
|
+
fit: item.text,
|
|
674
|
+
textStyle: item.textStyle,
|
|
675
|
+
diagnosticsHandled: Boolean(item.text),
|
|
670
676
|
options
|
|
671
677
|
});
|
|
672
678
|
}
|
|
@@ -687,11 +693,14 @@ function renderList(item, box, bound, options) {
|
|
|
687
693
|
function renderImage(item, box, bound, options) {
|
|
688
694
|
let asset = normalizeAsset(item.value);
|
|
689
695
|
const seen = new Set();
|
|
696
|
+
let missingReference;
|
|
690
697
|
while (asset.src?.startsWith("asset:")) {
|
|
691
698
|
const id = asset.src.slice(6);
|
|
692
699
|
if (seen.has(id)) throw new OPFRenderError("invalid-asset-reference", "Circular asset reference.", { path: item.path });
|
|
693
700
|
seen.add(id);
|
|
694
|
-
|
|
701
|
+
if (!Object.hasOwn(bound.assets,id)) { missingReference=id; break; }
|
|
702
|
+
const {src:_source,...overrides}=asset;
|
|
703
|
+
asset = {...normalizeAsset(bound.assets[id]),...overrides};
|
|
695
704
|
}
|
|
696
705
|
const source = options.imageResolver?.(asset.src, { asset, path: item.path }) ?? asset.src;
|
|
697
706
|
if (typeof source === "string" && /^data:image\/(png|jpeg|gif|webp);base64,/i.test(source)) {
|
|
@@ -699,8 +708,18 @@ function renderImage(item, box, bound, options) {
|
|
|
699
708
|
href: source, preserveAspectRatio: (options.imageFit ?? (bound.design.imageFill === "crop" ? "cover" : "contain")) === "cover" ? "xMidYMid slice" : "xMidYMid meet", role: "img", "aria-label": asset.alt ?? "Image",
|
|
700
709
|
...traceAttrs(options, item.path) });
|
|
701
710
|
}
|
|
702
|
-
|
|
711
|
+
const reason=missingReference?'missing-reference':!asset.src?'missing-source':'unsupported-source';
|
|
712
|
+
const message=missingReference?`Image asset ${missingReference} is missing; supply the referenced asset or resolve it in the host.`:'Image requires an embedded raster data URI or a host imageResolver.';
|
|
713
|
+
if (options.strictAssets) throw new OPFRenderError("unresolved-asset", message, { path: item.path,reason });
|
|
703
714
|
const fill = bound.design.colors.surface;
|
|
715
|
+
const scale=Math.min(bound.design.dimensions.width,bound.design.dimensions.height)/720;
|
|
716
|
+
const padding=Math.min(24*scale,box.width*.06,box.height*.1),inner=inset(box,padding);
|
|
717
|
+
const description=asset.alt??asset.title??'Image';
|
|
718
|
+
const label=`Image unavailable\n${description}`;
|
|
719
|
+
const style=resolveTextStyle({fontFamily:bound.design.fonts.body,fontWeight:600,path:item.path},options.textMeasurement);
|
|
720
|
+
const fit=fitText(label,inner,20*scale,((bound.composition??bound.geometry.composition).minFontSize??16)*scale,textWidthMeasurer(style,options.textMeasurement));
|
|
721
|
+
const textColor=textColorForFill(fill,bound.design.colors.text);
|
|
722
|
+
reportDiagnostic({code:'unresolved-asset',path:item.path,message,reason,source:asset.src,assetId:missingReference,description,placeholder:fit.overflow?'icon':'label'},options);
|
|
704
723
|
const children = [
|
|
705
724
|
tag("rect", {
|
|
706
725
|
x: box.x,
|
|
@@ -711,21 +730,28 @@ function renderImage(item, box, bound, options) {
|
|
|
711
730
|
fill,
|
|
712
731
|
stroke: bound.design.colors.border,
|
|
713
732
|
"stroke-width": 1,
|
|
733
|
+
"stroke-dasharray": "4 3",
|
|
714
734
|
...traceAttrs(options, item.path)
|
|
715
|
-
})
|
|
716
|
-
|
|
735
|
+
})
|
|
736
|
+
];
|
|
737
|
+
if(!fit.overflow)children.push(renderTextBox(label, inner, bound, {
|
|
717
738
|
path: item.path,
|
|
718
739
|
fontSize: 20,
|
|
719
|
-
fontFamily:
|
|
740
|
+
fontFamily: style.fontFamily,
|
|
720
741
|
fontWeight: 600,
|
|
721
|
-
|
|
742
|
+
textStyle:style,fit,diagnosticsHandled:true,
|
|
743
|
+
fill: textColor,
|
|
722
744
|
options,
|
|
723
745
|
align: "center",
|
|
724
746
|
verticalAlign: "middle"
|
|
725
|
-
})
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
747
|
+
}));
|
|
748
|
+
else {
|
|
749
|
+
// This is a status indicator, not shortened authored content. The complete
|
|
750
|
+
// description remains in the accessible name and the path-specific diagnostic.
|
|
751
|
+
const size=Math.max(0,Math.min(inner.width,inner.height,24*scale)),icon=centeredBox(inner,size,size);
|
|
752
|
+
children.push(tag('path',{d:`M ${icon.x} ${icon.y} L ${icon.x+size} ${icon.y+size} M ${icon.x+size} ${icon.y} L ${icon.x} ${icon.y+size}`,fill:'none',stroke:textColor,'stroke-width':Math.min(2*scale,size/8),'aria-hidden':'true'}));
|
|
753
|
+
}
|
|
754
|
+
return tag("g", {...traceAttrs(options,item.path),'data-opf-asset-status':'unresolved',role:'img','aria-label':`Image unavailable: ${description}`},children.join("\n"));
|
|
729
755
|
}
|
|
730
756
|
|
|
731
757
|
function renderMedia(item, box, bound, options) {
|
|
@@ -768,8 +794,14 @@ function renderMedia(item, box, bound, options) {
|
|
|
768
794
|
}
|
|
769
795
|
|
|
770
796
|
function renderCode(item, box, bound, options) {
|
|
771
|
-
const
|
|
772
|
-
|
|
797
|
+
const layout = item.codeLayout;
|
|
798
|
+
if (!layout) throw new OPFRenderError('missing-code-layout', 'Code rendering requires a coordinated core build with shared code geometry.', {path:item.path});
|
|
799
|
+
for (const part of layout.parts) {
|
|
800
|
+
// XML 1.0 Char excludes controls and unpaired UTF-16 surrogates. The u flag
|
|
801
|
+
// keeps valid supplementary characters (surrogate pairs) accepted.
|
|
802
|
+
const invalid = /[\u0000-\u0008\u000B\u000C\u000E-\u001F\uD800-\uDFFF\uFFFE\uFFFF]/u.exec(part.text);
|
|
803
|
+
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});
|
|
804
|
+
}
|
|
773
805
|
const children = [
|
|
774
806
|
tag("rect", {
|
|
775
807
|
x: box.x,
|
|
@@ -780,55 +812,57 @@ function renderCode(item, box, bound, options) {
|
|
|
780
812
|
stroke: "#334155",
|
|
781
813
|
"stroke-width": 1,
|
|
782
814
|
...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
815
|
})
|
|
802
816
|
];
|
|
803
|
-
|
|
817
|
+
for (const part of layout.parts) {
|
|
818
|
+
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});
|
|
819
|
+
const lines=part.fit.sourceLines.map((line,index)=>tag('text',{
|
|
820
|
+
x:stableNumber(part.box.x),y:stableNumber(part.box.y+part.fit.fontSize+index*part.fit.lineHeight),
|
|
821
|
+
'text-anchor':'start','font-family':fontStack(part.style.fontFamily,'monospace'),
|
|
822
|
+
'font-size':stableNumber(part.fit.fontSize),'font-weight':part.style.fontWeight,'font-style':part.style.italic?'italic':undefined,
|
|
823
|
+
'xml:space':'preserve',style:'white-space:pre','text-rendering':'geometricPrecision',fill:part.role==='body'?'#E5E7EB':'#93C5FD',
|
|
824
|
+
...traceAttrs(options,part.path),...(options.trace?{'data-opf-code-role':part.role,'data-opf-generated':part.generated?'true':undefined,
|
|
825
|
+
'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}:{}),
|
|
826
|
+
},line.segments.map(segment=>tag('tspan',{
|
|
827
|
+
x:stableNumber(part.box.x+segment.x),
|
|
828
|
+
// SVG's CSS tab-size does not place literal tabs at their measured stops.
|
|
829
|
+
...(segment.kind==='tab'?{textLength:stableNumber(segment.width),lengthAdjust:'spacingAndGlyphs'}:{}),
|
|
830
|
+
...(options.trace?{'data-opf-segment':segment.kind,'data-opf-text-start':segment.start,'data-opf-text-end':segment.end}:{}),
|
|
831
|
+
},escapeText(part.text.slice(segment.start,segment.end)))).join('')));
|
|
832
|
+
children.push(tag('g',{...traceAttrs(options,part.path),...(options.trace?{'data-opf-code-role':part.role,'data-opf-generated':part.generated?'true':undefined,
|
|
833
|
+
'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}:{}),
|
|
834
|
+
...(part.fit.overflow?{'data-opf-overflow':'true'}:{})},lines.join('\n')));
|
|
835
|
+
}
|
|
836
|
+
return tag("g", {...traceAttrs(options,item.path),...(options.trace?{'data-opf-code-container':'true'}:{})}, children.join("\n"));
|
|
804
837
|
}
|
|
805
838
|
|
|
806
839
|
function renderMetric(item, box, bound, options) {
|
|
807
|
-
const
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
840
|
+
const layout=item.metricLayout;
|
|
841
|
+
if (!layout) throw new OPFRenderError('missing-metric-layout','Metric rendering requires coordinated core metric geometry.',{path:item.path});
|
|
842
|
+
const children=[];
|
|
843
|
+
for (const part of layout.parts) {
|
|
844
|
+
const invalid=/[\u0000-\u0008\u000B\u000C\u000E-\u001F\uD800-\uDFFF\uFFFE\uFFFF]/u.exec(part.text);
|
|
845
|
+
if (invalid) throw new OPFRenderError('invalid-metric-text',`Metric 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});
|
|
846
|
+
if (!part.visible) continue;
|
|
847
|
+
if (!part.fit||part.linePositions?.length!==part.fit.sourceLines.length) throw new OPFRenderError('layout-overflow','Metric content has no accepted internal line positions; increase its cell size or coordinate package versions.',{path:part.path,issues:layout.diagnostics});
|
|
848
|
+
const lines=part.fit.sourceLines.map((line,index)=>{
|
|
849
|
+
const origin=part.linePositions[index];
|
|
850
|
+
return tag('text',{x:stableNumber(origin.x),y:stableNumber(origin.baseline),'text-anchor':'start',
|
|
851
|
+
'font-family':fontStack(part.style.fontFamily,bound.design.fontScheme.type),'font-size':stableNumber(part.fit.fontSize),
|
|
852
|
+
'font-weight':part.style.fontWeight,'font-style':part.style.italic?'italic':undefined,
|
|
853
|
+
'xml:space':'preserve',style:'white-space:pre','text-rendering':'geometricPrecision',fill:part.role==='value'?bound.design.colors.primary:bound.design.colors.text,
|
|
854
|
+
...traceAttrs(options,part.path),...(options.trace?{'data-opf-metric-role':part.role,'data-opf-text-start':line.start,
|
|
855
|
+
'data-opf-text-end':line.end,'data-opf-text-next-start':line.nextStart,'data-opf-line-boundary':line.boundary}:{}),
|
|
856
|
+
},line.segments.map(segment=>tag('tspan',{x:stableNumber(origin.x+segment.x),
|
|
857
|
+
...(segment.kind==='tab'?{textLength:stableNumber(segment.width),lengthAdjust:'spacingAndGlyphs'}:{}),
|
|
858
|
+
...(options.trace?{'data-opf-segment':segment.kind,'data-opf-text-start':segment.start,'data-opf-text-end':segment.end}:{}),
|
|
859
|
+
},escapeText(part.text.slice(segment.start,segment.end)))).join(''));
|
|
860
|
+
});
|
|
861
|
+
children.push(tag('g',{...traceAttrs(options,part.path),...(options.trace?{'data-opf-metric-role':part.role,
|
|
862
|
+
'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}:{}),
|
|
863
|
+
...(part.fit.overflow?{'data-opf-overflow':'true'}:{})},lines.join('\n')));
|
|
864
|
+
}
|
|
865
|
+
return tag('g',{...traceAttrs(options,item.path),...(options.trace?{'data-opf-metric-container':'true'}:{})},children.join('\n'));
|
|
832
866
|
}
|
|
833
867
|
|
|
834
868
|
function renderQuote(item, box, bound, options) {
|
|
@@ -846,53 +880,19 @@ function renderQuote(item, box, bound, options) {
|
|
|
846
880
|
}
|
|
847
881
|
|
|
848
882
|
function renderTimeline(item, box, bound, options) {
|
|
849
|
-
const
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
const
|
|
853
|
-
const
|
|
854
|
-
const
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
y2: y,
|
|
863
|
-
stroke: bound.design.colors.border,
|
|
864
|
-
"stroke-width": 3,
|
|
865
|
-
...traceAttrs(options, item.path)
|
|
866
|
-
})
|
|
867
|
-
];
|
|
868
|
-
|
|
869
|
-
events.forEach((event, index) => {
|
|
870
|
-
const x = start + index * gap;
|
|
871
|
-
const eventPath = `${item.path}.events.${index}`;
|
|
872
|
-
children.push(tag("circle", {
|
|
873
|
-
cx: x,
|
|
874
|
-
cy: y,
|
|
875
|
-
r: Math.min(9, labelWidth / 5, box.height * 0.04),
|
|
876
|
-
fill: bound.design.colors.primary,
|
|
877
|
-
...traceAttrs(options, eventPath)
|
|
878
|
-
}));
|
|
879
|
-
children.push(renderTextBox([event.when, event.what, event.description].filter(Boolean).join("\n"), {
|
|
880
|
-
x: x - labelWidth / 2,
|
|
881
|
-
y: index % 2 === 0 ? box.y : y + 24,
|
|
882
|
-
width: labelWidth,
|
|
883
|
-
height: box.height * 0.38
|
|
884
|
-
}, bound, {
|
|
885
|
-
path: eventPath,
|
|
886
|
-
fontSize: 16,
|
|
887
|
-
fontFamily: bound.design.fonts.body,
|
|
888
|
-
fontWeight: 500,
|
|
889
|
-
fill: bound.design.colors.text,
|
|
890
|
-
options,
|
|
891
|
-
align: "center"
|
|
892
|
-
}));
|
|
893
|
-
});
|
|
894
|
-
|
|
895
|
-
return tag("g", traceAttrs(options, item.path), children.join("\n"));
|
|
883
|
+
const layout=item.timelineLayout;
|
|
884
|
+
if(!layout)throw new OPFRenderError('missing-timeline-layout','Timeline rendering requires a coordinated core build with shared timeline geometry.',{path:item.path});
|
|
885
|
+
const scale=Math.min(bound.design.dimensions.width,bound.design.dimensions.height)/720;
|
|
886
|
+
const children=[tag('line',{...layout.connector,stroke:bound.design.colors.border,'stroke-width':3*scale,...traceAttrs(options,item.path)})];
|
|
887
|
+
for(const marker of layout.markers)children.push(tag('circle',{cx:marker.x,cy:marker.y,r:marker.radius,fill:bound.design.colors.primary,...traceAttrs(options,marker.path)}));
|
|
888
|
+
for(const part of layout.parts){
|
|
889
|
+
if(!part.fit)throw new OPFRenderError('layout-overflow','Timeline field has no usable space; change the arrangement or paginate events.',{path:part.path,issues:layout.diagnostics});
|
|
890
|
+
children.push(tag('g',options.trace?{'data-opf-timeline-role':part.role}:{},renderTextBox(part.text,part.box,bound,{
|
|
891
|
+
path:part.path,fit:part.fit,textStyle:part.style,fontFamily:part.requestedStyle.fontFamily,align:part.alignment,
|
|
892
|
+
fill:bound.design.colors.text,diagnosticsHandled:true,options,
|
|
893
|
+
})));
|
|
894
|
+
}
|
|
895
|
+
return tag('g',{...traceAttrs(options,item.path),...(options.trace?{'data-opf-timeline-arrangement':layout.arrangement}:{})},children.join('\n'));
|
|
896
896
|
}
|
|
897
897
|
|
|
898
898
|
function renderTable(item, box, bound, options) {
|
|
@@ -903,10 +903,11 @@ function renderTable(item, box, bound, options) {
|
|
|
903
903
|
const defaultEdges = [], explicitEdges = [];
|
|
904
904
|
for (const row of layout.rows) for (const cell of row.cells) {
|
|
905
905
|
const style = cell.style ?? {};
|
|
906
|
+
const fill = style.fill ?? (cell.header ? bound.design.colors.primary : bound.design.colors.surface);
|
|
906
907
|
children.push(tag("rect", {
|
|
907
908
|
x: stableNumber(cell.box.x), y: stableNumber(cell.box.y),
|
|
908
909
|
width: stableNumber(cell.box.width), height: stableNumber(cell.box.height),
|
|
909
|
-
fill
|
|
910
|
+
fill,
|
|
910
911
|
stroke: separateBorders ? undefined : bound.design.colors.border, "stroke-width":separateBorders ? undefined : 1,
|
|
911
912
|
...traceAttrs(options, cell.sourcePath ?? cell.path)
|
|
912
913
|
}));
|
|
@@ -922,7 +923,7 @@ function renderTable(item, box, bound, options) {
|
|
|
922
923
|
children.push((cell.rich ? renderRichTextBox : renderTextBox)(cell.rich ? cell.value : flattenText(cell.value ?? ""), cell.textBox, bound, {
|
|
923
924
|
path:cell.path, fontSize:15, fontFamily:bound.design.fonts.body,
|
|
924
925
|
fontWeight:cell.header ? 700 : 400, textStyle:cell.textStyle, fit:cell.fit,
|
|
925
|
-
fill:style.color ?? (cell.header ? "#FFFFFF" : bound.design.colors.text), align:style.align, options
|
|
926
|
+
fill:style.color ?? textColorForFill(fill, cell.header ? "#FFFFFF" : bound.design.colors.text), align:style.align, options
|
|
926
927
|
}));
|
|
927
928
|
}
|
|
928
929
|
|
|
@@ -967,9 +968,10 @@ function renderImportedChart(item, box, bound, options) {
|
|
|
967
968
|
const chart=item.value, rows=chart.data?.rows??[], columns=chart.data?.columns??[];
|
|
968
969
|
if(!rows.length||columns.length<2)return null;
|
|
969
970
|
const kind=chart.type, circular=['pie','donut','doughnut'].includes(kind);
|
|
970
|
-
const colors=[bound.design.colors.primary,bound.design.colors.secondary,'#9B6BCC','#D98944','#429B85','#CB5D79'];
|
|
971
|
+
const colors=[bound.design.colors.primary,bound.design.colors.secondary,'#9B6BCC','#D98944','#429B85','#CB5D79'].map(color=>chartColorForFill(bound.design.colors.surface,color));
|
|
971
972
|
const children=[];
|
|
972
|
-
const
|
|
973
|
+
const labelColor=textColorForFill(bound.design.colors.surface,bound.design.colors.text);
|
|
974
|
+
const text=(value,rect,path,size=16,align='center')=>renderTextBox(String(value),rect,bound,{path,fontSize:size,fontFamily:bound.design.fonts.body,fontWeight:400,fill:labelColor,options,align,verticalAlign:'middle'});
|
|
973
975
|
const number=value=>typeof value==='number'&&Number.isFinite(value)?value:typeof value==='string'&&value.trim()&&Number.isFinite(Number(value))?Number(value):null;
|
|
974
976
|
const series=columns.slice(1).map((name,j)=>({name,values:rows.map(row=>number(row[j+1]))}));
|
|
975
977
|
children.push(tag('rect',{x:box.x,y:box.y,width:box.width,height:box.height,fill:bound.design.colors.surface,stroke:bound.design.colors.border,...traceAttrs(options,item.path)}));
|
|
@@ -1021,7 +1023,7 @@ function renderImportedChart(item, box, bound, options) {
|
|
|
1021
1023
|
});
|
|
1022
1024
|
}
|
|
1023
1025
|
});
|
|
1024
|
-
children.push(tag('line',{x1:horizontal?zero:plot.x,x2:horizontal?zero:plot.x+plot.width,y1:horizontal?plot.y:zero,y2:horizontal?plot.y+plot.height:zero,stroke:
|
|
1026
|
+
children.push(tag('line',{x1:horizontal?zero:plot.x,x2:horizontal?zero:plot.x+plot.width,y1:horizontal?plot.y:zero,y2:horizontal?plot.y+plot.height:zero,stroke:labelColor,'stroke-width':1}));
|
|
1025
1027
|
}
|
|
1026
1028
|
return tag('g',traceAttrs(options,item.path),children.join('\n'));
|
|
1027
1029
|
}
|
|
@@ -1036,13 +1038,17 @@ function renderChart(item, box, bound, options) {
|
|
|
1036
1038
|
const data = inlineChartRows(chart.data);
|
|
1037
1039
|
const plot = inset(box, 28);
|
|
1038
1040
|
const max = Math.max(1, ...data.map((row) => Math.abs(row.value)));
|
|
1041
|
+
const panelFill = bound.design.colors.surface;
|
|
1042
|
+
const labelColor = textColorForFill(panelFill, bound.design.colors.text);
|
|
1043
|
+
const primary = chartColorForFill(panelFill, bound.design.colors.primary);
|
|
1044
|
+
const secondary = chartColorForFill(panelFill, bound.design.colors.secondary);
|
|
1039
1045
|
const children = [
|
|
1040
1046
|
tag("rect", {
|
|
1041
1047
|
x: box.x,
|
|
1042
1048
|
y: box.y,
|
|
1043
1049
|
width: box.width,
|
|
1044
1050
|
height: box.height,
|
|
1045
|
-
fill:
|
|
1051
|
+
fill: panelFill,
|
|
1046
1052
|
stroke: bound.design.colors.border,
|
|
1047
1053
|
"stroke-width": 1,
|
|
1048
1054
|
...traceAttrs(options, item.path)
|
|
@@ -1055,7 +1061,7 @@ function renderChart(item, box, bound, options) {
|
|
|
1055
1061
|
fontSize: 20,
|
|
1056
1062
|
fontFamily: bound.design.fonts.body,
|
|
1057
1063
|
fontWeight: 500,
|
|
1058
|
-
fill:
|
|
1064
|
+
fill: labelColor,
|
|
1059
1065
|
options,
|
|
1060
1066
|
align: "center",
|
|
1061
1067
|
verticalAlign: "middle"
|
|
@@ -1072,7 +1078,7 @@ function renderChart(item, box, bound, options) {
|
|
|
1072
1078
|
children.push(tag("polyline", {
|
|
1073
1079
|
points: points.map(([x, y]) => `${stableNumber(x)},${stableNumber(y)}`).join(" "),
|
|
1074
1080
|
fill: "none",
|
|
1075
|
-
stroke:
|
|
1081
|
+
stroke: primary,
|
|
1076
1082
|
"stroke-width": 4,
|
|
1077
1083
|
...traceAttrs(options, `${item.path}.data`)
|
|
1078
1084
|
}));
|
|
@@ -1080,7 +1086,7 @@ function renderChart(item, box, bound, options) {
|
|
|
1080
1086
|
cx: stableNumber(x),
|
|
1081
1087
|
cy: stableNumber(y),
|
|
1082
1088
|
r: 5,
|
|
1083
|
-
fill:
|
|
1089
|
+
fill: primary,
|
|
1084
1090
|
...traceAttrs(options, `${item.path}.data.rows.${index}`)
|
|
1085
1091
|
})));
|
|
1086
1092
|
} else {
|
|
@@ -1095,7 +1101,7 @@ function renderChart(item, box, bound, options) {
|
|
|
1095
1101
|
y: stableNumber(y),
|
|
1096
1102
|
width: stableNumber(barWidth),
|
|
1097
1103
|
height: stableNumber(barHeight),
|
|
1098
|
-
fill: index % 2 === 0 ?
|
|
1104
|
+
fill: index % 2 === 0 ? primary : secondary,
|
|
1099
1105
|
...traceAttrs(options, `${item.path}.data.rows.${index}`)
|
|
1100
1106
|
}));
|
|
1101
1107
|
});
|
|
@@ -1111,7 +1117,7 @@ function renderChart(item, box, bound, options) {
|
|
|
1111
1117
|
fontSize: 12,
|
|
1112
1118
|
fontFamily: bound.design.fonts.body,
|
|
1113
1119
|
fontWeight: 400,
|
|
1114
|
-
fill:
|
|
1120
|
+
fill: labelColor,
|
|
1115
1121
|
options,
|
|
1116
1122
|
align: "center"
|
|
1117
1123
|
}));
|
|
@@ -1141,7 +1147,7 @@ function renderFurniture(bound, presentation, width, height, options, kind) {
|
|
|
1141
1147
|
return tag('g',traceAttrs(options,root),['left','center','right'].map((zone,index)=>{
|
|
1142
1148
|
const item=furniture[zone];if(!item)return '';
|
|
1143
1149
|
const box={x:width*(.07+index*.3),y:kind==='header'?height*.025:height*.925,width:width*.26,height:height*.05};
|
|
1144
|
-
if(item.image)return renderImage({value:item.image,path:`${root}.${zone}.image`},box,bound,options);
|
|
1150
|
+
if(item.image)return renderImage({value:item.image,path:`${root}.${zone}.image`},box,bound,{...options,imageFit:'contain'});
|
|
1145
1151
|
const pieces=[item.text,item.organization?organization?.name:null,item.section?bound.slide.section:null,item.slideNumber?String(bound.index+1):null,typeof item.date==='string'?item.date:null].filter(value=>value!==undefined&&value!==null&&value!=='');
|
|
1146
1152
|
if(item.date===true)reportDiagnostic({code:'date-needs-value',path:`${root}.${zone}.date`,message:'Use a literal date string for a reproducible preview; the document does not define a presentation date.'},options);
|
|
1147
1153
|
const anchor=['start','middle','end'][index],x=index===0?width*.07:index===1?width/2:width*.93;
|
|
@@ -1152,7 +1158,7 @@ function renderBranding(bound,presentation,width,height,options) {
|
|
|
1152
1158
|
const design={...presentation.design,...bound.slide.design}, pieces=[];
|
|
1153
1159
|
const rootFor=key=>bound.slide.design?.[key]!==undefined?`${bound.path}.design.${key}`:`design.${key}`;
|
|
1154
1160
|
if(design.watermark){
|
|
1155
|
-
pieces.push(tag('g',{opacity:typeof design.watermark==='object'?design.watermark.opacity??.08:.08},renderImage({value:design.watermark,path:rootFor('watermark')},{x:width*.3,y:height*.3,width:width*.4,height:height*.4},bound,options)));
|
|
1161
|
+
pieces.push(tag('g',{opacity:typeof design.watermark==='object'?design.watermark.opacity??.08:.08},renderImage({value:design.watermark,path:rootFor('watermark')},{x:width*.3,y:height*.3,width:width*.4,height:height*.4},bound,{...options,imageFit:'contain'})));
|
|
1156
1162
|
}
|
|
1157
1163
|
return pieces.join('');
|
|
1158
1164
|
}
|
|
@@ -1175,32 +1181,50 @@ function renderEmbeddedFonts(fonts = []) {
|
|
|
1175
1181
|
function renderRichTextBox(value, box, bound, config) {
|
|
1176
1182
|
const scale=Math.min(bound.design.dimensions.width,bound.design.dimensions.height)/720;
|
|
1177
1183
|
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});
|
|
1178
|
-
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]});}
|
|
1184
|
+
if(fit.overflow&&!config.diagnosticsHandled){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]});}
|
|
1179
1185
|
return renderRichLines(value,fit,box,bound,config);
|
|
1180
1186
|
}
|
|
1181
1187
|
|
|
1182
1188
|
function renderRichLines(value,fit,box,bound,config) {
|
|
1183
|
-
const alignment=config.align??bound.design.contentAlignment;
|
|
1189
|
+
const alignment=fit.placement?.alignment??config.align??bound.design.contentAlignment;
|
|
1184
1190
|
let textOffset=0;
|
|
1185
1191
|
const runOffsets=value.map(run=>{const start=textOffset;textOffset+=(typeof run==='string'?run:run.text).length;return start;});
|
|
1186
|
-
|
|
1192
|
+
// With no measurement provider, fragment advances are estimates. Let SVG
|
|
1193
|
+
// shape adjacent runs naturally inside each estimated line instead of turning
|
|
1194
|
+
// those estimates into visible gaps. Supplied measurements keep exact origins.
|
|
1195
|
+
const flow=!config.options.textMeasurement?.measure&&!fit.placement;
|
|
1196
|
+
const content=fit.richLines.map((line,lineIndex)=>{
|
|
1197
|
+
const fragments=line.fragments.map(fragment=>{
|
|
1187
1198
|
const run=fragment.run,offset=alignment==='right'?box.width-line.width:alignment==='center'?(box.width-line.width)/2:0;
|
|
1188
|
-
const
|
|
1199
|
+
const placed=fit.placement?.lines[lineIndex];
|
|
1200
|
+
// Accepted outline placement owns the horizontal advance. Geometric precision
|
|
1201
|
+
// avoids hinted browser advances; textLength also removes fractional-size
|
|
1202
|
+
// quantization drift. Height and baseline retain the selected font size.
|
|
1203
|
+
const position=flow?{'baseline-shift':fragment.baselineShift?stableNumber(-fragment.baselineShift):undefined}:{x:stableNumber((placed?.x??box.x+offset)+fragment.x),y:stableNumber((placed?.baseline??box.y+line.baseline)+fragment.baselineShift)};
|
|
1204
|
+
const rendered=tag(flow?'tspan':'text',{...(config.options.trace?{'data-opf-text-start':runOffsets[fragment.runIndex]+fragment.start,'data-opf-text-end':runOffsets[fragment.runIndex]+fragment.end}:{}),...position,'xml:space':'preserve','text-rendering':flow?undefined:'geometricPrecision',textLength:placed&&fragment.width>0?stableNumber(fragment.width):undefined,lengthAdjust:placed&&fragment.width>0?'spacingAndGlyphs':undefined,'font-family':fontStack(fragment.style.fontFamily,bound.design.fontScheme.type),'font-size':stableNumber(fragment.fontSize),'font-weight':fragment.style.fontWeight,'font-style':fragment.style.italic?'italic':flow?'normal':undefined,'text-decoration':[run.underline?'underline':'',run.strikethrough?'line-through':''].filter(Boolean).join(' ')||undefined,fill:/^#(?:[0-9a-f]{3}|[0-9a-f]{6}|[0-9a-f]{8})$/i.test(run.color??'')?run.color:config.fill},escapeText(fragment.text));
|
|
1189
1205
|
if(run.link&&/^(https?:|mailto:)/i.test(run.link))return tag('a',{href:run.link,target:'_blank',rel:'noopener noreferrer'},rendered);
|
|
1190
1206
|
return rendered;
|
|
1191
|
-
|
|
1207
|
+
});
|
|
1208
|
+
if(!flow)return fragments.join('\n');
|
|
1209
|
+
const x=alignment==='right'?box.x+box.width:alignment==='center'?box.x+box.width/2:box.x;
|
|
1210
|
+
const first=line.fragments[0];
|
|
1211
|
+
return tag('text',{x:stableNumber(x),y:stableNumber(box.y+line.baseline),'text-anchor':alignment==='right'?'end':alignment==='center'?'middle':'start','xml:space':'preserve','font-family':fontStack(first?.style.fontFamily??config.fontFamily,bound.design.fontScheme.type),'font-size':stableNumber(first?.fontSize??fit.fontSize),'font-weight':first?.style.fontWeight??config.fontWeight??400,'font-style':first?.style.italic?'italic':undefined},fragments.join(''));
|
|
1212
|
+
});
|
|
1192
1213
|
let cursor=0;
|
|
1193
1214
|
const whole=value.map(run=>typeof run==='string'?run:run.text).join('');
|
|
1194
1215
|
const lineTrace=config.options.trace?fit.richLines.map((line,index)=>{
|
|
1195
1216
|
if(index){const newline=/^(\r\n|\r|\n)/.exec(whole.slice(cursor));if(newline)cursor+=newline[0].length;}
|
|
1196
1217
|
const start=cursor;cursor+=(fit.lines[index]??'').length;
|
|
1197
1218
|
const offset=alignment==='right'?box.width-line.width:alignment==='center'?(box.width-line.width)/2:0;
|
|
1198
|
-
|
|
1219
|
+
const placed=fit.placement?.lines[index];
|
|
1220
|
+
return {start,end:cursor,x:placed?.x??box.x+offset,y:placed?.y??box.y+line.y,height:placed?.height??line.height};
|
|
1199
1221
|
}):undefined;
|
|
1200
|
-
return tag('g',{...traceAttrs(config.options,config.path),...(config.options.trace?{'data-opf-box-width':box.width,'data-opf-rich-text':config.rich===false?undefined:'true','data-opf-rich-lines':JSON.stringify(lineTrace)}:{}),...(fit.overflow?{'data-opf-overflow':'true'}:{})},content.join('\n'));
|
|
1222
|
+
return tag('g',{...traceAttrs(config.options,config.path),...(config.options.trace?{'data-opf-box-width':box.width,'data-opf-rich-text':config.rich===false?undefined:'true','data-opf-rich-lines':JSON.stringify(lineTrace),'data-opf-rich-spacing':flow?'natural':'measured'}:{}),...(fit.overflow?{'data-opf-overflow':'true'}:{})},content.join('\n'));
|
|
1201
1223
|
}
|
|
1202
1224
|
|
|
1203
1225
|
function renderTextBox(text, box, bound, config) {
|
|
1226
|
+
const invalid=/[\u0000-\u0008\u000B\u000C\u000E-\u001F\uD800-\uDFFF\uFFFE\uFFFF]/u.exec(String(text??''));
|
|
1227
|
+
if(invalid)throw new OPFRenderError('invalid-text',`Text contains U+${invalid[0].codePointAt(0).toString(16).toUpperCase().padStart(4,'0')} at UTF-16 offset ${invalid.index}, which SVG XML cannot represent.`,{path:config.path});
|
|
1204
1228
|
const scale = Math.min(bound.design.dimensions.width, bound.design.dimensions.height) / 720;
|
|
1205
1229
|
const style = config.textStyle ?? resolveTextStyle({fontFamily:config.fontFamily,fontWeight:config.fontWeight ?? 400,italic:config.italic ?? false,path:config.path},config.options.textMeasurement);
|
|
1206
1230
|
const fit = config.fit ?? fitText(String(text ?? ""), box, config.fontSize * scale,
|
|
@@ -1215,16 +1239,30 @@ function renderTextBox(text, box, bound, config) {
|
|
|
1215
1239
|
const totalHeight = fit.lines.length * fit.lineHeight;
|
|
1216
1240
|
const startY = config.verticalAlign === "middle"
|
|
1217
1241
|
? box.y + Math.max(0, (box.height - totalHeight) / 2) + size : box.y + size;
|
|
1218
|
-
const alignment=config.align??bound.design.contentAlignment;
|
|
1242
|
+
const alignment=fit.placement?.alignment??config.align??bound.design.contentAlignment;
|
|
1219
1243
|
const anchor = alignment === "center" ? "middle" : alignment === "right" ? "end" : "start";
|
|
1220
1244
|
const x = alignment === "center" ? box.x + box.width / 2 : alignment === "right" ? box.x + box.width : box.x;
|
|
1221
|
-
const lines = fit.lines.map((line, index) =>
|
|
1222
|
-
|
|
1223
|
-
|
|
1245
|
+
const lines = fit.lines.map((line, index) => {
|
|
1246
|
+
const sourceLine=fit.sourceLines?.[index],placed=fit.placement?.lines[index],factor=alignment==='right'?1:alignment==='center'?.5:0;
|
|
1247
|
+
const origin=placed?.x??x-(sourceLine?.width??0)*factor;
|
|
1248
|
+
const tabs=sourceLine?.segments.some(segment=>segment.kind==='tab');
|
|
1249
|
+
const content=tabs?sourceLine.segments.map(segment=>tag('tspan',{
|
|
1250
|
+
x:stableNumber(origin+segment.x),textLength:segment.kind==='tab'||placed?stableNumber(segment.width):undefined,
|
|
1251
|
+
lengthAdjust:segment.kind==='tab'||placed?'spacingAndGlyphs':undefined,
|
|
1252
|
+
...(config.options.trace?{'data-opf-source-start':segment.start,'data-opf-source-end':segment.end,'data-opf-segment':segment.kind}:{}),
|
|
1253
|
+
},escapeText(line.slice(segment.start-sourceLine.start,segment.end-sourceLine.start)))).join(''):escapeText(line);
|
|
1254
|
+
return tag("text", {
|
|
1255
|
+
x: stableNumber(tabs?origin:placed?placed.x+placed.width*factor:x), y: stableNumber(placed?.baseline??startY + index * fit.lineHeight),
|
|
1256
|
+
"text-anchor": tabs?'start':anchor, "font-family": fontStack(style.fontFamily, config.fontFamily === bound.design.fonts.code ? "monospace" : bound.design.fontScheme.type),
|
|
1224
1257
|
"font-size": stableNumber(size), "font-weight": style.fontWeight, "font-style": style.italic ? "italic" : undefined, fill: config.fill,
|
|
1225
|
-
|
|
1226
|
-
|
|
1258
|
+
'xml:space':'preserve',style:'white-space:pre','text-rendering':config.options.textMeasurement?.measure?'geometricPrecision':undefined,
|
|
1259
|
+
textLength:!tabs&&placed?.width>0?stableNumber(placed.width):undefined,lengthAdjust:!tabs&&placed?.width>0?'spacingAndGlyphs':undefined,
|
|
1260
|
+
...traceAttrs(config.options, config.path),
|
|
1261
|
+
...(config.options.trace&&sourceLine?{'data-opf-source-start':sourceLine.start,'data-opf-source-end':sourceLine.end,'data-opf-source-next-start':sourceLine.nextStart,'data-opf-line-boundary':sourceLine.boundary}:{}),
|
|
1262
|
+
}, content);
|
|
1263
|
+
});
|
|
1227
1264
|
return tag("g", { ...traceAttrs(config.options, config.path),
|
|
1265
|
+
...(config.options.trace&&fit.sourceLines?{'data-opf-source-text':'true','data-opf-box-x':box.x,'data-opf-box-y':box.y,'data-opf-box-width':box.width,'data-opf-box-height':box.height}:{}),
|
|
1228
1266
|
...(fit.overflow ? { "data-opf-overflow": "true" } : {}) }, lines.join("\n"));
|
|
1229
1267
|
}
|
|
1230
1268
|
|
package/package.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@openpresentation/opf-render",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.0",
|
|
4
4
|
"description": "Deterministic local OPF renderer for SVG, PNG, and PDF output.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"private": false,
|
|
8
8
|
"engines": {
|
|
9
|
-
"node": "
|
|
9
|
+
"node": "24.x"
|
|
10
10
|
},
|
|
11
11
|
"repository": {
|
|
12
12
|
"type": "git",
|
|
@@ -55,16 +55,29 @@
|
|
|
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 && node --check test/rich-table.mjs && node --check test/styled-table.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 && node --check test/rich-table.mjs && node --check test/styled-table.mjs && node --check test/chart-colors.mjs && node --check test/image-placeholders.mjs && node --check test/image-placeholders-browser.mjs && node --check test/accepted-text.mjs && node --check test/accepted-text-fixtures.mjs && node --check test/accepted-text-browser.mjs && node --check test/rich-spacing-browser.mjs && node --check test/rich-flow-browser.mjs && node --check test/rich-flow-review-sheets.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/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",
|
|
60
|
+
"test": "npm run build && node test/font-outlines.mjs && node test/accepted-text.mjs && node test/image-placeholders.mjs && node test/chart-colors.mjs && 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
62
|
"test:packed": "node test/packed-install.mjs",
|
|
63
63
|
"test:browser": "npm run build:browser-check && node test/browser-check.mjs",
|
|
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",
|
|
70
|
+
"test:metric": "node test/shared-metric.mjs",
|
|
71
|
+
"test:images-browser": "node test/image-placeholders-browser.mjs",
|
|
72
|
+
"test:font-preparation": "node test/font-preparation.mjs artifacts/font-preparation.json",
|
|
73
|
+
"test:font-preparation-browser": "node test/font-preparation-browser.mjs artifacts/font-preparation-browser.json",
|
|
74
|
+
"test:font-variants": "node test/font-variants.mjs",
|
|
75
|
+
"test:font-variants-browser": "node test/font-variants-browser.mjs",
|
|
76
|
+
"test:text": "node test/font-outlines.mjs && node test/accepted-text.mjs",
|
|
77
|
+
"test:text-browser": "node test/accepted-text-browser.mjs",
|
|
78
|
+
"test:rich-spacing-browser": "node test/rich-spacing-browser.mjs",
|
|
79
|
+
"test:rich-flow-browser": "node test/rich-flow-browser.mjs",
|
|
80
|
+
"test:plain-whitespace-browser": "node test/plain-whitespace-browser.mjs"
|
|
68
81
|
},
|
|
69
82
|
"dependencies": {
|
|
70
83
|
"@expo-google-fonts/arimo": "0.4.3",
|
|
@@ -72,10 +85,10 @@
|
|
|
72
85
|
"@expo-google-fonts/carlito": "0.4.1",
|
|
73
86
|
"@expo-google-fonts/cousine": "0.4.3",
|
|
74
87
|
"@expo-google-fonts/gelasio": "0.4.1",
|
|
75
|
-
"@expo-google-fonts/roboto": "
|
|
76
|
-
"@expo-google-fonts/roboto-mono": "
|
|
88
|
+
"@expo-google-fonts/roboto": "0.4.3",
|
|
89
|
+
"@expo-google-fonts/roboto-mono": "0.4.2",
|
|
77
90
|
"@expo-google-fonts/tinos": "0.4.2",
|
|
78
|
-
"@openpresentation/opf": "^0.
|
|
91
|
+
"@openpresentation/opf": "^0.10.0",
|
|
79
92
|
"@resvg/resvg-js": "^2.6.2",
|
|
80
93
|
"fontkit": "^2.0.4",
|
|
81
94
|
"pdf-lib": "^1.17.1",
|