@openpresentation/opf-pptx 0.7.0 → 0.8.1

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.
@@ -0,0 +1,94 @@
1
+ import {XMLParser} from 'fast-xml-parser';
2
+ import {validatePresentation} from '@openpresentation/opf';
3
+ import {attachTextTags,decodeTextTag} from './code-provenance.js';
4
+
5
+ const TAG='OPF_METRIC_V1',REL='http://schemas.openxmlformats.org/officeDocument/2006/relationships/tags';
6
+ const parser=new XMLParser({ignoreAttributes:false,attributeNamePrefix:'',parseTagValue:false,trimValues:false});
7
+ const decoder=new TextDecoder('utf-8',{fatal:true});
8
+ const array=value=>value===undefined?[]:Array.isArray(value)?value:[value];
9
+ const scalar=value=>typeof value==='string'||typeof value==='number';
10
+ const sourceFor=(value,role)=>scalar(value)?value:value[role];
11
+
12
+ // The first value line carries the complete manifest: no invisible panel is introduced.
13
+ export function metricManifest(value,layout,group) {
14
+ return {v:1,group,role:'anchor',part:0,line:0,value,parts:layout.parts.map(part=>({role:part.role,visible:part.visible,
15
+ lines:part.fit?.sourceLines.map(({start,end,nextStart,boundary})=>({start,end,nextStart,boundary}))??[]}))};
16
+ }
17
+ export function attachMetricTags(entries,records) {return attachTextTags(entries,records,TAG,'opfMetric','metric');}
18
+
19
+ function validateManifest(manifest) {
20
+ if (!validatePresentation({slides:[{metric:manifest.value}]}).valid) throw new Error('Invalid metric source.');
21
+ const roles=scalar(manifest.value)?['value']:['value','unit','label','description','delta','trend'].filter(role=>manifest.value[role]!==undefined);
22
+ if (!Array.isArray(manifest.parts)||manifest.parts.length!==roles.length||manifest.part!==0||manifest.line!==0) throw new Error('Invalid metric parts.');
23
+ for (const [index,part] of manifest.parts.entries()) {
24
+ const source=String(sourceFor(manifest.value,roles[index])),visible=roles[index]==='value'||source.length>0;
25
+ if (part.role!==roles[index]||part.visible!==visible||!Array.isArray(part.lines)||visible&&!part.lines.length||!visible&&part.lines.length) throw new Error('Invalid source part.');
26
+ let cursor=0;
27
+ for (const [i,line] of part.lines.entries()) {
28
+ if (![line.start,line.end,line.nextStart].every(Number.isSafeInteger)||line.start!==cursor||line.end<line.start||line.nextStart<line.end||line.nextStart>source.length) throw new Error('Invalid source range.');
29
+ const separator=source.slice(line.end,line.nextStart),last=i===part.lines.length-1;
30
+ if (/\r|\n/.test(source.slice(line.start,line.end))||(line.boundary==='hard'?!/^(?:\r\n|\r|\n)$/.test(separator)||last:separator!==''||line.boundary!==(last?'end':'soft'))) throw new Error('Invalid source boundary.');
31
+ cursor=line.nextStart;
32
+ }
33
+ if (cursor!==source.length) throw new Error('Incomplete source range.');
34
+ }
35
+ }
36
+
37
+ export function importMetricGroups(shapes,paragraphs,relationships,entries,report) {
38
+ const groups=new Map(),tagCounts=new Map(),consumed=new Set(),items=[];
39
+ for (const [index,shape] of shapes.entries()) {
40
+ for (const link of array(shape['p:nvSpPr']?.['p:nvPr']?.['p:custDataLst']?.['p:tags'])) {
41
+ const rel=relationships.get(link['r:id']);
42
+ if (rel?.type!==REL||rel.targetMode==='External'||!rel.path||!entries[rel.path]) continue;
43
+ let tags;
44
+ try {
45
+ const all=array(parser.parse(decoder.decode(entries[rel.path]))['p:tagLst']?.['p:tag']);
46
+ tagCounts.set(shape,(tagCounts.get(shape)??0)+all.filter(tag=>/^OPF_/i.test(tag.name)).length);
47
+ tags=all.filter(tag=>tag.name?.toUpperCase()===TAG);
48
+ } catch {report({code:'invalid-metric-provenance',message:'Unreadable metric tags; visible native shapes were retained.'});continue;}
49
+ for (const tag of tags) try {
50
+ const data=decodeTextTag(tag.val);
51
+ if (data.v!==1||typeof data.group!=='string'||!/^\d+$/.test(data.group)) throw new Error('Invalid metric identity.');
52
+ const group=groups.get(data.group)??[];
53
+ group.push({data,shape,text:(paragraphs[index]??[]).map(p=>p.text).join('\n')});groups.set(data.group,group);
54
+ } catch {report({code:'invalid-metric-provenance',message:'Invalid metric tags; visible native shapes were retained.'});}
55
+ }
56
+ }
57
+ for (const group of groups.values()) try {
58
+ if (group.some(item=>tagCounts.get(item.shape)!==1)) throw new Error('Multiple source identities on one shape.');
59
+ const anchors=group.filter(item=>item.data.role==='anchor');
60
+ if (anchors.length!==1) throw new Error('Missing or duplicated metric anchor.');
61
+ const anchor=anchors[0],manifest=anchor.data;validateManifest(manifest);
62
+ const expected=manifest.parts.reduce((sum,part)=>sum+part.lines.length,0);
63
+ if (group.length!==expected||new Set(group.map(item=>item.shape)).size!==expected) throw new Error('Incomplete or duplicated metric shapes.');
64
+ const nativeLines=new Map();
65
+ for (const item of group) {
66
+ const {role,part,line}=item.data,key=`${part}:${line}`;
67
+ if ((item!==anchor&&role!=='line')||!Number.isSafeInteger(part)||!Number.isSafeInteger(line)||!manifest.parts[part]?.lines[line]||nativeLines.has(key)) throw new Error('Ambiguous metric source line.');
68
+ nativeLines.set(key,item.text);
69
+ }
70
+ const value=scalar(manifest.value)?{value:manifest.value}:{...manifest.value},typeChanges=[];
71
+ for (const [partIndex,part] of manifest.parts.entries()) {
72
+ if (!part.visible) continue;
73
+ const original=sourceFor(manifest.value,part.role),source=String(original),newline=source.match(/\r\n|\r|\n/)?.[0]??'\n';
74
+ let rebuilt='';
75
+ for (const [lineIndex,line] of part.lines.entries()) {
76
+ const actual=nativeLines.get(`${partIndex}:${lineIndex}`);
77
+ if (actual===undefined) throw new Error('Missing metric source line.');
78
+ rebuilt+=actual.replace(/\r\n|\r|\n/g,newline)+source.slice(line.end,line.nextStart);
79
+ }
80
+ // Preserve original types for unchanged text. Canonical finite numeric edits remain
81
+ // numeric; other spellings become strings so no whitespace or precision is discarded.
82
+ value[part.role]=typeof original==='number'&&rebuilt===source?original:
83
+ typeof original==='number'&&Number.isFinite(Number(rebuilt))&&String(Number(rebuilt))===rebuilt?Number(rebuilt):rebuilt;
84
+ if (typeof original!==typeof value[part.role]) typeChanges.push(part.role);
85
+ }
86
+ const metric=scalar(manifest.value)?value.value:value;
87
+ if (!validatePresentation({slides:[{metric}]}).valid) throw new Error('Edited native text cannot form a valid metric.');
88
+ for (const item of group) consumed.add(item.shape);
89
+ items.push({shape:anchor.shape,payload:{type:'metric',metric}});
90
+ if (typeChanges.length) report({code:'metric-value-type-changed',message:`Edited numeric ${typeChanges.join(', ')} retained as literal text to avoid discarding spelling or precision.`});
91
+ report({code:'metric-import-reflow',message:'Metric fields, source boundaries and native text were recovered. Native formatting, positioning, alignment and font theme are not reconstructed; review the reflowed OPF.'});
92
+ } catch (error) {report({code:'invalid-metric-provenance',message:`${error.message} Visible native shapes were retained without restoring old source text.`});}
93
+ return {consumed,items};
94
+ }
@@ -0,0 +1,54 @@
1
+ import {XMLParser} from 'fast-xml-parser';
2
+ import {attachTextTags,decodeTextTag} from './code-provenance.js';
3
+ const TAG='OPF_TEXT_V1',REL='http://schemas.openxmlformats.org/officeDocument/2006/relationships/tags';
4
+ const parser=new XMLParser({ignoreAttributes:false,attributeNamePrefix:'',parseTagValue:false,trimValues:false});
5
+ const decoder=new TextDecoder('utf-8',{fatal:true}),array=value=>value===undefined?[]:Array.isArray(value)?value:[value];
6
+ export const attachPlainTextTags=(entries,records)=>attachTextTags(entries,records,TAG,'opfText','plain text');
7
+
8
+ // Only boundaries are stored. Current native characters, including edits and
9
+ // empty lines, always supply the text; tags cannot restore discarded old words.
10
+ export function sourceLineParagraphs(ordered,paragraphs,{legacy=false}={}) {
11
+ const source=ordered.some(item=>item.data.boundary!==undefined||item.data.separator!==undefined);
12
+ if(!source&&legacy)return ordered.flatMap(item=>paragraphs[item.index]??[]);
13
+ let text='',maxFontSize=0;
14
+ for(const [index,item]of ordered.entries()) {
15
+ const {boundary,separator}=item.data,last=index===ordered.length-1;
16
+ if(typeof separator!=='string'||(boundary==='hard'?!/^(\r\n|\r|\n)$/.test(separator)||last:separator!==''||boundary!==(last?'end':'soft')))throw Error('Invalid source line boundary.');
17
+ const current=paragraphs[item.index]??[];
18
+ if(current.some(p=>p.bullet))throw Error('Tagged plain text now has native bullets.');
19
+ text+=current.map(p=>p.text).join('\n')+separator;
20
+ maxFontSize=Math.max(maxFontSize,...current.map(p=>p.maxFontSize??0));
21
+ }
22
+ return [{text,maxFontSize,bullet:false,level:0}];
23
+ }
24
+
25
+ export function importPlainTextGroups(shapes,paragraphs,relationships,entries,report) {
26
+ const groups=new Map(),consumed=new Set(),items=[];
27
+ for(const [index,shape]of shapes.entries()) {
28
+ const tags=[];let unreadable=false;
29
+ for(const link of array(shape['p:nvSpPr']?.['p:nvPr']?.['p:custDataLst']?.['p:tags'])) {
30
+ const rel=relationships.get(link['r:id']);if(rel?.type!==REL||rel.targetMode==='External'||!entries[rel.path])continue;
31
+ try{tags.push(...array(parser.parse(decoder.decode(entries[rel.path]))['p:tagLst']?.['p:tag']));}catch{unreadable=true;}
32
+ }
33
+ for(const tag of tags.filter(tag=>tag.name?.toUpperCase()===TAG))try {
34
+ const data=decodeTextTag(tag.val);
35
+ if(data?.v!==1||typeof data.group!=='string'||data.group.length>4096||!/^slides\.\d+(?:\.[A-Za-z0-9_:+-]+)*\.text$/.test(data.group))throw Error('Invalid plain text identity.');
36
+ const group=groups.get(data.group)??[];group.push({shape,index,data,ambiguous:unreadable||tags.filter(tag=>/^OPF_/i.test(tag.name)).length!==1});groups.set(data.group,group);
37
+ }catch{report({code:'invalid-text-provenance',message:'Invalid plain text tags; ordinary import retains current native text.'});}
38
+ }
39
+ for(const group of groups.values())try {
40
+ const count=group[0].data.count;
41
+ if(!Number.isSafeInteger(count)||count<1||count>10000||group.length!==count||new Set(group.map(item=>item.shape)).size!==count)throw Error('Incomplete or duplicated plain text lines.');
42
+ const ordered=Array(count);
43
+ for(const item of group) {
44
+ const data=item.data;
45
+ if(item.ambiguous||data.count!==count||!Number.isSafeInteger(data.line)||data.line<0||data.line>=count||ordered[data.line])throw Error('Ambiguous plain text line.');
46
+ ordered[data.line]=item;
47
+ }
48
+ const restored=sourceLineParagraphs(ordered,paragraphs);
49
+ for(const item of ordered)consumed.add(item.shape);
50
+ items.push({shapes:ordered.map(item=>item.shape),paragraphs:restored});
51
+ report({code:'text-import-reflow',message:'Complete tagged lines retain current native text, whitespace and authored line boundaries. Native formatting, geometry and OPF nesting are not reconstructed; review the reflowed text.'});
52
+ }catch(error){report({code:'invalid-text-provenance',message:`${error.message} Ordinary import retains current native text without restoring old source words.`});}
53
+ return {consumed,items};
54
+ }
@@ -0,0 +1,81 @@
1
+ import {XMLParser} from 'fast-xml-parser';
2
+ import {validatePresentation} from '@openpresentation/opf';
3
+ import {attachTextTags,decodeTextTag} from './code-provenance.js';
4
+ import {sourceLineParagraphs} from './text-provenance.js';
5
+
6
+ const TAG='OPF_TIMELINE_V1',REL='http://schemas.openxmlformats.org/officeDocument/2006/relationships/tags';
7
+ const parser=new XMLParser({ignoreAttributes:false,attributeNamePrefix:'',parseTagValue:false,trimValues:false});
8
+ const decoder=new TextDecoder('utf-8',{fatal:true}),array=value=>value===undefined?[]:Array.isArray(value)?value:[value];
9
+ export const attachTimelineTags=(entries,records)=>attachTextTags(entries,records,TAG,'opfTimeline','timeline');
10
+
11
+ // Only topology and line boundaries are retained. Every field value comes from
12
+ // current native text; clearing or editing a field cannot restore old words.
13
+ export function timelineManifest(value,layout) {
14
+ return {shorthand:Array.isArray(value),eventCount:(Array.isArray(value)?value:value.events).length,
15
+ parts:layout.parts.map(part=>({eventIndex:part.eventIndex,field:part.role==='event-description'?'description':part.role,lines:part.fit.sourceLines.length}))};
16
+ }
17
+ function validateManifest(manifest) {
18
+ if(!manifest||typeof manifest.shorthand!=='boolean'||!Number.isSafeInteger(manifest.eventCount)||manifest.eventCount<1||manifest.eventCount>10000||!Array.isArray(manifest.parts)||manifest.parts.length>10000)throw Error('Invalid timeline topology.');
19
+ const keys=[],fields=new Set();let count=0;
20
+ for(const part of manifest.parts){
21
+ if(!part||!Number.isSafeInteger(part.lines)||part.lines<1||part.lines>10000)throw Error('Invalid timeline line count.');
22
+ if(part.eventIndex===undefined){if(manifest.shorthand||!['name','description'].includes(part.field))throw Error('Invalid timeline metadata field.');}
23
+ else if(!Number.isSafeInteger(part.eventIndex)||part.eventIndex<0||part.eventIndex>=manifest.eventCount||!['when','what','description'].includes(part.field))throw Error('Invalid timeline event field.');
24
+ const key=`${part.eventIndex??'metadata'}:${part.field}`;if(fields.has(key))throw Error('Duplicated timeline field.');fields.add(key);keys.push(key);count+=part.lines;
25
+ }
26
+ if(count>10000)throw Error('Too many timeline source lines.');
27
+ const expected=['name','description'].map(field=>`metadata:${field}`).filter(key=>fields.has(key));
28
+ for(let index=0;index<manifest.eventCount;index++){
29
+ if(!fields.has(`${index}:what`))throw Error('Missing timeline event label.');
30
+ for(const field of ['when','what','description'])if(fields.has(`${index}:${field}`))expected.push(`${index}:${field}`);
31
+ }
32
+ if(JSON.stringify(keys)!==JSON.stringify(expected))throw Error('Invalid timeline source order.');
33
+ return count;
34
+ }
35
+
36
+ export function importTimelineGroups(shapes,paragraphs,relationships,entries,report) {
37
+ const groups=new Map(),consumed=new Set(),items=[];
38
+ for(const [index,shape]of shapes.entries()){
39
+ const tags=[];let unreadable=false;
40
+ for(const link of array(shape['p:nvSpPr']?.['p:nvPr']?.['p:custDataLst']?.['p:tags'])){
41
+ const rel=relationships.get(link['r:id']);if(rel?.type!==REL||rel.targetMode==='External'||!entries[rel.path])continue;
42
+ try{tags.push(...array(parser.parse(decoder.decode(entries[rel.path]))['p:tagLst']?.['p:tag']));}catch{unreadable=true;}
43
+ }
44
+ for(const tag of tags.filter(tag=>tag.name?.toUpperCase()===TAG))try{
45
+ const data=decodeTextTag(tag.val);if(data?.v!==1||typeof data.group!=='string'||!/^\d{1,12}$/.test(data.group))throw Error('Invalid timeline identity.');
46
+ const group=groups.get(data.group)??[];group.push({shape,index,data,ambiguous:unreadable||tags.filter(tag=>/^OPF_/i.test(tag.name)).length!==1});groups.set(data.group,group);
47
+ }catch{report({code:'invalid-timeline-provenance',message:'Invalid timeline tags; ordinary import retains current native text.'});}
48
+ }
49
+ for(const group of groups.values())try{
50
+ const anchors=group.filter(item=>item.data.anchor!==undefined);if(anchors.length!==1)throw Error('Missing or duplicated timeline anchor.');
51
+ const anchor=anchors[0],manifest=anchor.data.anchor,count=validateManifest(manifest);
52
+ const total=count+manifest.eventCount+1;
53
+ if(anchor.data.role!=='text'||anchor.data.part!==0||anchor.data.line!==0||group.length!==total||new Set(group.map(item=>item.shape)).size!==total)throw Error('Incomplete or duplicated timeline elements.');
54
+ const ordered=manifest.parts.map(part=>Array(part.lines)),markers=new Set();let connectors=0;
55
+ for(const item of group){
56
+ if(item.ambiguous)throw Error('Multiple source identities on one timeline shape.');
57
+ if(item.data.role==='connector'||item.data.role==='marker'){
58
+ const expected=item.data.role==='connector'?'line':'ellipse';
59
+ if(item.shape['p:spPr']?.['a:prstGeom']?.prst!==expected||(paragraphs[item.index]??[]).some(paragraph=>paragraph.text||paragraph.bullet))throw Error('Timeline graphic now contains different geometry or native text.');
60
+ if(item.data.role==='connector'){if(++connectors>1)throw Error('Duplicated timeline connector.');}
61
+ else{const index=item.data.eventIndex;if(!Number.isSafeInteger(index)||index<0||index>=manifest.eventCount||markers.has(index))throw Error('Ambiguous timeline marker.');markers.add(index);}
62
+ continue;
63
+ }
64
+ const {part,line}=item.data;
65
+ if(item.data.role!=='text'||!Number.isSafeInteger(part)||!Number.isSafeInteger(line)||part<0||line<0||!manifest.parts[part]||line>=manifest.parts[part].lines||item.data.count!==manifest.parts[part].lines||ordered[part][line])throw Error('Ambiguous timeline source line.');
66
+ ordered[part][line]=item;
67
+ }
68
+ if(connectors!==1||markers.size!==manifest.eventCount)throw Error('Incomplete timeline graphics.');
69
+ const result={events:Array.from({length:manifest.eventCount},()=>({}))};
70
+ for(const [index,part]of manifest.parts.entries()){
71
+ const text=sourceLineParagraphs(ordered[index],paragraphs)[0].text;
72
+ if(part.eventIndex===undefined)result[part.field]=text;else result.events[part.eventIndex][part.field]=text;
73
+ }
74
+ const timeline=manifest.shorthand?result.events:result;
75
+ if(!validatePresentation({slides:[{timeline}]}).valid)throw Error('Current native text does not form a valid timeline.');
76
+ for(const item of group)consumed.add(item.shape);
77
+ items.push({shapes:group.map(item=>item.shape),payload:{type:'timeline',timeline}});
78
+ report({code:'timeline-import-reflow',message:'Timeline field order, source boundaries and current native text were recovered. Native formatting, positions, marker styling and font theme are not reconstructed; review the reflowed timeline.'});
79
+ }catch(error){report({code:'invalid-timeline-provenance',message:`${error.message} Ordinary import retains current native text without restoring old source words.`});}
80
+ return {consumed,items};
81
+ }
package/package.json CHANGED
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "@openpresentation/opf-pptx",
3
- "version": "0.7.0",
3
+ "version": "0.8.1",
4
4
  "description": "Pure local OPF to PPTX export and PPTX to OPF import tooling.",
5
5
  "license": "MIT",
6
6
  "type": "module",
7
7
  "private": false,
8
8
  "engines": {
9
- "node": ">=20.9.0"
9
+ "node": "24.x"
10
10
  },
11
11
  "repository": {
12
12
  "type": "git",
@@ -37,8 +37,8 @@
37
37
  "sideEffects": false,
38
38
  "scripts": {
39
39
  "build": "node scripts/build.mjs",
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 && node test/content-layout.mjs && node test/shared-quote.mjs",
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 && node --check src/card-provenance.js && node --check test/content-cards.mjs && node --check src/chart-workbook.js && node --check test/chart-colors.mjs && node --check test/chart-workbook.mjs && node --check test/native-chart-colors.mjs && node --check src/heading-provenance.js && node --check test/accepted-text.mjs && node --check test/heading-provenance.mjs && node --check src/text-provenance.js && node --check test/plain-whitespace.mjs && node --check src/timeline-provenance.js && node --check test/shared-timeline.mjs && node --check test/shared-furniture.mjs && node --check src/furniture-provenance.js && node --check test/furniture-provenance.mjs",
41
+ "test": "npm run build && npm run test:furniture && node test/plain-whitespace.mjs && node test/accepted-text.mjs && node test/heading-provenance.mjs && node test/chart-colors.mjs && node test/content-cards.mjs && node test/chart-workbook.mjs && node test/dependency-boundary.mjs && npm run build:browser-check && npm run test:styled-table && node test/content-layout.mjs && node test/shared-quote.mjs && npm run test:timeline",
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",
@@ -46,17 +46,21 @@
46
46
  "prepack": "npm run build",
47
47
  "build:browser-check": "node scripts/build-browser-check.mjs",
48
48
  "test:styled-table": "node test/styled-table.mjs && node test/styled-table-import.mjs",
49
- "test:code": "node test/shared-code.mjs && node test/code-provenance.mjs"
49
+ "test:code": "node test/shared-code.mjs && node test/code-provenance.mjs",
50
+ "test:metric": "node test/shared-metric.mjs && node test/metric-provenance.mjs",
51
+ "test:font-variants": "node test/font-variants.mjs",
52
+ "test:timeline": "node test/shared-timeline.mjs",
53
+ "test:furniture": "node test/shared-furniture.mjs && node test/furniture-provenance.mjs"
50
54
  },
51
55
  "dependencies": {
52
- "@openpresentation/opf": "^0.9.0",
56
+ "@openpresentation/opf": "^0.10.1",
53
57
  "fast-xml-parser": "^5.8.0",
54
58
  "fflate": "^0.8.3",
55
- "jszip": "3.10.1",
59
+ "jszip": "3.10.2",
56
60
  "sharp": "0.35.4"
57
61
  },
58
62
  "peerDependencies": {
59
- "@openpresentation/opf-render": "^0.7.0"
63
+ "@openpresentation/opf-render": "^0.8.1"
60
64
  },
61
65
  "peerDependenciesMeta": {
62
66
  "@openpresentation/opf-render": {
@@ -64,7 +68,7 @@
64
68
  }
65
69
  },
66
70
  "devDependencies": {
67
- "@openpresentation/opf-render": "0.7.0",
71
+ "@openpresentation/opf-render": "0.8.1",
68
72
  "esbuild": "0.28.2",
69
73
  "playwright": "1.63.0"
70
74
  },