modern-text 0.1.1 → 0.1.3

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 CHANGED
@@ -21,9 +21,9 @@
21
21
  ## Usage
22
22
 
23
23
  ```ts
24
- import { Text } from 'modern-text'
24
+ import { measureText, renderText } from 'modern-text'
25
25
 
26
- const text = new Text({
26
+ const text = {
27
27
  style: {
28
28
  width: 100,
29
29
  height: 200,
@@ -58,9 +58,9 @@ const text = new Text({
58
58
  { content: ', ', color: 'grey' },
59
59
  { content: 'World!', color: 'black' },
60
60
  ],
61
- })
61
+ }
62
62
 
63
- document.body.append(text.view) // canvas 2d
63
+ document.body.append(renderText(text)) // canvas 2d
64
64
 
65
- console.log(text.measure()) // boundingBox with computed paragraphs
65
+ console.log(measureText(text)) // boundingBox with computed paragraphs
66
66
  ```
package/dist/index.cjs CHANGED
@@ -1 +1 @@
1
- "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});function C(k,e,o,n,r){var a;const h=((a=k.match(/linear-gradient\((.+)\)$/))==null?void 0:a[1])??"",s=h.split(",")[0],t=s.includes("deg")?s:"0deg",i=h.replace(t,"").matchAll(/(#|rgba|rgb)(.+?) ([\d.]+?%)/gi),x=(Number(t.replace("deg",""))||0)*Math.PI/180,g=n*Math.sin(x),f=r*Math.cos(x);return{x0:e+n/2-g,y0:o+r/2+f,x1:e+n/2+g,y1:o+r/2-f,stops:Array.from(i).map(c=>{let B=c[2];return B.startsWith("(")?B=B.split(",").length>3?`rgba${B}`:`rgb${B}`:B=`#${B}`,{offset:Number(c[3].replace("%",""))/100,color:B}})}}const u=class u{static get defaultStyle(){return{width:"auto",height:"auto",fontSize:14,fontWeight:"normal",fontFamily:"sans-serif",fontStyle:"normal",fontKerning:"normal",textWrap:"wrap",textAlign:"start",verticalAlign:"baseline",textTransform:"none",textDecoration:null,textStrokeWidth:0,direction:"inherit",lineHeight:1,letterSpacing:0,shadowOffsetX:0,shadowOffsetY:0,shadowBlur:0,writingMode:"horizontal-tb"}}constructor(e={}){const{view:o=document.createElement("canvas"),pixelRatio:n=window.devicePixelRatio||1,content:r="",style:h}=e;this.view=o,this.context=o.getContext("2d"),this.pixelRatio=n,this.content=r,this.style={...u.defaultStyle,...h},this.update()}_createBox(e=0,o=0,n=0,r=0){return{left:e,top:o,width:n,height:r,right:e+n,bottom:o+r}}_moveBox(e,o=0,n=0){e.left+=o,e.right+=o,e.top+=n,e.bottom+=n}_updateBoxSize(e){e.width=e.right-e.left,e.height=e.bottom-e.top}_mergeBoxes(e){const o=e.slice(1).reduce((n,r)=>(n.left=Math.min(n.left,r.left),n.top=Math.min(n.top,r.top),n.right=Math.max(n.right,r.right),n.bottom=Math.max(n.bottom,r.bottom),n),{...e[0]});return this._updateBoxSize(o),o}measure(){let{width:e}=this.style;e==="auto"&&(e=0);let o=this._createParagraphs(this.content);o=this._wrapParagraphs(o,e);const n=this.context;let r=0;for(let h=o.length,s=0;s<h;s++){const t=o[s],i=[];let l=0,x=null;for(const a of t.fragments){this._setContextStyle({...a.style,textAlign:"center",verticalAlign:"baseline"});const c=n.measureText(a.content),B=c.width,d=a.style.fontSize;a.inlineBox=this._createBox(l,r,B,d*a.style.lineHeight),a.contentBox=this._createBox(a.inlineBox.left,a.inlineBox.top+(a.inlineBox.height-d)/2,B,d);const p=c.fontBoundingBoxAscent+c.fontBoundingBoxDescent,w=c.actualBoundingBoxLeft+c.actualBoundingBoxRight,y=c.actualBoundingBoxAscent+c.actualBoundingBoxDescent;a.baseline=a.inlineBox.top+(a.inlineBox.height-p)/2+c.fontBoundingBoxAscent,a.glyphBox=this._createBox(a.contentBox.left,a.baseline-c.actualBoundingBoxAscent,w,y),a.centerX=a.glyphBox.left+c.actualBoundingBoxLeft,l+=a.contentBox.width,i.push(a.contentBox),t.contentBox=this._mergeBoxes(i),t.contentBox.height<a.contentBox.height&&(x=a)}t.lineBox=this._mergeBoxes([...t.fragments.map(a=>a.inlineBox),this._createBox(0,r,e)]),this._setContextStyle({...(x??t).style,textAlign:"left",verticalAlign:"baseline"});const g=n.measureText("x"),f=g.fontBoundingBoxAscent+g.fontBoundingBoxDescent;t.xHeight=g.actualBoundingBoxAscent,t.baseline=t.lineBox.top+(t.lineBox.height-f)/2+g.fontBoundingBoxAscent,r+=t.lineBox.height}for(let h=o.length,s=0;s<h;s++){const t=o[s];t.fragments.forEach(i=>{const l=i.inlineBox.left,x=i.inlineBox.top;let g,f=x;switch(i.style.textAlign){case"end":case"right":g=l+(t.lineBox.width-t.contentBox.width);break;case"center":g=l+(t.lineBox.width-t.contentBox.width)/2;break;case"start":case"left":default:g=l+t.lineBox.left;break}switch(i.style.verticalAlign){case"top":f=x+(t.lineBox.top-i.inlineBox.top);break;case"middle":f=t.baseline-t.xHeight/2-i.inlineBox.height/2;break;case"bottom":f=x+(t.lineBox.bottom-i.inlineBox.bottom);break;case"sub":f=x+(t.baseline-i.glyphBox.bottom);break;case"super":f=x+(t.baseline-i.glyphBox.top);break;case"text-top":f=x+(t.glyphBox.top-i.inlineBox.top);break;case"text-bottom":f=x+(t.glyphBox.bottom-i.inlineBox.bottom);break;case"baseline":default:i.inlineBox.height<t.lineBox.height&&(f=x+(t.baseline-i.baseline));break}const a=g-l,c=f-x;this._moveBox(i.inlineBox,a,c),this._moveBox(i.contentBox,a,c),this._moveBox(i.glyphBox,a,c),i.baseline+=c,i.centerX+=a}),t.contentBox=this._mergeBoxes(t.fragments.map(i=>i.contentBox)),t.glyphBox=this._mergeBoxes(t.fragments.map(i=>i.glyphBox))}return{actualContentBox:this._mergeBoxes(o.map(h=>h.contentBox)),contentBox:this._mergeBoxes(o.map(h=>h.lineBox)),glyphBox:this._mergeBoxes(o.map(h=>h.glyphBox)),paragraphs:o}}_createParagraphs(e){const o=new Set(["color","backgroundColor","textStrokeColor","shadowColor"]),n=(...t)=>{const i=t.pop();return t.reduce((l,x)=>{for(const g in x)o.has(g)||(l[g]=x[g]);return l},i)},r=(t={})=>{const{width:i,height:l,...x}=this.style;return{contentBox:this._createBox(),lineBox:this._createBox(),glyphBox:this._createBox(),baseline:0,fragments:[],...t,style:n(x,t.style??{})}},h=(t,i)=>{let l;if(typeof t=="string")l={content:t};else{const{content:B,...d}=t;l={content:B,style:d}}const{width:x,height:g,...f}=this.style,a=n(f,i??{},l.style??{});let c=l.content??"";switch(a.textTransform){case"uppercase":c=c.toUpperCase();break;case"lowercase":c=c.toLowerCase();break}return{contentBox:this._createBox(),inlineBox:this._createBox(),glyphBox:this._createBox(),centerX:0,baseline:0,...l,style:a,content:c}},s=[];if(typeof e=="string")s.push(r({fragments:[h(e)]}));else{e=Array.isArray(e)?e:[e];for(const t of e)if(typeof t=="string")s.push(r({fragments:[h(t)]}));else if(Array.isArray(t))s.push(r({fragments:t.map(i=>h(i))}));else if("fragments"in t){const{fragments:i,...l}=t;s.push(r({style:l,fragments:i.map(x=>h(x,l))}))}else if("content"in t){const{content:i,...l}=t;s.push(r({style:l,fragments:[h(i,l)]}))}}return s}_wrapParagraphs(e,o){var i;const n=l=>JSON.parse(JSON.stringify(l)),r=[],h=e.slice();let s,t;for(;s=h.shift();){const l=s.fragments.slice();let x=0;const g=[];for(;t=l.shift();){const f=t.style,a=(i=f.writingMode)==null?void 0:i.startsWith("vertical");this._setContextStyle(f);let c="",B=!1,d=0,p="";for(const w of t.content){if(p+=w,!a&&u.punctuationRegex.test(t.content[++d]))continue;const y=this.context.measureText(p).width,S=/^[\r\n]$/.test(p);if(S||a||f.textWrap==="wrap"&&o&&x+y>o){let m=S?c.length+1:c.length;!x&&!m&&(c+=p,m+=p.length),c.length&&g.push({...n(t),content:c}),g.length&&(r.push({...n(s),fragments:g.slice()}),g.length=0);const b=t.content.substring(m);(b.length||l.length)&&h.unshift({...n(s),fragments:(b.length?[{...n(t),content:b}]:[]).concat(l.slice())}),l.length=0,B=!0;break}else x+=y;c+=p,p=""}B||g.push(n(t))}g.length&&r.push({...n(s),fragments:g})}return r}_draw(e){const o=this.context,{width:n,height:r}=o.canvas,h={left:0,top:0,width:n,height:r};this.style.backgroundColor&&(o.fillStyle=this._parseColor(this.style.backgroundColor,h),o.fillRect(0,0,n,r)),e.forEach(s=>{s.style.backgroundColor&&(o.fillStyle=this._parseColor(s.style.backgroundColor,s.contentBox),o.fillRect(s.lineBox.left,s.lineBox.top,s.lineBox.width,s.lineBox.height)),s.fragments.forEach(t=>{t.style.backgroundColor&&(o.fillStyle=this._parseColor(t.style.backgroundColor,t.contentBox),o.fillRect(t.inlineBox.left,t.inlineBox.top,t.inlineBox.width,t.inlineBox.height))})}),this._setContextStyle(this.style,h),e.forEach(s=>{this._setContextStyle(s.style,s.contentBox),s.fragments.forEach(t=>{this._setContextStyle({...t.style,textAlign:"left",verticalAlign:"top"},t.contentBox);const{left:i,top:l,width:x,height:g}=t.contentBox;switch(t.style.textStrokeWidth&&o.strokeText(t.content,i,l),o.fillText(t.content,i,l),t.style.textDecoration){case"underline":o.beginPath(),o.moveTo(i,l+g-2),o.lineTo(i+x,l+g-2),o.stroke();break;case"line-through":o.beginPath(),o.moveTo(i,l+g/2),o.lineTo(i+x,l+g/2),o.stroke();break}})})}_resizeView(e,o){const n=this.view;n.style.width=`${e}px`,n.style.height=`${o}px`,n.dataset.width=String(e),n.dataset.height=String(o),n.width=Math.max(1,Math.floor(e*this.pixelRatio)),n.height=Math.max(1,Math.floor(o*this.pixelRatio))}_parseColor(e,o){if(e.startsWith("linear-gradient")){const{x0:n,y0:r,x1:h,y1:s,stops:t}=C(e,o.left,o.top,o.width,o.height),i=this.context.createLinearGradient(n,r,h,s);return t.forEach(l=>i.addColorStop(l.offset,l.color)),i}return e}_setContextStyle(e,o){const n=this.context;switch(e.shadowColor&&(n.shadowColor=e.shadowColor),n.shadowOffsetX=e.shadowOffsetX,n.shadowOffsetY=e.shadowOffsetY,n.shadowBlur=e.shadowBlur,e.textStrokeColor&&o&&(n.strokeStyle=this._parseColor(e.textStrokeColor,o)),n.lineWidth=e.textStrokeWidth,e.color&&o&&(n.fillStyle=this._parseColor(e.color,o)),n.direction=e.direction,n.textAlign=e.textAlign,e.verticalAlign){case"baseline":n.textBaseline="alphabetic";break;case"top":case"middle":case"bottom":n.textBaseline=e.verticalAlign;break}n.font=[e.fontStyle,e.fontWeight,`${e.fontSize}px`,e.fontFamily].join(" "),n.fontKerning=e.fontKerning,n.letterSpacing=`${e.letterSpacing}px`}update(){const e=this.context;let{width:o,height:n}=this.style;o==="auto"&&(o=0),n==="auto"&&(n=0);const{contentBox:r,paragraphs:h}=this.measure();o||(o=r.width),n=Math.max(n,r.height),this._resizeView(o,n);const s=this.pixelRatio;e.scale(s,s),e.clearRect(0,0,e.canvas.width,e.canvas.height),this._draw(h)}};u.punctuationRegex=/[\s\n\t\u200B\u200C\u200D\u200E\u200F.,?!:;"'(){}\[\]<>\/\\|~#\$%\*\+=&^,。?!:;“”‘’()【】《》……——]/;let _=u;exports.Text=_;
1
+ "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});class y{get left(){return this.x}get top(){return this.y}get right(){return this.x+this.width}get bottom(){return this.y+this.height}constructor({x:t=0,y:h=0,width:s=0,height:n=0}={}){this.x=t,this.y=h,this.width=s,this.height=n}static from(...t){const h=t[0],s=t.slice(1).reduce((n,a)=>(n.x=Math.min(n.x,a.x),n.y=Math.min(n.y,a.y),n.right=Math.max(n.right,a.right),n.bottom=Math.max(n.bottom,a.bottom),n),{x:h.x,y:h.y,right:h.right,bottom:h.bottom});return new y({x:s.x,y:s.y,width:s.right-s.x,height:s.bottom-s.y})}move(t,h){return this.x+=t,this.y+=h,this}clone(){return new y({x:this.x,y:this.y,width:this.width,height:this.height})}}class H{constructor({content:t="",style:h,parent:s,contentBox:n=new y,inlineBox:a=new y,glyphBox:d=new y,centerX:f=0,baseline:w=0}={}){switch(this.content=t,this.style=h,this.parent=s,this.contentBox=n,this.inlineBox=a,this.glyphBox=d,this.centerX=f,this.baseline=w,this.getComputedStyle().textTransform){case"uppercase":this.content=this.content.toUpperCase();break;case"lowercase":this.content=this.content.toLowerCase();break}}getComputedStyle(){var t;return{...(t=this.parent)==null?void 0:t.getComputedStyle(),...this.style}}clone(t){return new H({content:this.content,style:this.style,parent:this.parent,contentBox:this.contentBox.clone(),inlineBox:this.inlineBox.clone(),glyphBox:this.glyphBox.clone(),centerX:this.centerX,baseline:this.baseline,...t})}}class M{constructor({style:t,parent:h,contentBox:s=new y,lineBox:n=new y,glyphBox:a=new y,baseline:d=0,xHeight:f=0,maxCharWidth:w=0,fragments:m=[]}={}){this.style=t,this.parent=h,this.contentBox=s,this.lineBox=n,this.glyphBox=a,this.baseline=d,this.xHeight=f,this.maxCharWidth=w,this.fragments=m}addFragment(t){return this.fragments.push(new H({...t,parent:this})),this}getComputedStyle(){return{...this.parent,...this.style}}clone(t){return new M({style:this.style,parent:this.parent,contentBox:this.contentBox.clone(),lineBox:this.lineBox.clone(),glyphBox:this.glyphBox.clone(),baseline:this.baseline,xHeight:this.xHeight,maxCharWidth:this.maxCharWidth,fragments:this.fragments.map(h=>h.clone()),...t})}}function D(c,t){const h=[];if(typeof c=="string")h.push(new M({parent:t}).addFragment({content:c}));else{c=Array.isArray(c)?c:[c];for(const s of c)if(typeof s=="string")h.push(new M({parent:t}).addFragment({content:s}));else if(Array.isArray(s)){const n=new M({parent:t});s.forEach(a=>{if(typeof a=="string")n.addFragment({content:a});else{const{content:d,...f}=a;n.addFragment({content:d,style:f})}}),h.push(n)}else if("fragments"in s){const{fragments:n,...a}=s,d=new M({style:a,parent:t});n.forEach(f=>{const{content:w,...m}=f;d.addFragment({content:w,style:m})}),h.push(d)}else if("content"in s){const{content:n,...a}=s;h.push(new M({style:a,parent:t}).addFragment({content:n}))}}return h}const Y="OffscreenCanvas"in globalThis;let L;function N(){return L??(L=Y?new OffscreenCanvas(1,1):document.createElement("canvas"))}function T(c,t){switch(t.shadowColor&&(c.shadowColor=t.shadowColor),t.shadowOffsetX!==void 0&&(c.shadowOffsetX=t.shadowOffsetX),t.shadowOffsetY!==void 0&&(c.shadowOffsetY=t.shadowOffsetY),t.shadowBlur!==void 0&&(c.shadowBlur=t.shadowBlur),t.textStrokeColor&&(c.strokeStyle=t.textStrokeColor),t.textStrokeWidth!==void 0&&(c.lineWidth=t.textStrokeWidth),t.color&&(c.fillStyle=t.color),t.direction&&(c.direction=t.direction),t.textAlign&&(c.textAlign=t.textAlign),t.fontKerning&&(c.fontKerning=t.fontKerning),t.verticalAlign){case"baseline":c.textBaseline="alphabetic";break;case"top":case"middle":case"bottom":c.textBaseline=t.verticalAlign;break}(t.fontStyle||t.fontWeight!==void 0||t.fontSize!==void 0||t.fontFamily)&&(c.font=[t.fontStyle||"normal",t.fontWeight||"normal",`${t.fontSize||14}px`,t.fontFamily||"sans-serif"].join(" "))}function E(c,t){const h=N().getContext("2d");return T(h,t),h.measureText(c)}const K=/[\s\n\t\u200B\u200C\u200D\u200E\u200F.,?!:;"'(){}\[\]<>\/\\|~#\$%\*\+=&^,。?!:;“”‘’()【】《》……——]/;function j(c,t,h){const s=[],n=c.slice();let a,d;for(;a=n.shift();){const f=a.fragments.slice();let w=0;const m=[];for(;d=f.shift();){const p=d.getComputedStyle();let S="",l=!1,o=0,i="";for(const B of d.content){if(a.maxCharWidth=Math.max(a.maxCharWidth,E(B,p).width),i+=B,K.test(d.content[++o]))continue;let r,x;switch(p.writingMode){case"vertical-lr":case"vertical-rl":r=h,x=i.length*p.fontSize;break;case"horizontal-tb":default:r=t,x=E(i,p).width;break}x+=i.length*p.letterSpacing;const u=/^[\r\n]$/.test(i);if(u||p.textWrap==="wrap"&&r&&w+x>r){let e=u?S.length+1:S.length;!w&&!e&&(S+=i,e+=i.length),S.length&&m.push(d.clone({content:S})),m.length&&(s.push(a.clone({fragments:m.slice()})),m.length=0);const b=d.content.substring(e);(b.length||f.length)&&n.unshift(a.clone({maxCharWidth:0,fragments:(b.length?[d.clone({content:b})]:[]).concat(f.slice())})),f.length=0,l=!0;break}else w+=x;S+=i,i=""}l||m.push(d.clone())}m.length&&s.push(a.clone({fragments:m}))}return s}function G(c){return{width:0,height:0,color:null,backgroundColor:null,fontSize:14,fontWeight:"normal",fontFamily:"sans-serif",fontStyle:"normal",fontKerning:"normal",textWrap:"wrap",textAlign:"start",verticalAlign:"baseline",textTransform:"none",textDecoration:null,textStrokeWidth:0,textStrokeColor:null,direction:"inherit",lineHeight:1,letterSpacing:0,shadowColor:null,shadowOffsetX:0,shadowOffsetY:0,shadowBlur:0,writingMode:"horizontal-tb",...c}}function R(c){const{content:t}=c,{width:h,height:s,...n}=G(c.style);let a=D(t,n);a=j(a,h,s);const d=a.reduce((o,i)=>o+i.maxCharWidth,0);let f=0,w=0;a.forEach(o=>{const i=[];let B=null,r=f,x=w;o.fragments.forEach((e,b)=>{const g=e.getComputedStyle();switch(g.writingMode){case"vertical-rl":r=d-r;case"vertical-lr":{b||(x=0);const C=e.content.length,k=o.maxCharWidth,v=k*g.lineHeight,A=C*g.fontSize+(C-1)*g.letterSpacing;e.contentBox.x=r+(v-k)/2,e.contentBox.y=x,e.contentBox.width=k,e.contentBox.height=A,e.inlineBox.x=r,e.inlineBox.y=x,e.inlineBox.width=v,e.inlineBox.height=A,e.glyphBox.x=r+(v-k)/2,e.glyphBox.y=x,e.glyphBox.width=k,e.glyphBox.height=A,e.baseline=0,e.centerX=r+v/2,x+=A;break}case"horizontal-tb":{b||(r=0);const{fontBoundingBoxAscent:C,fontBoundingBoxDescent:k,actualBoundingBoxAscent:v,actualBoundingBoxDescent:A,actualBoundingBoxLeft:O,actualBoundingBoxRight:$,width:z}=E(e.content,{...g,textAlign:"center",verticalAlign:"baseline"}),F=g.fontSize,X=F*g.lineHeight,P=x+(X-(C+k))/2+C;e.contentBox.x=r,e.contentBox.y=x+(X-F)/2,e.contentBox.width=z,e.contentBox.height=F,e.inlineBox.x=r,e.inlineBox.y=x,e.inlineBox.width=z,e.inlineBox.height=X,e.glyphBox.x=r,e.glyphBox.y=P-v,e.glyphBox.width=O+$,e.glyphBox.height=v+A,e.baseline=P,e.centerX=r+O,i.push(e.contentBox),o.contentBox=y.from(...i),o.contentBox.height<e.contentBox.height&&(B=e),r+=z;break}}}),o.lineBox=y.from(...o.fragments.map(e=>e.inlineBox)),f+=o.lineBox.width,w+=o.lineBox.height;const u=E("x",{...(B??o).getComputedStyle(),textAlign:"left",verticalAlign:"baseline"});o.xHeight=u.actualBoundingBoxAscent,o.baseline=o.lineBox.y+(o.lineBox.height-(u.fontBoundingBoxAscent+u.fontBoundingBoxDescent))/2+u.fontBoundingBoxAscent});const m=y.from(...a.map(o=>o.lineBox),new y({width:h,height:s})),{width:p,height:S}=m;a.forEach(o=>{o.contentBox=y.from(...o.fragments.map(i=>i.contentBox)),o.glyphBox=y.from(...o.fragments.map(i=>i.glyphBox)),o.fragments.forEach(i=>{const B=i.getComputedStyle(),r=i.inlineBox.x,x=i.inlineBox.y;let u=r,e=x;switch(B.writingMode){case"vertical-rl":case"vertical-lr":switch(B.textAlign){case"end":case"right":e+=S-o.contentBox.height;break;case"center":e+=(S-o.contentBox.height)/2;break}break;case"horizontal-tb":{switch(B.textAlign){case"end":case"right":u+=p-o.contentBox.width;break;case"center":u+=(p-o.contentBox.width)/2;break}switch(B.verticalAlign){case"top":e+=o.lineBox.y-i.inlineBox.y;break;case"middle":e=o.baseline-o.xHeight/2-i.inlineBox.height/2;break;case"bottom":e+=o.lineBox.bottom-i.inlineBox.bottom;break;case"sub":e+=o.baseline-i.glyphBox.bottom;break;case"super":e+=o.baseline-i.glyphBox.y;break;case"text-top":e+=o.glyphBox.y-i.inlineBox.y;break;case"text-bottom":e+=o.glyphBox.bottom-i.inlineBox.bottom;break;case"baseline":default:i.inlineBox.height<o.lineBox.height&&(e+=o.baseline-i.baseline);break}break}}const b=u-r,g=e-x;i.inlineBox.move(b,g),i.contentBox.move(b,g),i.glyphBox.move(b,g),i.baseline+=g,i.centerX+=b}),o.contentBox=y.from(...o.fragments.map(i=>i.contentBox)),o.glyphBox=y.from(...o.fragments.map(i=>i.glyphBox))});const l=y.from(...a.map(o=>o.contentBox));return{box:m,contentBox:l,viewBox:y.from(m,l),glyphBox:y.from(...a.map(o=>o.glyphBox)),paragraphs:a}}function W(c,t,h){if(typeof t=="string"&&t.startsWith("linear-gradient")){const{x0:s,y0:n,x1:a,y1:d,stops:f}=U(t,h.left,h.top,h.width,h.height),w=c.createLinearGradient(s,n,a,d);return f.forEach(m=>w.addColorStop(m.offset,m.color)),w}return t}function U(c,t,h,s,n){var o;const a=((o=c.match(/linear-gradient\((.+)\)$/))==null?void 0:o[1])??"",d=a.split(",")[0],f=d.includes("deg")?d:"0deg",w=a.replace(f,"").matchAll(/(#|rgba|rgb)(.+?) ([\d.]+?%)/gi),p=(Number(f.replace("deg",""))||0)*Math.PI/180,S=s*Math.sin(p),l=n*Math.cos(p);return{x0:t+s/2-S,y0:h+n/2+l,x1:t+s/2+S,y1:h+n/2-l,stops:Array.from(w).map(i=>{let B=i[2];return B.startsWith("(")?B=B.split(",").length>3?`rgba${B}`:`rgb${B}`:B=`#${B}`,{offset:Number(i[3].replace("%",""))/100,color:B}})}}function V(c){const{view:t=document.createElement("canvas"),style:h,pixelRatio:s=1}=c,n={...h},{box:a,viewBox:d,paragraphs:f}=R(c),{x:w,y:m,width:p,height:S}=d,l=t.getContext("2d"),o=-w+p,i=-m+S;t.style.width=`${o}px`,t.style.height=`${i}px`,t.dataset.width=String(a.width),t.dataset.height=String(a.height),t.dataset.pixelRatio=String(s),t.width=Math.max(1,Math.floor(o*s)),t.height=Math.max(1,Math.floor(i*s)),l.scale(s,s),l.clearRect(0,0,t.width,t.height),l.translate(-w,-m);const B=new y({width:p,height:S});return n!=null&&n.color&&(n.color=W(l,n.color,B)),n!=null&&n.backgroundColor&&(n.backgroundColor=W(l,n.backgroundColor,B)),n!=null&&n.textStrokeColor&&(n.textStrokeColor=W(l,n.textStrokeColor,B)),n!=null&&n.backgroundColor&&(l.fillStyle=n.backgroundColor,l.fillRect(0,0,t.width,t.height)),f.forEach(r=>{var x,u,e,b;(x=r.style)!=null&&x.color&&(r.style.color=W(l,r.style.color,r.contentBox)),(u=r.style)!=null&&u.backgroundColor&&(r.style.backgroundColor=W(l,r.style.backgroundColor,r.contentBox)),(e=r.style)!=null&&e.textStrokeColor&&(r.style.textStrokeColor=W(l,r.style.textStrokeColor,r.contentBox)),(b=r.style)!=null&&b.backgroundColor&&(l.fillStyle=r.style.backgroundColor,l.fillRect(r.lineBox.x,r.lineBox.y,r.lineBox.width,r.lineBox.height)),r.fragments.forEach(g=>{var C,k,v,A;(C=g.style)!=null&&C.color&&(g.style.color=W(l,g.style.color,g.contentBox)),(k=g.style)!=null&&k.backgroundColor&&(g.style.backgroundColor=W(l,g.style.backgroundColor,g.contentBox)),(v=g.style)!=null&&v.textStrokeColor&&(g.style.textStrokeColor=W(l,g.style.textStrokeColor,g.contentBox)),(A=g.style)!=null&&A.backgroundColor&&(l.fillStyle=g.style.backgroundColor,l.fillRect(g.inlineBox.x,g.inlineBox.y,g.inlineBox.width,g.inlineBox.height))})}),T(l,n),f.forEach(r=>{r.style&&T(l,r.style),r.fragments.forEach(x=>{T(l,{...x.style,textAlign:"left",verticalAlign:"top"});const{x:u,y:e,width:b,height:g}=x.contentBox,C=x.getComputedStyle();switch(C.writingMode){case"vertical-rl":case"vertical-lr":{let k=0;for(const v of x.content)C.textStrokeWidth&&l.strokeText(v,u,e+k),l.fillText(v,u,e+k),k+=C.fontSize+C.letterSpacing;break}case"horizontal-tb":C.textStrokeWidth&&l.strokeText(x.content,u,e),l.fillText(x.content,u,e);break}switch(C.textDecoration){case"underline":l.beginPath(),l.moveTo(u,e+g-2),l.lineTo(u+b,e+g-2),l.stroke();break;case"line-through":l.beginPath(),l.moveTo(u,e+g/2),l.lineTo(u+b,e+g/2),l.stroke();break}})}),t}exports.measureText=R;exports.renderText=V;
package/dist/index.js CHANGED
@@ -1 +1 @@
1
- (function(u,y){typeof exports=="object"&&typeof module<"u"?y(exports):typeof define=="function"&&define.amd?define(["exports"],y):(u=typeof globalThis<"u"?globalThis:u||self,y(u.modernText={}))})(this,function(u){"use strict";function y(A,e,o,n,r){var a;const h=((a=A.match(/linear-gradient\((.+)\)$/))==null?void 0:a[1])??"",s=h.split(",")[0],t=s.includes("deg")?s:"0deg",i=h.replace(t,"").matchAll(/(#|rgba|rgb)(.+?) ([\d.]+?%)/gi),x=(Number(t.replace("deg",""))||0)*Math.PI/180,g=n*Math.sin(x),f=r*Math.cos(x);return{x0:e+n/2-g,y0:o+r/2+f,x1:e+n/2+g,y1:o+r/2-f,stops:Array.from(i).map(c=>{let B=c[2];return B.startsWith("(")?B=B.split(",").length>3?`rgba${B}`:`rgb${B}`:B=`#${B}`,{offset:Number(c[3].replace("%",""))/100,color:B}})}}const w=class w{static get defaultStyle(){return{width:"auto",height:"auto",fontSize:14,fontWeight:"normal",fontFamily:"sans-serif",fontStyle:"normal",fontKerning:"normal",textWrap:"wrap",textAlign:"start",verticalAlign:"baseline",textTransform:"none",textDecoration:null,textStrokeWidth:0,direction:"inherit",lineHeight:1,letterSpacing:0,shadowOffsetX:0,shadowOffsetY:0,shadowBlur:0,writingMode:"horizontal-tb"}}constructor(e={}){const{view:o=document.createElement("canvas"),pixelRatio:n=window.devicePixelRatio||1,content:r="",style:h}=e;this.view=o,this.context=o.getContext("2d"),this.pixelRatio=n,this.content=r,this.style={...w.defaultStyle,...h},this.update()}_createBox(e=0,o=0,n=0,r=0){return{left:e,top:o,width:n,height:r,right:e+n,bottom:o+r}}_moveBox(e,o=0,n=0){e.left+=o,e.right+=o,e.top+=n,e.bottom+=n}_updateBoxSize(e){e.width=e.right-e.left,e.height=e.bottom-e.top}_mergeBoxes(e){const o=e.slice(1).reduce((n,r)=>(n.left=Math.min(n.left,r.left),n.top=Math.min(n.top,r.top),n.right=Math.max(n.right,r.right),n.bottom=Math.max(n.bottom,r.bottom),n),{...e[0]});return this._updateBoxSize(o),o}measure(){let{width:e}=this.style;e==="auto"&&(e=0);let o=this._createParagraphs(this.content);o=this._wrapParagraphs(o,e);const n=this.context;let r=0;for(let h=o.length,s=0;s<h;s++){const t=o[s],i=[];let l=0,x=null;for(const a of t.fragments){this._setContextStyle({...a.style,textAlign:"center",verticalAlign:"baseline"});const c=n.measureText(a.content),B=c.width,d=a.style.fontSize;a.inlineBox=this._createBox(l,r,B,d*a.style.lineHeight),a.contentBox=this._createBox(a.inlineBox.left,a.inlineBox.top+(a.inlineBox.height-d)/2,B,d);const p=c.fontBoundingBoxAscent+c.fontBoundingBoxDescent,_=c.actualBoundingBoxLeft+c.actualBoundingBoxRight,m=c.actualBoundingBoxAscent+c.actualBoundingBoxDescent;a.baseline=a.inlineBox.top+(a.inlineBox.height-p)/2+c.fontBoundingBoxAscent,a.glyphBox=this._createBox(a.contentBox.left,a.baseline-c.actualBoundingBoxAscent,_,m),a.centerX=a.glyphBox.left+c.actualBoundingBoxLeft,l+=a.contentBox.width,i.push(a.contentBox),t.contentBox=this._mergeBoxes(i),t.contentBox.height<a.contentBox.height&&(x=a)}t.lineBox=this._mergeBoxes([...t.fragments.map(a=>a.inlineBox),this._createBox(0,r,e)]),this._setContextStyle({...(x??t).style,textAlign:"left",verticalAlign:"baseline"});const g=n.measureText("x"),f=g.fontBoundingBoxAscent+g.fontBoundingBoxDescent;t.xHeight=g.actualBoundingBoxAscent,t.baseline=t.lineBox.top+(t.lineBox.height-f)/2+g.fontBoundingBoxAscent,r+=t.lineBox.height}for(let h=o.length,s=0;s<h;s++){const t=o[s];t.fragments.forEach(i=>{const l=i.inlineBox.left,x=i.inlineBox.top;let g,f=x;switch(i.style.textAlign){case"end":case"right":g=l+(t.lineBox.width-t.contentBox.width);break;case"center":g=l+(t.lineBox.width-t.contentBox.width)/2;break;case"start":case"left":default:g=l+t.lineBox.left;break}switch(i.style.verticalAlign){case"top":f=x+(t.lineBox.top-i.inlineBox.top);break;case"middle":f=t.baseline-t.xHeight/2-i.inlineBox.height/2;break;case"bottom":f=x+(t.lineBox.bottom-i.inlineBox.bottom);break;case"sub":f=x+(t.baseline-i.glyphBox.bottom);break;case"super":f=x+(t.baseline-i.glyphBox.top);break;case"text-top":f=x+(t.glyphBox.top-i.inlineBox.top);break;case"text-bottom":f=x+(t.glyphBox.bottom-i.inlineBox.bottom);break;case"baseline":default:i.inlineBox.height<t.lineBox.height&&(f=x+(t.baseline-i.baseline));break}const a=g-l,c=f-x;this._moveBox(i.inlineBox,a,c),this._moveBox(i.contentBox,a,c),this._moveBox(i.glyphBox,a,c),i.baseline+=c,i.centerX+=a}),t.contentBox=this._mergeBoxes(t.fragments.map(i=>i.contentBox)),t.glyphBox=this._mergeBoxes(t.fragments.map(i=>i.glyphBox))}return{actualContentBox:this._mergeBoxes(o.map(h=>h.contentBox)),contentBox:this._mergeBoxes(o.map(h=>h.lineBox)),glyphBox:this._mergeBoxes(o.map(h=>h.glyphBox)),paragraphs:o}}_createParagraphs(e){const o=new Set(["color","backgroundColor","textStrokeColor","shadowColor"]),n=(...t)=>{const i=t.pop();return t.reduce((l,x)=>{for(const g in x)o.has(g)||(l[g]=x[g]);return l},i)},r=(t={})=>{const{width:i,height:l,...x}=this.style;return{contentBox:this._createBox(),lineBox:this._createBox(),glyphBox:this._createBox(),baseline:0,fragments:[],...t,style:n(x,t.style??{})}},h=(t,i)=>{let l;if(typeof t=="string")l={content:t};else{const{content:B,...d}=t;l={content:B,style:d}}const{width:x,height:g,...f}=this.style,a=n(f,i??{},l.style??{});let c=l.content??"";switch(a.textTransform){case"uppercase":c=c.toUpperCase();break;case"lowercase":c=c.toLowerCase();break}return{contentBox:this._createBox(),inlineBox:this._createBox(),glyphBox:this._createBox(),centerX:0,baseline:0,...l,style:a,content:c}},s=[];if(typeof e=="string")s.push(r({fragments:[h(e)]}));else{e=Array.isArray(e)?e:[e];for(const t of e)if(typeof t=="string")s.push(r({fragments:[h(t)]}));else if(Array.isArray(t))s.push(r({fragments:t.map(i=>h(i))}));else if("fragments"in t){const{fragments:i,...l}=t;s.push(r({style:l,fragments:i.map(x=>h(x,l))}))}else if("content"in t){const{content:i,...l}=t;s.push(r({style:l,fragments:[h(i,l)]}))}}return s}_wrapParagraphs(e,o){var i;const n=l=>JSON.parse(JSON.stringify(l)),r=[],h=e.slice();let s,t;for(;s=h.shift();){const l=s.fragments.slice();let x=0;const g=[];for(;t=l.shift();){const f=t.style,a=(i=f.writingMode)==null?void 0:i.startsWith("vertical");this._setContextStyle(f);let c="",B=!1,d=0,p="";for(const _ of t.content){if(p+=_,!a&&w.punctuationRegex.test(t.content[++d]))continue;const m=this.context.measureText(p).width,C=/^[\r\n]$/.test(p);if(C||a||f.textWrap==="wrap"&&o&&x+m>o){let S=C?c.length+1:c.length;!x&&!S&&(c+=p,S+=p.length),c.length&&g.push({...n(t),content:c}),g.length&&(r.push({...n(s),fragments:g.slice()}),g.length=0);const k=t.content.substring(S);(k.length||l.length)&&h.unshift({...n(s),fragments:(k.length?[{...n(t),content:k}]:[]).concat(l.slice())}),l.length=0,B=!0;break}else x+=m;c+=p,p=""}B||g.push(n(t))}g.length&&r.push({...n(s),fragments:g})}return r}_draw(e){const o=this.context,{width:n,height:r}=o.canvas,h={left:0,top:0,width:n,height:r};this.style.backgroundColor&&(o.fillStyle=this._parseColor(this.style.backgroundColor,h),o.fillRect(0,0,n,r)),e.forEach(s=>{s.style.backgroundColor&&(o.fillStyle=this._parseColor(s.style.backgroundColor,s.contentBox),o.fillRect(s.lineBox.left,s.lineBox.top,s.lineBox.width,s.lineBox.height)),s.fragments.forEach(t=>{t.style.backgroundColor&&(o.fillStyle=this._parseColor(t.style.backgroundColor,t.contentBox),o.fillRect(t.inlineBox.left,t.inlineBox.top,t.inlineBox.width,t.inlineBox.height))})}),this._setContextStyle(this.style,h),e.forEach(s=>{this._setContextStyle(s.style,s.contentBox),s.fragments.forEach(t=>{this._setContextStyle({...t.style,textAlign:"left",verticalAlign:"top"},t.contentBox);const{left:i,top:l,width:x,height:g}=t.contentBox;switch(t.style.textStrokeWidth&&o.strokeText(t.content,i,l),o.fillText(t.content,i,l),t.style.textDecoration){case"underline":o.beginPath(),o.moveTo(i,l+g-2),o.lineTo(i+x,l+g-2),o.stroke();break;case"line-through":o.beginPath(),o.moveTo(i,l+g/2),o.lineTo(i+x,l+g/2),o.stroke();break}})})}_resizeView(e,o){const n=this.view;n.style.width=`${e}px`,n.style.height=`${o}px`,n.dataset.width=String(e),n.dataset.height=String(o),n.width=Math.max(1,Math.floor(e*this.pixelRatio)),n.height=Math.max(1,Math.floor(o*this.pixelRatio))}_parseColor(e,o){if(e.startsWith("linear-gradient")){const{x0:n,y0:r,x1:h,y1:s,stops:t}=y(e,o.left,o.top,o.width,o.height),i=this.context.createLinearGradient(n,r,h,s);return t.forEach(l=>i.addColorStop(l.offset,l.color)),i}return e}_setContextStyle(e,o){const n=this.context;switch(e.shadowColor&&(n.shadowColor=e.shadowColor),n.shadowOffsetX=e.shadowOffsetX,n.shadowOffsetY=e.shadowOffsetY,n.shadowBlur=e.shadowBlur,e.textStrokeColor&&o&&(n.strokeStyle=this._parseColor(e.textStrokeColor,o)),n.lineWidth=e.textStrokeWidth,e.color&&o&&(n.fillStyle=this._parseColor(e.color,o)),n.direction=e.direction,n.textAlign=e.textAlign,e.verticalAlign){case"baseline":n.textBaseline="alphabetic";break;case"top":case"middle":case"bottom":n.textBaseline=e.verticalAlign;break}n.font=[e.fontStyle,e.fontWeight,`${e.fontSize}px`,e.fontFamily].join(" "),n.fontKerning=e.fontKerning,n.letterSpacing=`${e.letterSpacing}px`}update(){const e=this.context;let{width:o,height:n}=this.style;o==="auto"&&(o=0),n==="auto"&&(n=0);const{contentBox:r,paragraphs:h}=this.measure();o||(o=r.width),n=Math.max(n,r.height),this._resizeView(o,n);const s=this.pixelRatio;e.scale(s,s),e.clearRect(0,0,e.canvas.width,e.canvas.height),this._draw(h)}};w.punctuationRegex=/[\s\n\t\u200B\u200C\u200D\u200E\u200F.,?!:;"'(){}\[\]<>\/\\|~#\$%\*\+=&^,。?!:;“”‘’()【】《》……——]/;let b=w;u.Text=b,Object.defineProperty(u,Symbol.toStringTag,{value:"Module"})});
1
+ (function(T,f){typeof exports=="object"&&typeof module<"u"?f(exports):typeof define=="function"&&define.amd?define(["exports"],f):(T=typeof globalThis<"u"?globalThis:T||self,f(T.modernText={}))})(this,function(T){"use strict";class f{get left(){return this.x}get top(){return this.y}get right(){return this.x+this.width}get bottom(){return this.y+this.height}constructor({x:t=0,y:h=0,width:l=0,height:n=0}={}){this.x=t,this.y=h,this.width=l,this.height=n}static from(...t){const h=t[0],l=t.slice(1).reduce((n,a)=>(n.x=Math.min(n.x,a.x),n.y=Math.min(n.y,a.y),n.right=Math.max(n.right,a.right),n.bottom=Math.max(n.bottom,a.bottom),n),{x:h.x,y:h.y,right:h.right,bottom:h.bottom});return new f({x:l.x,y:l.y,width:l.right-l.x,height:l.bottom-l.y})}move(t,h){return this.x+=t,this.y+=h,this}clone(){return new f({x:this.x,y:this.y,width:this.width,height:this.height})}}class F{constructor({content:t="",style:h,parent:l,contentBox:n=new f,inlineBox:a=new f,glyphBox:d=new f,centerX:u=0,baseline:w=0}={}){switch(this.content=t,this.style=h,this.parent=l,this.contentBox=n,this.inlineBox=a,this.glyphBox=d,this.centerX=u,this.baseline=w,this.getComputedStyle().textTransform){case"uppercase":this.content=this.content.toUpperCase();break;case"lowercase":this.content=this.content.toLowerCase();break}}getComputedStyle(){var t;return{...(t=this.parent)==null?void 0:t.getComputedStyle(),...this.style}}clone(t){return new F({content:this.content,style:this.style,parent:this.parent,contentBox:this.contentBox.clone(),inlineBox:this.inlineBox.clone(),glyphBox:this.glyphBox.clone(),centerX:this.centerX,baseline:this.baseline,...t})}}class M{constructor({style:t,parent:h,contentBox:l=new f,lineBox:n=new f,glyphBox:a=new f,baseline:d=0,xHeight:u=0,maxCharWidth:w=0,fragments:y=[]}={}){this.style=t,this.parent=h,this.contentBox=l,this.lineBox=n,this.glyphBox=a,this.baseline=d,this.xHeight=u,this.maxCharWidth=w,this.fragments=y}addFragment(t){return this.fragments.push(new F({...t,parent:this})),this}getComputedStyle(){return{...this.parent,...this.style}}clone(t){return new M({style:this.style,parent:this.parent,contentBox:this.contentBox.clone(),lineBox:this.lineBox.clone(),glyphBox:this.glyphBox.clone(),baseline:this.baseline,xHeight:this.xHeight,maxCharWidth:this.maxCharWidth,fragments:this.fragments.map(h=>h.clone()),...t})}}function D(c,t){const h=[];if(typeof c=="string")h.push(new M({parent:t}).addFragment({content:c}));else{c=Array.isArray(c)?c:[c];for(const l of c)if(typeof l=="string")h.push(new M({parent:t}).addFragment({content:l}));else if(Array.isArray(l)){const n=new M({parent:t});l.forEach(a=>{if(typeof a=="string")n.addFragment({content:a});else{const{content:d,...u}=a;n.addFragment({content:d,style:u})}}),h.push(n)}else if("fragments"in l){const{fragments:n,...a}=l,d=new M({style:a,parent:t});n.forEach(u=>{const{content:w,...y}=u;d.addFragment({content:w,style:y})}),h.push(d)}else if("content"in l){const{content:n,...a}=l;h.push(new M({style:a,parent:t}).addFragment({content:n}))}}return h}const Y="OffscreenCanvas"in globalThis;let L;function N(){return L??(L=Y?new OffscreenCanvas(1,1):document.createElement("canvas"))}function E(c,t){switch(t.shadowColor&&(c.shadowColor=t.shadowColor),t.shadowOffsetX!==void 0&&(c.shadowOffsetX=t.shadowOffsetX),t.shadowOffsetY!==void 0&&(c.shadowOffsetY=t.shadowOffsetY),t.shadowBlur!==void 0&&(c.shadowBlur=t.shadowBlur),t.textStrokeColor&&(c.strokeStyle=t.textStrokeColor),t.textStrokeWidth!==void 0&&(c.lineWidth=t.textStrokeWidth),t.color&&(c.fillStyle=t.color),t.direction&&(c.direction=t.direction),t.textAlign&&(c.textAlign=t.textAlign),t.fontKerning&&(c.fontKerning=t.fontKerning),t.verticalAlign){case"baseline":c.textBaseline="alphabetic";break;case"top":case"middle":case"bottom":c.textBaseline=t.verticalAlign;break}(t.fontStyle||t.fontWeight!==void 0||t.fontSize!==void 0||t.fontFamily)&&(c.font=[t.fontStyle||"normal",t.fontWeight||"normal",`${t.fontSize||14}px`,t.fontFamily||"sans-serif"].join(" "))}function z(c,t){const h=N().getContext("2d");return E(h,t),h.measureText(c)}const K=/[\s\n\t\u200B\u200C\u200D\u200E\u200F.,?!:;"'(){}\[\]<>\/\\|~#\$%\*\+=&^,。?!:;“”‘’()【】《》……——]/;function j(c,t,h){const l=[],n=c.slice();let a,d;for(;a=n.shift();){const u=a.fragments.slice();let w=0;const y=[];for(;d=u.shift();){const p=d.getComputedStyle();let S="",s=!1,o=0,i="";for(const B of d.content){if(a.maxCharWidth=Math.max(a.maxCharWidth,z(B,p).width),i+=B,K.test(d.content[++o]))continue;let r,x;switch(p.writingMode){case"vertical-lr":case"vertical-rl":r=h,x=i.length*p.fontSize;break;case"horizontal-tb":default:r=t,x=z(i,p).width;break}x+=i.length*p.letterSpacing;const m=/^[\r\n]$/.test(i);if(m||p.textWrap==="wrap"&&r&&w+x>r){let e=m?S.length+1:S.length;!w&&!e&&(S+=i,e+=i.length),S.length&&y.push(d.clone({content:S})),y.length&&(l.push(a.clone({fragments:y.slice()})),y.length=0);const b=d.content.substring(e);(b.length||u.length)&&n.unshift(a.clone({maxCharWidth:0,fragments:(b.length?[d.clone({content:b})]:[]).concat(u.slice())})),u.length=0,s=!0;break}else w+=x;S+=i,i=""}s||y.push(d.clone())}y.length&&l.push(a.clone({fragments:y}))}return l}function G(c){return{width:0,height:0,color:null,backgroundColor:null,fontSize:14,fontWeight:"normal",fontFamily:"sans-serif",fontStyle:"normal",fontKerning:"normal",textWrap:"wrap",textAlign:"start",verticalAlign:"baseline",textTransform:"none",textDecoration:null,textStrokeWidth:0,textStrokeColor:null,direction:"inherit",lineHeight:1,letterSpacing:0,shadowColor:null,shadowOffsetX:0,shadowOffsetY:0,shadowBlur:0,writingMode:"horizontal-tb",...c}}function P(c){const{content:t}=c,{width:h,height:l,...n}=G(c.style);let a=D(t,n);a=j(a,h,l);const d=a.reduce((o,i)=>o+i.maxCharWidth,0);let u=0,w=0;a.forEach(o=>{const i=[];let B=null,r=u,x=w;o.fragments.forEach((e,b)=>{const g=e.getComputedStyle();switch(g.writingMode){case"vertical-rl":r=d-r;case"vertical-lr":{b||(x=0);const C=e.content.length,k=o.maxCharWidth,v=k*g.lineHeight,W=C*g.fontSize+(C-1)*g.letterSpacing;e.contentBox.x=r+(v-k)/2,e.contentBox.y=x,e.contentBox.width=k,e.contentBox.height=W,e.inlineBox.x=r,e.inlineBox.y=x,e.inlineBox.width=v,e.inlineBox.height=W,e.glyphBox.x=r+(v-k)/2,e.glyphBox.y=x,e.glyphBox.width=k,e.glyphBox.height=W,e.baseline=0,e.centerX=r+v/2,x+=W;break}case"horizontal-tb":{b||(r=0);const{fontBoundingBoxAscent:C,fontBoundingBoxDescent:k,actualBoundingBoxAscent:v,actualBoundingBoxDescent:W,actualBoundingBoxLeft:R,actualBoundingBoxRight:I,width:X}=z(e.content,{...g,textAlign:"center",verticalAlign:"baseline"}),H=g.fontSize,O=H*g.lineHeight,$=x+(O-(C+k))/2+C;e.contentBox.x=r,e.contentBox.y=x+(O-H)/2,e.contentBox.width=X,e.contentBox.height=H,e.inlineBox.x=r,e.inlineBox.y=x,e.inlineBox.width=X,e.inlineBox.height=O,e.glyphBox.x=r,e.glyphBox.y=$-v,e.glyphBox.width=R+I,e.glyphBox.height=v+W,e.baseline=$,e.centerX=r+R,i.push(e.contentBox),o.contentBox=f.from(...i),o.contentBox.height<e.contentBox.height&&(B=e),r+=X;break}}}),o.lineBox=f.from(...o.fragments.map(e=>e.inlineBox)),u+=o.lineBox.width,w+=o.lineBox.height;const m=z("x",{...(B??o).getComputedStyle(),textAlign:"left",verticalAlign:"baseline"});o.xHeight=m.actualBoundingBoxAscent,o.baseline=o.lineBox.y+(o.lineBox.height-(m.fontBoundingBoxAscent+m.fontBoundingBoxDescent))/2+m.fontBoundingBoxAscent});const y=f.from(...a.map(o=>o.lineBox),new f({width:h,height:l})),{width:p,height:S}=y;a.forEach(o=>{o.contentBox=f.from(...o.fragments.map(i=>i.contentBox)),o.glyphBox=f.from(...o.fragments.map(i=>i.glyphBox)),o.fragments.forEach(i=>{const B=i.getComputedStyle(),r=i.inlineBox.x,x=i.inlineBox.y;let m=r,e=x;switch(B.writingMode){case"vertical-rl":case"vertical-lr":switch(B.textAlign){case"end":case"right":e+=S-o.contentBox.height;break;case"center":e+=(S-o.contentBox.height)/2;break}break;case"horizontal-tb":{switch(B.textAlign){case"end":case"right":m+=p-o.contentBox.width;break;case"center":m+=(p-o.contentBox.width)/2;break}switch(B.verticalAlign){case"top":e+=o.lineBox.y-i.inlineBox.y;break;case"middle":e=o.baseline-o.xHeight/2-i.inlineBox.height/2;break;case"bottom":e+=o.lineBox.bottom-i.inlineBox.bottom;break;case"sub":e+=o.baseline-i.glyphBox.bottom;break;case"super":e+=o.baseline-i.glyphBox.y;break;case"text-top":e+=o.glyphBox.y-i.inlineBox.y;break;case"text-bottom":e+=o.glyphBox.bottom-i.inlineBox.bottom;break;case"baseline":default:i.inlineBox.height<o.lineBox.height&&(e+=o.baseline-i.baseline);break}break}}const b=m-r,g=e-x;i.inlineBox.move(b,g),i.contentBox.move(b,g),i.glyphBox.move(b,g),i.baseline+=g,i.centerX+=b}),o.contentBox=f.from(...o.fragments.map(i=>i.contentBox)),o.glyphBox=f.from(...o.fragments.map(i=>i.glyphBox))});const s=f.from(...a.map(o=>o.contentBox));return{box:y,contentBox:s,viewBox:f.from(y,s),glyphBox:f.from(...a.map(o=>o.glyphBox)),paragraphs:a}}function A(c,t,h){if(typeof t=="string"&&t.startsWith("linear-gradient")){const{x0:l,y0:n,x1:a,y1:d,stops:u}=U(t,h.left,h.top,h.width,h.height),w=c.createLinearGradient(l,n,a,d);return u.forEach(y=>w.addColorStop(y.offset,y.color)),w}return t}function U(c,t,h,l,n){var o;const a=((o=c.match(/linear-gradient\((.+)\)$/))==null?void 0:o[1])??"",d=a.split(",")[0],u=d.includes("deg")?d:"0deg",w=a.replace(u,"").matchAll(/(#|rgba|rgb)(.+?) ([\d.]+?%)/gi),p=(Number(u.replace("deg",""))||0)*Math.PI/180,S=l*Math.sin(p),s=n*Math.cos(p);return{x0:t+l/2-S,y0:h+n/2+s,x1:t+l/2+S,y1:h+n/2-s,stops:Array.from(w).map(i=>{let B=i[2];return B.startsWith("(")?B=B.split(",").length>3?`rgba${B}`:`rgb${B}`:B=`#${B}`,{offset:Number(i[3].replace("%",""))/100,color:B}})}}function V(c){const{view:t=document.createElement("canvas"),style:h,pixelRatio:l=1}=c,n={...h},{box:a,viewBox:d,paragraphs:u}=P(c),{x:w,y,width:p,height:S}=d,s=t.getContext("2d"),o=-w+p,i=-y+S;t.style.width=`${o}px`,t.style.height=`${i}px`,t.dataset.width=String(a.width),t.dataset.height=String(a.height),t.dataset.pixelRatio=String(l),t.width=Math.max(1,Math.floor(o*l)),t.height=Math.max(1,Math.floor(i*l)),s.scale(l,l),s.clearRect(0,0,t.width,t.height),s.translate(-w,-y);const B=new f({width:p,height:S});return n!=null&&n.color&&(n.color=A(s,n.color,B)),n!=null&&n.backgroundColor&&(n.backgroundColor=A(s,n.backgroundColor,B)),n!=null&&n.textStrokeColor&&(n.textStrokeColor=A(s,n.textStrokeColor,B)),n!=null&&n.backgroundColor&&(s.fillStyle=n.backgroundColor,s.fillRect(0,0,t.width,t.height)),u.forEach(r=>{var x,m,e,b;(x=r.style)!=null&&x.color&&(r.style.color=A(s,r.style.color,r.contentBox)),(m=r.style)!=null&&m.backgroundColor&&(r.style.backgroundColor=A(s,r.style.backgroundColor,r.contentBox)),(e=r.style)!=null&&e.textStrokeColor&&(r.style.textStrokeColor=A(s,r.style.textStrokeColor,r.contentBox)),(b=r.style)!=null&&b.backgroundColor&&(s.fillStyle=r.style.backgroundColor,s.fillRect(r.lineBox.x,r.lineBox.y,r.lineBox.width,r.lineBox.height)),r.fragments.forEach(g=>{var C,k,v,W;(C=g.style)!=null&&C.color&&(g.style.color=A(s,g.style.color,g.contentBox)),(k=g.style)!=null&&k.backgroundColor&&(g.style.backgroundColor=A(s,g.style.backgroundColor,g.contentBox)),(v=g.style)!=null&&v.textStrokeColor&&(g.style.textStrokeColor=A(s,g.style.textStrokeColor,g.contentBox)),(W=g.style)!=null&&W.backgroundColor&&(s.fillStyle=g.style.backgroundColor,s.fillRect(g.inlineBox.x,g.inlineBox.y,g.inlineBox.width,g.inlineBox.height))})}),E(s,n),u.forEach(r=>{r.style&&E(s,r.style),r.fragments.forEach(x=>{E(s,{...x.style,textAlign:"left",verticalAlign:"top"});const{x:m,y:e,width:b,height:g}=x.contentBox,C=x.getComputedStyle();switch(C.writingMode){case"vertical-rl":case"vertical-lr":{let k=0;for(const v of x.content)C.textStrokeWidth&&s.strokeText(v,m,e+k),s.fillText(v,m,e+k),k+=C.fontSize+C.letterSpacing;break}case"horizontal-tb":C.textStrokeWidth&&s.strokeText(x.content,m,e),s.fillText(x.content,m,e);break}switch(C.textDecoration){case"underline":s.beginPath(),s.moveTo(m,e+g-2),s.lineTo(m+b,e+g-2),s.stroke();break;case"line-through":s.beginPath(),s.moveTo(m,e+g/2),s.lineTo(m+b,e+g/2),s.stroke();break}})}),t}T.measureText=P,T.renderText=V,Object.defineProperty(T,Symbol.toStringTag,{value:"Module"})});
package/dist/index.mjs CHANGED
@@ -1,361 +1,450 @@
1
- function C(k, e, o, n, r) {
2
- var a;
3
- const h = ((a = k.match(/linear-gradient\((.+)\)$/)) == null ? void 0 : a[1]) ?? "", s = h.split(",")[0], t = s.includes("deg") ? s : "0deg", i = h.replace(t, "").matchAll(/(#|rgba|rgb)(.+?) ([\d.]+?%)/gi), x = (Number(t.replace("deg", "")) || 0) * Math.PI / 180, g = n * Math.sin(x), f = r * Math.cos(x);
4
- return {
5
- x0: e + n / 2 - g,
6
- y0: o + r / 2 + f,
7
- x1: e + n / 2 + g,
8
- y1: o + r / 2 - f,
9
- stops: Array.from(i).map((c) => {
10
- let B = c[2];
11
- return B.startsWith("(") ? B = B.split(",").length > 3 ? `rgba${B}` : `rgb${B}` : B = `#${B}`, {
12
- offset: Number(c[3].replace("%", "")) / 100,
13
- color: B
14
- };
15
- })
16
- };
1
+ class y {
2
+ get left() {
3
+ return this.x;
4
+ }
5
+ get top() {
6
+ return this.y;
7
+ }
8
+ get right() {
9
+ return this.x + this.width;
10
+ }
11
+ get bottom() {
12
+ return this.y + this.height;
13
+ }
14
+ constructor({ x: t = 0, y: h = 0, width: s = 0, height: n = 0 } = {}) {
15
+ this.x = t, this.y = h, this.width = s, this.height = n;
16
+ }
17
+ static from(...t) {
18
+ const h = t[0], s = t.slice(1).reduce((n, a) => (n.x = Math.min(n.x, a.x), n.y = Math.min(n.y, a.y), n.right = Math.max(n.right, a.right), n.bottom = Math.max(n.bottom, a.bottom), n), { x: h.x, y: h.y, right: h.right, bottom: h.bottom });
19
+ return new y({
20
+ x: s.x,
21
+ y: s.y,
22
+ width: s.right - s.x,
23
+ height: s.bottom - s.y
24
+ });
25
+ }
26
+ move(t, h) {
27
+ return this.x += t, this.y += h, this;
28
+ }
29
+ clone() {
30
+ return new y({
31
+ x: this.x,
32
+ y: this.y,
33
+ width: this.width,
34
+ height: this.height
35
+ });
36
+ }
17
37
  }
18
- const u = class u {
19
- static get defaultStyle() {
38
+ class H {
39
+ constructor({
40
+ content: t = "",
41
+ style: h,
42
+ parent: s,
43
+ contentBox: n = new y(),
44
+ inlineBox: a = new y(),
45
+ glyphBox: d = new y(),
46
+ centerX: f = 0,
47
+ baseline: w = 0
48
+ } = {}) {
49
+ switch (this.content = t, this.style = h, this.parent = s, this.contentBox = n, this.inlineBox = a, this.glyphBox = d, this.centerX = f, this.baseline = w, this.getComputedStyle().textTransform) {
50
+ case "uppercase":
51
+ this.content = this.content.toUpperCase();
52
+ break;
53
+ case "lowercase":
54
+ this.content = this.content.toLowerCase();
55
+ break;
56
+ }
57
+ }
58
+ getComputedStyle() {
59
+ var t;
20
60
  return {
21
- width: "auto",
22
- height: "auto",
23
- fontSize: 14,
24
- fontWeight: "normal",
25
- fontFamily: "sans-serif",
26
- fontStyle: "normal",
27
- fontKerning: "normal",
28
- textWrap: "wrap",
29
- textAlign: "start",
30
- verticalAlign: "baseline",
31
- textTransform: "none",
32
- textDecoration: null,
33
- textStrokeWidth: 0,
34
- direction: "inherit",
35
- lineHeight: 1,
36
- letterSpacing: 0,
37
- shadowOffsetX: 0,
38
- shadowOffsetY: 0,
39
- shadowBlur: 0,
40
- writingMode: "horizontal-tb"
61
+ ...(t = this.parent) == null ? void 0 : t.getComputedStyle(),
62
+ ...this.style
41
63
  };
42
64
  }
43
- constructor(e = {}) {
44
- const {
45
- view: o = document.createElement("canvas"),
46
- pixelRatio: n = window.devicePixelRatio || 1,
47
- content: r = "",
48
- style: h
49
- } = e;
50
- this.view = o, this.context = o.getContext("2d"), this.pixelRatio = n, this.content = r, this.style = {
51
- ...u.defaultStyle,
52
- ...h
53
- }, this.update();
65
+ clone(t) {
66
+ return new H({
67
+ content: this.content,
68
+ style: this.style,
69
+ parent: this.parent,
70
+ contentBox: this.contentBox.clone(),
71
+ inlineBox: this.inlineBox.clone(),
72
+ glyphBox: this.glyphBox.clone(),
73
+ centerX: this.centerX,
74
+ baseline: this.baseline,
75
+ ...t
76
+ });
77
+ }
78
+ }
79
+ class M {
80
+ constructor({
81
+ style: t,
82
+ parent: h,
83
+ contentBox: s = new y(),
84
+ lineBox: n = new y(),
85
+ glyphBox: a = new y(),
86
+ baseline: d = 0,
87
+ xHeight: f = 0,
88
+ maxCharWidth: w = 0,
89
+ fragments: m = []
90
+ } = {}) {
91
+ this.style = t, this.parent = h, this.contentBox = s, this.lineBox = n, this.glyphBox = a, this.baseline = d, this.xHeight = f, this.maxCharWidth = w, this.fragments = m;
92
+ }
93
+ addFragment(t) {
94
+ return this.fragments.push(new H({ ...t, parent: this })), this;
54
95
  }
55
- _createBox(e = 0, o = 0, n = 0, r = 0) {
96
+ getComputedStyle() {
56
97
  return {
57
- left: e,
58
- top: o,
59
- width: n,
60
- height: r,
61
- right: e + n,
62
- bottom: o + r
98
+ ...this.parent,
99
+ ...this.style
63
100
  };
64
101
  }
65
- _moveBox(e, o = 0, n = 0) {
66
- e.left += o, e.right += o, e.top += n, e.bottom += n;
102
+ clone(t) {
103
+ return new M({
104
+ style: this.style,
105
+ parent: this.parent,
106
+ contentBox: this.contentBox.clone(),
107
+ lineBox: this.lineBox.clone(),
108
+ glyphBox: this.glyphBox.clone(),
109
+ baseline: this.baseline,
110
+ xHeight: this.xHeight,
111
+ maxCharWidth: this.maxCharWidth,
112
+ fragments: this.fragments.map((h) => h.clone()),
113
+ ...t
114
+ });
67
115
  }
68
- _updateBoxSize(e) {
69
- e.width = e.right - e.left, e.height = e.bottom - e.top;
116
+ }
117
+ function $(c, t) {
118
+ const h = [];
119
+ if (typeof c == "string")
120
+ h.push(new M({ parent: t }).addFragment({ content: c }));
121
+ else {
122
+ c = Array.isArray(c) ? c : [c];
123
+ for (const s of c)
124
+ if (typeof s == "string")
125
+ h.push(new M({ parent: t }).addFragment({ content: s }));
126
+ else if (Array.isArray(s)) {
127
+ const n = new M({ parent: t });
128
+ s.forEach((a) => {
129
+ if (typeof a == "string")
130
+ n.addFragment({ content: a });
131
+ else {
132
+ const { content: d, ...f } = a;
133
+ n.addFragment({ content: d, style: f });
134
+ }
135
+ }), h.push(n);
136
+ } else if ("fragments" in s) {
137
+ const { fragments: n, ...a } = s, d = new M({ style: a, parent: t });
138
+ n.forEach((f) => {
139
+ const { content: w, ...m } = f;
140
+ d.addFragment({ content: w, style: m });
141
+ }), h.push(d);
142
+ } else if ("content" in s) {
143
+ const { content: n, ...a } = s;
144
+ h.push(new M({ style: a, parent: t }).addFragment({ content: n }));
145
+ }
70
146
  }
71
- _mergeBoxes(e) {
72
- const o = e.slice(1).reduce((n, r) => (n.left = Math.min(n.left, r.left), n.top = Math.min(n.top, r.top), n.right = Math.max(n.right, r.right), n.bottom = Math.max(n.bottom, r.bottom), n), { ...e[0] });
73
- return this._updateBoxSize(o), o;
147
+ return h;
148
+ }
149
+ const D = "OffscreenCanvas" in globalThis;
150
+ let Y;
151
+ function L() {
152
+ return Y ?? (Y = D ? new OffscreenCanvas(1, 1) : document.createElement("canvas"));
153
+ }
154
+ function E(c, t) {
155
+ switch (t.shadowColor && (c.shadowColor = t.shadowColor), t.shadowOffsetX !== void 0 && (c.shadowOffsetX = t.shadowOffsetX), t.shadowOffsetY !== void 0 && (c.shadowOffsetY = t.shadowOffsetY), t.shadowBlur !== void 0 && (c.shadowBlur = t.shadowBlur), t.textStrokeColor && (c.strokeStyle = t.textStrokeColor), t.textStrokeWidth !== void 0 && (c.lineWidth = t.textStrokeWidth), t.color && (c.fillStyle = t.color), t.direction && (c.direction = t.direction), t.textAlign && (c.textAlign = t.textAlign), t.fontKerning && (c.fontKerning = t.fontKerning), t.verticalAlign) {
156
+ case "baseline":
157
+ c.textBaseline = "alphabetic";
158
+ break;
159
+ case "top":
160
+ case "middle":
161
+ case "bottom":
162
+ c.textBaseline = t.verticalAlign;
163
+ break;
74
164
  }
75
- measure() {
76
- let { width: e } = this.style;
77
- e === "auto" && (e = 0);
78
- let o = this._createParagraphs(this.content);
79
- o = this._wrapParagraphs(o, e);
80
- const n = this.context;
81
- let r = 0;
82
- for (let h = o.length, s = 0; s < h; s++) {
83
- const t = o[s], i = [];
84
- let l = 0, x = null;
85
- for (const a of t.fragments) {
86
- this._setContextStyle({
87
- ...a.style,
88
- textAlign: "center",
89
- verticalAlign: "baseline"
90
- });
91
- const c = n.measureText(a.content), B = c.width, d = a.style.fontSize;
92
- a.inlineBox = this._createBox(
93
- l,
94
- r,
95
- B,
96
- d * a.style.lineHeight
97
- ), a.contentBox = this._createBox(
98
- a.inlineBox.left,
99
- a.inlineBox.top + (a.inlineBox.height - d) / 2,
100
- B,
101
- d
102
- );
103
- const p = c.fontBoundingBoxAscent + c.fontBoundingBoxDescent, w = c.actualBoundingBoxLeft + c.actualBoundingBoxRight, y = c.actualBoundingBoxAscent + c.actualBoundingBoxDescent;
104
- a.baseline = a.inlineBox.top + (a.inlineBox.height - p) / 2 + c.fontBoundingBoxAscent, a.glyphBox = this._createBox(
105
- a.contentBox.left,
106
- a.baseline - c.actualBoundingBoxAscent,
107
- w,
108
- y
109
- ), a.centerX = a.glyphBox.left + c.actualBoundingBoxLeft, l += a.contentBox.width, i.push(a.contentBox), t.contentBox = this._mergeBoxes(i), t.contentBox.height < a.contentBox.height && (x = a);
110
- }
111
- t.lineBox = this._mergeBoxes([
112
- ...t.fragments.map((a) => a.inlineBox),
113
- this._createBox(0, r, e)
114
- ]), this._setContextStyle({
115
- ...(x ?? t).style,
116
- textAlign: "left",
117
- verticalAlign: "baseline"
118
- });
119
- const g = n.measureText("x"), f = g.fontBoundingBoxAscent + g.fontBoundingBoxDescent;
120
- t.xHeight = g.actualBoundingBoxAscent, t.baseline = t.lineBox.top + (t.lineBox.height - f) / 2 + g.fontBoundingBoxAscent, r += t.lineBox.height;
121
- }
122
- for (let h = o.length, s = 0; s < h; s++) {
123
- const t = o[s];
124
- t.fragments.forEach((i) => {
125
- const l = i.inlineBox.left, x = i.inlineBox.top;
126
- let g, f = x;
127
- switch (i.style.textAlign) {
128
- case "end":
129
- case "right":
130
- g = l + (t.lineBox.width - t.contentBox.width);
131
- break;
132
- case "center":
133
- g = l + (t.lineBox.width - t.contentBox.width) / 2;
134
- break;
135
- case "start":
136
- case "left":
137
- default:
138
- g = l + t.lineBox.left;
139
- break;
140
- }
141
- switch (i.style.verticalAlign) {
142
- case "top":
143
- f = x + (t.lineBox.top - i.inlineBox.top);
144
- break;
145
- case "middle":
146
- f = t.baseline - t.xHeight / 2 - i.inlineBox.height / 2;
147
- break;
148
- case "bottom":
149
- f = x + (t.lineBox.bottom - i.inlineBox.bottom);
150
- break;
151
- case "sub":
152
- f = x + (t.baseline - i.glyphBox.bottom);
153
- break;
154
- case "super":
155
- f = x + (t.baseline - i.glyphBox.top);
156
- break;
157
- case "text-top":
158
- f = x + (t.glyphBox.top - i.inlineBox.top);
159
- break;
160
- case "text-bottom":
161
- f = x + (t.glyphBox.bottom - i.inlineBox.bottom);
165
+ (t.fontStyle || t.fontWeight !== void 0 || t.fontSize !== void 0 || t.fontFamily) && (c.font = [
166
+ t.fontStyle || "normal",
167
+ t.fontWeight || "normal",
168
+ `${t.fontSize || 14}px`,
169
+ t.fontFamily || "sans-serif"
170
+ ].join(" "));
171
+ }
172
+ function T(c, t) {
173
+ const h = L().getContext("2d");
174
+ return E(h, t), h.measureText(c);
175
+ }
176
+ const N = /[\s\n\t\u200B\u200C\u200D\u200E\u200F.,?!:;"'(){}\[\]<>\/\\|~#\$%\*\+=&^,。?!:;“”‘’()【】《》……——]/;
177
+ function K(c, t, h) {
178
+ const s = [], n = c.slice();
179
+ let a, d;
180
+ for (; a = n.shift(); ) {
181
+ const f = a.fragments.slice();
182
+ let w = 0;
183
+ const m = [];
184
+ for (; d = f.shift(); ) {
185
+ const p = d.getComputedStyle();
186
+ let C = "", l = !1, o = 0, i = "";
187
+ for (const B of d.content) {
188
+ if (a.maxCharWidth = Math.max(a.maxCharWidth, T(B, p).width), i += B, N.test(d.content[++o]))
189
+ continue;
190
+ let r, x;
191
+ switch (p.writingMode) {
192
+ case "vertical-lr":
193
+ case "vertical-rl":
194
+ r = h, x = i.length * p.fontSize;
162
195
  break;
163
- case "baseline":
196
+ case "horizontal-tb":
164
197
  default:
165
- i.inlineBox.height < t.lineBox.height && (f = x + (t.baseline - i.baseline));
198
+ r = t, x = T(i, p).width;
166
199
  break;
167
200
  }
168
- const a = g - l, c = f - x;
169
- this._moveBox(i.inlineBox, a, c), this._moveBox(i.contentBox, a, c), this._moveBox(i.glyphBox, a, c), i.baseline += c, i.centerX += a;
170
- }), t.contentBox = this._mergeBoxes(t.fragments.map((i) => i.contentBox)), t.glyphBox = this._mergeBoxes(t.fragments.map((i) => i.glyphBox));
201
+ x += i.length * p.letterSpacing;
202
+ const u = /^[\r\n]$/.test(i);
203
+ if (u || p.textWrap === "wrap" && r && w + x > r) {
204
+ let e = u ? C.length + 1 : C.length;
205
+ !w && !e && (C += i, e += i.length), C.length && m.push(d.clone({ content: C })), m.length && (s.push(
206
+ a.clone({
207
+ fragments: m.slice()
208
+ })
209
+ ), m.length = 0);
210
+ const b = d.content.substring(e);
211
+ (b.length || f.length) && n.unshift(
212
+ a.clone({
213
+ maxCharWidth: 0,
214
+ fragments: (b.length ? [d.clone({ content: b })] : []).concat(f.slice())
215
+ })
216
+ ), f.length = 0, l = !0;
217
+ break;
218
+ } else
219
+ w += x;
220
+ C += i, i = "";
221
+ }
222
+ l || m.push(d.clone());
171
223
  }
172
- return {
173
- actualContentBox: this._mergeBoxes(o.map((h) => h.contentBox)),
174
- contentBox: this._mergeBoxes(o.map((h) => h.lineBox)),
175
- glyphBox: this._mergeBoxes(o.map((h) => h.glyphBox)),
176
- paragraphs: o
177
- };
224
+ m.length && s.push(a.clone({ fragments: m }));
178
225
  }
179
- _createParagraphs(e) {
180
- const o = /* @__PURE__ */ new Set([
181
- "color",
182
- "backgroundColor",
183
- "textStrokeColor",
184
- "shadowColor"
185
- ]), n = (...t) => {
186
- const i = t.pop();
187
- return t.reduce((l, x) => {
188
- for (const g in x)
189
- o.has(g) || (l[g] = x[g]);
190
- return l;
191
- }, i);
192
- }, r = (t = {}) => {
193
- const { width: i, height: l, ...x } = this.style;
194
- return {
195
- contentBox: this._createBox(),
196
- lineBox: this._createBox(),
197
- glyphBox: this._createBox(),
198
- baseline: 0,
199
- fragments: [],
200
- ...t,
201
- style: n(x, t.style ?? {})
202
- };
203
- }, h = (t, i) => {
204
- let l;
205
- if (typeof t == "string")
206
- l = { content: t };
207
- else {
208
- const { content: B, ...d } = t;
209
- l = { content: B, style: d };
210
- }
211
- const { width: x, height: g, ...f } = this.style, a = n(f, i ?? {}, l.style ?? {});
212
- let c = l.content ?? "";
213
- switch (a.textTransform) {
214
- case "uppercase":
215
- c = c.toUpperCase();
226
+ return s;
227
+ }
228
+ function G(c) {
229
+ return {
230
+ width: 0,
231
+ height: 0,
232
+ color: null,
233
+ backgroundColor: null,
234
+ fontSize: 14,
235
+ fontWeight: "normal",
236
+ fontFamily: "sans-serif",
237
+ fontStyle: "normal",
238
+ fontKerning: "normal",
239
+ textWrap: "wrap",
240
+ textAlign: "start",
241
+ verticalAlign: "baseline",
242
+ textTransform: "none",
243
+ textDecoration: null,
244
+ textStrokeWidth: 0,
245
+ textStrokeColor: null,
246
+ direction: "inherit",
247
+ lineHeight: 1,
248
+ letterSpacing: 0,
249
+ shadowColor: null,
250
+ shadowOffsetX: 0,
251
+ shadowOffsetY: 0,
252
+ shadowBlur: 0,
253
+ writingMode: "horizontal-tb",
254
+ ...c
255
+ };
256
+ }
257
+ function U(c) {
258
+ const { content: t } = c, { width: h, height: s, ...n } = G(c.style);
259
+ let a = $(t, n);
260
+ a = K(a, h, s);
261
+ const d = a.reduce((o, i) => o + i.maxCharWidth, 0);
262
+ let f = 0, w = 0;
263
+ a.forEach((o) => {
264
+ const i = [];
265
+ let B = null, r = f, x = w;
266
+ o.fragments.forEach((e, b) => {
267
+ const g = e.getComputedStyle();
268
+ switch (g.writingMode) {
269
+ case "vertical-rl":
270
+ r = d - r;
271
+ case "vertical-lr": {
272
+ b || (x = 0);
273
+ const S = e.content.length, k = o.maxCharWidth, v = k * g.lineHeight, A = S * g.fontSize + (S - 1) * g.letterSpacing;
274
+ e.contentBox.x = r + (v - k) / 2, e.contentBox.y = x, e.contentBox.width = k, e.contentBox.height = A, e.inlineBox.x = r, e.inlineBox.y = x, e.inlineBox.width = v, e.inlineBox.height = A, e.glyphBox.x = r + (v - k) / 2, e.glyphBox.y = x, e.glyphBox.width = k, e.glyphBox.height = A, e.baseline = 0, e.centerX = r + v / 2, x += A;
216
275
  break;
217
- case "lowercase":
218
- c = c.toLowerCase();
276
+ }
277
+ case "horizontal-tb": {
278
+ b || (r = 0);
279
+ const {
280
+ fontBoundingBoxAscent: S,
281
+ fontBoundingBoxDescent: k,
282
+ actualBoundingBoxAscent: v,
283
+ actualBoundingBoxDescent: A,
284
+ actualBoundingBoxLeft: O,
285
+ actualBoundingBoxRight: P,
286
+ width: z
287
+ } = T(e.content, {
288
+ ...g,
289
+ textAlign: "center",
290
+ verticalAlign: "baseline"
291
+ }), F = g.fontSize, X = F * g.lineHeight, R = x + (X - (S + k)) / 2 + S;
292
+ e.contentBox.x = r, e.contentBox.y = x + (X - F) / 2, e.contentBox.width = z, e.contentBox.height = F, e.inlineBox.x = r, e.inlineBox.y = x, e.inlineBox.width = z, e.inlineBox.height = X, e.glyphBox.x = r, e.glyphBox.y = R - v, e.glyphBox.width = O + P, e.glyphBox.height = v + A, e.baseline = R, e.centerX = r + O, i.push(e.contentBox), o.contentBox = y.from(...i), o.contentBox.height < e.contentBox.height && (B = e), r += z;
219
293
  break;
220
- }
221
- return {
222
- contentBox: this._createBox(),
223
- inlineBox: this._createBox(),
224
- glyphBox: this._createBox(),
225
- centerX: 0,
226
- baseline: 0,
227
- ...l,
228
- style: a,
229
- content: c
230
- };
231
- }, s = [];
232
- if (typeof e == "string")
233
- s.push(r({ fragments: [h(e)] }));
234
- else {
235
- e = Array.isArray(e) ? e : [e];
236
- for (const t of e)
237
- if (typeof t == "string")
238
- s.push(r({ fragments: [h(t)] }));
239
- else if (Array.isArray(t))
240
- s.push(r({ fragments: t.map((i) => h(i)) }));
241
- else if ("fragments" in t) {
242
- const { fragments: i, ...l } = t;
243
- s.push(r({
244
- style: l,
245
- fragments: i.map((x) => h(x, l))
246
- }));
247
- } else if ("content" in t) {
248
- const { content: i, ...l } = t;
249
- s.push(r({
250
- style: l,
251
- fragments: [h(i, l)]
252
- }));
253
294
  }
254
- }
255
- return s;
256
- }
257
- _wrapParagraphs(e, o) {
258
- var i;
259
- const n = (l) => JSON.parse(JSON.stringify(l)), r = [], h = e.slice();
260
- let s, t;
261
- for (; s = h.shift(); ) {
262
- const l = s.fragments.slice();
263
- let x = 0;
264
- const g = [];
265
- for (; t = l.shift(); ) {
266
- const f = t.style, a = (i = f.writingMode) == null ? void 0 : i.startsWith("vertical");
267
- this._setContextStyle(f);
268
- let c = "", B = !1, d = 0, p = "";
269
- for (const w of t.content) {
270
- if (p += w, !a && u.punctuationRegex.test(t.content[++d]))
271
- continue;
272
- const y = this.context.measureText(p).width, _ = /^[\r\n]$/.test(p);
273
- if (_ || a || f.textWrap === "wrap" && o && x + y > o) {
274
- let m = _ ? c.length + 1 : c.length;
275
- !x && !m && (c += p, m += p.length), c.length && g.push({ ...n(t), content: c }), g.length && (r.push({ ...n(s), fragments: g.slice() }), g.length = 0);
276
- const b = t.content.substring(m);
277
- (b.length || l.length) && h.unshift({
278
- ...n(s),
279
- fragments: (b.length ? [{ ...n(t), content: b }] : []).concat(l.slice())
280
- }), l.length = 0, B = !0;
281
- break;
282
- } else
283
- x += y;
284
- c += p, p = "";
295
+ }
296
+ }), o.lineBox = y.from(...o.fragments.map((e) => e.inlineBox)), f += o.lineBox.width, w += o.lineBox.height;
297
+ const u = T("x", {
298
+ ...(B ?? o).getComputedStyle(),
299
+ textAlign: "left",
300
+ verticalAlign: "baseline"
301
+ });
302
+ o.xHeight = u.actualBoundingBoxAscent, o.baseline = o.lineBox.y + (o.lineBox.height - (u.fontBoundingBoxAscent + u.fontBoundingBoxDescent)) / 2 + u.fontBoundingBoxAscent;
303
+ });
304
+ const m = y.from(
305
+ ...a.map((o) => o.lineBox),
306
+ new y({ width: h, height: s })
307
+ ), { width: p, height: C } = m;
308
+ a.forEach((o) => {
309
+ o.contentBox = y.from(...o.fragments.map((i) => i.contentBox)), o.glyphBox = y.from(...o.fragments.map((i) => i.glyphBox)), o.fragments.forEach((i) => {
310
+ const B = i.getComputedStyle(), r = i.inlineBox.x, x = i.inlineBox.y;
311
+ let u = r, e = x;
312
+ switch (B.writingMode) {
313
+ case "vertical-rl":
314
+ case "vertical-lr":
315
+ switch (B.textAlign) {
316
+ case "end":
317
+ case "right":
318
+ e += C - o.contentBox.height;
319
+ break;
320
+ case "center":
321
+ e += (C - o.contentBox.height) / 2;
322
+ break;
323
+ }
324
+ break;
325
+ case "horizontal-tb": {
326
+ switch (B.textAlign) {
327
+ case "end":
328
+ case "right":
329
+ u += p - o.contentBox.width;
330
+ break;
331
+ case "center":
332
+ u += (p - o.contentBox.width) / 2;
333
+ break;
334
+ }
335
+ switch (B.verticalAlign) {
336
+ case "top":
337
+ e += o.lineBox.y - i.inlineBox.y;
338
+ break;
339
+ case "middle":
340
+ e = o.baseline - o.xHeight / 2 - i.inlineBox.height / 2;
341
+ break;
342
+ case "bottom":
343
+ e += o.lineBox.bottom - i.inlineBox.bottom;
344
+ break;
345
+ case "sub":
346
+ e += o.baseline - i.glyphBox.bottom;
347
+ break;
348
+ case "super":
349
+ e += o.baseline - i.glyphBox.y;
350
+ break;
351
+ case "text-top":
352
+ e += o.glyphBox.y - i.inlineBox.y;
353
+ break;
354
+ case "text-bottom":
355
+ e += o.glyphBox.bottom - i.inlineBox.bottom;
356
+ break;
357
+ case "baseline":
358
+ default:
359
+ i.inlineBox.height < o.lineBox.height && (e += o.baseline - i.baseline);
360
+ break;
361
+ }
362
+ break;
285
363
  }
286
- B || g.push(n(t));
287
364
  }
288
- g.length && r.push({ ...n(s), fragments: g });
289
- }
290
- return r;
365
+ const b = u - r, g = e - x;
366
+ i.inlineBox.move(b, g), i.contentBox.move(b, g), i.glyphBox.move(b, g), i.baseline += g, i.centerX += b;
367
+ }), o.contentBox = y.from(...o.fragments.map((i) => i.contentBox)), o.glyphBox = y.from(...o.fragments.map((i) => i.glyphBox));
368
+ });
369
+ const l = y.from(...a.map((o) => o.contentBox));
370
+ return {
371
+ box: m,
372
+ contentBox: l,
373
+ viewBox: y.from(m, l),
374
+ glyphBox: y.from(...a.map((o) => o.glyphBox)),
375
+ paragraphs: a
376
+ };
377
+ }
378
+ function W(c, t, h) {
379
+ if (typeof t == "string" && t.startsWith("linear-gradient")) {
380
+ const { x0: s, y0: n, x1: a, y1: d, stops: f } = V(t, h.left, h.top, h.width, h.height), w = c.createLinearGradient(s, n, a, d);
381
+ return f.forEach((m) => w.addColorStop(m.offset, m.color)), w;
291
382
  }
292
- _draw(e) {
293
- const o = this.context, { width: n, height: r } = o.canvas, h = { left: 0, top: 0, width: n, height: r };
294
- this.style.backgroundColor && (o.fillStyle = this._parseColor(this.style.backgroundColor, h), o.fillRect(0, 0, n, r)), e.forEach((s) => {
295
- s.style.backgroundColor && (o.fillStyle = this._parseColor(s.style.backgroundColor, s.contentBox), o.fillRect(s.lineBox.left, s.lineBox.top, s.lineBox.width, s.lineBox.height)), s.fragments.forEach((t) => {
296
- t.style.backgroundColor && (o.fillStyle = this._parseColor(t.style.backgroundColor, t.contentBox), o.fillRect(t.inlineBox.left, t.inlineBox.top, t.inlineBox.width, t.inlineBox.height));
383
+ return t;
384
+ }
385
+ function V(c, t, h, s, n) {
386
+ var o;
387
+ const a = ((o = c.match(/linear-gradient\((.+)\)$/)) == null ? void 0 : o[1]) ?? "", d = a.split(",")[0], f = d.includes("deg") ? d : "0deg", w = a.replace(f, "").matchAll(/(#|rgba|rgb)(.+?) ([\d.]+?%)/gi), p = (Number(f.replace("deg", "")) || 0) * Math.PI / 180, C = s * Math.sin(p), l = n * Math.cos(p);
388
+ return {
389
+ x0: t + s / 2 - C,
390
+ y0: h + n / 2 + l,
391
+ x1: t + s / 2 + C,
392
+ y1: h + n / 2 - l,
393
+ stops: Array.from(w).map((i) => {
394
+ let B = i[2];
395
+ return B.startsWith("(") ? B = B.split(",").length > 3 ? `rgba${B}` : `rgb${B}` : B = `#${B}`, {
396
+ offset: Number(i[3].replace("%", "")) / 100,
397
+ color: B
398
+ };
399
+ })
400
+ };
401
+ }
402
+ function j(c) {
403
+ const {
404
+ view: t = document.createElement("canvas"),
405
+ style: h,
406
+ pixelRatio: s = 1
407
+ } = c, n = { ...h }, { box: a, viewBox: d, paragraphs: f } = U(c), { x: w, y: m, width: p, height: C } = d, l = t.getContext("2d"), o = -w + p, i = -m + C;
408
+ t.style.width = `${o}px`, t.style.height = `${i}px`, t.dataset.width = String(a.width), t.dataset.height = String(a.height), t.dataset.pixelRatio = String(s), t.width = Math.max(1, Math.floor(o * s)), t.height = Math.max(1, Math.floor(i * s)), l.scale(s, s), l.clearRect(0, 0, t.width, t.height), l.translate(-w, -m);
409
+ const B = new y({ width: p, height: C });
410
+ return n != null && n.color && (n.color = W(l, n.color, B)), n != null && n.backgroundColor && (n.backgroundColor = W(l, n.backgroundColor, B)), n != null && n.textStrokeColor && (n.textStrokeColor = W(l, n.textStrokeColor, B)), n != null && n.backgroundColor && (l.fillStyle = n.backgroundColor, l.fillRect(0, 0, t.width, t.height)), f.forEach((r) => {
411
+ var x, u, e, b;
412
+ (x = r.style) != null && x.color && (r.style.color = W(l, r.style.color, r.contentBox)), (u = r.style) != null && u.backgroundColor && (r.style.backgroundColor = W(l, r.style.backgroundColor, r.contentBox)), (e = r.style) != null && e.textStrokeColor && (r.style.textStrokeColor = W(l, r.style.textStrokeColor, r.contentBox)), (b = r.style) != null && b.backgroundColor && (l.fillStyle = r.style.backgroundColor, l.fillRect(r.lineBox.x, r.lineBox.y, r.lineBox.width, r.lineBox.height)), r.fragments.forEach((g) => {
413
+ var S, k, v, A;
414
+ (S = g.style) != null && S.color && (g.style.color = W(l, g.style.color, g.contentBox)), (k = g.style) != null && k.backgroundColor && (g.style.backgroundColor = W(l, g.style.backgroundColor, g.contentBox)), (v = g.style) != null && v.textStrokeColor && (g.style.textStrokeColor = W(l, g.style.textStrokeColor, g.contentBox)), (A = g.style) != null && A.backgroundColor && (l.fillStyle = g.style.backgroundColor, l.fillRect(g.inlineBox.x, g.inlineBox.y, g.inlineBox.width, g.inlineBox.height));
415
+ });
416
+ }), E(l, n), f.forEach((r) => {
417
+ r.style && E(l, r.style), r.fragments.forEach((x) => {
418
+ E(l, {
419
+ ...x.style,
420
+ textAlign: "left",
421
+ verticalAlign: "top"
297
422
  });
298
- }), this._setContextStyle(this.style, h), e.forEach((s) => {
299
- this._setContextStyle(s.style, s.contentBox), s.fragments.forEach((t) => {
300
- this._setContextStyle({
301
- ...t.style,
302
- textAlign: "left",
303
- verticalAlign: "top"
304
- }, t.contentBox);
305
- const { left: i, top: l, width: x, height: g } = t.contentBox;
306
- switch (t.style.textStrokeWidth && o.strokeText(t.content, i, l), o.fillText(t.content, i, l), t.style.textDecoration) {
307
- case "underline":
308
- o.beginPath(), o.moveTo(i, l + g - 2), o.lineTo(i + x, l + g - 2), o.stroke();
309
- break;
310
- case "line-through":
311
- o.beginPath(), o.moveTo(i, l + g / 2), o.lineTo(i + x, l + g / 2), o.stroke();
312
- break;
423
+ const { x: u, y: e, width: b, height: g } = x.contentBox, S = x.getComputedStyle();
424
+ switch (S.writingMode) {
425
+ case "vertical-rl":
426
+ case "vertical-lr": {
427
+ let k = 0;
428
+ for (const v of x.content)
429
+ S.textStrokeWidth && l.strokeText(v, u, e + k), l.fillText(v, u, e + k), k += S.fontSize + S.letterSpacing;
430
+ break;
313
431
  }
314
- });
432
+ case "horizontal-tb":
433
+ S.textStrokeWidth && l.strokeText(x.content, u, e), l.fillText(x.content, u, e);
434
+ break;
435
+ }
436
+ switch (S.textDecoration) {
437
+ case "underline":
438
+ l.beginPath(), l.moveTo(u, e + g - 2), l.lineTo(u + b, e + g - 2), l.stroke();
439
+ break;
440
+ case "line-through":
441
+ l.beginPath(), l.moveTo(u, e + g / 2), l.lineTo(u + b, e + g / 2), l.stroke();
442
+ break;
443
+ }
315
444
  });
316
- }
317
- _resizeView(e, o) {
318
- const n = this.view;
319
- n.style.width = `${e}px`, n.style.height = `${o}px`, n.dataset.width = String(e), n.dataset.height = String(o), n.width = Math.max(1, Math.floor(e * this.pixelRatio)), n.height = Math.max(1, Math.floor(o * this.pixelRatio));
320
- }
321
- _parseColor(e, o) {
322
- if (e.startsWith("linear-gradient")) {
323
- const { x0: n, y0: r, x1: h, y1: s, stops: t } = C(e, o.left, o.top, o.width, o.height), i = this.context.createLinearGradient(n, r, h, s);
324
- return t.forEach((l) => i.addColorStop(l.offset, l.color)), i;
325
- }
326
- return e;
327
- }
328
- _setContextStyle(e, o) {
329
- const n = this.context;
330
- switch (e.shadowColor && (n.shadowColor = e.shadowColor), n.shadowOffsetX = e.shadowOffsetX, n.shadowOffsetY = e.shadowOffsetY, n.shadowBlur = e.shadowBlur, e.textStrokeColor && o && (n.strokeStyle = this._parseColor(e.textStrokeColor, o)), n.lineWidth = e.textStrokeWidth, e.color && o && (n.fillStyle = this._parseColor(e.color, o)), n.direction = e.direction, n.textAlign = e.textAlign, e.verticalAlign) {
331
- case "baseline":
332
- n.textBaseline = "alphabetic";
333
- break;
334
- case "top":
335
- case "middle":
336
- case "bottom":
337
- n.textBaseline = e.verticalAlign;
338
- break;
339
- }
340
- n.font = [
341
- e.fontStyle,
342
- e.fontWeight,
343
- `${e.fontSize}px`,
344
- e.fontFamily
345
- ].join(" "), n.fontKerning = e.fontKerning, n.letterSpacing = `${e.letterSpacing}px`;
346
- }
347
- update() {
348
- const e = this.context;
349
- let { width: o, height: n } = this.style;
350
- o === "auto" && (o = 0), n === "auto" && (n = 0);
351
- const { contentBox: r, paragraphs: h } = this.measure();
352
- o || (o = r.width), n = Math.max(n, r.height), this._resizeView(o, n);
353
- const s = this.pixelRatio;
354
- e.scale(s, s), e.clearRect(0, 0, e.canvas.width, e.canvas.height), this._draw(h);
355
- }
356
- };
357
- u.punctuationRegex = /[\s\n\t\u200B\u200C\u200D\u200E\u200F.,?!:;"'(){}\[\]<>\/\\|~#\$%\*\+=&^,。?!:;“”‘’()【】《》……——]/;
358
- let S = u;
445
+ }), t;
446
+ }
359
447
  export {
360
- S as Text
448
+ U as measureText,
449
+ j as renderText
361
450
  };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "modern-text",
3
3
  "type": "module",
4
- "version": "0.1.1",
4
+ "version": "0.1.3",
5
5
  "packageManager": "pnpm@8.14.1",
6
6
  "description": "Measure and render text in a way that describes the DOM.",
7
7
  "author": "wxm",
@@ -60,7 +60,7 @@
60
60
  "bumpp": "^9.3.0",
61
61
  "conventional-changelog-cli": "^4.1.0",
62
62
  "eslint": "^8.56.0",
63
- "typescript": "^5.3.3",
63
+ "typescript": "^4.9.5",
64
64
  "vite": "^5.0.11",
65
65
  "vitest": "^1.2.1"
66
66
  }
@@ -0,0 +1,20 @@
1
+ export interface BoundingBoxOptions {
2
+ x?: number;
3
+ y?: number;
4
+ width?: number;
5
+ height?: number;
6
+ }
7
+ export declare class BoundingBox {
8
+ x: number;
9
+ y: number;
10
+ width: number;
11
+ height: number;
12
+ get left(): number;
13
+ get top(): number;
14
+ get right(): number;
15
+ get bottom(): number;
16
+ constructor({ x, y, width, height }?: BoundingBoxOptions);
17
+ static from(...boxes: Array<BoundingBox>): BoundingBox;
18
+ move(tx: number, ty: number): this;
19
+ clone(): BoundingBox;
20
+ }
@@ -0,0 +1,4 @@
1
+ import type { TextStyle } from './types';
2
+ export declare function getCurrentCanvas(): OffscreenCanvas | HTMLCanvasElement;
3
+ export declare function setContextStyle(ctx: OffscreenCanvasRenderingContext2D | CanvasRenderingContext2D, style: Partial<TextStyle>): void;
4
+ export declare function canvasMeasureText(text: string, style: TextStyle): TextMetrics;
@@ -0,0 +1,26 @@
1
+ import { BoundingBox } from './bounding-box';
2
+ import type { Paragraph } from './paragraph';
3
+ import type { TextStyle } from './types';
4
+ export interface FragmentOptions {
5
+ content?: string;
6
+ style?: Partial<TextStyle>;
7
+ parent?: Paragraph;
8
+ contentBox?: BoundingBox;
9
+ inlineBox?: BoundingBox;
10
+ glyphBox?: BoundingBox;
11
+ centerX?: number;
12
+ baseline?: number;
13
+ }
14
+ export declare class Fragment {
15
+ content: string;
16
+ style?: Partial<TextStyle>;
17
+ parent?: Paragraph;
18
+ contentBox: BoundingBox;
19
+ inlineBox: BoundingBox;
20
+ glyphBox: BoundingBox;
21
+ centerX: number;
22
+ baseline: number;
23
+ constructor({ content, style, parent, contentBox, inlineBox, glyphBox, centerX, baseline, }?: FragmentOptions);
24
+ getComputedStyle(): TextStyle;
25
+ clone(options?: Partial<FragmentOptions>): Fragment;
26
+ }
package/types/index.d.ts CHANGED
@@ -1 +1,3 @@
1
- export * from './Text';
1
+ export * from './measure-text';
2
+ export * from './render-text';
3
+ export * from './types';
@@ -0,0 +1,17 @@
1
+ import { BoundingBox } from './bounding-box';
2
+ import type { TextContent, TextStyle } from './types';
3
+ export interface MeasureTextStyle extends TextStyle {
4
+ width: number;
5
+ height: number;
6
+ }
7
+ export interface MeasureTextOptions {
8
+ content: TextContent;
9
+ style?: Partial<MeasureTextStyle>;
10
+ }
11
+ export declare function measureText(options: MeasureTextOptions): {
12
+ box: BoundingBox;
13
+ contentBox: BoundingBox;
14
+ viewBox: BoundingBox;
15
+ glyphBox: BoundingBox;
16
+ paragraphs: import("./paragraph").Paragraph[];
17
+ };
@@ -0,0 +1,30 @@
1
+ import { BoundingBox } from './bounding-box';
2
+ import { Fragment } from './fragment';
3
+ import type { FragmentOptions } from './fragment';
4
+ import type { TextStyle } from './types';
5
+ export interface ParagraphOptions {
6
+ style?: Partial<TextStyle>;
7
+ parent?: TextStyle;
8
+ contentBox?: BoundingBox;
9
+ lineBox?: BoundingBox;
10
+ glyphBox?: BoundingBox;
11
+ baseline?: number;
12
+ xHeight?: number;
13
+ maxCharWidth?: number;
14
+ fragments?: Array<Fragment>;
15
+ }
16
+ export declare class Paragraph {
17
+ style?: Partial<TextStyle>;
18
+ parent?: TextStyle;
19
+ contentBox: BoundingBox;
20
+ lineBox: BoundingBox;
21
+ glyphBox: BoundingBox;
22
+ baseline: number;
23
+ xHeight: number;
24
+ maxCharWidth: number;
25
+ fragments: Array<Fragment>;
26
+ constructor({ style, parent, contentBox, lineBox, glyphBox, baseline, xHeight, maxCharWidth, fragments, }?: ParagraphOptions);
27
+ addFragment(options: FragmentOptions): this;
28
+ getComputedStyle(): TextStyle;
29
+ clone(options?: Partial<ParagraphOptions>): Paragraph;
30
+ }
@@ -0,0 +1,2 @@
1
+ import type { BoundingBox } from './bounding-box';
2
+ export declare function parseColor(ctx: CanvasRenderingContext2D, source: string | CanvasGradient | CanvasPattern, box: BoundingBox): string | CanvasGradient | CanvasPattern;
@@ -0,0 +1,3 @@
1
+ import { Paragraph } from './paragraph';
2
+ import type { TextContent, TextStyle } from './types';
3
+ export declare function parseParagraphs(content: TextContent, style: TextStyle): Array<Paragraph>;
@@ -0,0 +1,6 @@
1
+ import type { MeasureTextOptions } from './measure-text';
2
+ export interface RenderTextOptions extends MeasureTextOptions {
3
+ view?: HTMLCanvasElement;
4
+ pixelRatio?: number;
5
+ }
6
+ export declare function renderText(options: RenderTextOptions): HTMLCanvasElement;
@@ -0,0 +1,40 @@
1
+ export type FontWeight = 'normal' | 'bold' | 'lighter' | 'bolder' | 100 | 200 | 300 | 400 | 500 | 600 | 700 | 800 | 900;
2
+ export type FontStyle = 'normal' | 'italic' | 'oblique' | `oblique ${string}`;
3
+ export type FontKerning = 'auto' | 'none' | 'normal';
4
+ export type TextWrap = 'wrap' | 'nowrap';
5
+ export type TextAlign = 'center' | 'end' | 'left' | 'right' | 'start';
6
+ export type VerticalAlign = 'baseline' | 'top' | 'middle' | 'bottom' | 'sub' | 'super' | 'text-top' | 'text-bottom';
7
+ export type TextDecoration = 'underline' | 'line-through';
8
+ export type TextTransform = 'uppercase' | 'lowercase' | 'none';
9
+ export type WritingMode = 'horizontal-tb' | 'vertical-lr' | 'vertical-rl';
10
+ export interface TextStyle {
11
+ color: string | CanvasGradient | CanvasPattern | null;
12
+ backgroundColor: string | CanvasGradient | CanvasPattern | null;
13
+ fontSize: number;
14
+ fontWeight: FontWeight;
15
+ fontFamily: string;
16
+ fontStyle: FontStyle;
17
+ fontKerning: FontKerning;
18
+ textWrap: TextWrap;
19
+ textAlign: TextAlign;
20
+ verticalAlign: VerticalAlign;
21
+ textTransform: TextTransform;
22
+ textDecoration: TextDecoration | null;
23
+ textStrokeWidth: number;
24
+ textStrokeColor: string | CanvasGradient | CanvasPattern | null;
25
+ direction: 'inherit' | 'ltr' | 'rtl';
26
+ lineHeight: number;
27
+ letterSpacing: number;
28
+ shadowColor: string | null;
29
+ shadowOffsetX: number;
30
+ shadowOffsetY: number;
31
+ shadowBlur: number;
32
+ writingMode: WritingMode;
33
+ }
34
+ export interface FragmentContent extends Partial<TextStyle> {
35
+ content: string;
36
+ }
37
+ export interface ParagraphContent extends Partial<TextStyle> {
38
+ fragments: Array<FragmentContent>;
39
+ }
40
+ export type TextContent = string | FragmentContent | ParagraphContent | Array<string | Array<string | FragmentContent> | FragmentContent | ParagraphContent>;
@@ -0,0 +1,2 @@
1
+ import type { Paragraph } from './paragraph';
2
+ export declare function wrapParagraphs(paragraphs: Array<Paragraph>, width?: number, height?: number): Array<Paragraph>;
package/types/Text.d.ts DELETED
@@ -1,107 +0,0 @@
1
- export type FontWeight = 'normal' | 'bold' | 'lighter' | 'bolder' | 100 | 200 | 300 | 400 | 500 | 600 | 700 | 800 | 900;
2
- export type FontStyle = 'normal' | 'italic' | 'oblique' | `oblique ${string}`;
3
- export type FontKerning = 'auto' | 'none' | 'normal';
4
- export type TextWrap = 'wrap' | 'nowrap';
5
- export type TextAlign = 'center' | 'end' | 'left' | 'right' | 'start';
6
- export type VerticalAlign = 'baseline' | 'top' | 'middle' | 'bottom' | 'sub' | 'super' | 'text-top' | 'text-bottom';
7
- export type TextDecoration = 'underline' | 'line-through';
8
- export type TextTransform = 'uppercase' | 'lowercase' | 'none';
9
- export type WritingMode = 'horizontal-tb' | 'vertical-lr' | 'vertical-rl';
10
- export interface BoundingBox {
11
- left: number;
12
- right: number;
13
- top: number;
14
- bottom: number;
15
- width: number;
16
- height: number;
17
- }
18
- export interface TextParagraph {
19
- contentBox: BoundingBox;
20
- lineBox: BoundingBox;
21
- glyphBox: BoundingBox;
22
- baseline: number;
23
- xHeight: number;
24
- fragments: Array<TextFragment>;
25
- style: TextFragmentStyle;
26
- }
27
- export interface TextFragment {
28
- contentBox: BoundingBox;
29
- inlineBox: BoundingBox;
30
- glyphBox: BoundingBox;
31
- centerX: number;
32
- baseline: number;
33
- content: string;
34
- style: TextFragmentStyle;
35
- }
36
- export interface TextFragmentStyle {
37
- color?: string;
38
- backgroundColor?: string;
39
- fontSize: number;
40
- fontWeight: FontWeight;
41
- fontFamily: string;
42
- fontStyle: FontStyle;
43
- fontKerning: FontKerning;
44
- textWrap: TextWrap;
45
- textAlign: TextAlign;
46
- verticalAlign: VerticalAlign;
47
- textTransform: TextTransform;
48
- textDecoration: TextDecoration | null;
49
- textStrokeWidth: number;
50
- textStrokeColor?: string;
51
- direction: 'inherit' | 'ltr' | 'rtl';
52
- lineHeight: number;
53
- letterSpacing: number;
54
- shadowColor?: string;
55
- shadowOffsetX: number;
56
- shadowOffsetY: number;
57
- shadowBlur: number;
58
- writingMode: WritingMode;
59
- }
60
- export interface TextStyle extends TextFragmentStyle {
61
- width: number | 'auto';
62
- height: number | 'auto';
63
- }
64
- export interface TextParagraphWithContentAndStyle extends Partial<TextFragmentStyle> {
65
- content: string;
66
- }
67
- export interface TextParagraphWithFragmentsAndStyle extends Partial<TextFragmentStyle> {
68
- fragments: Array<TextFragmentWithStyle>;
69
- }
70
- export interface TextFragmentWithStyle extends Partial<TextFragmentStyle> {
71
- content: string;
72
- }
73
- export type TextContent = string | TextParagraphWithContentAndStyle | TextParagraphWithFragmentsAndStyle | Array<string | Array<string | TextParagraphWithContentAndStyle> | TextParagraphWithContentAndStyle | TextParagraphWithFragmentsAndStyle>;
74
- export interface TextOptions {
75
- view?: HTMLCanvasElement;
76
- pixelRatio?: number;
77
- content?: TextContent;
78
- style?: Partial<TextStyle>;
79
- }
80
- export interface Metrics {
81
- contentBox: BoundingBox;
82
- glyphBox: BoundingBox;
83
- actualContentBox: BoundingBox;
84
- paragraphs: Array<TextParagraph>;
85
- }
86
- export declare class Text {
87
- static punctuationRegex: RegExp;
88
- static get defaultStyle(): TextStyle;
89
- readonly view: HTMLCanvasElement;
90
- readonly context: CanvasRenderingContext2D;
91
- pixelRatio: number;
92
- style: TextStyle;
93
- content: TextContent;
94
- constructor(options?: TextOptions);
95
- protected _createBox(left?: number, top?: number, width?: number, height?: number): BoundingBox;
96
- protected _moveBox(box: BoundingBox, tx?: number, ty?: number): void;
97
- protected _updateBoxSize(box: BoundingBox): void;
98
- protected _mergeBoxes(boxes: Array<BoundingBox>): BoundingBox;
99
- measure(): Metrics;
100
- protected _createParagraphs(content: TextContent): Array<TextParagraph>;
101
- protected _wrapParagraphs(paragraphs: Array<TextParagraph>, width: number): Array<TextParagraph>;
102
- protected _draw(paragraphs: Array<TextParagraph>): void;
103
- protected _resizeView(width: number, height: number): void;
104
- protected _parseColor(cssColor: string, box: Omit<BoundingBox, 'right' | 'bottom'>): string | CanvasGradient;
105
- protected _setContextStyle(style: TextFragmentStyle, box?: Omit<BoundingBox, 'right' | 'bottom'>): void;
106
- update(): void;
107
- }
package/types/utils.d.ts DELETED
@@ -1,10 +0,0 @@
1
- export declare function parseCssLinearGradient(css: string, x: number, y: number, width: number, height: number): {
2
- x0: number;
3
- y0: number;
4
- x1: number;
5
- y1: number;
6
- stops: {
7
- offset: number;
8
- color: string;
9
- }[];
10
- };