@zanim/web 0.1.0-beta.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.
package/src/svg.js ADDED
@@ -0,0 +1,96 @@
1
+ import { Transform2D } from './core.js';
2
+
3
+ const TOKEN_RE=/[A-Za-z]|[-+]?(?:\d*\.\d+|\d+\.?)(?:[eE][-+]?\d+)?/g;
4
+ const NUMBER_RE=/[-+]?(?:\d*\.\d+|\d+\.?)(?:[eE][-+]?\d+)?/g;
5
+ const TRANSFORM_RE=/([A-Za-z]+)\s*\(([^)]*)\)/g;
6
+ const lerp=(a,b,t)=>[a[0]+(b[0]-a[0])*t,a[1]+(b[1]-a[1])*t];
7
+ const line=(a,b)=>[a,lerp(a,b,1/3),lerp(a,b,2/3),b];
8
+ const quadratic=(a,q,b)=>[a,[a[0]+(q[0]-a[0])*2/3,a[1]+(q[1]-a[1])*2/3],[b[0]+(q[0]-b[0])*2/3,b[1]+(q[1]-b[1])*2/3],b];
9
+ const eq=(a,b)=>Math.abs(a[0]-b[0])<1e-12&&Math.abs(a[1]-b[1])<1e-12;
10
+
11
+ function mapEllipse(cx,cy,rx,ry,phi,p){const c=Math.cos(phi),s=Math.sin(phi);return[cx+rx*p[0]*c-ry*p[1]*s,cy+rx*p[0]*s+ry*p[1]*c];}
12
+ function vectorAngle(ux,uy,vx,vy){return Math.atan2(ux*vy-uy*vx,ux*vx+uy*vy);}
13
+ function arcToCubics(start,rx,ry,rotationDeg,largeArc,sweep,end){
14
+ rx=Math.abs(rx);ry=Math.abs(ry);if(rx===0||ry===0||eq(start,end))return eq(start,end)?[]:[line(start,end)];
15
+ const phi=(rotationDeg%360)*Math.PI/180,cp=Math.cos(phi),sp=Math.sin(phi),dx=(start[0]-end[0])*.5,dy=(start[1]-end[1])*.5;
16
+ const x1p=cp*dx+sp*dy,y1p=-sp*dx+cp*dy,need=x1p*x1p/(rx*rx)+y1p*y1p/(ry*ry);
17
+ if(need>1){const scale=Math.sqrt(need);rx*=scale;ry*=scale;}
18
+ const numerator=rx*rx*ry*ry-rx*rx*y1p*y1p-ry*ry*x1p*x1p,denominator=rx*rx*y1p*y1p+ry*ry*x1p*x1p;
19
+ let factor=denominator===0?0:Math.sqrt(Math.max(0,numerator/denominator));if(largeArc===sweep)factor=-factor;
20
+ const cxp=factor*(rx*y1p/ry),cyp=factor*(-ry*x1p/rx),cx=cp*cxp-sp*cyp+(start[0]+end[0])*.5,cy=sp*cxp+cp*cyp+(start[1]+end[1])*.5;
21
+ const ux=(x1p-cxp)/rx,uy=(y1p-cyp)/ry,vx=(-x1p-cxp)/rx,vy=(-y1p-cyp)/ry;
22
+ const theta=Math.atan2(uy,ux);let delta=vectorAngle(ux,uy,vx,vy);if(!sweep&&delta>0)delta-=2*Math.PI;else if(sweep&&delta<0)delta+=2*Math.PI;
23
+ const count=Math.max(1,Math.ceil(Math.abs(delta)/(Math.PI/2))),step=delta/count,out=[];let previous=start;
24
+ for(let index=0;index<count;index++){
25
+ const a0=theta+index*step,a1=a0+step,k=4/3*Math.tan(step/4),u0=[Math.cos(a0),Math.sin(a0)],u3=[Math.cos(a1),Math.sin(a1)],u1=[u0[0]-k*u0[1],u0[1]+k*u0[0]],u2=[u3[0]+k*u3[1],u3[1]-k*u3[0]];
26
+ const p1=mapEllipse(cx,cy,rx,ry,phi,u1),p2=mapEllipse(cx,cy,rx,ry,phi,u2),p3=index===count-1?end:mapEllipse(cx,cy,rx,ry,phi,u3);out.push([previous,p1,p2,p3]);previous=p3;
27
+ }
28
+ return out;
29
+ }
30
+
31
+ export function parseSvgPathData(data){
32
+ const tokens=String(data).replaceAll(',',' ').match(TOKEN_RE)??[];let i=0,command=null,current=[0,0],start=[0,0],segments=[],contours=[],prevC=null,prevQ=null;
33
+ const number=()=>{if(i>=tokens.length||/^[A-Za-z]$/.test(tokens[i]))throw new Error(`invalid SVG path near token ${i}`);return Number(tokens[i++]);};
34
+ const point=relative=>{const x=number(),y=number();return relative?[current[0]+x,current[1]+y]:[x,y];};
35
+ const finish=closed=>{if(segments.length){contours.push({closed,segments});segments=[];}};
36
+ while(i<tokens.length){if(/^[A-Za-z]$/.test(tokens[i]))command=tokens[i++];if(!command)throw new Error('SVG path does not start with a command');const relative=command===command.toLowerCase(),op=command.toUpperCase();
37
+ if(op==='Z'){if(!eq(current,start))segments.push(line(current,start));current=start;finish(true);prevC=prevQ=null;command=null;continue;}
38
+ if(op==='M'){const p=point(relative);if(segments.length)finish(false);current=start=p;prevC=prevQ=null;command=relative?'l':'L';continue;}
39
+ if(op==='L'){const p=point(relative);segments.push(line(current,p));current=p;prevC=prevQ=null;}
40
+ else if(op==='H'){const x=number()+(relative?current[0]:0),p=[x,current[1]];segments.push(line(current,p));current=p;prevC=prevQ=null;}
41
+ else if(op==='V'){const y=number()+(relative?current[1]:0),p=[current[0],y];segments.push(line(current,p));current=p;prevC=prevQ=null;}
42
+ else if(op==='C'){const c1=point(relative),c2=point(relative),p=point(relative);segments.push([current,c1,c2,p]);current=p;prevC=c2;prevQ=null;}
43
+ else if(op==='S'){const c1=prevC?[2*current[0]-prevC[0],2*current[1]-prevC[1]]:current,c2=point(relative),p=point(relative);segments.push([current,c1,c2,p]);current=p;prevC=c2;prevQ=null;}
44
+ else if(op==='Q'){const q=point(relative),p=point(relative);segments.push(quadratic(current,q,p));current=p;prevQ=q;prevC=null;}
45
+ else if(op==='T'){const q=prevQ?[2*current[0]-prevQ[0],2*current[1]-prevQ[1]]:current,p=point(relative);segments.push(quadratic(current,q,p));current=p;prevQ=q;prevC=null;}
46
+ else if(op==='A'){const rx=number(),ry=number(),rotation=number(),large=!!Number(number()),sweep=!!Number(number()),p=point(relative);segments.push(...arcToCubics(current,rx,ry,rotation,large,sweep,p));current=p;prevC=prevQ=null;}
47
+ else throw new Error(`unsupported SVG path command ${command}`);
48
+ }
49
+ if(segments.length)finish(false);return contours;
50
+ }
51
+
52
+ function parseTransform(value){
53
+ let out=Transform2D.identity();if(!value)return out;TRANSFORM_RE.lastIndex=0;let match;
54
+ while((match=TRANSFORM_RE.exec(value))){const name=match[1],args=(match[2].match(NUMBER_RE)??[]).map(Number);let local;
55
+ if(name==='matrix'&&args.length===6){const[a,b,c,d,e,f]=args;local=new Transform2D(a,c,b,d,e,f);}
56
+ else if(name==='translate'&&args.length>=1&&args.length<=2)local=Transform2D.translation(args[0],args[1]??0);
57
+ else if(name==='scale'&&args.length>=1&&args.length<=2)local=Transform2D.scaling(args[0],args[1]??args[0]);
58
+ else if(name==='rotate'&&(args.length===1||args.length===3)){const r=Transform2D.rotation(args[0]*Math.PI/180);local=args.length===3?Transform2D.translation(args[1],args[2]).mul(r).mul(Transform2D.translation(-args[1],-args[2])):r;}
59
+ else if(name==='skewX'&&args.length===1)local=new Transform2D(1,Math.tan(args[0]*Math.PI/180),0,1,0,0);
60
+ else if(name==='skewY'&&args.length===1)local=new Transform2D(1,0,Math.tan(args[0]*Math.PI/180),1,0,0);
61
+ else throw new Error(`unsupported SVG transform ${name}(${match[2]})`);
62
+ out=out.mul(local);
63
+ }
64
+ return out;
65
+ }
66
+ function applyTransform(contours,t){return contours.map(c=>({closed:c.closed,segments:c.segments.map(seg=>seg.map(([x,y])=>t.apply(x,y)))}));}
67
+ function lengthValue(v){const m=String(v??'').match(NUMBER_RE);return m?Number(m[0]):null;}
68
+ function styleMap(el){const out={};for(const part of String(el.getAttribute('style')??'').split(';'))if(part.includes(':')){const i=part.indexOf(':');out[part.slice(0,i).trim()]=part.slice(i+1).trim();}for(const key of ['fill','fill-opacity','stroke','stroke-opacity','stroke-width','opacity'])if(el.hasAttribute(key))out[key]=el.getAttribute(key);return out;}
69
+ function parseColor(value,def){if(value==null)return def;const v=String(value).trim();if(v==='none')return null;if(v.startsWith('#'))return v;if(/^rgba?\(/i.test(v))return v;if(v==='black')return '#000000';if(v==='white')return '#ffffff';if(v==='red')return '#ff0000';if(v==='blue')return '#0000ff';if(v.startsWith('var('))return def;throw new Error(`unsupported SVG color ${v}`);}
70
+ function colorWithOpacity(color,opacity){if(color==null||opacity>=.999999)return color;if(color.startsWith('#')){let h=color.slice(1);if(h.length===3)h=[...h].map(x=>x+x).join('');if(h.length===6){const a=Math.max(0,Math.min(255,Math.round(opacity*255))).toString(16).padStart(2,'0');return `#${h}${a}`;}}return color;}
71
+
72
+ export function vectorDocumentFromSvg(source,{unitScale=1/72}={}){
73
+ if(typeof DOMParser==='undefined')throw new Error('SVG import requires a browser DOMParser');
74
+ const parsed=new DOMParser().parseFromString(String(source),'image/svg+xml');
75
+ let root=parsed.documentElement;
76
+ let rootTag=root.tagName?.split(':').pop()?.toLowerCase();
77
+ if(rootTag==='parsererror')throw new Error('invalid SVG');
78
+ if(rootTag!=='svg'){
79
+ const nested=parsed.querySelector('svg');
80
+ if(nested){root=nested;rootTag='svg';}
81
+ }
82
+ if(rootTag!=='svg')throw new Error(`SVG document root must contain <svg>, got <${root.tagName}>`);
83
+ const vb=root.getAttribute('viewBox')||root.getAttribute('viewbox');let x0=0,y0=0,width,height;
84
+ if(vb){const values=(vb.match(NUMBER_RE)??[]).slice(0,4).map(Number);if(values.length===4)[x0,y0,width,height]=values;}
85
+ if(!(width>0&&height>0)){width=lengthValue(root.getAttribute('width')||root.getAttribute('data-width'));height=lengthValue(root.getAttribute('height')||root.getAttribute('data-height'));}
86
+ if(!(width>0&&height>0))throw new Error(`SVG requires viewBox or width/height; root=${root.outerHTML.slice(0,180)}`);
87
+ const idMap=new Map([...root.querySelectorAll('[id]')].map(el=>[el.id,el])),paths=[];let nextGroup=0;
88
+ function render(el,parent,inheritFill,inheritStroke,inheritOpacity,groupOverride=null,fromUse=false){const tag=el.tagName?.split(':').pop()?.toLowerCase();if(tag==='defs'&&!fromUse)return;const transform=parent.mul(parseTransform(el.getAttribute?.('transform'))),styles=styleMap(el),opacity=inheritOpacity*Number(styles.opacity||1);let fill=parseColor(styles.fill,inheritFill);fill=colorWithOpacity(fill,opacity*Number(styles['fill-opacity']||1));let strokeColor=parseColor(styles.stroke,inheritStroke?.color??null),stroke=null;if(strokeColor!=null){strokeColor=colorWithOpacity(strokeColor,opacity*Number(styles['stroke-opacity']||1));stroke={color:strokeColor,width:Number(styles['stroke-width']??inheritStroke?.width??1)*unitScale};}
89
+ if(tag==='use'){const href=el.getAttribute('href')||el.getAttribute('xlink:href');if(!href?.startsWith('#')||!idMap.has(href.slice(1)))return;const x=lengthValue(el.getAttribute('x'))||0,y=lengthValue(el.getAttribute('y'))||0,group=groupOverride??nextGroup++;render(idMap.get(href.slice(1)),transform.mul(Transform2D.translation(x,y)),fill,stroke,opacity,group,true);return;}
90
+ if(tag==='path'&&el.getAttribute('d')){const contours=parseSvgPathData(el.getAttribute('d'));if(contours.length){const group=groupOverride??nextGroup++;paths.push({group,fill,stroke,contours:applyTransform(contours,transform)});}return;}
91
+ if(['svg','g','symbol'].includes(tag)||fromUse)for(const child of el.children)render(child,transform,fill,stroke,opacity,groupOverride,fromUse);
92
+ }
93
+ render(root,Transform2D.identity(),'#000000',null,1);
94
+ const final=Transform2D.scaling(unitScale,-unitScale).mul(Transform2D.translation(-(x0+width/2),-(y0+height/2)));
95
+ return {width:width*unitScale,height:height*unitScale,group_count:paths.length?nextGroup:0,paths:paths.map(p=>({...p,contours:applyTransform(p.contours,final)}))};
96
+ }
package/src/three.js ADDED
@@ -0,0 +1,129 @@
1
+ import { BLUE, ZObject, parseWebColor, sampleValue } from './core.js';
2
+
3
+ export class Vec3 {
4
+ constructor(x=0,y=0,z=0){this.x=Number(x);this.y=Number(y);this.z=Number(z);}
5
+ static from(value){if(value instanceof Vec3)return value;if(Array.isArray(value)&&value.length===3)return new Vec3(...value);throw new TypeError('expected Vec3 or [x,y,z]');}
6
+ add(v){v=Vec3.from(v);return new Vec3(this.x+v.x,this.y+v.y,this.z+v.z);}
7
+ sub(v){v=Vec3.from(v);return new Vec3(this.x-v.x,this.y-v.y,this.z-v.z);}
8
+ mul(s){return new Vec3(this.x*s,this.y*s,this.z*s);}
9
+ dot(v){v=Vec3.from(v);return this.x*v.x+this.y*v.y+this.z*v.z;}
10
+ cross(v){v=Vec3.from(v);return new Vec3(this.y*v.z-this.z*v.y,this.z*v.x-this.x*v.z,this.x*v.y-this.y*v.x);}
11
+ get length(){return Math.hypot(this.x,this.y,this.z);}
12
+ normalized(){const n=this.length;if(n<=1e-15)throw new RangeError('cannot normalize zero Vec3');return this.mul(1/n);}
13
+ }
14
+
15
+ export class Transform3D {
16
+ constructor(
17
+ m00=1,m01=0,m02=0,m03=0,
18
+ m10=0,m11=1,m12=0,m13=0,
19
+ m20=0,m21=0,m22=1,m23=0,
20
+ m30=0,m31=0,m32=0,m33=1,
21
+ ){Object.assign(this,{m00,m01,m02,m03,m10,m11,m12,m13,m20,m21,m22,m23,m30,m31,m32,m33});}
22
+ static identity(){return new Transform3D();}
23
+ static translation(x,y,z){return new Transform3D(1,0,0,x,0,1,0,y,0,0,1,z,0,0,0,1);}
24
+ static scaling(x,y=x,z=x){return new Transform3D(x,0,0,0,0,y,0,0,0,0,z,0,0,0,0,1);}
25
+ static rotationAxis(axis,radians){
26
+ const a=Vec3.from(axis).normalized(),{x,y,z}=a,c=Math.cos(radians),s=Math.sin(radians),q=1-c;
27
+ return new Transform3D(
28
+ c+x*x*q,x*y*q-z*s,x*z*q+y*s,0,
29
+ y*x*q+z*s,c+y*y*q,y*z*q-x*s,0,
30
+ z*x*q-y*s,z*y*q+x*s,c+z*z*q,0,
31
+ 0,0,0,1,
32
+ );
33
+ }
34
+ static rotationX(r){return Transform3D.rotationAxis(new Vec3(1,0,0),r);}
35
+ static rotationY(r){return Transform3D.rotationAxis(new Vec3(0,1,0),r);}
36
+ static rotationZ(r){return Transform3D.rotationAxis(new Vec3(0,0,1),r);}
37
+ mul(b){
38
+ if(!(b instanceof Transform3D))throw new TypeError('Transform3D.mul requires Transform3D');
39
+ const a=this,A=a.asArray(),B=b.asArray(),out=new Array(16).fill(0);
40
+ for(let r=0;r<4;r++)for(let c=0;c<4;c++)for(let k=0;k<4;k++)out[r*4+c]+=A[r*4+k]*B[k*4+c];
41
+ return new Transform3D(...out);
42
+ }
43
+ translate(x,y,z){return this.mul(Transform3D.translation(x,y,z));}
44
+ scale(x,y=x,z=x){return this.mul(Transform3D.scaling(x,y,z));}
45
+ rotateX(r){return this.mul(Transform3D.rotationX(r));}
46
+ rotateY(r){return this.mul(Transform3D.rotationY(r));}
47
+ rotateZ(r){return this.mul(Transform3D.rotationZ(r));}
48
+ apply(point){
49
+ const p=Vec3.from(point),x=this.m00*p.x+this.m01*p.y+this.m02*p.z+this.m03,y=this.m10*p.x+this.m11*p.y+this.m12*p.z+this.m13,z=this.m20*p.x+this.m21*p.y+this.m22*p.z+this.m23,w=this.m30*p.x+this.m31*p.y+this.m32*p.z+this.m33;
50
+ if(Math.abs(w)<=1e-15)throw new RangeError('Transform3D produced a point at infinity');return new Vec3(x/w,y/w,z/w);
51
+ }
52
+ asArray(){return [this.m00,this.m01,this.m02,this.m03,this.m10,this.m11,this.m12,this.m13,this.m20,this.m21,this.m22,this.m23,this.m30,this.m31,this.m32,this.m33];}
53
+ }
54
+
55
+ function flattenVec3(values,name){
56
+ if(values instanceof Float32Array){if(values.length%3)throw new RangeError(`${name} must contain xyz triples`);return values;}
57
+ const out=[];for(const value of values){const v=Vec3.from(value);out.push(v.x,v.y,v.z);}return new Float32Array(out);
58
+ }
59
+
60
+ export class TriangleMesh {
61
+ constructor(vertices,normals,indices){
62
+ this.positions=flattenVec3(vertices,'vertices');this.normals=flattenVec3(normals,'normals');this.indices=indices instanceof Uint32Array?indices:new Uint32Array(indices);
63
+ this.vertexCount=this.positions.length/3;this.indexCount=this.indices.length;
64
+ if(this.vertexCount<3)throw new RangeError('TriangleMesh requires at least 3 vertices');
65
+ if(this.normals.length!==this.positions.length)throw new RangeError('TriangleMesh normals must match vertices');
66
+ if(!this.indexCount||this.indexCount%3)throw new RangeError('TriangleMesh indices must contain triangles');
67
+ for(const index of this.indices)if(index>=this.vertexCount)throw new RangeError('TriangleMesh index is outside vertex range');
68
+ }
69
+ }
70
+
71
+ function packColorRGBA(color){
72
+ const rgba=parseWebColor(color);if(!rgba)throw new TypeError('3D mesh color is required');
73
+ return (((rgba[0]&255)<<24)|((rgba[1]&255)<<16)|((rgba[2]&255)<<8)|(rgba[3]&255))>>>0;
74
+ }
75
+
76
+ function atTime(value,time,object){return typeof value==='function'?value(time,object):value;}
77
+
78
+ export class MeshObject3D {
79
+ constructor(mesh,{transform=Transform3D.identity(),geometryTransform=Transform3D.identity(),color=BLUE,opacity=1}={}){
80
+ if(!(mesh instanceof TriangleMesh))throw new TypeError('MeshObject3D requires TriangleMesh');
81
+ this.mesh=mesh;this.transform=transform;this.geometryTransform=geometryTransform;this.color=color;this.opacity=opacity;
82
+ }
83
+ stateAt(time){
84
+ const transform=atTime(this.transform,time,this);if(!(transform instanceof Transform3D))throw new TypeError('3D transform provider must return Transform3D');
85
+ const geometry=atTime(this.geometryTransform,time,this);if(!(geometry instanceof Transform3D))throw new TypeError('3D geometry transform must be Transform3D');
86
+ const opacity=sampleValue(this.opacity,time);if(!(opacity>=0&&opacity<=1))throw new RangeError('3D opacity must be in [0,1]');
87
+ return {model:transform.mul(geometry).asArray(),colorRGBA:packColorRGBA(atTime(this.color,time,this)),opacity};
88
+ }
89
+ }
90
+
91
+ let boxMeshCache=null;
92
+ export function unitBoxMesh(){
93
+ if(boxMeshCache)return boxMeshCache;
94
+ const h=.5,faces=[
95
+ [[0,0,1],[[-h,-h,h],[h,-h,h],[h,h,h],[-h,h,h]]],
96
+ [[0,0,-1],[[h,-h,-h],[-h,-h,-h],[-h,h,-h],[h,h,-h]]],
97
+ [[1,0,0],[[h,-h,h],[h,-h,-h],[h,h,-h],[h,h,h]]],
98
+ [[-1,0,0],[[-h,-h,-h],[-h,-h,h],[-h,h,h],[-h,h,-h]]],
99
+ [[0,1,0],[[-h,h,h],[h,h,h],[h,h,-h],[-h,h,-h]]],
100
+ [[0,-1,0],[[-h,-h,-h],[h,-h,-h],[h,-h,h],[-h,-h,h]]],
101
+ ],vertices=[],normals=[],indices=[];
102
+ for(const [normal,corners] of faces){const base=vertices.length;for(const p of corners){vertices.push(p);normals.push(normal);}indices.push(base,base+1,base+2,base,base+2,base+3);}
103
+ return boxMeshCache=new TriangleMesh(vertices,normals,indices);
104
+ }
105
+
106
+ export function Box3D(size=new Vec3(2,2,2),options={}){const s=Vec3.from(size);if(s.x<=0||s.y<=0||s.z<=0)throw new RangeError('box dimensions must be positive');return new MeshObject3D(unitBoxMesh(),{...options,geometryTransform:Transform3D.scaling(s.x,s.y,s.z)});}
107
+ export function Cube3D(side=2,options={}){return Box3D(new Vec3(side,side,side),options);}
108
+
109
+ export class Camera3D {
110
+ constructor({position=new Vec3(4.5,3.2,5.5),target=new Vec3(),up=new Vec3(0,1,0),fovYDegrees=45,near=.05,far=100,orthographicHeight=null,layerZIndex=0}={}){
111
+ this.position=Vec3.from(position);this.target=Vec3.from(target);this.up=Vec3.from(up);this.fovYDegrees=Number(fovYDegrees);this.near=Number(near);this.far=Number(far);this.orthographicHeight=orthographicHeight==null?null:Number(orthographicHeight);this.layerZIndex=Number(layerZIndex);
112
+ if(!(this.near>0&&this.far>this.near))throw new RangeError('Camera3D requires 0 < near < far');if(!(this.fovYDegrees>=1&&this.fovYDegrees<179))throw new RangeError('Camera3D fovYDegrees must be in [1,179)');if(this.orthographicHeight!=null&&!(this.orthographicHeight>0))throw new RangeError('Camera3D orthographicHeight must be positive');if(this.target.sub(this.position).length<=1e-12)throw new RangeError('Camera3D position and target must differ');if(this.up.length<=1e-12)throw new RangeError('Camera3D up vector must be non-zero');
113
+ }
114
+ }
115
+
116
+ export class Scene3DLayer extends ZObject {
117
+ constructor(meshes,{camera=new Camera3D(),resolution=1,maxWidth=1280,maxHeight=720,...rest}={}){
118
+ super({zIndex:camera.layerZIndex,...rest});this.meshes=[...meshes];if(this.meshes.some(mesh=>!(mesh instanceof MeshObject3D)))throw new TypeError('Scene3DLayer meshes must be MeshObject3D');this.camera=camera;this.resolution=Number(resolution);this.maxWidth=Math.min(1280,Math.round(maxWidth));this.maxHeight=Math.min(720,Math.round(maxHeight));this._wasm=null;this._upload=null;this._canvas=null;
119
+ }
120
+ draw(renderer){
121
+ if(!this.meshes.length)return;const fullW=renderer.canvas.width,fullH=renderer.canvas.height;if(fullW<=0||fullH<=0)return;
122
+ let width=Math.max(1,Math.min(this.maxWidth,Math.round(fullW*this.resolution))),height=Math.max(1,Math.round(width*fullH/fullW));if(height>this.maxHeight){width=Math.max(1,Math.round(width*this.maxHeight/height));height=this.maxHeight;}
123
+ if(this._wasm!==renderer.wasm||!this._upload||renderer.wasm._3dUpload!==this._upload){this._wasm=renderer.wasm;this._upload=renderer.wasm.upload3DGeometry(this.meshes.map(mesh=>mesh.mesh));}
124
+ const pixels=renderer.wasm.render3D(width,height,this.camera,this._upload,this.meshes.map(mesh=>mesh.stateAt(renderer.time??0)));
125
+ if(!this._canvas)this._canvas=document.createElement('canvas');if(this._canvas.width!==width||this._canvas.height!==height){this._canvas.width=width;this._canvas.height=height;}
126
+ this._canvas.getContext('2d').putImageData(new ImageData(new Uint8ClampedArray(pixels),width,height),0,0);
127
+ const ctx=renderer.ctx;ctx.save();ctx.setTransform(1,0,0,1,0,0);ctx.globalAlpha*=Math.max(0,Math.min(1,this.opacity));ctx.drawImage(this._canvas,0,0,fullW,fullH);ctx.restore();
128
+ }
129
+ }
package/src/typst.js ADDED
@@ -0,0 +1,39 @@
1
+ import { VectorObject2D } from './core.js';
2
+ import { vectorDocumentFromSvg } from './svg.js';
3
+
4
+ const EMPTY_DOCUMENT=Object.freeze({width:1,height:1,group_count:0,paths:[]});
5
+ let configuredCompiler=null;
6
+ const documentCache=new Map();
7
+
8
+ function colorCSS(value){if(value==null)return null;if(typeof value==='string')return value;const[r,g,b,a=255]=value.map(Number);return `#${[r,g,b,a].map(v=>globalThis.Math.max(0,globalThis.Math.min(255,globalThis.Math.round(v))).toString(16).padStart(2,'0')).join('')}`;}
9
+ function documentFromData(raw){if(!raw||!(Number(raw.width)>0)||!(Number(raw.height)>0)||!Array.isArray(raw.paths))throw new TypeError('Typst compiler returned an invalid VectorDocument');return{width:Number(raw.width),height:Number(raw.height),group_count:Number(raw.group_count??1),paths:raw.paths.map(path=>({group:Number(path.group??0),fill:colorCSS(path.fill),stroke:path.stroke?{color:colorCSS(path.stroke.color),width:Number(path.stroke.width)}:null,contours:path.contours.map(contour=>({closed:!!contour.closed,segments:contour.segments.map(seg=>seg.map(point=>[Number(point[0]),Number(point[1])]))}))}))};}
10
+ async function documentFromCompiledSvg(value){
11
+ if(typeof value!=='string'||!value)throw new TypeError('precompiled Typst asset must be an SVG string or URL');
12
+ if(documentCache.has(value))return documentCache.get(value);
13
+ const svg=value.trimStart().startsWith('<svg')?value:await fetch(value).then(response=>{if(!response.ok)throw new Error(`failed to load precompiled Typst SVG (${response.status})`);return response.text();});
14
+ const document=vectorDocumentFromSvg(svg);documentCache.set(value,document);return document;
15
+ }
16
+ function missingCompilerError(kind){return new Error(`${kind} requires authoring-time Typst compilation. With Vite, add zanim() from @zanim/web/vite. Otherwise call configureTypstCompiler(...) in your development environment. Production browsers never compile Typst.`);}
17
+
18
+ export function configureTypstCompiler(compiler){if(compiler!=null&&typeof compiler!=='function')throw new TypeError('Typst compiler must be a function or null');configuredCompiler=compiler;}
19
+ export function clearTypstCache(){documentCache.clear();}
20
+
21
+ export class Typst extends VectorObject2D {
22
+ constructor(source,{compiler=null,__zanimCompiledSvg=null,reveal=1,...options}={}){super(EMPTY_DOCUMENT,{reveal,...options});this.source=String(source);this.compiler=compiler??configuredCompiler;this._compiledSvg=__zanimCompiledSvg;this._webRuntimeOnly='typst';this.ready=this.compile();}
23
+ async compile(source=this.source){
24
+ const nextSource=String(source);
25
+ if(this._compiledSvg!=null&&nextSource!==this.source)throw new Error('precompiled Web Math/Typst source is immutable at runtime; change the source and rebuild, or use a development compiler explicitly');
26
+ this.source=nextSource;
27
+ if(this._compiledSvg!=null)this.document=await documentFromCompiledSvg(this._compiledSvg);
28
+ else if(this.compiler){const result=await this.compiler({kind:'typst',source:this.source},{object:this});this.document=result?.paths?documentFromData(result):documentFromData(result?.document??result);}
29
+ else throw missingCompilerError('Typst');
30
+ this.invalidate();if(this._scene?.renderer?.ctx)this._scene.render();return this;
31
+ }
32
+ }
33
+ export class Math extends Typst {
34
+ constructor(source,{fontSize=36,color='#eef2fa',compiler=null,__zanimCompiledSvg=null,...options}={}){
35
+ const resolvedCompiler=compiler??configuredCompiler;
36
+ super(source,{...options,__zanimCompiledSvg,compiler:resolvedCompiler?async(payload,context)=>resolvedCompiler({kind:'math',source:payload.source,font_size:Number(fontSize),color},context):null});
37
+ this.fontSize=Number(fontSize);this.color=color;this._webRuntimeOnly='math';
38
+ }
39
+ }
package/src/zanim.d.ts ADDED
@@ -0,0 +1,223 @@
1
+ export type Point2 = readonly [number, number];
2
+ export type EasingFunction = (t:number)=>number;
3
+ export type ScalarLike = number | ScalarValue | ((time:number)=>number);
4
+ export type TransformFrame = 'local'|'parent'|'world';
5
+ export interface TimeOptions { duration?:number; easing?:EasingFunction; at?:number }
6
+ export interface ObjectOptions { transform?:Transform2D; opacity?:number; zIndex?:number }
7
+ export interface StyleState { fill?:string|null; stroke?:string|null; width?:number|null; worldStroke?:boolean }
8
+
9
+ export const PI:number; export const TAU:number; export const DEGREES:number;
10
+ export const LOCAL:'local'; export const PARENT:'parent'; export const WORLD:'world';
11
+ export const ORIGIN:Point2; export const RIGHT:Point2; export const LEFT:Point2; export const UP:Point2; export const DOWN:Point2;
12
+ export const WHITE:string; export const MUTED:string; export const BLUE:string; export const GREEN:string;
13
+ export const RED:string; export const ORANGE:string; export const YELLOW:string; export const CYAN:string;
14
+ export const PINK:string; export const PURPLE:string; export const GRAY:string; export const BLACK:string;
15
+ export const Easing:Readonly<{LINEAR:EasingFunction;SMOOTHSTEP:EasingFunction;SMOOTH:EasingFunction;EASE_IN_OUT:EasingFunction}>;
16
+ export const DEFAULT_WASM_URL:URL;
17
+
18
+ export class Vec2 {
19
+ x:number; y:number; constructor(x?:number,y?:number);
20
+ add(v:Vec2):Vec2; sub(v:Vec2):Vec2; mul(k:number):Vec2; readonly length:number;
21
+ static from(value:Vec2|Point2):Vec2;
22
+ }
23
+ export class Bounds2D {
24
+ left:number; bottom:number; right:number; top:number;
25
+ constructor(left:number,bottom:number,right:number,top:number);
26
+ readonly width:number; readonly height:number; readonly center:Vec2;
27
+ static union(...items:Bounds2D[]):Bounds2D;
28
+ }
29
+ export class Anchor { x:number; y:number; constructor(x?:number,y?:number); }
30
+ export const CENTER:Anchor; export const TOP:Anchor; export const BOTTOM:Anchor;
31
+ export const LEFT_CENTER:Anchor; export const RIGHT_CENTER:Anchor; export const TOP_LEFT:Anchor; export const TOP_RIGHT:Anchor;
32
+ export const BOTTOM_LEFT:Anchor; export const BOTTOM_RIGHT:Anchor;
33
+ export class Frame {
34
+ xMin:number; yMin:number; xMax:number; yMax:number;
35
+ constructor(xMin:number,yMin:number,xMax:number,yMax:number);
36
+ static fromRenderer(renderer:CanvasRenderer):Frame;
37
+ readonly width:number; readonly height:number; readonly center:Vec2; readonly top:Vec2; readonly bottom:Vec2; readonly left:Vec2; readonly right:Vec2;
38
+ anchor(anchor:Anchor|Point2|Vec2):Vec2; inset(x:number,y?:number):Frame; topRegion(height:number):Frame; below(other:Frame,gap?:number):Frame;
39
+ }
40
+
41
+ export class Mat2 {
42
+ xx:number; xy:number; yx:number; yy:number;
43
+ constructor(xx?:number,xy?:number,yx?:number,yy?:number);
44
+ static identity():Mat2; static rotation(radians:number):Mat2; static scaling(x:number,y?:number):Mat2; static shear(x?:number,y?:number):Mat2; static lerp(a:Mat2,b:Mat2,t:number):Mat2;
45
+ mul(other:Mat2):Mat2; inverse():Mat2; apply(x:number,y:number):[number,number]; readonly determinant:number;
46
+ }
47
+ export class Transform2D {
48
+ xx:number; xy:number; yx:number; yy:number; tx:number; ty:number;
49
+ constructor(xx?:number,xy?:number,yx?:number,yy?:number,tx?:number,ty?:number);
50
+ static identity():Transform2D; static translation(x:number,y:number):Transform2D; static scaling(x:number,y?:number):Transform2D; static rotation(radians:number):Transform2D; static shear(x?:number,y?:number):Transform2D;
51
+ static fromMat2(m:Mat2):Transform2D; static affine(options?:{position?:Point2;rotation?:number;scale?:number|Point2;shear?:Point2}):Transform2D; static lerp(a:Transform2D,b:Transform2D,t:number):Transform2D;
52
+ mul(other:Transform2D):Transform2D; inverse():Transform2D; apply(x:number,y:number):[number,number]; vector(x:number,y:number):[number,number]; readonly linear:Mat2; readonly determinant:number;
53
+ }
54
+ export const affine2d: typeof Transform2D.affine;
55
+
56
+ export type Point3 = readonly [number, number, number];
57
+ export class Vec3 {
58
+ x:number; y:number; z:number; constructor(x?:number,y?:number,z?:number);
59
+ static from(value:Vec3|Point3):Vec3; add(v:Vec3|Point3):Vec3; sub(v:Vec3|Point3):Vec3; mul(k:number):Vec3; dot(v:Vec3|Point3):number; cross(v:Vec3|Point3):Vec3; readonly length:number; normalized():Vec3;
60
+ }
61
+ export class Transform3D {
62
+ constructor(...values:number[]); static identity():Transform3D; static translation(x:number,y:number,z:number):Transform3D; static scaling(x:number,y?:number,z?:number):Transform3D; static rotationAxis(axis:Vec3|Point3,radians:number):Transform3D; static rotationX(radians:number):Transform3D; static rotationY(radians:number):Transform3D; static rotationZ(radians:number):Transform3D;
63
+ mul(other:Transform3D):Transform3D; translate(x:number,y:number,z:number):Transform3D; scale(x:number,y?:number,z?:number):Transform3D; rotateX(radians:number):Transform3D; rotateY(radians:number):Transform3D; rotateZ(radians:number):Transform3D; apply(point:Vec3|Point3):Vec3; asArray():number[];
64
+ }
65
+ export class TriangleMesh { readonly positions:Float32Array; readonly normals:Float32Array; readonly indices:Uint32Array; readonly vertexCount:number; readonly indexCount:number; constructor(vertices:readonly (Vec3|Point3)[]|Float32Array,normals:readonly (Vec3|Point3)[]|Float32Array,indices:readonly number[]|Uint32Array); }
66
+ export class MeshObject3D {
67
+ mesh:TriangleMesh; transform:Transform3D|((time:number,object:MeshObject3D)=>Transform3D); geometryTransform:Transform3D|((time:number,object:MeshObject3D)=>Transform3D); color:string|((time:number,object:MeshObject3D)=>string); opacity:ScalarLike;
68
+ constructor(mesh:TriangleMesh,options?:{transform?:Transform3D|((time:number,object:MeshObject3D)=>Transform3D);geometryTransform?:Transform3D|((time:number,object:MeshObject3D)=>Transform3D);color?:string|((time:number,object:MeshObject3D)=>string);opacity?:ScalarLike});
69
+ }
70
+ export function unitBoxMesh():TriangleMesh; export function Box3D(size?:Vec3|Point3,options?:ConstructorParameters<typeof MeshObject3D>[1]):MeshObject3D; export function Cube3D(side?:number,options?:ConstructorParameters<typeof MeshObject3D>[1]):MeshObject3D;
71
+ export class Camera3D { position:Vec3; target:Vec3; up:Vec3; fovYDegrees:number; near:number; far:number; orthographicHeight:number|null; layerZIndex:number; constructor(options?:{position?:Vec3|Point3;target?:Vec3|Point3;up?:Vec3|Point3;fovYDegrees?:number;near?:number;far?:number;orthographicHeight?:number|null;layerZIndex?:number}); }
72
+ export class Scene3DLayer extends ZObject { readonly meshes:MeshObject3D[]; camera:Camera3D; resolution:number; maxWidth:number; maxHeight:number; constructor(meshes:readonly MeshObject3D[],options?:ObjectOptions&{camera?:Camera3D;resolution?:number;maxWidth?:number;maxHeight?:number}); }
73
+
74
+ export class ScalarValue { readonly id:number; value:number; initial:number; constructor(value?:number); }
75
+ export function sampleValue(value:ScalarLike,time?:number):number;
76
+
77
+ export class ZanimWasm {
78
+ readonly instance:WebAssembly.Instance; readonly exports:WebAssembly.Exports;
79
+ constructor(instance:WebAssembly.Instance); static load(url:string|URL):Promise<ZanimWasm>;
80
+ determinant(matrix:Mat2):number; resolveGrid(width:number,height:number,unitSize:number,step:number,matrix:Mat2):Float64Array;
81
+ renderFractal(kind:1|2,width:number,height:number,centerRe:number,centerIm:number,worldPerPixel:number,maxIter?:number,juliaRe?:number,juliaIm?:number,colorShift?:number,colorScale?:number,inside?:readonly number[],palette?:readonly number[]):Uint8ClampedArray;
82
+ renderComplexGrid(kind:1|2|3|4,width:number,height:number,centerRe:number,centerIm:number,worldPerPixel:number,stepX:number,stepY:number,progress:number,strokePx?:number,params?:readonly number[]):Uint8ClampedArray;
83
+ upload3DGeometry(meshes:readonly TriangleMesh[]):unknown;
84
+ render3D(width:number,height:number,camera:Camera3D,upload:unknown,states:readonly {model:readonly number[];colorRGBA:number;opacity:number}[]):Uint8ClampedArray;
85
+ }
86
+
87
+ export class ZObject {
88
+ readonly id:number; transform:Transform2D; opacity:number; zIndex:number; visible:boolean; birth:number; death:number;
89
+ constructor(options?:ObjectOptions); draw(renderer:CanvasRenderer,parent?:Transform2D):void; world(parent?:Transform2D):Transform2D;
90
+ bounds():Bounds2D; readonly center:Vec2; anchor(anchor?:Anchor|Point2|Vec2):Vec2; shift(x:number|Point2|Vec2,y?:number):this; place(options?:{anchor?:Anchor|Point2|Vec2;at?:Point2|Vec2}):this;
91
+ fadeIn(options?:TimeOptions):this; fadeOut(options?:TimeOptions):this; opacityTo(to:number,options?:TimeOptions):this; styleTo(style:StyleState,options?:TimeOptions):this;
92
+ transformFunction(provider:(alpha:number)=>Transform2D,options?:TimeOptions):this; affine(options?:TimeOptions&{position?:Point2;rotation?:number;scale?:number|Point2;shear?:Point2}):this;
93
+ move(by:Point2|Vec2,options?:TimeOptions&{frame?:TransformFrame}):this; rotate(by:number,options?:TimeOptions&{frame?:TransformFrame;about?:Point2|Vec2|null}):this; scale(by:number,options?:TimeOptions&{frame?:TransformFrame;about?:Point2|Vec2|null}):this;
94
+ create(options?:TimeOptions):this; trimTo(to:number,options?:TimeOptions):this; remove():this;
95
+ }
96
+ export class Camera2D extends ZObject { affine(options?:TimeOptions&{position?:Point2;rotation?:number;scale?:number|Point2;shear?:Point2}):this; pan(by:Point2|Vec2,options?:TimeOptions):this; }
97
+ export class CustomObject2D extends ZObject { constructor(draw:(context:{renderer:CanvasRenderer;ctx:CanvasRenderingContext2D;time:number;transform:Transform2D;object:CustomObject2D})=>void,options?:ObjectOptions); }
98
+
99
+ export class Line extends ZObject { constructor(start?:Point2,end?:Point2,options?:ObjectOptions&{stroke?:string;width?:number;strokeWidth?:number}); }
100
+ export class Polyline extends ZObject {
101
+ points:readonly Point2[]; reveal:ScalarLike; trim:ScalarLike; worldStroke:boolean;
102
+ constructor(points:readonly Point2[],options?:ObjectOptions&{stroke?:string|null;width?:number;strokeWidth?:number;closed?:boolean;fill?:string|null;reveal?:ScalarLike;trim?:ScalarLike}); invalidate():this;
103
+ }
104
+ export function resamplePolylineByArcLength(points:readonly Point2[],segmentCount:number):Point2[];
105
+ export class Polygon extends Polyline { constructor(points:readonly Point2[],options?:ConstructorParameters<typeof Polyline>[1]); }
106
+ export class Rectangle extends Polygon { readonly rectWidth:number; readonly rectHeight:number; constructor(width?:number,height?:number,options?:ConstructorParameters<typeof Polygon>[1]); }
107
+ export class Square extends Rectangle { constructor(side?:number,options?:ConstructorParameters<typeof Polygon>[1]); }
108
+ export class RegularPolygon extends Polygon { constructor(sides?:number,radius?:number,options?:ConstructorParameters<typeof Polygon>[1]&{phase?:number}); }
109
+ export class Circle extends ZObject { radius:number; reveal:ScalarLike; trim:ScalarLike; constructor(radius?:number,options?:ObjectOptions&{fill?:string|null;stroke?:string|null;width?:number;strokeWidth?:number;reveal?:ScalarLike;trim?:ScalarLike}); }
110
+ export class Dot extends Circle { constructor(point?:Point2,options?:ObjectOptions&{radius?:number;color?:string}); }
111
+ export class Arrow extends Line {}
112
+ export class Text extends ZObject { text:string|((time:number,object:Text)=>string); fontSize:number; color:string; fontFamily:string; constructor(text:string|((time:number,object:Text)=>string),options?:ObjectOptions&{fontSize?:number;color?:string;fontFamily?:string;align?:CanvasTextAlign;weight?:number}); }
113
+ export interface VectorDocumentData { width:number; height:number; group_count:number; paths:Array<{group:number;fill:string|null;stroke:{color:string;width:number}|null;contours:Array<{closed:boolean;segments:Array<[Point2,Point2,Point2,Point2]>}>}> }
114
+ export class VectorObject2D extends ZObject { document:VectorDocumentData; reveal:ScalarLike; tint:string|null; constructor(document:VectorDocumentData,options?:ObjectOptions&{reveal?:ScalarLike;tint?:string|null}); invalidate():this; }
115
+ export interface VectorMorphPlan { source:VectorDocumentData; target:VectorDocumentData; matched:readonly (readonly [number,number])[]; sourceOnly:readonly number[]; targetOnly:readonly number[]; sample(alpha:number):VectorDocumentData; }
116
+ export function prepareVectorMorph(source:VectorDocumentData,target:VectorDocumentData):VectorMorphPlan;
117
+ export class DynamicVectorObject2D extends VectorObject2D { provider:(time:number,object:DynamicVectorObject2D|null)=>VectorDocumentData; constructor(provider:(time:number,object:DynamicVectorObject2D|null)=>VectorDocumentData,options?:ObjectOptions&{reveal?:ScalarLike;tint?:string|null}); }
118
+ export class Group extends ZObject { readonly children:ZObject[]; constructor(children?:ZObject[],options?:ObjectOptions); add(...items:ZObject[]):this; }
119
+
120
+ export class InfiniteLine extends ZObject { constructor(point?:Point2,direction?:Point2,options?:ObjectOptions&{stroke?:string;width?:number;strokeWidth?:number}); }
121
+ export class InfiniteGrid extends ZObject { constructor(options?:ObjectOptions&{step?:number;stroke?:string;width?:number;strokeWidth?:number}); }
122
+ export class Axes extends ZObject { constructor(options?:ObjectOptions&{xColor?:string;yColor?:string;width?:number}); }
123
+
124
+ export type CircleItem = readonly [number,number,number,string?,string?,number?];
125
+ export type LineItem = readonly [number,number,number,number,string?,number?];
126
+ export type RectItem = readonly [number,number,number,number,string?,string?,number?];
127
+ export type TextItem = readonly [number,number,string|number,string?,number?,number?];
128
+ export class CircleSet extends ZObject { items:readonly CircleItem[]; constructor(items?:readonly CircleItem[],options?:ObjectOptions&{fill?:string;stroke?:string|null;width?:number;worldStroke?:boolean}); invalidate():this; batchTo(to:readonly CircleItem[]|CircleSet,options?:TimeOptions):this; }
129
+ export class LineSet extends ZObject { items:readonly LineItem[]; constructor(items?:readonly LineItem[],options?:ObjectOptions&{stroke?:string;width?:number;worldStroke?:boolean}); invalidate():this; batchTo(to:readonly LineItem[]|LineSet,options?:TimeOptions):this; }
130
+ export class RectSet extends ZObject { items:readonly RectItem[]; constructor(items?:readonly RectItem[],options?:ObjectOptions&{fill?:string;stroke?:string|null;width?:number;worldStroke?:boolean}); invalidate():this; batchTo(to:readonly RectItem[]|RectSet,options?:TimeOptions):this; }
131
+ export class TextSet extends ZObject { items:readonly TextItem[]; constructor(items?:readonly TextItem[],options?:ObjectOptions&{color?:string;fontSize?:number;fontFamily?:string;weight?:number;align?:CanvasTextAlign}); }
132
+ export class ScalarExpr {
133
+ readonly op:string; readonly args:readonly unknown[];
134
+ constructor(op:string,args?:readonly unknown[]);
135
+ static constant(value:number):ScalarExpr; static variable(name:'x'|'time'):ScalarExpr; static fromData(value:unknown):ScalarExpr;
136
+ toData():unknown[]; evaluate(values?:{x?:number;time?:number}):number;
137
+ add(value:ScalarExpr|number):ScalarExpr; sub(value:ScalarExpr|number):ScalarExpr; mul(value:ScalarExpr|number):ScalarExpr; div(value:ScalarExpr|number):ScalarExpr; pow(value:ScalarExpr|number):ScalarExpr;
138
+ neg():ScalarExpr; sin():ScalarExpr; cos():ScalarExpr; exp():ScalarExpr; log():ScalarExpr; abs():ScalarExpr;
139
+ }
140
+ export const X:ScalarExpr; export const TIME:ScalarExpr;
141
+ export class FunctionPlot extends Polyline {
142
+ readonly expression:ScalarExpr; readonly xRange:number[]; readonly axesXRange:number[]; readonly axesYRange:number[]; readonly plotWidth:number; readonly plotHeight:number; readonly plotCenter:number[]; readonly samples:number;
143
+ constructor(expression:ScalarExpr|number,options?:ConstructorParameters<typeof Polyline>[1]&{xRange?:Point2;axesXRange?:Point2;axesYRange?:Point2;width?:number;height?:number;center?:Point2;samples?:number});
144
+ pointsAt(time:number):Point2[];
145
+ }
146
+ export class DynamicPolyline extends Polyline { constructor(provider:(time:number,object:DynamicPolyline)=>readonly Point2[],options?:ConstructorParameters<typeof Polyline>[1]); }
147
+ export class DynamicLineSet extends LineSet { constructor(provider:(time:number,object:DynamicLineSet)=>readonly LineItem[],options?:ConstructorParameters<typeof LineSet>[1]); }
148
+ export class DynamicCircleSet extends CircleSet { constructor(provider:(time:number,object:DynamicCircleSet)=>readonly CircleItem[],options?:ConstructorParameters<typeof CircleSet>[1]); }
149
+ export class DynamicRectSet extends RectSet { constructor(provider:(time:number,object:DynamicRectSet)=>readonly RectItem[],options?:ConstructorParameters<typeof RectSet>[1]); }
150
+ export interface FourierTerm2D { frequency:number; coefficient?:Point2; re?:number; im?:number; }
151
+ export class FourierEpicycles extends ZObject {
152
+ readonly terms:Array<{frequency:number;re:number;im:number}>;
153
+ readonly visualIndices:number[];
154
+ readonly startTime:number; readonly drawDuration:number; readonly circleSamples:number; readonly traceSamples:number;
155
+ constructor(terms:readonly (FourierTerm2D|readonly [number,number,number])[],options?:ObjectOptions&{startTime?:number;drawDuration?:number;circleSamples?:number;traceSamples?:number;visualIndices?:readonly number[];circleColor?:string;circleWidth?:number;arrowColor?:string;traceColor?:string;traceWidth?:number;tipColor?:string;tipRadius?:number;tipSides?:number});
156
+ phaseAt(time:number):number;
157
+ }
158
+ export class DynamicTextSet extends TextSet { constructor(provider:(time:number,object:DynamicTextSet)=>readonly TextItem[],options?:ConstructorParameters<typeof TextSet>[1]); }
159
+ export class DynamicNumber extends Text { constructor(value:ScalarLike,options?:ObjectOptions&{digits?:number;prefix?:string;suffix?:string;format?:(value:number,time:number)=>string;fontSize?:number;color?:string;fontFamily?:string;align?:CanvasTextAlign;weight?:number}); }
160
+
161
+ export interface ProceduralQuality { resolution?:number; minWidth?:number; maxWidth?:number; maxHeight?:number }
162
+ export class FractalField extends ZObject {
163
+ kind:number; viewportCenter:[ScalarLike,ScalarLike]; zoom:ScalarLike; maxIter:ScalarLike; juliaC:[ScalarLike,ScalarLike]; colorShift:ScalarLike; colorScale:ScalarLike; insideColor:string; paletteColor:string; viewport:'parameters'|'transform';
164
+ resolution:number; minWidth:number; maxWidth:number; maxHeight:number;
165
+ constructor(kind:1|2,options?:ObjectOptions&ProceduralQuality&{center?:readonly [ScalarLike,ScalarLike];zoom?:ScalarLike;maxIter?:ScalarLike;juliaC?:readonly [ScalarLike,ScalarLike];colorShift?:ScalarLike;colorScale?:ScalarLike;insideColor?:string;paletteColor?:string;viewport?:'parameters'|'transform'});
166
+ }
167
+ export class MandelbrotSet extends FractalField { constructor(options?:ConstructorParameters<typeof FractalField>[1]); }
168
+ export class JuliaSet extends FractalField { constructor(c?:Point2,options?:ConstructorParameters<typeof FractalField>[1]); }
169
+ export class ComplexMappedGrid extends ZObject {
170
+ mapping:'square'|'exp'|'reciprocal'|'mobius'; progress:ScalarLike; span:ScalarLike; viewportCenter:[ScalarLike,ScalarLike]; viewport:'parameters'|'canvas';
171
+ constructor(mapping:'square'|'exp'|'reciprocal'|'mobius',options?:ObjectOptions&ProceduralQuality&{step?:number|Point2;progress?:ScalarLike;center?:readonly [ScalarLike,ScalarLike];span?:ScalarLike;strokePx?:number;mapParams?:readonly number[];viewport?:'parameters'|'canvas';frame?:{center:Point2;size:Point2}|null});
172
+ }
173
+
174
+ export class CanvasRenderer {
175
+ readonly canvas:HTMLCanvasElement; readonly ctx:CanvasRenderingContext2D; readonly wasm:ZanimWasm; baseUnitSize:number; unitSize:number; dpr:number; background:string; time:number;
176
+ constructor(canvas:HTMLCanvasElement,wasm:ZanimWasm,options?:{unitSize?:number;background?:string}); resize():void; toDevice(x:number,y:number):[number,number]; clear():void;
177
+ }
178
+
179
+ export class Audio extends ZObject { url:string; duration:number|null; gain:number; readonly ready:Promise<this>; constructor(url:string|URL,options?:ObjectOptions&{gain?:number;duration?:number|null;crossOrigin?:string|null;preload?:string}); media(options?:MediaPlaybackOptions):this; destroy():void; }
180
+ export class MediaObject2D extends ZObject { url:string; width:number; height:number; sourceWidth:number; sourceHeight:number; duration:number|null; readonly ready:Promise<this>; media(options?:MediaPlaybackOptions):this; }
181
+ export class SceneRasterObject2D extends ZObject {
182
+ scene:Scene; width:number; height:number; sourceTime:ScalarLike; ownsScene:boolean;
183
+ constructor(scene:Scene,options?:ObjectOptions&{width?:number|null;height?:number|null;sourceTime?:ScalarLike;ownsScene?:boolean});
184
+ sample(time:number):this; destroy():void;
185
+ }
186
+ export class Image extends MediaObject2D { constructor(url:string|URL,options?:ObjectOptions&{width?:number|null;height?:number|null;sourceWidth?:number;sourceHeight?:number;crossOrigin?:string|null}); }
187
+ export class GIF extends MediaObject2D { constructor(url:string|URL,options?:ObjectOptions&{width?:number|null;height?:number|null;sourceWidth?:number;sourceHeight?:number;duration?:number|null;crossOrigin?:string|null}); }
188
+ export class Video extends MediaObject2D { constructor(url:string|URL,options?:ObjectOptions&{width?:number|null;height?:number|null;sourceWidth?:number;sourceHeight?:number;duration?:number|null;crossOrigin?:string|null;muted?:boolean;playsInline?:boolean;preload?:string}); destroy():void; }
189
+ export interface MediaPlaybackOptions { duration?:number|null; sourceStart?:number; speed?:number; loop?:boolean; sourceDuration?:number|null; at?:number }
190
+ export type TypstCompilerResult = VectorDocumentData | {document:VectorDocumentData} | string | {svg:string};
191
+ export type TypstCompiler = (payload:{kind:'typst'|'math';source:string;font_size?:number;color?:string},context:{object:Typst})=>Promise<TypstCompilerResult>;
192
+ export function configureTypstCompiler(compiler:TypstCompiler|null):void;
193
+ export class Typst extends VectorObject2D { source:string; compiler:TypstCompiler|null; readonly ready:Promise<this>; constructor(source:string,options?:ObjectOptions&{reveal?:number;tint?:string|null;compiler?:TypstCompiler|null}); compile(source?:string):Promise<this>; }
194
+ export class Math extends Typst { fontSize:number; color:string; constructor(source:string,options?:ObjectOptions&{reveal?:number;tint?:string|null;compiler?:TypstCompiler|null;fontSize?:number;color?:string}); }
195
+
196
+ export interface AnimationOptions extends TimeOptions { transform?:Transform2D; opacity?:number; reveal?:number; style?:StyleState }
197
+ export interface ParallelAPI {
198
+ animate(object:ZObject,options?:AnimationOptions):ZObject; animateValue(value:ScalarValue,options:{to:number}&TimeOptions):ScalarValue; transformFunction(object:ZObject,provider:(alpha:number)=>Transform2D,options?:TimeOptions):ZObject;
199
+ fadeIn(object:ZObject,options?:TimeOptions):ZObject; fadeOut(object:ZObject,options?:TimeOptions):ZObject; create(object:ZObject,options?:TimeOptions):ZObject; style(object:ZObject,options:{to:StyleState}&TimeOptions):ZObject;
200
+ batch(object:CircleSet|LineSet|RectSet,options:{to:readonly unknown[]|CircleSet|LineSet|RectSet}&TimeOptions):ZObject; media(object:MediaObject2D,options?:MediaPlaybackOptions):MediaObject2D;
201
+ move(object:ZObject,by:Point2|Vec2,options?:TimeOptions&{frame?:TransformFrame}):ZObject; rotate(object:ZObject,by:number,options?:TimeOptions&{frame?:TransformFrame;about?:Point2|Vec2|null}):ZObject; scale(object:ZObject,by:number,options?:TimeOptions&{frame?:TransformFrame;about?:Point2|Vec2|null}):ZObject;
202
+ affine(object:ZObject,options?:TimeOptions&{position?:Point2;rotation?:number;scale?:number|Point2;shear?:Point2}):ZObject; interpolate(source:ZObject,target:ZObject,options?:TimeOptions):ZObject;
203
+ }
204
+ export class Scene {
205
+ readonly renderer:CanvasRenderer; readonly objects:ZObject[]; readonly camera:Camera2D; readonly frame:Frame; fps:number; cursor:number; duration:number; time:number; readonly stats:{renderMs:number;seekMs:number;frames:number};
206
+ constructor(renderer:CanvasRenderer,options?:{fps?:number});
207
+ static headless(options?:{width?:number;height?:number;unitSize?:number;fps?:number}):Scene;
208
+ static create(canvas:HTMLCanvasElement|string,options?:{wasmURL?:string|URL;wasm?:ZanimWasm|null;renderer?:{unitSize?:number;background?:string};fps?:number;observeResize?:boolean}):Promise<Scene>;
209
+ add<T extends ZObject>(object:T):T; add<T extends ZObject[]>(...objects:T):T; addLater<T extends ZObject>(object:T):T; addLater<T extends ZObject[]>(...objects:T):T; remove(...objects:ZObject[]):this; invalidateOrder():this;
210
+ addValue<T extends ScalarValue>(value:T):T; addValue<T extends ScalarValue[]>(...values:T):T; animateValue(value:ScalarValue,options:{to:number}&TimeOptions):ScalarValue; valueAt(value:ScalarValue,time:number):number; authoredValue(value:ScalarValue):number;
211
+ authoredState(object:ZObject):{transform:Transform2D;opacity:number;reveal:number|null;style:StyleState|null}; authoredCenter(object:ZObject):Vec2;
212
+ wait(seconds?:number):this; at(seconds:number):this; animate(object:ZObject,options?:AnimationOptions):ZObject; transformFunction(object:ZObject,provider:(alpha:number)=>Transform2D,options?:TimeOptions):ZObject;
213
+ fadeIn(object:ZObject,options?:TimeOptions):ZObject; fadeOut(object:ZObject,options?:TimeOptions):ZObject; style(object:ZObject,options:{to:StyleState}&TimeOptions):ZObject; trim(object:ZObject,options:{to:number}&TimeOptions):ZObject; create(object:ZObject,options?:TimeOptions):ZObject;
214
+ batch(object:CircleSet|LineSet|RectSet,options:{to:readonly unknown[]|CircleSet|LineSet|RectSet}&TimeOptions):ZObject; media(object:MediaObject2D,options?:MediaPlaybackOptions):MediaObject2D; mediaPlaybackAt(object:MediaObject2D,time:number):unknown|null; mediaTimeAt(object:MediaObject2D,time:number):number|null; move(object:ZObject,by:Point2|Vec2,options?:TimeOptions&{frame?:TransformFrame}):ZObject; rotate(object:ZObject,by:number,options?:TimeOptions&{frame?:TransformFrame;about?:Point2|Vec2|null}):ZObject; scale(object:ZObject,by:number,options?:TimeOptions&{frame?:TransformFrame;about?:Point2|Vec2|null}):ZObject; affine(object:ZObject,options?:TimeOptions&{position?:Point2;rotation?:number;scale?:number|Point2;shear?:Point2}):ZObject;
215
+ interpolate(source:ZObject,target:ZObject,options?:TimeOptions):ZObject; replace<T extends ZObject>(source:ZObject,target:T,options?:Omit<TimeOptions,'at'>):T;
216
+ layout(...args:[...ZObject[],{to:Row|Column|Grid;duration?:number;easing?:EasingFunction;at?:number}]):ZObject[];
217
+ parallel(callback:(api:ParallelAPI)=>void):this; parallel(duration:number,callback:(api:ParallelAPI)=>void):this;
218
+ stateAt(object:ZObject,time:number):{transform:Transform2D;opacity:number;reveal:number|null;style:StyleState|null}; worldTransformAt(object:ZObject,time?:number):Transform2D; seek(time:number):this; render():void; play(options?:{loop?:boolean;from?:number}):this; pause():this; destroy():this; setMatrix(matrix:Mat2):void; animateTo(target:Mat2,duration?:number):void;
219
+ }
220
+
221
+ export class Row { constructor(options?:{gap?:number;anchor?:Anchor|Point2|Vec2;at?:Point2|Vec2;align?:Anchor|Point2|Vec2}); targets(...objects:ZObject[]):Transform2D[]; place<T extends ZObject[]>(...objects:T):T; }
222
+ export class Column { constructor(options?:{gap?:number;anchor?:Anchor|Point2|Vec2;at?:Point2|Vec2;align?:Anchor|Point2|Vec2}); targets(...objects:ZObject[]):Transform2D[]; place<T extends ZObject[]>(...objects:T):T; }
223
+ export class Grid { constructor(options?:{rows?:number|null;cols?:number|null;gap?:number|Point2|Vec2;anchor?:Anchor|Point2|Vec2;at?:Point2|Vec2}); targets(...objects:ZObject[]):Transform2D[]; place<T extends ZObject[]>(...objects:T):T; }
package/src/zanim.js ADDED
@@ -0,0 +1,97 @@
1
+ export {
2
+ Anchor,
3
+ Arrow,
4
+ Axes,
5
+ BLACK,
6
+ BLUE,
7
+ BOTTOM,
8
+ BOTTOM_LEFT,
9
+ BOTTOM_RIGHT,
10
+ Bounds2D,
11
+ CENTER,
12
+ CYAN,
13
+ Camera2D,
14
+ CanvasRenderer,
15
+ Circle,
16
+ CircleSet,
17
+ ComplexMappedGrid,
18
+ CustomObject2D,
19
+ DEFAULT_WASM_URL,
20
+ DEGREES,
21
+ DOWN,
22
+ Dot,
23
+ DynamicCircleSet,
24
+ DynamicLineSet,
25
+ DynamicNumber,
26
+ DynamicPolyline,
27
+ DynamicRectSet,
28
+ DynamicTextSet,
29
+ DynamicVectorObject2D,
30
+ Easing,
31
+ FourierEpicycles,
32
+ FractalField,
33
+ Frame,
34
+ FunctionPlot,
35
+ GRAY,
36
+ GREEN,
37
+ Grid,
38
+ Group,
39
+ InfiniteGrid,
40
+ InfiniteLine,
41
+ JuliaSet,
42
+ LEFT,
43
+ LEFT_CENTER,
44
+ LOCAL,
45
+ Line,
46
+ LineSet,
47
+ MandelbrotSet,
48
+ Mat2,
49
+ MUTED,
50
+ ORANGE,
51
+ ORIGIN,
52
+ PARENT,
53
+ PI,
54
+ PINK,
55
+ PURPLE,
56
+ Polygon,
57
+ Polyline,
58
+ RED,
59
+ RIGHT,
60
+ RIGHT_CENTER,
61
+ RectSet,
62
+ Rectangle,
63
+ RegularPolygon,
64
+ Row,
65
+ ScalarExpr,
66
+ ScalarValue,
67
+ Square,
68
+ TAU,
69
+ TIME,
70
+ TOP,
71
+ TOP_LEFT,
72
+ TOP_RIGHT,
73
+ Text,
74
+ TextSet,
75
+ Transform2D,
76
+ UP,
77
+ Vec2,
78
+ VectorObject2D,
79
+ WHITE,
80
+ WORLD,
81
+ X,
82
+ YELLOW,
83
+ ZObject,
84
+ ZanimWasm,
85
+ affine2d,
86
+ prepareVectorMorph,
87
+ resamplePolylineByArcLength,
88
+ sampleValue,
89
+ } from './core.js';
90
+ export { Column } from './core.js';
91
+ export { Scene } from './scene.js';
92
+
93
+ export { Audio, GIF, Image, MediaObject2D, Video } from './media.js';
94
+ export { SceneRasterObject2D } from './compositing.js';
95
+ export { Math, Typst, configureTypstCompiler } from './typst.js';
96
+
97
+ export { Box3D, Camera3D, Cube3D, MeshObject3D, Scene3DLayer, Transform3D, TriangleMesh, Vec3, unitBoxMesh } from './three.js';
package/vite.d.ts ADDED
@@ -0,0 +1,4 @@
1
+ import type { Plugin } from 'vite';
2
+ export interface ZanimViteOptions { typst?: string; cacheDir?: string; }
3
+ export declare function zanim(options?: ZanimViteOptions): Plugin;
4
+ export default zanim;