@codename/deck 0.1.1 → 0.2.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 -3
- package/dist/deck.bundle.mjs +57 -57
- package/dist/deck.bundle.mjs.map +3 -3
- package/dist/deck.css +11 -1
- package/dist/deck.js +81 -71
- package/dist/deck.js.map +3 -3
- package/dist/deck.mjs +34 -34
- package/dist/deck.mjs.map +3 -3
- package/dist/template.mjs +2 -0
- package/dist/template.mjs.map +7 -0
- package/dist/types/index.d.ts +1 -1
- package/dist/types/kit.d.ts +10 -1
- package/dist/types/look.d.ts +2 -1
- package/dist/types/template.d.ts +16 -0
- package/dist/types/version.d.ts +1 -1
- package/docs/format.md +1 -1
- package/docs/host.md +4 -2
- package/docs/kits.md +14 -2
- package/package.json +9 -5
- package/src/blocks.js +1 -1
- package/src/deck.css +11 -1
- package/src/deck.js +3 -1
- package/src/index.js +1 -1
- package/src/kit.js +32 -14
- package/src/look.js +5 -2
- package/src/pptx.js +10 -3
- package/src/render.js +9 -1
- package/src/standalone.js +1 -1
- package/src/template.js +161 -0
- package/src/version.js +1 -1
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
var D=/^[a-z][a-z0-9-]{0,31}$/,L=/^(auto|min-content|max-content|\d{1,2}(\.\d)?fr|\d{1,3}%)$/,K=["Title Slide","Title and Content","Section Header","Two Content","Comparison","Title Only","Blank","Content with Caption","Picture with Caption"],z={align:["start","center","end"],justify:["start","center","end"],paper:["paper","fill","ink","soft"],title:["normal","large","huge"],logo:["small","big","none"]},R=["top-end","top-start","bottom-end","bottom-start"],B=e=>typeof e=="string"&&e.length<=7e5&&/^data:image\/(png|jpeg|gif|webp|svg\+xml);base64,[a-z0-9+/]+=*$/i.test(e)?e:null;var _={name:"standard",tokens:{},layouts:Object.fromEntries(Object.entries({default:{label:{en:"Title and content",fr:"Titre et contenu"},pptx:"Title and Content"},cover:{label:{en:"Cover",fr:"Couverture"},justify:"center",paper:"fill",title:"huge",logo:"big",pptx:"Title Slide",numbered:!1},intro:{label:{en:"Introduction",fr:"Introduction"},justify:"center",paper:"fill",title:"large",logo:"big",pptx:"Title Slide",numbered:!1},section:{label:{en:"Section",fr:"Section"},justify:"center",paper:"ink",title:"huge",pptx:"Section Header",numbered:!1},center:{label:{en:"Centred",fr:"Centré"},justify:"center",align:"center",pptx:"Title Only"},statement:{label:{en:"Statement",fr:"Affirmation"},justify:"center",align:"center",title:"large",pptx:"Title Only"},fact:{label:{en:"Big number",fr:"Grand chiffre"},justify:"center",align:"center",title:"huge",pptx:"Title Only"},quote:{label:{en:"Quote",fr:"Citation"},justify:"center",pptx:"Title Only"},end:{label:{en:"Closing",fr:"Clôture"},justify:"center",align:"center",paper:"fill",title:"large",logo:"big",pptx:"Title Slide",numbered:!1},full:{label:{en:"Full",fr:"Plein cadre"},logo:"none",pptx:"Blank",numbered:!1},none:{label:{en:"Blank",fr:"Vide"},logo:"none",pptx:"Blank",numbered:!1},"two-cols":{label:{en:"Two columns",fr:"Deux colonnes"},slots:["default","right"],areas:"default right",columns:"1fr 1fr",pptx:"Two Content"},"two-cols-header":{label:{en:"Comparison",fr:"Comparaison"},slots:["default","left","right","bottom"],areas:"default default | left right | bottom bottom",columns:"1fr 1fr",rows:"auto 1fr auto",pptx:"Comparison"},"three-cols":{label:{en:"Three columns",fr:"Trois colonnes"},slots:["default","left","middle","right"],areas:"default default default | left middle right",columns:"1fr 1fr 1fr",rows:"auto 1fr",pptx:"Comparison"},"image-left":{label:{en:"Picture left",fr:"Image à gauche"},pptx:"Picture with Caption",justify:"center"},"image-right":{label:{en:"Picture right",fr:"Image à droite"},pptx:"Picture with Caption",justify:"center"},image:{label:{en:"Picture",fr:"Image"},logo:"none",pptx:"Blank",numbered:!1}}).map(([e,o])=>[e,F(o).layout]))};function F(e){let o=[];if(!e||typeof e!="object")return{layout:null,problems:["not a layout"]};let s={},i=Array.isArray(e.slots)||typeof e.slots=="string"?[e.slots].flat().map(String):e.slots&&typeof e.slots=="object"?Object.entries(e.slots).map(([l,d])=>(d&&typeof d=="object"&&(s[l]=W(d)),l==="title"?"default":l)):["default"],n=[...new Set(i)];if(!n.every(l=>D.test(l)))return{layout:null,problems:["a slot name is not a-z, 0-9 and -"]};n.includes("default")||n.unshift("default");let p=(Array.isArray(e.areas)?e.areas.map(l=>[l].flat().join(" ")):String(e.areas??n.join(" ")).split(/\||\n/)).map(l=>l.trim().replace(/^"|"$/g,"").split(/\s+/)).filter(l=>l.length&&l[0]),y=p[0]?.length??0;if(!p.length||p.some(l=>l.length!==y)||p.flat().some(l=>l!=="."&&!n.includes(l)))return{layout:null,problems:["areas are not a rectangle of its slots"]};for(let l of n){let d=p.flatMap((T,S)=>T.map((A,N)=>[A,N,S])).filter(([T])=>T===l);if(!d.length){l!=="default"&&o.push(`slot ${l} has no area`);continue}let $=d.map(T=>T[1]),j=d.map(T=>T[2]);if(d.length!==(Math.max(...$)-Math.min(...$)+1)*(Math.max(...j)-Math.min(...j)+1))return{layout:null,problems:[`slot ${l} is not a rectangle`]}}let h=(l,d)=>{let $=l===void 0?Array(d).fill("1fr"):(Array.isArray(l)?l:String(l).split(/\s+/)).map(String);return $.length===d&&$.every(j=>L.test(j))?$:null},b=h(e.columns,y),x=h(e.rows,p.length);if(!b||!x)return{layout:null,problems:["columns or rows are not tracks deck knows (auto, 1fr, 40%…), one per column or row"]};let m=(l,d)=>{let $=String(e[l]??d);return z[l].includes($)?$:(o.push(`${l} ${$} is not one of ${z[l].join(", ")}`),d)},w=typeof e.label=="string"||e.label&&typeof e.label=="object"?e.label:void 0,a=l=>typeof l=="string"?l.slice(0,300):void 0;return{layout:{label:w,intent:a(e.intent),use_when:a(e.use_when),avoid_when:a(e.avoid_when),slots:n,budgets:s,areas:p,columns:b,rows:x,align:m("align","start"),justify:m("justify","start"),paper:m("paper","paper"),title:m("title","normal"),logo:m("logo","small"),pptx:K.includes(e.pptx)?e.pptx:n.length>1?"Comparison":"Title and Content",numbered:e.numbered!==!1},problems:o}}function W(e){let o={};for(let s of["max_words","max_chars","item_max_words","heading_max_words"])Number.isInteger(e[s])&&e[s]>0&&(o[s]=e[s]);return Array.isArray(e.items)&&e.items.length===2&&e.items.every(Number.isInteger)&&(o.items=e.items),typeof e.required=="boolean"&&(o.required=e.required),typeof e.kind=="string"&&/^[a-z-]{1,20}$/.test(e.kind)&&(o.kind=e.kind),o}function q(e){if(!e||typeof e!="object")return{};if(!JSON.stringify(e).includes('"$value"'))return e;let o=w=>w&&typeof w=="object"&&"$value"in w?w.$value:w,s=w=>{let a=o(w);return typeof a=="string"?a:typeof a?.hex=="string"?a.hex:void 0},i=w=>{let a=o(w);return Array.isArray(a)?a.map(String):typeof a=="string"?[a]:void 0},n={bg:"paper",background:"paper",paper:"paper",fg:"ink",text:"ink",ink:"ink",accent:"primary",primary:"primary",muted:"muted",surface:"soft",soft:"soft"},u=w=>Object.fromEntries(Object.entries(w??{}).filter(([a])=>a in n).map(([a,l])=>[n[a],s(l)]).filter(([,a])=>a)),p=e.color??e.colour??{},y={...u(p),...u(p.light)},h=u(p.dark);Object.keys(h).length&&(y.dark=h);let b=e.font??e.fontFamily??{},x=i(b.heading??b.display??b.sans),m=i(b.body??b.text??b.sans);return(x||m)&&(y.fonts={heading:x??m,body:m??x}),y}function I(...e){let o={name:"standard",tokens:{},layouts:{..._.layouts}},s=[];for(let i of e)if(!(!i||typeof i!="object")){if(typeof i.name=="string"&&(o.name=i.name),i.tokens&&typeof i.tokens=="object"){let n=q(i.tokens),u=n.dark&&typeof n.dark=="object"?{...o.tokens.dark,...n.dark}:o.tokens.dark;o.tokens={...o.tokens,...n,...u&&{dark:u}}}for(let[n,u]of Object.entries(i.layouts??i.types??{})){if(!D.test(n)){s.push(`layout ${n}: not a-z, 0-9 and -`);continue}if(u===!1){n!=="default"&&delete o.layouts[n];continue}let{layout:p,problems:y}=F(u);s.push(...y.map(h=>`layout ${n}: ${h}`)),p&&(o.layouts[n]=p)}}for(let[i,n]of[["logo",o.tokens],["dark logo",o.tokens.dark]])n?.logo!==void 0&&!B(n.logo)&&(s.push(`${i}: not a picture inside the kit (PNG, JPEG, GIF, WebP or SVG as data:, 512 KB at most)`),delete n.logo);return o.tokens.logoAt!==void 0&&!R.includes(o.tokens.logoAt)&&(s.push(`logoAt ${o.tokens.logoAt} is not one of ${R.join(", ")}`),delete o.tokens.logoAt),{kit:o,problems:s}}var G={"Title Slide":"cover","Title and Content":"default","Section Header":"section","Two Content":"two-cols-header","Title Only":"center",Blank:"none"},H=["Title Slide","Title and Content","Section Header","Two Content","Comparison","Title Only","Blank","Content with Caption","Picture with Caption"],V={png:"image/png",jpg:"image/jpeg",jpeg:"image/jpeg",gif:"image/gif",svg:"image/svg+xml"};function X(e){let o=new DataView(e.buffer,e.byteOffset,e.byteLength),s=e.length-22;for(;s>=0&&o.getUint32(s,!0)!==101010256;)s--;if(s<0)throw new Error("not a PowerPoint file");let i=new Map;for(let n=o.getUint32(s+16,!0),u=o.getUint16(s+10,!0);u>0;u--){let p=o.getUint16(n+10,!0),y=o.getUint32(n+20,!0),h=new TextDecoder().decode(e.subarray(n+46,n+46+o.getUint16(n+28,!0))),b=o.getUint32(n+42,!0),x=b+30+o.getUint16(b+26,!0)+o.getUint16(b+28,!0),m=e.subarray(x,x+y);i.set(h,async()=>p===0?m:new Uint8Array(await new Response(new Blob([m]).stream().pipeThrough(new DecompressionStream("deflate-raw"))).arrayBuffer())),n+=46+o.getUint16(n+28,!0)+o.getUint16(n+30,!0)+o.getUint16(n+32,!0)}return i}var k=(e,o)=>new RegExp(`\\b${o}="([^"]*)"`).exec(e)?.[1],J=(e,o)=>new Map([...e.matchAll(/<Relationship\b[^>]*>/g)].map(([s])=>[String(k(s,"Id")),new URL(String(k(s,"Target")),`file:///${o}/`).pathname.slice(1)]));async function tt(e,{name:o}={}){let s=X(e),i=[],n=async t=>new TextDecoder().decode(await(s.get(t)?.()??new Uint8Array)),u=[...s.keys()].filter(t=>/^(ppt|theme)\/slideMasters\/slideMaster\d+\.xml$/.test(t)).sort()[0];if(!u)throw new Error("no slide master: not a PowerPoint template");let p=u.split("/")[0],y=await n(u),h=J(await n(u.replace(/([^/]+)$/,"_rels/$1.rels")),`${p}/slideMasters`),b=[...h.values()].find(t=>/theme\/theme\d+\.xml$/.test(t))??`${p}/theme/theme1.xml`,x=await n(b),[m,w]=(t=>[Number(k(t,"cx")??12192e3),Number(k(t,"cy")??6858e3)])(/<p:sldSz\b[^>]*>/.exec(await n(`${p}/presentation.xml`))?.[0]??""),a=Object.fromEntries([...x.matchAll(/<a:(dk1|lt1|dk2|lt2|accent[1-6])>\s*<a:(?:srgbClr\b[^>]*\bval|sysClr\b[^>]*\blastClr)="([0-9A-Fa-f]{6})"/g)].map(([,t,r])=>[t,`#${r.toLowerCase()}`])),l=t=>k(new RegExp(`<a:${t}>[\\s\\S]*?<a:latin\\b[^>]*>`).exec(x)?.[0].replace(/^[\s\S]*<a:latin/,"")??"","typeface"),[d,$]=[l("majorFont"),l("minorFont")].map(t=>t&&!t.startsWith("+")?[t,"ui-sans-serif","system-ui","sans-serif"]:null),j={...a.lt1&&{paper:a.lt1},...a.dk1&&{ink:a.dk1},...a.accent1&&{primary:a.accent1},...a.dk2&&{muted:a.dk2},...a.lt2&&{soft:a.lt2}},T=[1,2,3,4,5,6].map(t=>a[`accent${t}`]).filter(Boolean);T.length&&(j.series=T),(d||$)&&(j.fonts={heading:d??$,body:$??d});for(let[t]of y.matchAll(/<p:pic>[\s\S]*?<\/p:pic>/g)){let r=h.get(String(k(/<a:blip\b[^>]*>/.exec(t)?.[0]??"","r:embed"))),c=V[/\.(\w+)$/.exec(r??"")?.[1].toLowerCase()??""];if(!r||!c||!s.has(r))continue;let f=await s.get(r)?.(),g=f&&`data:${c};base64,${Y(f)}`;if(!B(g)){i.push(`the logo ${r.split("/").pop()} is larger than 512 KB`);break}let[P,C]=(U=>[Number(k(U,"x")??0),Number(k(U,"y")??0)])(/<a:off\b[^>]*>/.exec(t)?.[0]??"");Object.assign(j,{logo:g,logoAt:`${C<w/2?"top":"bottom"}-${P<m/2?"start":"end"}`});break}let S=t=>[...t.matchAll(/<p:sp>[\s\S]*?<\/p:sp>/g)].flatMap(([r])=>{let c=/<p:ph\b[^>]*>/.exec(r)?.[0];if(!c)return[];let f=/<a:off\b[^>]*>/.exec(r)?.[0],g=/<a:ext\b[^>]*>/.exec(r)?.[0];return[{type:k(c,"type")??"body",name:k(/<p:cNvPr\b[^>]*>/.exec(r)?.[0]??"","name")??"",box:f&&g?[Number(k(f,"x")),Number(k(f,"y")),Number(k(g,"cx")),Number(k(g,"cy"))]:null}]}),A=S(y),N=t=>{let r=/<p:bg>[\s\S]*?<\/p:bg>/.exec(t)?.[0]??"",c=k(/<a:schemeClr\b[^>]*>/.exec(r)?.[0]??"","val"),f=k(/<a:srgbClr\b[^>]*>/.exec(r)?.[0]??"","val")?.toLowerCase();return c==="accent1"||f&&`#${f}`===a.accent1?"fill":c==="tx1"||c==="dk1"||f&&`#${f}`===a.dk1?"ink":c==="bg2"||c==="lt2"?"soft":"paper"},O={},M=[...h.values()].filter(t=>/slideLayouts\/slideLayout\d+\.xml$/.test(t)).sort((t,r)=>Number(/(\d+)\.xml$/.exec(t)?.[1])-Number(/(\d+)\.xml$/.exec(r)?.[1]));for(let t of M){let r=await n(t),c=(k(/<p:cSld\b[^>]*>/.exec(r)?.[0]??"","name")??"").replace(/&/g,"&").trim(),f=S(r).filter(C=>!["dt","ftr","sldNum"].includes(C.type)).map(C=>({...C,box:C.box??A.find(U=>U.type===C.type||C.type==="ctrTitle"&&U.type==="title")?.box??null})).filter(C=>C.box),g=G[c]??(c.toLowerCase().normalize("NFKD").replace(/[^a-z0-9]+/g,"-").replace(/^-+|-+$/g,"").replace(/^(?=\d)/,"l-").slice(0,32)||`layout-${M.indexOf(t)+1}`),P=Q(f,m,w);if(!P){i.push(`layout ${c||g}: its placeholders make no grid deck lays out`);continue}O[g]={label:c||g,...P,paper:N(r),title:f.some(C=>C.type==="ctrTitle")?"huge":"normal",logo:_.layouts[g]?.logo??"small",...f.some(C=>C.type==="ctrTitle")&&{justify:"center"},pptx:H.includes(c)?c:P.slots.length>1?"Comparison":"Title and Content",numbered:/<p:ph\b[^>]*type="sldNum"/.test(r)}}let E=I({name:o??"template",tokens:j,layouts:O});return{kit:{...E.kit,name:o??"template"},problems:[...i,...E.problems]}}function Q(e,o,s){let i=t=>["title","ctrTitle","subTitle"].includes(t.type),n=t=>t.box,u=(t,r)=>Math.abs(t[0]-r[0])<o*.05&&Math.abs(t[2]-r[2])<o*.05&&t[1]>r[1],p=e.filter(i),y=t=>/^body-([a-z][a-z0-9-]{0,31})$/.exec(t.name)?.[1],h=e.filter(t=>!i(t)).sort((t,r)=>n(t)[0]-n(r)[0]||n(t)[1]-n(r)[1]),b=p.length?h.length===1&&!y(h[0])?h[0]:h.find(t=>y(t)==="default"||!y(t)&&u(n(t),n(p[0]))):void 0,x=b?[...p,b]:p,m=h.filter(t=>t!==b),w=t=>t.length?((r,c,f,g)=>[r,c,f-r,g-c])(Math.min(...t.map(r=>r[0])),Math.min(...t.map(r=>r[1])),Math.max(...t.map(r=>r[0]+r[2])),Math.max(...t.map(r=>r[1]+r[3]))):null,a=x.length?m:m.slice(1),l=x.length?w(x.map(n)):m.length?n(m[0]):[0,0,o,s],d=a.every((t,r)=>!r||n(t)[0]>=(n(a[r-1])[0]+n(a[r-1])[2])*.98),$=d&&a.length===2&&x.length?["left","right"]:d&&a.length===3&&x.length?["left","middle","right"]:d&&a.length===1?["right"]:d&&a.length===2?["middle","right"]:a.map((t,r)=>`s${r+1}`),j=[["default",l],...a.map((t,r)=>[y(t)??$[r],n(t)])],T=(t,r,c)=>[...new Set(j.flatMap(([,f])=>[f[t],f[t]+f[r]]))].sort((f,g)=>f-g).reduce((f,g)=>f.length&&g-f[f.length-1]<c*.03?f:[...f,g],[]),S=T(0,2,o),A=T(1,3,s),N=(t,r,c)=>r>=t[0]-o*.03&&r<=t[0]+t[2]+o*.03&&c>=t[1]-s*.03&&c<=t[1]+t[3]+s*.03,O=A.slice(1).map((t,r)=>S.slice(1).map((c,f)=>j.find(([,g])=>N(g,(S[f]+c)/2,(A[r]+t)/2))?.[0]??"."));if(!O.length||!O[0].length)return null;let M=(t,r)=>t.length===2?["1fr"]:t.slice(1).map((c,f)=>`${Math.max(.1,Math.round((c-t[f])/r*100)/10)}fr`),E=M(A,s).map((t,r)=>j.length>1&&O[r].every(c=>c==="default"||c===".")?"auto":t);return{slots:j.map(([t])=>t),areas:O.map(t=>t.join(" ")).join(" | "),columns:M(S,o).join(" "),rows:E.join(" ")}}function Y(e){let o="";for(let s=0;s<e.length;s+=32768)o+=String.fromCharCode(...e.subarray(s,s+32768));return btoa(o)}function et(e){let o=n=>typeof n=="string"?{$type:"color",$value:n}:void 0,s=n=>Object.fromEntries(Object.entries({bg:n.paper,fg:n.ink,accent:n.primary,muted:n.muted,surface:n.soft}).flatMap(([u,p])=>o(p)?[[u,o(p)]]:[])),i=e.fonts??{};return{color:{light:s(e),...e.dark&&{dark:s(e.dark)},...e.series&&{series:Object.fromEntries(e.series.map((n,u)=>[`s${u+1}`,o(n)]))}},...(i.heading||i.body)&&{font:{...i.heading&&{heading:{$type:"fontFamily",$value:i.heading}},...i.body&&{body:{$type:"fontFamily",$value:i.body}}}}}}export{tt as kitFromTemplate,et as tokensToDtcg};
|
|
2
|
+
//# sourceMappingURL=template.mjs.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../src/kit.js", "../src/template.js"],
|
|
4
|
+
"sourcesContent": ["// A kit: a design system and the layouts that go with it, as data and never as CSS or code, so a\n// person, an organisation or a brain can make one and no kit can do more than lay out a slide.\n// deck's own kit holds the usual layouts; a host's (an organisation's, a project's) and the deck\n// file's own `kit:` go over it, in that order. A layout is a grid: its slots by name, the areas\n// they take, its tracks, how its words sit, what paper it is on, how big its logo shows, and the\n// PowerPoint layout it is nearest to; a kit may turn one of the usual layouts off. Its tokens may\n// hold a logo, a picture inside the kit and never an address. Everything is checked against a small\n// grammar; what fails is dropped, with a reason. Pure.\n\n/** @typedef {{ required?: boolean, max_words?: number, max_chars?: number, items?: [number, number], item_max_words?: number, heading_max_words?: number, kind?: string }} Budget */\n/** @typedef {{ label?: Record<string, string> | string, intent?: string, use_when?: string, avoid_when?: string, slots: string[], budgets: Record<string, Budget>, areas: string[][], columns: string[], rows: string[], align: 'start' | 'center' | 'end', justify: 'start' | 'center' | 'end', paper: 'paper' | 'fill' | 'ink' | 'soft', title: 'normal' | 'large' | 'huge', logo: 'big' | 'small' | 'none', pptx: string, numbered: boolean }} Layout */\n/** @typedef {{ name?: string, tokens: Record<string, any>, layouts: Record<string, Layout> }} Kit */\n\nconst NAME = /^[a-z][a-z0-9-]{0,31}$/\nconst TRACK = /^(auto|min-content|max-content|\\d{1,2}(\\.\\d)?fr|\\d{1,3}%)$/\nconst PPTX = ['Title Slide', 'Title and Content', 'Section Header', 'Two Content', 'Comparison', 'Title Only', 'Blank', 'Content with Caption', 'Picture with Caption']\nconst ENUMS = /** @type {const} */ ({ align: ['start', 'center', 'end'], justify: ['start', 'center', 'end'], paper: ['paper', 'fill', 'ink', 'soft'], title: ['normal', 'large', 'huge'], logo: ['small', 'big', 'none'] })\n/** The corners a logo sits in, as `logoAt` names them, the first by default. */\nexport const CORNERS = ['top-end', 'top-start', 'bottom-end', 'bottom-start']\n\n/**\n * A logo as a kit holds it: a picture inside the kit, PNG, JPEG, GIF, WebP or SVG as a data: address of 512 KB at most,\n * never an address to fetch; else null. @param {unknown} value\n */\nexport const logoOf = (value) => (typeof value === 'string' && value.length <= 700_000 && /^data:image\\/(png|jpeg|gif|webp|svg\\+xml);base64,[a-z0-9+/]+=*$/i.test(value) ? value : null)\n\n/** The names brains give the usual layouts, for the ones deck has. */\nexport const ALIASES = /** @type {Record<string, string>} */ ({\n title: 'cover', 'title-slide': 'cover', 'two-columns': 'two-cols', 'two-column': 'two-cols', columns: 'two-cols', comparison: 'two-cols-header',\n 'three-columns': 'three-cols', 'section-header': 'section', divider: 'section', big: 'fact', number: 'fact', 'image-text': 'image-right', closing: 'end', thanks: 'end',\n})\n\n/** deck's own kit: the usual layouts, the ones Slidev names and the ones PowerPoint has, as data. */\nexport const STANDARD = /** @type {Kit} */ ({\n name: 'standard',\n tokens: {},\n layouts: Object.fromEntries(Object.entries(/** @type {Record<string, any>} */ ({\n default: { label: { en: 'Title and content', fr: 'Titre et contenu' }, pptx: 'Title and Content' },\n cover: { label: { en: 'Cover', fr: 'Couverture' }, justify: 'center', paper: 'fill', title: 'huge', logo: 'big', pptx: 'Title Slide', numbered: false },\n intro: { label: { en: 'Introduction', fr: 'Introduction' }, justify: 'center', paper: 'fill', title: 'large', logo: 'big', pptx: 'Title Slide', numbered: false },\n section: { label: { en: 'Section', fr: 'Section' }, justify: 'center', paper: 'ink', title: 'huge', pptx: 'Section Header', numbered: false },\n center: { label: { en: 'Centred', fr: 'Centré' }, justify: 'center', align: 'center', pptx: 'Title Only' },\n statement: { label: { en: 'Statement', fr: 'Affirmation' }, justify: 'center', align: 'center', title: 'large', pptx: 'Title Only' },\n fact: { label: { en: 'Big number', fr: 'Grand chiffre' }, justify: 'center', align: 'center', title: 'huge', pptx: 'Title Only' },\n quote: { label: { en: 'Quote', fr: 'Citation' }, justify: 'center', pptx: 'Title Only' },\n end: { label: { en: 'Closing', fr: 'Clôture' }, justify: 'center', align: 'center', paper: 'fill', title: 'large', logo: 'big', pptx: 'Title Slide', numbered: false },\n full: { label: { en: 'Full', fr: 'Plein cadre' }, logo: 'none', pptx: 'Blank', numbered: false },\n none: { label: { en: 'Blank', fr: 'Vide' }, logo: 'none', pptx: 'Blank', numbered: false },\n 'two-cols': { label: { en: 'Two columns', fr: 'Deux colonnes' }, slots: ['default', 'right'], areas: 'default right', columns: '1fr 1fr', pptx: 'Two Content' },\n 'two-cols-header': { label: { en: 'Comparison', fr: 'Comparaison' }, slots: ['default', 'left', 'right', 'bottom'], areas: 'default default | left right | bottom bottom', columns: '1fr 1fr', rows: 'auto 1fr auto', pptx: 'Comparison' },\n 'three-cols': { label: { en: 'Three columns', fr: 'Trois colonnes' }, slots: ['default', 'left', 'middle', 'right'], areas: 'default default default | left middle right', columns: '1fr 1fr 1fr', rows: 'auto 1fr', pptx: 'Comparison' },\n 'image-left': { label: { en: 'Picture left', fr: 'Image à gauche' }, pptx: 'Picture with Caption', justify: 'center' },\n 'image-right': { label: { en: 'Picture right', fr: 'Image à droite' }, pptx: 'Picture with Caption', justify: 'center' },\n image: { label: { en: 'Picture', fr: 'Image' }, logo: 'none', pptx: 'Blank', numbered: false },\n })).map(([name, given]) => [name, /** @type {Layout} */ (layoutOf(given).layout)])),\n})\n\n/**\n * A layout as given, checked: its slots, areas and tracks in the grammar, its words' place, paper\n * and size among their names. @param {any} given @returns {{ layout: Layout | null, problems: string[] }}\n */\nexport function layoutOf(given) {\n const problems = []\n if (!given || typeof given !== 'object') return { layout: null, problems: ['not a layout'] }\n // Slots as names, or as a catalog writes them: each a budget, `title` being the heading the\n // default slot opens with rather than a slot of its own.\n /** @type {Record<string, Budget>} */\n const budgets = {}\n const named = Array.isArray(given.slots) || typeof given.slots === 'string' ? [given.slots].flat().map(String)\n : given.slots && typeof given.slots === 'object' ? Object.entries(given.slots).map(([name, budget]) => {\n if (budget && typeof budget === 'object') budgets[name] = clean(budget)\n return name === 'title' ? 'default' : name\n }) : ['default']\n const slots = [...new Set(named)]\n if (!slots.every((slot) => NAME.test(slot))) return { layout: null, problems: ['a slot name is not a-z, 0-9 and -'] }\n if (!slots.includes('default')) slots.unshift('default')\n // Areas as Slidev's and CSS's grid write them, rows split by | or new lines.\n /** @type {string[]} */\n const lines = Array.isArray(given.areas) ? given.areas.map((/** @type {any} */ row) => [row].flat().join(' ')) : String(given.areas ?? slots.join(' ')).split(/\\||\\n/)\n const rows = lines.map((row) => row.trim().replace(/^\"|\"$/g, '').split(/\\s+/)).filter((row) => row.length && row[0])\n const width = rows[0]?.length ?? 0\n if (!rows.length || rows.some((row) => row.length !== width) || rows.flat().some((cell) => cell !== '.' && !slots.includes(cell))) return { layout: null, problems: ['areas are not a rectangle of its slots'] }\n for (const slot of slots) {\n const cells = rows.flatMap((row, y) => row.map((cell, x) => /** @type {[string, number, number]} */ ([cell, x, y]))).filter(([cell]) => cell === slot)\n if (!cells.length) { if (slot !== 'default') problems.push(`slot ${slot} has no area`); continue }\n const xs = cells.map((c) => c[1]), ys = cells.map((c) => c[2])\n if (cells.length !== (Math.max(...xs) - Math.min(...xs) + 1) * (Math.max(...ys) - Math.min(...ys) + 1)) return { layout: null, problems: [`slot ${slot} is not a rectangle`] }\n }\n const tracks = (/** @type {any} */ value, /** @type {number} */ count) => {\n const list = value === undefined ? Array(count).fill('1fr') : (Array.isArray(value) ? value : String(value).split(/\\s+/)).map(String)\n return list.length === count && list.every((t) => TRACK.test(t)) ? list : null\n }\n const columns = tracks(given.columns, width), rowTracks = tracks(given.rows, rows.length)\n if (!columns || !rowTracks) return { layout: null, problems: ['columns or rows are not tracks deck knows (auto, 1fr, 40%…), one per column or row'] }\n const pick = (/** @type {keyof typeof ENUMS} */ key, /** @type {string} */ fallback) => {\n const value = String(given[key] ?? fallback)\n if (!(/** @type {readonly string[]} */ (ENUMS[key])).includes(value)) { problems.push(`${key} ${value} is not one of ${ENUMS[key].join(', ')}`); return fallback }\n return value\n }\n const label = typeof given.label === 'string' || (given.label && typeof given.label === 'object') ? given.label : undefined\n const words = (/** @type {any} */ v) => (typeof v === 'string' ? v.slice(0, 300) : undefined)\n return {\n layout: /** @type {Layout} */ ({\n label, intent: words(given.intent), use_when: words(given.use_when), avoid_when: words(given.avoid_when), slots, budgets, areas: rows, columns, rows: rowTracks,\n align: pick('align', 'start'), justify: pick('justify', 'start'), paper: pick('paper', 'paper'), title: pick('title', 'normal'), logo: pick('logo', 'small'),\n pptx: PPTX.includes(given.pptx) ? given.pptx : slots.length > 1 ? 'Comparison' : 'Title and Content', numbered: given.numbered !== false,\n }),\n problems,\n }\n}\n\n/** A slot's budget, its numbers only. @param {Record<string, any>} given @returns {Budget} */\nfunction clean(given) {\n /** @type {Record<string, any>} */\n const kept = {}\n for (const key of ['max_words', 'max_chars', 'item_max_words', 'heading_max_words']) if (Number.isInteger(given[key]) && given[key] > 0) kept[key] = given[key]\n if (Array.isArray(given.items) && given.items.length === 2 && given.items.every(Number.isInteger)) kept.items = given.items\n if (typeof given.required === 'boolean') kept.required = given.required\n if (typeof given.kind === 'string' && /^[a-z-]{1,20}$/.test(given.kind)) kept.kind = given.kind\n return kept\n}\n\n/**\n * Design tokens as the W3C's Design Tokens format (DTCG 2025.10) writes them, read into a kit's:\n * `color.light` and `color.dark` groups (bg, surface, fg, muted, accent) or flat colours, `font.sans`\n * and the like; each `$value` a hex string or a colour object with its `hex`. Tokens already flat\n * pass through. @param {any} given @returns {Record<string, any>}\n */\nexport function tokensFrom(given) {\n if (!given || typeof given !== 'object') return {}\n if (!JSON.stringify(given).includes('\"$value\"')) return given\n const value = (/** @type {any} */ t) => (t && typeof t === 'object' && '$value' in t ? t.$value : t)\n const colour = (/** @type {any} */ t) => { const v = value(t); return typeof v === 'string' ? v : typeof v?.hex === 'string' ? v.hex : undefined }\n const face = (/** @type {any} */ t) => { const v = value(t); return Array.isArray(v) ? v.map(String) : typeof v === 'string' ? [v] : undefined }\n const NAMES = /** @type {const} */ ({ bg: 'paper', background: 'paper', paper: 'paper', fg: 'ink', text: 'ink', ink: 'ink', accent: 'primary', primary: 'primary', muted: 'muted', surface: 'soft', soft: 'soft' })\n const group = (/** @type {any} */ g) => Object.fromEntries(Object.entries(g ?? {}).filter(([k]) => k in NAMES).map(([k, t]) => [NAMES[/** @type {keyof typeof NAMES} */ (k)], colour(t)]).filter(([, v]) => v))\n const colours = given.color ?? given.colour ?? {}\n /** @type {Record<string, any>} */\n const tokens = { ...group(colours), ...group(colours.light) }\n const dark = group(colours.dark)\n if (Object.keys(dark).length) tokens.dark = dark\n const fonts = given.font ?? given.fontFamily ?? {}\n const heading = face(fonts.heading ?? fonts.display ?? fonts.sans), body = face(fonts.body ?? fonts.text ?? fonts.sans)\n if (heading || body) tokens.fonts = { heading: heading ?? body, body: body ?? heading }\n return tokens\n}\n\n/**\n * Kits merged, the later over the earlier: their tokens, their dark ones within theirs, and their layouts by name, each\n * checked; a layout given as false is taken away, but the default. A logo that isn't a picture inside the kit is dropped.\n * @param {...any} kits @returns {{ kit: Kit, problems: string[] }}\n */\nexport function mergeKits(...kits) {\n /** @type {Kit} */\n const kit = { name: 'standard', tokens: {}, layouts: { ...STANDARD.layouts } }\n const problems = []\n for (const given of kits) {\n if (!given || typeof given !== 'object') continue\n if (typeof given.name === 'string') kit.name = given.name\n if (given.tokens && typeof given.tokens === 'object') {\n const next = tokensFrom(given.tokens), dark = next.dark && typeof next.dark === 'object' ? { ...kit.tokens.dark, ...next.dark } : kit.tokens.dark\n kit.tokens = { ...kit.tokens, ...next, ...(dark && { dark }) }\n }\n for (const [name, definition] of Object.entries(given.layouts ?? given.types ?? {})) {\n if (!NAME.test(name)) { problems.push(`layout ${name}: not a-z, 0-9 and -`); continue }\n if (definition === false) { if (name !== 'default') delete kit.layouts[name]; continue }\n const { layout, problems: found } = layoutOf(definition)\n problems.push(...found.map((p) => `layout ${name}: ${p}`))\n if (layout) kit.layouts[name] = layout\n }\n }\n for (const [where, tokens] of /** @type {[string, Record<string, any> | undefined][]} */ ([['logo', kit.tokens], ['dark logo', kit.tokens.dark]])) {\n if (tokens?.logo !== undefined && !logoOf(tokens.logo)) { problems.push(`${where}: not a picture inside the kit (PNG, JPEG, GIF, WebP or SVG as data:, 512 KB at most)`); delete tokens.logo }\n }\n if (kit.tokens.logoAt !== undefined && !CORNERS.includes(kit.tokens.logoAt)) { problems.push(`logoAt ${kit.tokens.logoAt} is not one of ${CORNERS.join(', ')}`); delete kit.tokens.logoAt }\n return { kit, problems }\n}\n\n/** A layout's name in a language, else its own name. @param {Layout} layout @param {string} name @param {string} lang */\nexport function labelOf(layout, name, lang) {\n if (typeof layout.label === 'string') return layout.label\n return layout.label?.[lang] ?? layout.label?.[lang.split('-')[0]] ?? layout.label?.en ?? name\n}\n\n/**\n * The layout a slide that names none is given, from what it holds, as Pandoc's PowerPoint writer\n * chooses: the first slide is the cover; a heading alone, a section; slots beyond the first, the\n * layout of two (or three) columns that has them; a number alone as its heading, a fact; a quote\n * alone, a quote; else the default. @param {{ index: number, slots: { name: string, text: string }[] }} slide\n */\nexport function inferLayout(slide) {\n const names = slide.slots.map((slot) => slot.name)\n if (names.includes('middle')) return 'three-cols'\n if (names.includes('left')) return 'two-cols-header'\n if (names.includes('right')) return 'two-cols'\n if (slide.index === 0) return 'cover'\n const text = slide.slots.map((slot) => slot.text).join('\\n').trim()\n const lines = text.split('\\n').filter((line) => line.trim())\n if (lines.length === 1 && /^#{1,2}\\s+\\S/.test(lines[0])) return 'section'\n if (/^#\\s+[^\\s]*\\d[\\d\\s.,%€$£¥]*\\S*\\s*$/u.test(lines[0] ?? '') && lines.length <= 3 && !/^\\s*[-*\\d]+[.)]?\\s/m.test(lines.slice(1).join('\\n'))) return 'fact'\n if (lines.length && lines.every((line, i) => line.startsWith('>') || (i === lines.length - 1 && !line.startsWith('#')))) return lines[0].startsWith('>') ? 'quote' : 'default'\n return 'default'\n}\n", "// A kit from a company's PowerPoint template (.potx, .pptx, or an Office theme, .thmx), and a kit's tokens out in the W3C's Design Tokens format:\n// a module of its own (@codename/deck/template), which a host loads only when it reads or writes one. From the template,\n// its theme's colours (the first light its paper, the first dark its ink, the first accent its primary, the six accents a\n// chart's colours) and faces (headings', words'); the first picture on its slide master, its logo, in the corner it sits\n// in; and each slide layout, a layout of the kit by its name, its placeholders a grid of slots, on the paper its\n// background is. The file is read with the platform's own DecompressionStream and its XML with regular expressions, as a\n// page under Trusted Types parses no markup. What can't be read is left out, with a reason.\n\nimport { STANDARD, mergeKits, logoOf } from './kit.js'\n\n/** The slide layouts PowerPoint names as deck's own are: a template's take their place. */\nconst OWN = /** @type {Record<string, string>} */ ({ 'Title Slide': 'cover', 'Title and Content': 'default', 'Section Header': 'section', 'Two Content': 'two-cols-header', 'Title Only': 'center', Blank: 'none' })\nconst PPTX = ['Title Slide', 'Title and Content', 'Section Header', 'Two Content', 'Comparison', 'Title Only', 'Blank', 'Content with Caption', 'Picture with Caption']\nconst PICTURES = /** @type {Record<string, string>} */ ({ png: 'image/png', jpg: 'image/jpeg', jpeg: 'image/jpeg', gif: 'image/gif', svg: 'image/svg+xml' })\n\n/** A zip's files by name, each read when asked. @param {Uint8Array} bytes */\nfunction unzip(bytes) {\n const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength)\n let end = bytes.length - 22\n while (end >= 0 && view.getUint32(end, true) !== 0x06054b50) end--\n if (end < 0) throw new Error('not a PowerPoint file')\n /** @type {Map<string, () => Promise<Uint8Array>>} */\n const files = new Map()\n for (let at = view.getUint32(end + 16, true), n = view.getUint16(end + 10, true); n > 0; n--) {\n const method = view.getUint16(at + 10, true), size = view.getUint32(at + 20, true), name = new TextDecoder().decode(bytes.subarray(at + 46, at + 46 + view.getUint16(at + 28, true)))\n const local = view.getUint32(at + 42, true), start = local + 30 + view.getUint16(local + 26, true) + view.getUint16(local + 28, true), data = bytes.subarray(start, start + size)\n files.set(name, async () => (method === 0 ? data : new Uint8Array(await new Response(new Blob([/** @type {BlobPart} */ (data)]).stream().pipeThrough(new DecompressionStream('deflate-raw'))).arrayBuffer())))\n at += 46 + view.getUint16(at + 28, true) + view.getUint16(at + 30, true) + view.getUint16(at + 32, true)\n }\n return files\n}\n\n/** An attribute's value in a piece of XML. @param {string} xml @param {string} name */\nconst attr = (xml, name) => new RegExp(`\\\\b${name}=\"([^\"]*)\"`).exec(xml)?.[1]\n/** A part's relationships, by their ids, as paths from the package's root. @param {string} rels @param {string} from the part's folder */\nconst targets = (rels, from) => new Map([...rels.matchAll(/<Relationship\\b[^>]*>/g)].map(([one]) => [String(attr(one, 'Id')), new URL(String(attr(one, 'Target')), `file:///${from}/`).pathname.slice(1)]))\n\n/**\n * A kit read from a PowerPoint template, with what couldn't be read.\n * @param {Uint8Array} bytes a .potx, .pptx or .thmx @param {{ name?: string }} [options]\n * @returns {Promise<{ kit: import('./kit.js').Kit, problems: string[] }>}\n */\nexport async function kitFromTemplate(bytes, { name } = {}) {\n const files = unzip(bytes), problems = /** @type {string[]} */ ([])\n const text = async (/** @type {string} */ path) => new TextDecoder().decode(await (files.get(path)?.() ?? new Uint8Array()))\n // A template's parts are under ppt/, an Office theme's under theme/.\n const master = [...files.keys()].filter((path) => /^(ppt|theme)\\/slideMasters\\/slideMaster\\d+\\.xml$/.test(path)).sort()[0]\n if (!master) throw new Error('no slide master: not a PowerPoint template')\n const root = master.split('/')[0], masterXml = await text(master), masterRels = targets(await text(master.replace(/([^/]+)$/, '_rels/$1.rels')), `${root}/slideMasters`)\n const themePath = [...masterRels.values()].find((path) => /theme\\/theme\\d+\\.xml$/.test(path)) ?? `${root}/theme/theme1.xml`, theme = await text(themePath)\n const [cx, cy] = ((size) => [Number(attr(size, 'cx') ?? 12192000), Number(attr(size, 'cy') ?? 6858000)])(/<p:sldSz\\b[^>]*>/.exec(await text(`${root}/presentation.xml`))?.[0] ?? '')\n\n // Its theme: colours by their role, faces for headings and words.\n const scheme = Object.fromEntries([...theme.matchAll(/<a:(dk1|lt1|dk2|lt2|accent[1-6])>\\s*<a:(?:srgbClr\\b[^>]*\\bval|sysClr\\b[^>]*\\blastClr)=\"([0-9A-Fa-f]{6})\"/g)].map(([, role, value]) => [role, `#${value.toLowerCase()}`]))\n const face = (/** @type {string} */ part) => attr(new RegExp(`<a:${part}>[\\\\s\\\\S]*?<a:latin\\\\b[^>]*>`).exec(theme)?.[0].replace(/^[\\s\\S]*<a:latin/, '') ?? '', 'typeface')\n const [heading, body] = [face('majorFont'), face('minorFont')].map((one) => (one && !one.startsWith('+') ? [one, 'ui-sans-serif', 'system-ui', 'sans-serif'] : null))\n /** @type {Record<string, any>} */\n const tokens = { ...(scheme.lt1 && { paper: scheme.lt1 }), ...(scheme.dk1 && { ink: scheme.dk1 }), ...(scheme.accent1 && { primary: scheme.accent1 }), ...(scheme.dk2 && { muted: scheme.dk2 }), ...(scheme.lt2 && { soft: scheme.lt2 }) }\n const accents = [1, 2, 3, 4, 5, 6].map((n) => scheme[`accent${n}`]).filter(Boolean)\n if (accents.length) tokens.series = accents\n if (heading || body) tokens.fonts = { heading: heading ?? body, body: body ?? heading }\n\n // Its logo: the first picture on its master that a browser draws, in the corner it sits in.\n for (const [pic] of masterXml.matchAll(/<p:pic>[\\s\\S]*?<\\/p:pic>/g)) {\n const path = masterRels.get(String(attr(/<a:blip\\b[^>]*>/.exec(pic)?.[0] ?? '', 'r:embed'))), type = PICTURES[/\\.(\\w+)$/.exec(path ?? '')?.[1].toLowerCase() ?? '']\n if (!path || !type || !files.has(path)) continue\n const data = await files.get(path)?.(), url = data && `data:${type};base64,${base64(data)}`\n if (!logoOf(url)) { problems.push(`the logo ${path.split('/').pop()} is larger than 512 KB`); break }\n const [x, y] = ((off) => [Number(attr(off, 'x') ?? 0), Number(attr(off, 'y') ?? 0)])(/<a:off\\b[^>]*>/.exec(pic)?.[0] ?? '')\n Object.assign(tokens, { logo: url, logoAt: `${y < cy / 2 ? 'top' : 'bottom'}-${x < cx / 2 ? 'start' : 'end'}` })\n break\n }\n\n // Its layouts: each placeholder where it sits, else where the master's of its kind sits.\n const placed = (/** @type {string} */ xml) => [...xml.matchAll(/<p:sp>[\\s\\S]*?<\\/p:sp>/g)].flatMap(([sp]) => {\n const ph = /<p:ph\\b[^>]*>/.exec(sp)?.[0]\n if (!ph) return []\n const off = /<a:off\\b[^>]*>/.exec(sp)?.[0], ext = /<a:ext\\b[^>]*>/.exec(sp)?.[0]\n return [{ type: attr(ph, 'type') ?? 'body', name: attr(/<p:cNvPr\\b[^>]*>/.exec(sp)?.[0] ?? '', 'name') ?? '', box: off && ext ? [Number(attr(off, 'x')), Number(attr(off, 'y')), Number(attr(ext, 'cx')), Number(attr(ext, 'cy'))] : null }]\n })\n const masterBoxes = placed(masterXml)\n const paperOf = (/** @type {string} */ xml) => {\n const fill = /<p:bg>[\\s\\S]*?<\\/p:bg>/.exec(xml)?.[0] ?? '', role = attr(/<a:schemeClr\\b[^>]*>/.exec(fill)?.[0] ?? '', 'val'), rgb = attr(/<a:srgbClr\\b[^>]*>/.exec(fill)?.[0] ?? '', 'val')?.toLowerCase()\n return role === 'accent1' || (rgb && `#${rgb}` === scheme.accent1) ? 'fill' : role === 'tx1' || role === 'dk1' || (rgb && `#${rgb}` === scheme.dk1) ? 'ink' : role === 'bg2' || role === 'lt2' ? 'soft' : 'paper'\n }\n /** @type {Record<string, any>} */\n const layouts = {}\n const paths = [...masterRels.values()].filter((path) => /slideLayouts\\/slideLayout\\d+\\.xml$/.test(path)).sort((a, b) => Number(/(\\d+)\\.xml$/.exec(a)?.[1]) - Number(/(\\d+)\\.xml$/.exec(b)?.[1]))\n for (const path of paths) {\n const xml = await text(path), title = (attr(/<p:cSld\\b[^>]*>/.exec(xml)?.[0] ?? '', 'name') ?? '').replace(/&/g, '&').trim()\n const boxes = placed(xml).filter((one) => !['dt', 'ftr', 'sldNum'].includes(one.type)).map((one) => ({ ...one, box: one.box ?? masterBoxes.find((m) => m.type === one.type || (one.type === 'ctrTitle' && m.type === 'title'))?.box ?? null }))\n .filter((one) => one.box)\n const key = OWN[title] ?? (title.toLowerCase().normalize('NFKD').replace(/[^a-z0-9]+/g, '-').replace(/^-+|-+$/g, '').replace(/^(?=\\d)/, 'l-').slice(0, 32) || `layout-${paths.indexOf(path) + 1}`)\n const layout = gridOf(boxes, cx, cy)\n if (!layout) { problems.push(`layout ${title || key}: its placeholders make no grid deck lays out`); continue }\n layouts[key] = { label: title || key, ...layout, paper: paperOf(xml), title: boxes.some((one) => one.type === 'ctrTitle') ? 'huge' : 'normal', logo: STANDARD.layouts[key]?.logo ?? 'small',\n ...(boxes.some((one) => one.type === 'ctrTitle') && { justify: 'center' }), pptx: PPTX.includes(title) ? title : layout.slots.length > 1 ? 'Comparison' : 'Title and Content',\n numbered: /<p:ph\\b[^>]*type=\"sldNum\"/.test(xml) }\n }\n const merged = mergeKits({ name: name ?? 'template', tokens, layouts })\n return { kit: { ...merged.kit, name: name ?? 'template' }, problems: [...problems, ...merged.problems] }\n}\n\n/**\n * Placeholders as a grid of slots: the titles the default slot, with the box under them across the same columns; the other\n * boxes by the names deck gave them (`body-right`, as its PowerPoint writes them), else `left`, `middle` and `right` side by\n * side, else `s1`, `s2`…; the margins left to deck. @param {{ type: string, name: string, box: number[] | null }[]} boxes\n * @param {number} cx @param {number} cy\n */\nfunction gridOf(boxes, cx, cy) {\n const isTitle = (/** @type {{ type: string }} */ one) => ['title', 'ctrTitle', 'subTitle'].includes(one.type)\n const box = (/** @type {{ box: number[] | null }} */ one) => /** @type {number[]} */ (one.box)\n const across = (/** @type {number[]} */ a, /** @type {number[]} */ b) => Math.abs(a[0] - b[0]) < cx * 0.05 && Math.abs(a[2] - b[2]) < cx * 0.05 && a[1] > b[1]\n const heading = boxes.filter(isTitle), own = (/** @type {{ name: string }} */ one) => /^body-([a-z][a-z0-9-]{0,31})$/.exec(one.name)?.[1]\n const sorted = boxes.filter((one) => !isTitle(one)).sort((a, b) => box(a)[0] - box(b)[0] || box(a)[1] - box(b)[1])\n // The heading's slot's own words: the only box, or the one under the heading across its columns.\n const under = !heading.length ? undefined : sorted.length === 1 && !own(sorted[0]) ? sorted[0] : sorted.find((one) => own(one) === 'default' || (!own(one) && across(box(one), box(heading[0]))))\n const titles = under ? [...heading, under] : heading, others = sorted.filter((one) => one !== under)\n const union = (/** @type {number[][]} */ list) => (list.length ? ((xs, ys, xe, ye) => [xs, ys, xe - xs, ye - ys])(Math.min(...list.map((b) => b[0])), Math.min(...list.map((b) => b[1])),\n Math.max(...list.map((b) => b[0] + b[2])), Math.max(...list.map((b) => b[1] + b[3]))) : null)\n // The default slot: the heading and, when it is the only one, the box under it; else the heading, else the first box.\n const rest = titles.length ? others : others.slice(1)\n const first = titles.length ? /** @type {number[]} */ (union(titles.map(box))) : others.length ? box(others[0]) : [0, 0, cx, cy]\n const side = rest.every((one, i) => !i || box(one)[0] >= (box(rest[i - 1])[0] + box(rest[i - 1])[2]) * 0.98)\n const names = side && rest.length === 2 && titles.length ? ['left', 'right'] : side && rest.length === 3 && titles.length ? ['left', 'middle', 'right'] : side && rest.length === 1 ? ['right'] : side && rest.length === 2 ? ['middle', 'right']\n : rest.map((_, i) => `s${i + 1}`)\n /** @type {[string, number[]][]} */\n const slots = [['default', first], ...rest.map((one, i) => /** @type {[string, number[]]} */ ([own(one) ?? names[i], box(one)]))]\n // The grid's lines, near ones as one, then each cell the slot whose box holds its middle.\n const lines = (/** @type {number} */ from, /** @type {number} */ size, /** @type {number} */ total) => [...new Set(slots.flatMap(([, b]) => [b[from], b[from] + b[size]]))].sort((a, b) => a - b)\n .reduce((/** @type {number[]} */ kept, at) => (kept.length && at - kept[kept.length - 1] < total * 0.03 ? kept : [...kept, at]), [])\n const xs = lines(0, 2, cx), ys = lines(1, 3, cy)\n const holds = (/** @type {number[]} */ b, /** @type {number} */ x, /** @type {number} */ y) => x >= b[0] - cx * 0.03 && x <= b[0] + b[2] + cx * 0.03 && y >= b[1] - cy * 0.03 && y <= b[1] + b[3] + cy * 0.03\n const areas = ys.slice(1).map((y, r) => xs.slice(1).map((x, c) => slots.find(([, b]) => holds(b, (xs[c] + x) / 2, (ys[r] + y) / 2))?.[0] ?? '.'))\n if (!areas.length || !areas[0].length) return null\n const fr = (/** @type {number[]} */ edges, /** @type {number} */ total) => (edges.length === 2 ? ['1fr'] : edges.slice(1).map((at, i) => `${Math.max(0.1, Math.round(((at - edges[i]) / total) * 100) / 10)}fr`))\n // A row that holds only the heading takes what it needs.\n const rows = fr(ys, cy).map((track, r) => (slots.length > 1 && areas[r].every((cell) => cell === 'default' || cell === '.') ? 'auto' : track))\n return { slots: slots.map(([one]) => one), areas: areas.map((row) => row.join(' ')).join(' | '), columns: fr(xs, cx).join(' '), rows: rows.join(' ') }\n}\n\n/** Bytes as base64, a piece at a time. @param {Uint8Array} bytes */\nfunction base64(bytes) {\n let binary = ''\n for (let i = 0; i < bytes.length; i += 0x8000) binary += String.fromCharCode(...bytes.subarray(i, i + 0x8000))\n return btoa(binary)\n}\n\n/**\n * A kit's tokens in the W3C's Design Tokens format (DTCG 2025.10), as design tools read them: its colours light and\n * dark, its chart colours and its faces; `tokensFrom()` reads them back. @param {Record<string, any>} tokens\n */\nexport function tokensToDtcg(tokens) {\n const colour = (/** @type {unknown} */ value) => (typeof value === 'string' ? { $type: 'color', $value: value } : undefined)\n const group = (/** @type {Record<string, any>} */ own) => Object.fromEntries(Object.entries({ bg: own.paper, fg: own.ink, accent: own.primary, muted: own.muted, surface: own.soft }).flatMap(([key, value]) => (colour(value) ? [[key, colour(value)]] : [])))\n const fonts = tokens.fonts ?? {}\n return {\n color: { light: group(tokens), ...(tokens.dark && { dark: group(tokens.dark) }), ...(tokens.series && { series: Object.fromEntries(tokens.series.map((/** @type {string} */ one, /** @type {number} */ i) => [`s${i + 1}`, colour(one)])) }) },\n ...((fonts.heading || fonts.body) && { font: { ...(fonts.heading && { heading: { $type: 'fontFamily', $value: fonts.heading } }), ...(fonts.body && { body: { $type: 'fontFamily', $value: fonts.body } }) } }),\n }\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": ["NAME", "TRACK", "PPTX", "ENUMS", "CORNERS", "logoOf", "value", "STANDARD", "name", "given", "layoutOf", "problems", "budgets", "named", "budget", "clean", "slots", "slot", "NAME", "rows", "row", "width", "cell", "cells", "y", "x", "xs", "c", "ys", "tracks", "value", "count", "list", "t", "TRACK", "columns", "rowTracks", "pick", "key", "fallback", "ENUMS", "label", "words", "v", "PPTX", "kept", "tokensFrom", "colour", "face", "NAMES", "group", "g", "k", "colours", "tokens", "dark", "fonts", "heading", "body", "mergeKits", "kits", "kit", "next", "definition", "layout", "found", "p", "where", "logoOf", "CORNERS", "OWN", "PPTX", "PICTURES", "unzip", "bytes", "view", "end", "files", "at", "n", "method", "size", "name", "local", "start", "data", "attr", "xml", "targets", "rels", "from", "one", "kitFromTemplate", "problems", "text", "path", "master", "root", "masterXml", "masterRels", "themePath", "theme", "cx", "cy", "scheme", "role", "value", "face", "part", "heading", "body", "tokens", "accents", "pic", "type", "url", "base64", "logoOf", "x", "y", "off", "placed", "sp", "ph", "ext", "masterBoxes", "paperOf", "fill", "rgb", "layouts", "paths", "a", "b", "title", "boxes", "m", "key", "layout", "gridOf", "STANDARD", "merged", "mergeKits", "isTitle", "box", "across", "own", "sorted", "under", "titles", "others", "union", "list", "xs", "ys", "xe", "ye", "rest", "first", "side", "i", "names", "_", "slots", "lines", "total", "kept", "holds", "areas", "c", "fr", "edges", "rows", "track", "cell", "row", "binary", "tokensToDtcg", "colour", "group", "fonts"]
|
|
7
|
+
}
|
package/dist/types/index.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
export { createDeck, markdownOf } from './deck.js';
|
|
2
2
|
export { parseDeck } from './parse.js';
|
|
3
3
|
export { renderSlide } from './render.js';
|
|
4
|
-
export { STANDARD, mergeKits, layoutOf, inferLayout, labelOf, tokensFrom } from './kit.js';
|
|
4
|
+
export { STANDARD, CORNERS, mergeKits, layoutOf, logoOf, inferLayout, labelOf, tokensFrom } from './kit.js';
|
|
5
5
|
export { tokensOf, schemeOf, LOOKS, readable, contrast } from './look.js';
|
|
6
6
|
export { readDeckFile, writeDeckFile, rendererUrl, escape, unescape } from './file.js';
|
|
7
7
|
export { toPptx } from './pptx.js';
|
package/dist/types/kit.d.ts
CHANGED
|
@@ -21,6 +21,7 @@ export type Layout = {
|
|
|
21
21
|
justify: 'start' | 'center' | 'end';
|
|
22
22
|
paper: 'paper' | 'fill' | 'ink' | 'soft';
|
|
23
23
|
title: 'normal' | 'large' | 'huge';
|
|
24
|
+
logo: 'big' | 'small' | 'none';
|
|
24
25
|
pptx: string;
|
|
25
26
|
numbered: boolean;
|
|
26
27
|
};
|
|
@@ -29,6 +30,13 @@ export type Kit = {
|
|
|
29
30
|
tokens: Record<string, any>;
|
|
30
31
|
layouts: Record<string, Layout>;
|
|
31
32
|
};
|
|
33
|
+
/** The corners a logo sits in, as `logoAt` names them, the first by default. */
|
|
34
|
+
export declare const CORNERS: string[];
|
|
35
|
+
/**
|
|
36
|
+
* A logo as a kit holds it: a picture inside the kit, PNG, JPEG, GIF, WebP or SVG as a data: address of 512 KB at most,
|
|
37
|
+
* never an address to fetch; else null. @param {unknown} value
|
|
38
|
+
*/
|
|
39
|
+
export declare const logoOf: (value: unknown) => string | null;
|
|
32
40
|
/** The names brains give the usual layouts, for the ones deck has. */
|
|
33
41
|
export declare const ALIASES: Record<string, string>;
|
|
34
42
|
/** deck's own kit: the usual layouts, the ones Slidev names and the ones PowerPoint has, as data. */
|
|
@@ -49,7 +57,8 @@ export declare function layoutOf(given: any): {
|
|
|
49
57
|
*/
|
|
50
58
|
export declare function tokensFrom(given: any): Record<string, any>;
|
|
51
59
|
/**
|
|
52
|
-
* Kits merged, the later over the earlier: their tokens, and their layouts by name, each
|
|
60
|
+
* Kits merged, the later over the earlier: their tokens, their dark ones within theirs, and their layouts by name, each
|
|
61
|
+
* checked; a layout given as false is taken away, but the default. A logo that isn't a picture inside the kit is dropped.
|
|
53
62
|
* @param {...any} kits @returns {{ kit: Kit, problems: string[] }}
|
|
54
63
|
*/
|
|
55
64
|
export declare function mergeKits(...kits: any[]): {
|
package/dist/types/look.d.ts
CHANGED
|
@@ -6,6 +6,7 @@ export type Tokens = {
|
|
|
6
6
|
radius: number;
|
|
7
7
|
muted?: string;
|
|
8
8
|
soft?: string;
|
|
9
|
+
series?: string[];
|
|
9
10
|
dark?: Record<string, string>;
|
|
10
11
|
fonts?: {
|
|
11
12
|
heading: string[];
|
|
@@ -22,7 +23,7 @@ export type Scheme = {
|
|
|
22
23
|
soft: string;
|
|
23
24
|
series: string[];
|
|
24
25
|
};
|
|
25
|
-
/** @typedef {{ paper: string, ink: string, primary: string, voice: string, radius: number, muted?: string, soft?: string, dark?: Record<string, string>, fonts?: { heading: string[], body: string[] } }} Tokens */
|
|
26
|
+
/** @typedef {{ paper: string, ink: string, primary: string, voice: string, radius: number, muted?: string, soft?: string, series?: string[], dark?: Record<string, string>, fonts?: { heading: string[], body: string[] } }} Tokens */
|
|
26
27
|
/** @typedef {{ paper: string, ink: string, muted: string, line: string, primary: string, onPrimary: string, soft: string, series: string[] }} Scheme */
|
|
27
28
|
/** Looks a deck can name (`theme:`), each a handful of tokens. */
|
|
28
29
|
export declare const LOOKS: Record<string, Tokens>;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A kit read from a PowerPoint template, with what couldn't be read.
|
|
3
|
+
* @param {Uint8Array} bytes a .potx, .pptx or .thmx @param {{ name?: string }} [options]
|
|
4
|
+
* @returns {Promise<{ kit: import('./kit.js').Kit, problems: string[] }>}
|
|
5
|
+
*/
|
|
6
|
+
export declare function kitFromTemplate(bytes: Uint8Array, { name }?: {
|
|
7
|
+
name?: string;
|
|
8
|
+
}): Promise<{
|
|
9
|
+
kit: import('./kit.js').Kit;
|
|
10
|
+
problems: string[];
|
|
11
|
+
}>;
|
|
12
|
+
/**
|
|
13
|
+
* A kit's tokens in the W3C's Design Tokens format (DTCG 2025.10), as design tools read them: its colours light and
|
|
14
|
+
* dark, its chart colours and its faces; `tokensFrom()` reads them back. @param {Record<string, any>} tokens
|
|
15
|
+
*/
|
|
16
|
+
export declare function tokensToDtcg(tokens: Record<string, any>): any;
|
package/dist/types/version.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const VERSION = "0.
|
|
1
|
+
export declare const VERSION = "0.2.0";
|
package/docs/format.md
CHANGED
|
@@ -43,7 +43,7 @@ Slidev's form, as `@slidev/parser` splits it, and the parts a deck for knowledge
|
|
|
43
43
|
- **Code** in fences, with lines marked as Slidev marks them: ` ```ts {2,4-5} `.
|
|
44
44
|
- **Charts:** ` ```echarts ` holding an ECharts option as JSON, data only.
|
|
45
45
|
- **Diagrams:** ` ```mermaid ` holding Mermaid's language.
|
|
46
|
-
- **Markdown** as CommonMark and GFM have it: headings, lists, task lists, tables, quotes, emphasis, links, pictures.
|
|
46
|
+
- **Markdown** as CommonMark and GFM have it: headings, lists, task lists, tables, quotes, emphasis, links, pictures. A picture alone on its line fills what its slot leaves, whole, whatever its own size.
|
|
47
47
|
|
|
48
48
|
### Added by deck
|
|
49
49
|
|
package/docs/host.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Drawing decks in a host
|
|
2
2
|
|
|
3
|
-
A host is a page that draws decks itself
|
|
3
|
+
A host is a page that draws decks itself. deck builds every element with the DOM's own calls, sets custom properties through the CSSOM rather than style attributes, and needs no Trusted Types policy, so it runs under a policy with `style-src 'self'` and `trusted-types 'none'`. Load `@codename/deck` (with your own `marked` through an import map) or `@codename/deck/bundle`, and `@codename/deck/css` as a stylesheet.
|
|
4
4
|
|
|
5
5
|
## createDeck(element, options)
|
|
6
6
|
|
|
@@ -40,7 +40,9 @@ Every block that can be changed by hand carries `data-edit="from-to"`, the offse
|
|
|
40
40
|
const { blob, report } = await toPptx(view, { PptxGenJS, JSZip })
|
|
41
41
|
```
|
|
42
42
|
|
|
43
|
-
|
|
43
|
+
A kit from a PowerPoint template, and a kit's tokens out, are `@codename/deck/template`'s (`kitFromTemplate`, `tokensToDtcg`, [kits.md](kits.md)), which a host imports only when it reads or writes one; `logoOf` and `CORNERS` check a kit's logo and its corner before it is kept.
|
|
44
|
+
|
|
45
|
+
PptxGenJS 4 (its bundle carries JSZip) is the host's to load, on demand. The file is made from the deck as drawn: a slide layout per deck layout, named by its label in the deck's language, with a placeholder for its title and each slot; lists as lists with their levels; tables; bars, lines, areas, pies, rings, points and radars as native charts with their numbers, other charts and diagrams as pictures, and pictures, alone on their line or beside the words, as pictures; notes; transitions; the kit's colours as the theme. `report` counts slides, charts, tables, pictures and placeholders.
|
|
44
46
|
|
|
45
47
|
## Files
|
|
46
48
|
|
package/docs/kits.md
CHANGED
|
@@ -32,12 +32,17 @@ Flat:
|
|
|
32
32
|
| `voice` | `calm`, `elegant`, `bold`, `friendly`: the device's own faces for headings and text |
|
|
33
33
|
| `fonts` | Optional: `{ heading: [...], body: [...] }`, face stacks, over the voice |
|
|
34
34
|
| `radius` | Corners, in pixels |
|
|
35
|
-
| `
|
|
35
|
+
| `series` | Optional: a chart's colours, up to six (a template's accents), else the primary turned round the wheel |
|
|
36
|
+
| `logo` | Optional: a picture inside the kit, PNG, JPEG, GIF, WebP or SVG as a `data:` address of 512 KB at most, never an address to fetch |
|
|
37
|
+
| `logoAt` | The corner it sits in: `top-end` (the default), `top-start`, `bottom-end`, `bottom-start` |
|
|
38
|
+
| `dark` | Optional: `{ paper, ink, primary, muted, soft, logo }` for the dark scheme and dark paper, else worked out; merged with the kits' under it |
|
|
36
39
|
|
|
37
40
|
Or in the W3C's Design Tokens format (DTCG 2025.10): `color.light` and `color.dark` groups of `bg`, `surface`, `fg`, `muted`, `accent` (or flat colours), `font.sans`, `font.heading`, `font.body`; each `$value` a hex string or a colour object with its `hex`. `tokensFrom()` reads them.
|
|
38
41
|
|
|
39
42
|
Whatever the tokens, light and dark are each checked: words at 4.5:1 on their paper, a chart's marks at 3:1, each colour deepened or lightened at its hue until it reads.
|
|
40
43
|
|
|
44
|
+
A logo shows in its corner, small, or big where a layout says (`logo`: a cover, an introduction, the end); on dark paper (ink, a dark primary as a fill, the dark scheme's paper) its `dark` one shows, else the logo sits on a light plate. In PowerPoint it is on each layout's slide layout, so a slide added there has it too.
|
|
45
|
+
|
|
41
46
|
## Layouts
|
|
42
47
|
|
|
43
48
|
| Field | Grammar | Default |
|
|
@@ -49,13 +54,20 @@ Whatever the tokens, light and dark are each checked: words at 4.5:1 on their pa
|
|
|
49
54
|
| `justify` | `start`, `center`, `end`: down | `start` |
|
|
50
55
|
| `paper` | `paper`, `fill` (the primary), `ink`, `soft` | `paper` |
|
|
51
56
|
| `title` | `normal`, `large`, `huge` | `normal` |
|
|
57
|
+
| `logo` | `small`, `big`, `none`: how the kit's logo shows | `small`; `big` on `cover`, `intro`, `end`; `none` on `image`, `full`, `none` |
|
|
52
58
|
| `pptx` | Title Slide, Title and Content, Section Header, Two Content, Comparison, Title Only, Blank, Content with Caption, Picture with Caption | by its slots |
|
|
53
59
|
| `numbered` | `false` for no slide number | `true` |
|
|
54
60
|
| `label` | Words, or words by language: `{ en: …, fr: … }`; PowerPoint's layout is named so | the name |
|
|
55
61
|
| `intent`, `use_when`, `avoid_when` | Words for the brain choosing a layout | none |
|
|
56
62
|
| budgets | `required`, `max_words`, `max_chars`, `items: [min, max]`, `item_max_words`, `heading_max_words`, `kind` | none |
|
|
57
63
|
|
|
58
|
-
A catalog's `types` are read as `layouts`. Budgets are kept with each layout for a linter and for the brain; deck
|
|
64
|
+
A catalog's `types` are read as `layouts`. A layout given as `false` turns one of deck's own off (`image: false`), but `default`. Budgets are kept with each layout for a linter and for the brain; deck does not check them yet.
|
|
65
|
+
|
|
66
|
+
## From a PowerPoint template, and out
|
|
67
|
+
|
|
68
|
+
`@codename/deck/template`, a module of its own that a host loads only when it needs it, reads a company's template (`.potx`, `.pptx`, or an Office theme, `.thmx`) into a kit: `kitFromTemplate(bytes, { name })` gives `{ kit, problems }`. Its theme's first light is `paper`, its first dark `ink`, its first accent `primary`, its second dark and light `muted` and `soft`, its six accents `series`, its heading and body faces `fonts`; the first picture on its slide master a browser draws is its `logo`, in the corner it sits in. Each slide layout is a layout of the kit by its name: PowerPoint's Title Slide, Title and Content, Section Header, Two Content, Title Only and Blank take the place of `cover`, `default`, `section`, `two-cols-header`, `center` and `none`; the others are added, slugged (`content-with-caption`). Its placeholders become a grid: the title, with the box under it across its columns, the `default` slot; the other boxes `left`, `middle` and `right` side by side, else `s1`, `s2`…, or the names deck gave them when deck wrote the file (`body-right`); the margins are deck's. Its paper is its background's colour by its role, its title `huge` for a title slide's; a layout whose placeholders make no grid is left out, with a reason.
|
|
69
|
+
|
|
70
|
+
`tokensToDtcg(tokens)` writes a kit's tokens in the W3C's Design Tokens format, which `tokensFrom()` reads back.
|
|
59
71
|
|
|
60
72
|
## deck's own layouts
|
|
61
73
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@codename/deck",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "Slide decks as one web page: Markdown in Slidev's form inside a .deck.html file any browser presents, a renderer that runs anywhere, kits of layouts and looks, and an editable PowerPoint out",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -15,6 +15,10 @@
|
|
|
15
15
|
},
|
|
16
16
|
"./file": "./dist/deck.js",
|
|
17
17
|
"./css": "./dist/deck.css",
|
|
18
|
+
"./template": {
|
|
19
|
+
"types": "./dist/types/template.d.ts",
|
|
20
|
+
"default": "./dist/template.mjs"
|
|
21
|
+
},
|
|
18
22
|
"./src/*": "./src/*"
|
|
19
23
|
},
|
|
20
24
|
"files": [
|
|
@@ -48,11 +52,11 @@
|
|
|
48
52
|
"marked": "^18.0.14"
|
|
49
53
|
},
|
|
50
54
|
"devDependencies": {
|
|
51
|
-
"echarts": "6.1.0",
|
|
52
|
-
"esbuild": "0.28.2",
|
|
55
|
+
"echarts": "^6.1.0",
|
|
56
|
+
"esbuild": "^0.28.2",
|
|
53
57
|
"marked": "^18.1.1",
|
|
54
|
-
"pele": "1.10.0",
|
|
55
|
-
"playwright": "1.64.1",
|
|
58
|
+
"pele": "^1.10.0",
|
|
59
|
+
"playwright": "^1.64.1",
|
|
56
60
|
"pptxgenjs": "^4.0.0",
|
|
57
61
|
"typescript": "^7.0.2"
|
|
58
62
|
},
|
package/src/blocks.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
// from `base` (a CDN by default) the first time a slide needs it, never before. A chart is data,
|
|
3
3
|
// read with JSON.parse; it takes the deck's colours and type, light or dark, and is drawn as SVG.
|
|
4
4
|
// A diagram is drawn by Pele from Mermaid's language as SVG, then shown as a picture, so nothing it
|
|
5
|
-
// makes runs or joins the page; its words are its picture's text for whoever can't see it. A host
|
|
5
|
+
// makes runs or joins the page; its words are its picture's text for whoever can't see it. A host gives
|
|
6
6
|
// its own drawers instead.
|
|
7
7
|
|
|
8
8
|
/** @typedef {{ base: string, root: HTMLElement, versions?: { echarts?: string, pele?: string } }} Where */
|
package/src/deck.css
CHANGED
|
@@ -33,7 +33,7 @@
|
|
|
33
33
|
.deck-slide pre.has-marks .deck-line:not(.is-marked) { opacity: 0.45; }
|
|
34
34
|
.deck-slide blockquote { border-inline-start: 0.25em solid var(--deck-fill); padding-inline-start: 0.9em; margin-inline: 0; font-style: italic; }
|
|
35
35
|
.deck-slide img { max-inline-size: 100%; max-block-size: 100%; object-fit: contain; }
|
|
36
|
-
.deck-slide .deck-picture { flex: 1; min-block-size: 0; display: grid; place-items: center; }
|
|
36
|
+
.deck-slide .deck-picture { flex: 1; min-block-size: 0; display: grid; grid-template: minmax(0, 1fr) / minmax(0, 1fr); place-items: center; }
|
|
37
37
|
.deck-slide .deck-picture img { block-size: 100%; }
|
|
38
38
|
.deck-table { overflow: hidden; }
|
|
39
39
|
.deck-slide table { border-collapse: collapse; inline-size: 100%; font-size: 0.85em; }
|
|
@@ -47,6 +47,16 @@
|
|
|
47
47
|
.deck-block .deck-block-source { display: none; }
|
|
48
48
|
.deck-block.is-failed .deck-block-source { display: block; }
|
|
49
49
|
.deck-number { position: absolute; inset-block-end: 2.6cqi; inset-inline-end: 3cqi; font-size: 1.5cqi; color: var(--deck-muted); font-variant-numeric: tabular-nums; }
|
|
50
|
+
/* The kit's logo (docs/kits.md): in its corner, small, or big on a cover and at the end; on dark paper its dark one, else on a light plate. */
|
|
51
|
+
.deck-logo { position: absolute; z-index: 1; inset-block-start: 3.2cqi; inset-inline-end: 3.5cqi; block-size: 4.5cqi; inline-size: auto; max-inline-size: 20cqi; object-fit: contain; pointer-events: none; }
|
|
52
|
+
.deck-logo[data-size="big"] { block-size: 8cqi; max-inline-size: 32cqi; }
|
|
53
|
+
.deck-logo[data-at$="start"] { inset-inline: 3.5cqi auto; }
|
|
54
|
+
.deck-logo[data-at^="bottom"] { inset-block: auto 3.2cqi; }
|
|
55
|
+
.deck-slide:has(.deck-logo[data-at="bottom-end"]) .deck-number { inset-inline: 3cqi auto; }
|
|
56
|
+
.deck-logo.is-dark { display: none; }
|
|
57
|
+
:is(.deck[data-scheme="light"] :is(.deck-slide[data-paper="ink"], .deck-class-invert), .deck[data-scheme="dark"] .deck-slide:is([data-paper="paper"], [data-paper="soft"]):not(.deck-class-invert), .deck[data-fill="dark"] .deck-slide[data-paper="fill"]) .deck-logo.has-dark { display: none; }
|
|
58
|
+
:is(.deck[data-scheme="light"] :is(.deck-slide[data-paper="ink"], .deck-class-invert), .deck[data-scheme="dark"] .deck-slide:is([data-paper="paper"], [data-paper="soft"]):not(.deck-class-invert), .deck[data-fill="dark"] .deck-slide[data-paper="fill"]) .deck-logo.is-dark { display: block; }
|
|
59
|
+
:is(.deck[data-scheme="light"] :is(.deck-slide[data-paper="ink"], .deck-class-invert), .deck[data-scheme="dark"] .deck-slide:is([data-paper="paper"], [data-paper="soft"]):not(.deck-class-invert), .deck[data-fill="dark"] .deck-slide[data-paper="fill"]) .deck-logo:not(.has-dark, .is-dark) { background: #fff; padding: 0.5cqi; border-radius: 0.5cqi; }
|
|
50
60
|
|
|
51
61
|
/* Layouts from their kit's definition: the paper they are on, where their words sit, how big their heading is. */
|
|
52
62
|
.deck-slide[data-paper="fill"] { background: var(--deck-fill); color: var(--deck-on-fill); }
|
package/src/deck.js
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
// the deck changes or is saved.
|
|
9
9
|
import { parseDeck } from './parse.js'
|
|
10
10
|
import { renderSlide, h } from './render.js'
|
|
11
|
-
import { tokensOf, schemeOf, VOICES } from './look.js'
|
|
11
|
+
import { tokensOf, schemeOf, VOICES, hex, contrast } from './look.js'
|
|
12
12
|
import { mergeKits } from './kit.js'
|
|
13
13
|
import { stringsFor } from './strings.js'
|
|
14
14
|
import { Lexer } from 'marked'
|
|
@@ -107,6 +107,8 @@ export function createDeck(root, options) {
|
|
|
107
107
|
...Object.fromEntries(colours.series.map((colour, n) => [`series-${n + 1}`, colour])),
|
|
108
108
|
})) root.style.setProperty(`--deck-${name}`, value)
|
|
109
109
|
root.dataset.scheme = scheme
|
|
110
|
+
// Whether the primary as a fill is dark, for a logo's dark one there.
|
|
111
|
+
root.dataset.fill = ((fill) => (contrast(fill, [255, 255, 255]) > contrast(fill, [0, 0, 0]) ? 'dark' : 'light'))(hex(tokens.primary) ?? [247, 197, 49])
|
|
110
112
|
root.dispatchEvent(new CustomEvent('deck-scheme', { detail: { scheme, colours, tokens } }))
|
|
111
113
|
}
|
|
112
114
|
const chooseScheme = () => {
|
package/src/index.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
export { createDeck, markdownOf } from './deck.js'
|
|
3
3
|
export { parseDeck } from './parse.js'
|
|
4
4
|
export { renderSlide } from './render.js'
|
|
5
|
-
export { STANDARD, mergeKits, layoutOf, inferLayout, labelOf, tokensFrom } from './kit.js'
|
|
5
|
+
export { STANDARD, CORNERS, mergeKits, layoutOf, logoOf, inferLayout, labelOf, tokensFrom } from './kit.js'
|
|
6
6
|
export { tokensOf, schemeOf, LOOKS, readable, contrast } from './look.js'
|
|
7
7
|
export { readDeckFile, writeDeckFile, rendererUrl, escape, unescape } from './file.js'
|
|
8
8
|
export { toPptx } from './pptx.js'
|
package/src/kit.js
CHANGED
|
@@ -2,18 +2,27 @@
|
|
|
2
2
|
// person, an organisation or a brain can make one and no kit can do more than lay out a slide.
|
|
3
3
|
// deck's own kit holds the usual layouts; a host's (an organisation's, a project's) and the deck
|
|
4
4
|
// file's own `kit:` go over it, in that order. A layout is a grid: its slots by name, the areas
|
|
5
|
-
// they take, its tracks, how its words sit, what paper it is on,
|
|
6
|
-
//
|
|
7
|
-
//
|
|
5
|
+
// they take, its tracks, how its words sit, what paper it is on, how big its logo shows, and the
|
|
6
|
+
// PowerPoint layout it is nearest to; a kit may turn one of the usual layouts off. Its tokens may
|
|
7
|
+
// hold a logo, a picture inside the kit and never an address. Everything is checked against a small
|
|
8
|
+
// grammar; what fails is dropped, with a reason. Pure.
|
|
8
9
|
|
|
9
10
|
/** @typedef {{ required?: boolean, max_words?: number, max_chars?: number, items?: [number, number], item_max_words?: number, heading_max_words?: number, kind?: string }} Budget */
|
|
10
|
-
/** @typedef {{ label?: Record<string, string> | string, intent?: string, use_when?: string, avoid_when?: string, slots: string[], budgets: Record<string, Budget>, areas: string[][], columns: string[], rows: string[], align: 'start' | 'center' | 'end', justify: 'start' | 'center' | 'end', paper: 'paper' | 'fill' | 'ink' | 'soft', title: 'normal' | 'large' | 'huge', pptx: string, numbered: boolean }} Layout */
|
|
11
|
+
/** @typedef {{ label?: Record<string, string> | string, intent?: string, use_when?: string, avoid_when?: string, slots: string[], budgets: Record<string, Budget>, areas: string[][], columns: string[], rows: string[], align: 'start' | 'center' | 'end', justify: 'start' | 'center' | 'end', paper: 'paper' | 'fill' | 'ink' | 'soft', title: 'normal' | 'large' | 'huge', logo: 'big' | 'small' | 'none', pptx: string, numbered: boolean }} Layout */
|
|
11
12
|
/** @typedef {{ name?: string, tokens: Record<string, any>, layouts: Record<string, Layout> }} Kit */
|
|
12
13
|
|
|
13
14
|
const NAME = /^[a-z][a-z0-9-]{0,31}$/
|
|
14
15
|
const TRACK = /^(auto|min-content|max-content|\d{1,2}(\.\d)?fr|\d{1,3}%)$/
|
|
15
16
|
const PPTX = ['Title Slide', 'Title and Content', 'Section Header', 'Two Content', 'Comparison', 'Title Only', 'Blank', 'Content with Caption', 'Picture with Caption']
|
|
16
|
-
const ENUMS = /** @type {const} */ ({ align: ['start', 'center', 'end'], justify: ['start', 'center', 'end'], paper: ['paper', 'fill', 'ink', 'soft'], title: ['normal', 'large', 'huge'] })
|
|
17
|
+
const ENUMS = /** @type {const} */ ({ align: ['start', 'center', 'end'], justify: ['start', 'center', 'end'], paper: ['paper', 'fill', 'ink', 'soft'], title: ['normal', 'large', 'huge'], logo: ['small', 'big', 'none'] })
|
|
18
|
+
/** The corners a logo sits in, as `logoAt` names them, the first by default. */
|
|
19
|
+
export const CORNERS = ['top-end', 'top-start', 'bottom-end', 'bottom-start']
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* A logo as a kit holds it: a picture inside the kit, PNG, JPEG, GIF, WebP or SVG as a data: address of 512 KB at most,
|
|
23
|
+
* never an address to fetch; else null. @param {unknown} value
|
|
24
|
+
*/
|
|
25
|
+
export const logoOf = (value) => (typeof value === 'string' && value.length <= 700_000 && /^data:image\/(png|jpeg|gif|webp|svg\+xml);base64,[a-z0-9+/]+=*$/i.test(value) ? value : null)
|
|
17
26
|
|
|
18
27
|
/** The names brains give the usual layouts, for the ones deck has. */
|
|
19
28
|
export const ALIASES = /** @type {Record<string, string>} */ ({
|
|
@@ -27,22 +36,22 @@ export const STANDARD = /** @type {Kit} */ ({
|
|
|
27
36
|
tokens: {},
|
|
28
37
|
layouts: Object.fromEntries(Object.entries(/** @type {Record<string, any>} */ ({
|
|
29
38
|
default: { label: { en: 'Title and content', fr: 'Titre et contenu' }, pptx: 'Title and Content' },
|
|
30
|
-
cover: { label: { en: 'Cover', fr: 'Couverture' }, justify: 'center', paper: 'fill', title: 'huge', pptx: 'Title Slide', numbered: false },
|
|
31
|
-
intro: { label: { en: 'Introduction', fr: 'Introduction' }, justify: 'center', paper: 'fill', title: 'large', pptx: 'Title Slide', numbered: false },
|
|
39
|
+
cover: { label: { en: 'Cover', fr: 'Couverture' }, justify: 'center', paper: 'fill', title: 'huge', logo: 'big', pptx: 'Title Slide', numbered: false },
|
|
40
|
+
intro: { label: { en: 'Introduction', fr: 'Introduction' }, justify: 'center', paper: 'fill', title: 'large', logo: 'big', pptx: 'Title Slide', numbered: false },
|
|
32
41
|
section: { label: { en: 'Section', fr: 'Section' }, justify: 'center', paper: 'ink', title: 'huge', pptx: 'Section Header', numbered: false },
|
|
33
42
|
center: { label: { en: 'Centred', fr: 'Centré' }, justify: 'center', align: 'center', pptx: 'Title Only' },
|
|
34
43
|
statement: { label: { en: 'Statement', fr: 'Affirmation' }, justify: 'center', align: 'center', title: 'large', pptx: 'Title Only' },
|
|
35
44
|
fact: { label: { en: 'Big number', fr: 'Grand chiffre' }, justify: 'center', align: 'center', title: 'huge', pptx: 'Title Only' },
|
|
36
45
|
quote: { label: { en: 'Quote', fr: 'Citation' }, justify: 'center', pptx: 'Title Only' },
|
|
37
|
-
end: { label: { en: 'Closing', fr: 'Clôture' }, justify: 'center', align: 'center', paper: 'fill', title: 'large', pptx: 'Title Slide', numbered: false },
|
|
38
|
-
full: { label: { en: 'Full', fr: 'Plein cadre' }, pptx: 'Blank', numbered: false },
|
|
39
|
-
none: { label: { en: 'Blank', fr: 'Vide' }, pptx: 'Blank', numbered: false },
|
|
46
|
+
end: { label: { en: 'Closing', fr: 'Clôture' }, justify: 'center', align: 'center', paper: 'fill', title: 'large', logo: 'big', pptx: 'Title Slide', numbered: false },
|
|
47
|
+
full: { label: { en: 'Full', fr: 'Plein cadre' }, logo: 'none', pptx: 'Blank', numbered: false },
|
|
48
|
+
none: { label: { en: 'Blank', fr: 'Vide' }, logo: 'none', pptx: 'Blank', numbered: false },
|
|
40
49
|
'two-cols': { label: { en: 'Two columns', fr: 'Deux colonnes' }, slots: ['default', 'right'], areas: 'default right', columns: '1fr 1fr', pptx: 'Two Content' },
|
|
41
50
|
'two-cols-header': { label: { en: 'Comparison', fr: 'Comparaison' }, slots: ['default', 'left', 'right', 'bottom'], areas: 'default default | left right | bottom bottom', columns: '1fr 1fr', rows: 'auto 1fr auto', pptx: 'Comparison' },
|
|
42
51
|
'three-cols': { label: { en: 'Three columns', fr: 'Trois colonnes' }, slots: ['default', 'left', 'middle', 'right'], areas: 'default default default | left middle right', columns: '1fr 1fr 1fr', rows: 'auto 1fr', pptx: 'Comparison' },
|
|
43
52
|
'image-left': { label: { en: 'Picture left', fr: 'Image à gauche' }, pptx: 'Picture with Caption', justify: 'center' },
|
|
44
53
|
'image-right': { label: { en: 'Picture right', fr: 'Image à droite' }, pptx: 'Picture with Caption', justify: 'center' },
|
|
45
|
-
image: { label: { en: 'Picture', fr: 'Image' }, pptx: 'Blank', numbered: false },
|
|
54
|
+
image: { label: { en: 'Picture', fr: 'Image' }, logo: 'none', pptx: 'Blank', numbered: false },
|
|
46
55
|
})).map(([name, given]) => [name, /** @type {Layout} */ (layoutOf(given).layout)])),
|
|
47
56
|
})
|
|
48
57
|
|
|
@@ -93,7 +102,7 @@ export function layoutOf(given) {
|
|
|
93
102
|
return {
|
|
94
103
|
layout: /** @type {Layout} */ ({
|
|
95
104
|
label, intent: words(given.intent), use_when: words(given.use_when), avoid_when: words(given.avoid_when), slots, budgets, areas: rows, columns, rows: rowTracks,
|
|
96
|
-
align: pick('align', 'start'), justify: pick('justify', 'start'), paper: pick('paper', 'paper'), title: pick('title', 'normal'),
|
|
105
|
+
align: pick('align', 'start'), justify: pick('justify', 'start'), paper: pick('paper', 'paper'), title: pick('title', 'normal'), logo: pick('logo', 'small'),
|
|
97
106
|
pptx: PPTX.includes(given.pptx) ? given.pptx : slots.length > 1 ? 'Comparison' : 'Title and Content', numbered: given.numbered !== false,
|
|
98
107
|
}),
|
|
99
108
|
problems,
|
|
@@ -137,7 +146,8 @@ export function tokensFrom(given) {
|
|
|
137
146
|
}
|
|
138
147
|
|
|
139
148
|
/**
|
|
140
|
-
* Kits merged, the later over the earlier: their tokens, and their layouts by name, each
|
|
149
|
+
* Kits merged, the later over the earlier: their tokens, their dark ones within theirs, and their layouts by name, each
|
|
150
|
+
* checked; a layout given as false is taken away, but the default. A logo that isn't a picture inside the kit is dropped.
|
|
141
151
|
* @param {...any} kits @returns {{ kit: Kit, problems: string[] }}
|
|
142
152
|
*/
|
|
143
153
|
export function mergeKits(...kits) {
|
|
@@ -147,14 +157,22 @@ export function mergeKits(...kits) {
|
|
|
147
157
|
for (const given of kits) {
|
|
148
158
|
if (!given || typeof given !== 'object') continue
|
|
149
159
|
if (typeof given.name === 'string') kit.name = given.name
|
|
150
|
-
if (given.tokens && typeof given.tokens === 'object')
|
|
160
|
+
if (given.tokens && typeof given.tokens === 'object') {
|
|
161
|
+
const next = tokensFrom(given.tokens), dark = next.dark && typeof next.dark === 'object' ? { ...kit.tokens.dark, ...next.dark } : kit.tokens.dark
|
|
162
|
+
kit.tokens = { ...kit.tokens, ...next, ...(dark && { dark }) }
|
|
163
|
+
}
|
|
151
164
|
for (const [name, definition] of Object.entries(given.layouts ?? given.types ?? {})) {
|
|
152
165
|
if (!NAME.test(name)) { problems.push(`layout ${name}: not a-z, 0-9 and -`); continue }
|
|
166
|
+
if (definition === false) { if (name !== 'default') delete kit.layouts[name]; continue }
|
|
153
167
|
const { layout, problems: found } = layoutOf(definition)
|
|
154
168
|
problems.push(...found.map((p) => `layout ${name}: ${p}`))
|
|
155
169
|
if (layout) kit.layouts[name] = layout
|
|
156
170
|
}
|
|
157
171
|
}
|
|
172
|
+
for (const [where, tokens] of /** @type {[string, Record<string, any> | undefined][]} */ ([['logo', kit.tokens], ['dark logo', kit.tokens.dark]])) {
|
|
173
|
+
if (tokens?.logo !== undefined && !logoOf(tokens.logo)) { problems.push(`${where}: not a picture inside the kit (PNG, JPEG, GIF, WebP or SVG as data:, 512 KB at most)`); delete tokens.logo }
|
|
174
|
+
}
|
|
175
|
+
if (kit.tokens.logoAt !== undefined && !CORNERS.includes(kit.tokens.logoAt)) { problems.push(`logoAt ${kit.tokens.logoAt} is not one of ${CORNERS.join(', ')}`); delete kit.tokens.logoAt }
|
|
158
176
|
return { kit, problems }
|
|
159
177
|
}
|
|
160
178
|
|
package/src/look.js
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
// lightened until it reads at AA on the paper it sits on (4.5:1 for words, 3:1 for a chart's
|
|
5
5
|
// marks), so a brain's or a person's colours can't make a slide unreadable. Pure.
|
|
6
6
|
|
|
7
|
-
/** @typedef {{ paper: string, ink: string, primary: string, voice: string, radius: number, muted?: string, soft?: string, dark?: Record<string, string>, fonts?: { heading: string[], body: string[] } }} Tokens */
|
|
7
|
+
/** @typedef {{ paper: string, ink: string, primary: string, voice: string, radius: number, muted?: string, soft?: string, series?: string[], dark?: Record<string, string>, fonts?: { heading: string[], body: string[] } }} Tokens */
|
|
8
8
|
/** @typedef {{ paper: string, ink: string, muted: string, line: string, primary: string, onPrimary: string, soft: string, series: string[] }} Scheme */
|
|
9
9
|
|
|
10
10
|
/** Looks a deck can name (`theme:`), each a handful of tokens. */
|
|
@@ -46,6 +46,7 @@ function clean(given) {
|
|
|
46
46
|
if (given.fonts && Array.isArray(given.fonts.heading) && Array.isArray(given.fonts.body)) kept.fonts = { heading: given.fonts.heading.map(String), body: given.fonts.body.map(String) }
|
|
47
47
|
if (typeof given.voice === 'string') kept.voice = given.voice
|
|
48
48
|
if (typeof given.radius === 'number') kept.radius = given.radius
|
|
49
|
+
if (Array.isArray(given.series) && given.series.length && given.series.every((/** @type {unknown} */ c) => typeof c === 'string' && hex(c))) kept.series = given.series.slice(0, 6)
|
|
49
50
|
return kept
|
|
50
51
|
}
|
|
51
52
|
|
|
@@ -63,7 +64,9 @@ export function schemeOf(tokens, scheme) {
|
|
|
63
64
|
// The primary as a fill keeps its own colour; as words or marks it is made to read on the paper.
|
|
64
65
|
const text = readable(primary, paper, 4.5)
|
|
65
66
|
const onPrimary = contrast(ink, primary) >= contrast(paper, primary) ? ink : paper
|
|
66
|
-
|
|
67
|
+
// A chart's colours: the kit's own (a template's accents), else the primary turned round the wheel; each made to read at 3:1.
|
|
68
|
+
const given = (tokens.series ?? []).map(hex).filter((c) => c !== null)
|
|
69
|
+
const series = [0, 1, 2, 3, 4, 5].map((n) => readable(/** @type {number[]} */ (given[n % (given.length || 1)] ?? rotate(primary, n * 57)), paper, 3))
|
|
67
70
|
return {
|
|
68
71
|
paper: css(paper), ink: css(ink), muted: css(readable(round(hex(own.muted ?? (scheme === 'light' ? tokens.muted ?? '' : '')) ?? mix(ink, paper, 0.38)), paper, 4.5)), line: css(mix(ink, paper, 0.82)),
|
|
69
72
|
primary: css(text), onPrimary: css(onPrimary), soft: css(hex(own.soft ?? (scheme === 'light' ? tokens.soft ?? '' : '')) ?? mix(primary, paper, 0.78)), series: series.map(css),
|