@inottn/miniposter
Advanced tools
Comparing version 0.0.6 to 0.0.7
@@ -1,1 +0,1 @@ | ||
"use strict";var m=require("@inottn/fp-utils"),F=Object.defineProperty,T=Object.defineProperties,C=Object.getOwnPropertyDescriptors,O=Object.getOwnPropertySymbols,S=Object.prototype.hasOwnProperty,E=Object.prototype.propertyIsEnumerable,I=(n,e,t)=>e in n?F(n,e,{enumerable:!0,configurable:!0,writable:!0,value:t}):n[e]=t,$=(n,e)=>{for(var t in e||(e={}))S.call(e,t)&&I(n,t,e[t]);if(O)for(var t of O(e))E.call(e,t)&&I(n,t,e[t]);return n},D=(n,e)=>T(n,C(e));const L=function(n,e){let t=0,r=n.length;for(;t<r;){const s=t+(r-t>>1);e(s)?r=s:t=s+1}return r-1},j=function({left:n,textAlign:e,textWidth:t,width:r}){if(m.isUndefined(r))return n;const s=r-(t||0);switch(e){case"center":return n+s/2;case"right":return n+s;default:return n}},U=function(n,e){return m.isUndefined(e)?n:D($({},n),{left:n.left+e.left,top:n.top+e.top})};var W=Object.defineProperty,k=Object.defineProperties,q=Object.getOwnPropertyDescriptors,R=Object.getOwnPropertySymbols,z=Object.prototype.hasOwnProperty,B=Object.prototype.propertyIsEnumerable,v=(n,e,t)=>e in n?W(n,e,{enumerable:!0,configurable:!0,writable:!0,value:t}):n[e]=t,b=(n,e)=>{for(var t in e||(e={}))z.call(e,t)&&v(n,t,e[t]);if(R)for(var t of R(e))B.call(e,t)&&v(n,t,e[t]);return n},A=(n,e)=>k(n,q(e)),y=(n,e,t)=>(v(n,typeof e!="symbol"?e+"":e,t),t);class H{constructor(e,t){y(this,"canvas"),y(this,"context"),y(this,"options"),y(this,"images",new Map),y(this,"fonts",new Map),this.canvas=e,this.context=e.getContext("2d"),this.options=t}async render(e){const{canvas:t,context:r,options:s}=this,{width:i,height:o,pixelRatio:a=1}=b(b({},s),e);if(!i||!o)throw Error("\u7F3A\u5C11 width \u6216 height \u53C2\u6570");t.width=i*a,t.height=o*a,r.scale(a,a),this.renderContainer(A(b({type:"container"},e),{left:0,top:0,width:i,height:o}))}async renderContainer(e){const{context:t}=this,{left:r,top:s,width:i,height:o,backgroundColor:a,borderRadius:h=0,overflow:l,children:f}=e;if(t.save(),this.drawRoundedRect(r,s,i,o,h),t.clip(),a&&(t.fillStyle=a,t.fillRect(r,s,i,o)),l!=="hidden"&&t.restore(),m.isNonEmptyArray(f)){this.loadAssets(f);for(const d of f){const c=U(d,{left:r,top:s});c.type==="container"&&await this.renderContainer(c),c.type==="image"&&await this.renderImage(c),c.type==="text"&&await this.renderText(c)}}l==="hidden"&&t.restore()}async renderImage(e){const{context:t}=this,{src:r,backgroundColor:s,borderRadius:i=0,objectFit:o="fill"}=e,[a,h]=this.images.get(r);let{left:l,top:f,width:d,height:c}=e;await h,t.save(),this.drawRoundedRect(l,f,d,c,i),t.clip(),s&&(t.fillStyle=s,t.fillRect(l,f,d,c));const g=a.width/a.height,u=d/c;if(!(o==="fill"||g===u)&&(o==="contain"||o==="cover"))if(o==="contain"?g>u:g<u){const p=a.width/d,w=c;c=a.height/p,f+=(w-c)*.5}else{const p=a.height/c,w=d;d=a.width/p,l+=(w-d)*.5}t.drawImage(a,l,f,d,c),t.restore()}async renderText(e){const{context:t}=this,{left:r,top:s,width:i,fontSize:o=14,lineHeight:a=o*1.43,color:h="#333",fontFamily:l="sans-serif",fontWeight:f=400,fontSrc:d,textAlign:c="left",textDecoration:g}=e;d&&await this.fonts.get(d),t.save(),i&&(t.textAlign=c),t.textBaseline="alphabetic",t.fillStyle=h,t.font=`${f} ${o}px ${l}`;const u=j({left:r,textAlign:c,width:i});(i?this.getAllLines(e):[e.content]).forEach((p,w)=>{const x=s+(a-o)/2+a*w;if(t.fillText(p,u,x+o),g==="line-through"){const{width:P}=t.measureText(p),M=j({left:r,textAlign:c,textWidth:P,width:i});t.fillRect(M,x+o*.64,P,o/14)}}),t.restore()}getAllLines(e){const{context:t}=this,{width:r,content:s,lineClamp:i=1/0}=e,o=[];let a=0;for(;a<s.length&&o.length<i;){const h=a;a=L(s,l=>t.measureText(s.slice(a,l+1)).width>r)+1,a===h&&(a=h+1),i===o.length+1?o.push(s.slice(h,a-1)+"..."):o.push(s.slice(h,a))}return o}drawRoundedRect(e,t,r,s,i){const{context:o}=this;typeof i=="number"?i=[i,i,i,i]:i.length===1?i=[i[0],i[0],i[0],i[0]]:i.length===2?i=[i[0],i[1],i[0],i[1]]:i.length===3&&(i=[i[0],i[1],i[2],i[1]]);const[a,h,l,f]=i.map(d=>Math.min(d,r/2,s/2));o.save(),o.translate(e,t),o.beginPath(),o.moveTo(a,0),o.lineTo(r-h,0),o.arc(r-h,h,h,Math.PI*3/2,0,!1),o.lineTo(r,s-l),o.arc(r-l,s-l,l,0,Math.PI/2,!1),o.lineTo(f,s),o.arc(f,s-f,f,Math.PI/2,Math.PI,!1),o.lineTo(0,a),o.arc(a,a,a,Math.PI,Math.PI*3/2,!1),o.closePath(),o.restore()}loadAssets(e){e.forEach(t=>{const{type:r}=t;r==="image"&&this.loadImage(t),r==="text"&&t.fontFamily&&this.loadFont(t)})}loadImage(e){const{src:t}=e;if(!this.images.has(t)){const r=this.canvas.createImage();r.src=t,this.images.set(t,[r,new Promise((s,i)=>{r.onload=s,r.onerror=i})])}}loadFont(e){const{fontFamily:t,fontSrc:r}=e;this.fonts.has(r)||this.fonts.set(r,new Promise((s,i)=>{my.loadFontFace({family:t,source:`url('${r}')`,success:s,fail:i})}))}export(e){const{canvas:t}=this,{promise:r,resolve:s,reject:i}=m.withResolvers();return t.toTempFilePath(A(b({x:0,y:0,width:t.width,height:t.height},e),{success:s,fail:i})),r}}exports.MiniPoster=H; | ||
"use strict";var p=require("@inottn/fp-utils"),z=Object.defineProperty,$=Object.defineProperties,D=Object.getOwnPropertyDescriptors,j=Object.getOwnPropertySymbols,L=Object.prototype.hasOwnProperty,U=Object.prototype.propertyIsEnumerable,A=(o,e,t)=>e in o?z(o,e,{enumerable:!0,configurable:!0,writable:!0,value:t}):o[e]=t,T=(o,e)=>{for(var t in e||(e={}))L.call(e,t)&&A(o,t,e[t]);if(j)for(var t of j(e))U.call(e,t)&&A(o,t,e[t]);return o},W=(o,e)=>$(o,D(e));const k=function(o,e){let t=0,r=o.length;for(;t<r;){const s=t+(r-t>>1);e(s)?r=s:t=s+1}return r-1},M=function({left:o,textAlign:e,textWidth:t,width:r}){if(p.isUndefined(r))return o;const s=r-(t||0);switch(e){case"center":return o+s/2;case"right":return o+s;default:return o}},N=function(o,e){if(p.isUndefined(e))return o;const t=p.isFunction(o.left)?o.left():o.left,r=p.isFunction(o.top)?o.top():o.top;return W(T({},o),{left:e.left+t,top:e.top+r})},v=function(o){const e=T({},o);return p.isFunction(o.left)&&(e.left=o.left()),p.isFunction(o.top)&&(e.top=o.top()),e};var q=Object.defineProperty,B=Object.defineProperties,H=Object.getOwnPropertyDescriptors,R=Object.getOwnPropertySymbols,G=Object.prototype.hasOwnProperty,J=Object.prototype.propertyIsEnumerable,b=(o,e,t)=>e in o?q(o,e,{enumerable:!0,configurable:!0,writable:!0,value:t}):o[e]=t,x=(o,e)=>{for(var t in e||(e={}))G.call(e,t)&&b(o,t,e[t]);if(R)for(var t of R(e))J.call(e,t)&&b(o,t,e[t]);return o},C=(o,e)=>B(o,H(e)),w=(o,e,t)=>(b(o,typeof e!="symbol"?e+"":e,t),t);class K{constructor(e,t){w(this,"canvas"),w(this,"context"),w(this,"options"),w(this,"images",new Map),w(this,"fonts",new Map),w(this,"sizes",new Map),this.canvas=e,this.context=e.getContext("2d"),this.options=t}async render(e){const{canvas:t,context:r,options:s}=this,{width:i,height:a,pixelRatio:n=1}=x(x({},s),e);if(!i||!a)throw Error("\u7F3A\u5C11 width \u6216 height \u53C2\u6570");t.width=i*n,t.height=a*n,r.scale(n,n),await this.renderContainer(C(x({type:"container"},e),{left:0,top:0,width:i,height:a}))}async draw(e){if(Array.isArray(e))for(const t of e)await this.draw(t);else e.type==="container"&&await this.renderContainer(v(e)),e.type==="image"&&await this.renderImage(v(e)),e.type==="text"&&await this.renderText(v(e))}async renderContainer(e){const{context:t}=this,{left:r,top:s,width:i,height:a,backgroundColor:n,borderRadius:c=0,overflow:l,children:h}=e;if(t.save(),this.drawRoundedRect(r,s,i,a,c),t.clip(),n&&(t.fillStyle=n,t.fillRect(r,s,i,a)),l!=="hidden"&&t.restore(),p.isNonEmptyArray(h)){this.loadAssets(h);for(const f of h){const d=N(f,{left:r,top:s});await this.draw(d)}}l==="hidden"&&t.restore()}async renderImage(e){const{context:t}=this,{src:r,backgroundColor:s,borderRadius:i=0,objectFit:a="fill"}=e,[n,c]=this.images.get(r);let{left:l,top:h,width:f,height:d}=e;await c,t.save(),this.drawRoundedRect(l,h,f,d,i),t.clip(),s&&(t.fillStyle=s,t.fillRect(l,h,f,d));const u=n.width/n.height,g=f/d;if(!(a==="fill"||u===g)&&(a==="contain"||a==="cover"))if(a==="contain"?u>g:u<g){const y=n.width/f,m=d;d=n.height/y,h+=(m-d)*.5}else{const y=n.height/d,m=f;f=n.width/y,l+=(m-f)*.5}t.drawImage(n,l,h,f,d),t.restore()}async renderText(e){const{context:t}=this,{id:r,content:s,left:i,top:a,width:n,fontSize:c=14,lineHeight:l=c*1.43,color:h="#333",fontFamily:f="sans-serif",fontWeight:d=400,fontSrc:u,textAlign:g="left",textDecoration:y}=e;u&&await this.fonts.get(u),t.save(),n&&(t.textAlign=g),t.textBaseline="alphabetic",t.fillStyle=h,t.font=`${d} ${c}px ${f}`;const m=M({left:i,textAlign:g,width:n}),P=n?this.getAllLines(e):[s];P.forEach((O,S)=>{const F=a+(l-c)/2+l*S;if(t.fillText(O,m,F+c),y==="line-through"){const{width:I}=t.measureText(O),E=M({left:i,textAlign:g,textWidth:I,width:n});t.fillRect(E,F+c*.64,I,c/14)}}),r&&this.sizes.set(r,{width:n||t.measureText(s).width,height:l*P.length}),t.restore()}getAllLines(e){const{context:t}=this,{width:r,content:s,lineClamp:i=1/0}=e,a=[];let n=0;for(;n<s.length&&a.length<i;){const c=n;n=k(s,l=>t.measureText(s.slice(n,l+1)).width>r)+1,n===c&&(n=c+1),i===a.length+1?a.push(s.slice(c,n-1)+"..."):a.push(s.slice(c,n))}return a}getSize(e){return this.sizes.get(e)}drawRoundedRect(e,t,r,s,i){const{context:a}=this;typeof i=="number"?i=[i,i,i,i]:i.length===1?i=[i[0],i[0],i[0],i[0]]:i.length===2?i=[i[0],i[1],i[0],i[1]]:i.length===3&&(i=[i[0],i[1],i[2],i[1]]);const[n,c,l,h]=i.map(f=>Math.min(f,r/2,s/2));a.save(),a.translate(e,t),a.beginPath(),a.moveTo(n,0),a.lineTo(r-c,0),a.arc(r-c,c,c,Math.PI*3/2,0,!1),a.lineTo(r,s-l),a.arc(r-l,s-l,l,0,Math.PI/2,!1),a.lineTo(h,s),a.arc(h,s-h,h,Math.PI/2,Math.PI,!1),a.lineTo(0,n),a.arc(n,n,n,Math.PI,Math.PI*3/2,!1),a.closePath(),a.restore()}loadAssets(e){e.forEach(t=>{const{type:r}=t;r==="image"&&this.loadImage(t),r==="text"&&t.fontFamily&&this.loadFont(t)})}loadImage(e){const{src:t}=e;if(!this.images.has(t)){const r=this.canvas.createImage();r.src=t,this.images.set(t,[r,new Promise((s,i)=>{r.onload=s,r.onerror=i})])}}loadFont(e){const{fontFamily:t,fontSrc:r}=e;this.fonts.has(r)||this.fonts.set(r,new Promise((s,i)=>{my.loadFontFace({family:t,source:`url('${r}')`,success:s,fail:i})}))}export(e){const{canvas:t}=this,{promise:r,resolve:s,reject:i}=p.withResolvers();return t.toTempFilePath(C(x({x:0,y:0,width:t.width,height:t.height},e),{success:s,fail:i})),r}}exports.MiniPoster=K; |
@@ -1,1 +0,1 @@ | ||
import{isUndefined as P,isNonEmptyArray as M,withResolvers as T}from"@inottn/fp-utils";var C=Object.defineProperty,S=Object.defineProperties,E=Object.getOwnPropertyDescriptors,O=Object.getOwnPropertySymbols,$=Object.prototype.hasOwnProperty,D=Object.prototype.propertyIsEnumerable,I=(n,e,t)=>e in n?C(n,e,{enumerable:!0,configurable:!0,writable:!0,value:t}):n[e]=t,L=(n,e)=>{for(var t in e||(e={}))$.call(e,t)&&I(n,t,e[t]);if(O)for(var t of O(e))D.call(e,t)&&I(n,t,e[t]);return n},W=(n,e)=>S(n,E(e));const k=function(n,e){let t=0,o=n.length;for(;t<o;){const s=t+(o-t>>1);e(s)?o=s:t=s+1}return o-1},j=function({left:n,textAlign:e,textWidth:t,width:o}){if(P(o))return n;const s=o-(t||0);switch(e){case"center":return n+s/2;case"right":return n+s;default:return n}},z=function(n,e){return P(e)?n:W(L({},n),{left:n.left+e.left,top:n.top+e.top})};var B=Object.defineProperty,H=Object.defineProperties,N=Object.getOwnPropertyDescriptors,R=Object.getOwnPropertySymbols,U=Object.prototype.hasOwnProperty,q=Object.prototype.propertyIsEnumerable,b=(n,e,t)=>e in n?B(n,e,{enumerable:!0,configurable:!0,writable:!0,value:t}):n[e]=t,m=(n,e)=>{for(var t in e||(e={}))U.call(e,t)&&b(n,t,e[t]);if(R)for(var t of R(e))q.call(e,t)&&b(n,t,e[t]);return n},A=(n,e)=>H(n,N(e)),y=(n,e,t)=>(b(n,typeof e!="symbol"?e+"":e,t),t);class G{constructor(e,t){y(this,"canvas"),y(this,"context"),y(this,"options"),y(this,"images",new Map),y(this,"fonts",new Map),this.canvas=e,this.context=e.getContext("2d"),this.options=t}async render(e){const{canvas:t,context:o,options:s}=this,{width:i,height:r,pixelRatio:a=1}=m(m({},s),e);if(!i||!r)throw Error("\u7F3A\u5C11 width \u6216 height \u53C2\u6570");t.width=i*a,t.height=r*a,o.scale(a,a),this.renderContainer(A(m({type:"container"},e),{left:0,top:0,width:i,height:r}))}async renderContainer(e){const{context:t}=this,{left:o,top:s,width:i,height:r,backgroundColor:a,borderRadius:h=0,overflow:l,children:f}=e;if(t.save(),this.drawRoundedRect(o,s,i,r,h),t.clip(),a&&(t.fillStyle=a,t.fillRect(o,s,i,r)),l!=="hidden"&&t.restore(),M(f)){this.loadAssets(f);for(const d of f){const c=z(d,{left:o,top:s});c.type==="container"&&await this.renderContainer(c),c.type==="image"&&await this.renderImage(c),c.type==="text"&&await this.renderText(c)}}l==="hidden"&&t.restore()}async renderImage(e){const{context:t}=this,{src:o,backgroundColor:s,borderRadius:i=0,objectFit:r="fill"}=e,[a,h]=this.images.get(o);let{left:l,top:f,width:d,height:c}=e;await h,t.save(),this.drawRoundedRect(l,f,d,c,i),t.clip(),s&&(t.fillStyle=s,t.fillRect(l,f,d,c));const g=a.width/a.height,u=d/c;if(!(r==="fill"||g===u)&&(r==="contain"||r==="cover"))if(r==="contain"?g>u:g<u){const p=a.width/d,w=c;c=a.height/p,f+=(w-c)*.5}else{const p=a.height/c,w=d;d=a.width/p,l+=(w-d)*.5}t.drawImage(a,l,f,d,c),t.restore()}async renderText(e){const{context:t}=this,{left:o,top:s,width:i,fontSize:r=14,lineHeight:a=r*1.43,color:h="#333",fontFamily:l="sans-serif",fontWeight:f=400,fontSrc:d,textAlign:c="left",textDecoration:g}=e;d&&await this.fonts.get(d),t.save(),i&&(t.textAlign=c),t.textBaseline="alphabetic",t.fillStyle=h,t.font=`${f} ${r}px ${l}`;const u=j({left:o,textAlign:c,width:i});(i?this.getAllLines(e):[e.content]).forEach((p,w)=>{const v=s+(a-r)/2+a*w;if(t.fillText(p,u,v+r),g==="line-through"){const{width:x}=t.measureText(p),F=j({left:o,textAlign:c,textWidth:x,width:i});t.fillRect(F,v+r*.64,x,r/14)}}),t.restore()}getAllLines(e){const{context:t}=this,{width:o,content:s,lineClamp:i=1/0}=e,r=[];let a=0;for(;a<s.length&&r.length<i;){const h=a;a=k(s,l=>t.measureText(s.slice(a,l+1)).width>o)+1,a===h&&(a=h+1),i===r.length+1?r.push(s.slice(h,a-1)+"..."):r.push(s.slice(h,a))}return r}drawRoundedRect(e,t,o,s,i){const{context:r}=this;typeof i=="number"?i=[i,i,i,i]:i.length===1?i=[i[0],i[0],i[0],i[0]]:i.length===2?i=[i[0],i[1],i[0],i[1]]:i.length===3&&(i=[i[0],i[1],i[2],i[1]]);const[a,h,l,f]=i.map(d=>Math.min(d,o/2,s/2));r.save(),r.translate(e,t),r.beginPath(),r.moveTo(a,0),r.lineTo(o-h,0),r.arc(o-h,h,h,Math.PI*3/2,0,!1),r.lineTo(o,s-l),r.arc(o-l,s-l,l,0,Math.PI/2,!1),r.lineTo(f,s),r.arc(f,s-f,f,Math.PI/2,Math.PI,!1),r.lineTo(0,a),r.arc(a,a,a,Math.PI,Math.PI*3/2,!1),r.closePath(),r.restore()}loadAssets(e){e.forEach(t=>{const{type:o}=t;o==="image"&&this.loadImage(t),o==="text"&&t.fontFamily&&this.loadFont(t)})}loadImage(e){const{src:t}=e;if(!this.images.has(t)){const o=this.canvas.createImage();o.src=t,this.images.set(t,[o,new Promise((s,i)=>{o.onload=s,o.onerror=i})])}}loadFont(e){const{fontFamily:t,fontSrc:o}=e;this.fonts.has(o)||this.fonts.set(o,new Promise((s,i)=>{my.loadFontFace({family:t,source:`url('${o}')`,success:s,fail:i})}))}export(e){const{canvas:t}=this,{promise:o,resolve:s,reject:i}=T();return t.toTempFilePath(A(m({x:0,y:0,width:t.width,height:t.height},e),{success:s,fail:i})),o}}export{G as MiniPoster}; | ||
import{isUndefined as A,isFunction as y,isNonEmptyArray as $,withResolvers as D}from"@inottn/fp-utils";var L=Object.defineProperty,W=Object.defineProperties,k=Object.getOwnPropertyDescriptors,T=Object.getOwnPropertySymbols,N=Object.prototype.hasOwnProperty,B=Object.prototype.propertyIsEnumerable,F=(o,e,t)=>e in o?L(o,e,{enumerable:!0,configurable:!0,writable:!0,value:t}):o[e]=t,R=(o,e)=>{for(var t in e||(e={}))N.call(e,t)&&F(o,t,e[t]);if(T)for(var t of T(e))B.call(e,t)&&F(o,t,e[t]);return o},H=(o,e)=>W(o,k(e));const U=function(o,e){let t=0,r=o.length;for(;t<r;){const s=t+(r-t>>1);e(s)?r=s:t=s+1}return r-1},C=function({left:o,textAlign:e,textWidth:t,width:r}){if(A(r))return o;const s=r-(t||0);switch(e){case"center":return o+s/2;case"right":return o+s;default:return o}},q=function(o,e){if(A(e))return o;const t=y(o.left)?o.left():o.left,r=y(o.top)?o.top():o.top;return H(R({},o),{left:e.left+t,top:e.top+r})},b=function(o){const e=R({},o);return y(o.left)&&(e.left=o.left()),y(o.top)&&(e.top=o.top()),e};var G=Object.defineProperty,J=Object.defineProperties,K=Object.getOwnPropertyDescriptors,M=Object.getOwnPropertySymbols,Q=Object.prototype.hasOwnProperty,V=Object.prototype.propertyIsEnumerable,v=(o,e,t)=>e in o?G(o,e,{enumerable:!0,configurable:!0,writable:!0,value:t}):o[e]=t,x=(o,e)=>{for(var t in e||(e={}))Q.call(e,t)&&v(o,t,e[t]);if(M)for(var t of M(e))V.call(e,t)&&v(o,t,e[t]);return o},S=(o,e)=>J(o,K(e)),w=(o,e,t)=>(v(o,typeof e!="symbol"?e+"":e,t),t);class X{constructor(e,t){w(this,"canvas"),w(this,"context"),w(this,"options"),w(this,"images",new Map),w(this,"fonts",new Map),w(this,"sizes",new Map),this.canvas=e,this.context=e.getContext("2d"),this.options=t}async render(e){const{canvas:t,context:r,options:s}=this,{width:i,height:a,pixelRatio:n=1}=x(x({},s),e);if(!i||!a)throw Error("\u7F3A\u5C11 width \u6216 height \u53C2\u6570");t.width=i*n,t.height=a*n,r.scale(n,n),await this.renderContainer(S(x({type:"container"},e),{left:0,top:0,width:i,height:a}))}async draw(e){if(Array.isArray(e))for(const t of e)await this.draw(t);else e.type==="container"&&await this.renderContainer(b(e)),e.type==="image"&&await this.renderImage(b(e)),e.type==="text"&&await this.renderText(b(e))}async renderContainer(e){const{context:t}=this,{left:r,top:s,width:i,height:a,backgroundColor:n,borderRadius:l=0,overflow:c,children:h}=e;if(t.save(),this.drawRoundedRect(r,s,i,a,l),t.clip(),n&&(t.fillStyle=n,t.fillRect(r,s,i,a)),c!=="hidden"&&t.restore(),$(h)){this.loadAssets(h);for(const f of h){const d=q(f,{left:r,top:s});await this.draw(d)}}c==="hidden"&&t.restore()}async renderImage(e){const{context:t}=this,{src:r,backgroundColor:s,borderRadius:i=0,objectFit:a="fill"}=e,[n,l]=this.images.get(r);let{left:c,top:h,width:f,height:d}=e;await l,t.save(),this.drawRoundedRect(c,h,f,d,i),t.clip(),s&&(t.fillStyle=s,t.fillRect(c,h,f,d));const g=n.width/n.height,p=f/d;if(!(a==="fill"||g===p)&&(a==="contain"||a==="cover"))if(a==="contain"?g>p:g<p){const u=n.width/f,m=d;d=n.height/u,h+=(m-d)*.5}else{const u=n.height/d,m=f;f=n.width/u,c+=(m-f)*.5}t.drawImage(n,c,h,f,d),t.restore()}async renderText(e){const{context:t}=this,{id:r,content:s,left:i,top:a,width:n,fontSize:l=14,lineHeight:c=l*1.43,color:h="#333",fontFamily:f="sans-serif",fontWeight:d=400,fontSrc:g,textAlign:p="left",textDecoration:u}=e;g&&await this.fonts.get(g),t.save(),n&&(t.textAlign=p),t.textBaseline="alphabetic",t.fillStyle=h,t.font=`${d} ${l}px ${f}`;const m=C({left:i,textAlign:p,width:n}),P=n?this.getAllLines(e):[s];P.forEach((O,E)=>{const I=a+(c-l)/2+c*E;if(t.fillText(O,m,I+l),u==="line-through"){const{width:j}=t.measureText(O),z=C({left:i,textAlign:p,textWidth:j,width:n});t.fillRect(z,I+l*.64,j,l/14)}}),r&&this.sizes.set(r,{width:n||t.measureText(s).width,height:c*P.length}),t.restore()}getAllLines(e){const{context:t}=this,{width:r,content:s,lineClamp:i=1/0}=e,a=[];let n=0;for(;n<s.length&&a.length<i;){const l=n;n=U(s,c=>t.measureText(s.slice(n,c+1)).width>r)+1,n===l&&(n=l+1),i===a.length+1?a.push(s.slice(l,n-1)+"..."):a.push(s.slice(l,n))}return a}getSize(e){return this.sizes.get(e)}drawRoundedRect(e,t,r,s,i){const{context:a}=this;typeof i=="number"?i=[i,i,i,i]:i.length===1?i=[i[0],i[0],i[0],i[0]]:i.length===2?i=[i[0],i[1],i[0],i[1]]:i.length===3&&(i=[i[0],i[1],i[2],i[1]]);const[n,l,c,h]=i.map(f=>Math.min(f,r/2,s/2));a.save(),a.translate(e,t),a.beginPath(),a.moveTo(n,0),a.lineTo(r-l,0),a.arc(r-l,l,l,Math.PI*3/2,0,!1),a.lineTo(r,s-c),a.arc(r-c,s-c,c,0,Math.PI/2,!1),a.lineTo(h,s),a.arc(h,s-h,h,Math.PI/2,Math.PI,!1),a.lineTo(0,n),a.arc(n,n,n,Math.PI,Math.PI*3/2,!1),a.closePath(),a.restore()}loadAssets(e){e.forEach(t=>{const{type:r}=t;r==="image"&&this.loadImage(t),r==="text"&&t.fontFamily&&this.loadFont(t)})}loadImage(e){const{src:t}=e;if(!this.images.has(t)){const r=this.canvas.createImage();r.src=t,this.images.set(t,[r,new Promise((s,i)=>{r.onload=s,r.onerror=i})])}}loadFont(e){const{fontFamily:t,fontSrc:r}=e;this.fonts.has(r)||this.fonts.set(r,new Promise((s,i)=>{my.loadFontFace({family:t,source:`url('${r}')`,success:s,fail:i})}))}export(e){const{canvas:t}=this,{promise:r,resolve:s,reject:i}=D();return t.toTempFilePath(S(x({x:0,y:0,width:t.width,height:t.height},e),{success:s,fail:i})),r}}export{X as MiniPoster}; |
@@ -24,2 +24,3 @@ interface Canvas { | ||
type Radius = number | [number] | [number, number] | [number, number, number] | [number, number, number, number]; | ||
type ElementConfig = ContainerConfig | ImageConfig | TextConfig; | ||
type Config = { | ||
@@ -29,5 +30,9 @@ backgroundColor?: string; | ||
overflow?: 'visible' | 'hidden'; | ||
children?: (ContainerConfig | ImageConfig | TextConfig)[]; | ||
children?: ElementConfig[]; | ||
}; | ||
type PositionConfig = { | ||
left: number | (() => number); | ||
top: number | (() => number); | ||
}; | ||
type NormalizedPositionConfig = { | ||
left: number; | ||
@@ -43,2 +48,3 @@ top: number; | ||
type ObjectFit = 'fill' | 'contain' | 'cover'; | ||
type NormalizedConfig<Config> = Omit<Config, 'left' | 'top'> & NormalizedPositionConfig; | ||
type ContainerConfig = PositionConfig & SizeConfig & { | ||
@@ -49,3 +55,3 @@ type: 'container'; | ||
overflow?: 'visible' | 'hidden'; | ||
children?: (ContainerConfig | ImageConfig | TextConfig)[]; | ||
children?: ElementConfig[]; | ||
}; | ||
@@ -63,2 +69,3 @@ type ImageConfig = PositionConfig & SizeConfig & { | ||
type: 'text'; | ||
id?: string; | ||
content: string; | ||
@@ -82,10 +89,19 @@ color?: string; | ||
fonts: Map<any, any>; | ||
sizes: Map<string, { | ||
width: number; | ||
height: number; | ||
}>; | ||
constructor(canvas: Canvas, options: Options); | ||
render(config: Config): Promise<void>; | ||
renderContainer(data: ContainerConfig): Promise<void>; | ||
renderImage(data: ImageConfig): Promise<void>; | ||
renderText(data: TextConfig): Promise<void>; | ||
draw(data: ElementConfig | ElementConfig[]): Promise<void>; | ||
renderContainer(data: NormalizedConfig<ContainerConfig>): Promise<void>; | ||
renderImage(data: NormalizedConfig<ImageConfig>): Promise<void>; | ||
renderText(data: NormalizedConfig<TextConfig>): Promise<void>; | ||
getAllLines(data: TextConfig): string[]; | ||
getSize(id: string): { | ||
width: number; | ||
height: number; | ||
} | undefined; | ||
drawRoundedRect(x: number, y: number, width: number, height: number, radius: Radius): void; | ||
loadAssets(data: NonNullable<Config['children']>): void; | ||
loadAssets(data: ElementConfig[]): void; | ||
loadImage(data: ImageConfig): void; | ||
@@ -92,0 +108,0 @@ loadFont(data: TextConfig): void; |
{ | ||
"name": "@inottn/miniposter", | ||
"version": "0.0.6", | ||
"version": "0.0.7", | ||
"packageManager": "pnpm@8.7.0", | ||
@@ -5,0 +5,0 @@ "description": "使用 canvas 轻松绘制小程序海报", |
146
README.md
@@ -0,1 +1,5 @@ | ||
<p align="center"> | ||
<img alt="logo" src="https://fastly.jsdelivr.net/npm/@inottn/assets/miniposter/logo.svg" width="420" style="margin-bottom: 10px;"> | ||
</p> | ||
<p align="center">使用 canvas 轻松绘制小程序海报</p> | ||
@@ -32,1 +36,143 @@ | ||
``` | ||
## 快速上手 | ||
```js | ||
const miniposter = new MiniPoster(canvas, { | ||
width: 375, | ||
height: 600, | ||
pixelRatio: 2, | ||
}); | ||
const renderConfig = { | ||
backgroundColor: '#fff', | ||
borderRadius: 8, | ||
overflow: 'hidden', | ||
children: [ | ||
{ | ||
type: 'image', | ||
top: 12, | ||
left: 12, | ||
width: 32, | ||
height: 32, | ||
src: 'xxxxx', | ||
borderRadius: 16, | ||
objectFit: 'cover', | ||
}, | ||
{ | ||
type: 'text', | ||
top: 18, | ||
left: 53, | ||
content: 'hello', | ||
}, | ||
], | ||
}; // 渲染配置,参考下方文档 | ||
miniposter.render(renderConfig).then(() => { | ||
const exportConfig = { ... }; // 导出配置,参考下方文档 | ||
miniposter.export(exportConfig).then(({ tempFilePath }) => { | ||
// tempFilePath 对应图片文件路径 | ||
}); | ||
}); | ||
``` | ||
## 实例化 MiniPoster | ||
使用 canvas 和 config 实例化一个 miniposter 对象 | ||
```js | ||
const miniposter = new MiniPoster(canvas, config); | ||
``` | ||
### canvas | ||
画布实例 | ||
### config | ||
| 字段名 | 类型 | 默认值 | 说明 | | ||
| ---------- | ------ | ------ | ---------------- | | ||
| width | number | - | (必填)画布宽度 | | ||
| height | number | - | (必填)画布高度 | | ||
| pixelRatio | number | 1 | 像素缩放比 | | ||
## miniposter.render(config) | ||
### config | ||
| 字段名 | 类型 | 默认值 | 说明 | | ||
| --------------- | -------- | ------ | -------- | | ||
| backgroundColor | number | - | 背景颜色 | | ||
| borderRadius | number | 0 | 边框圆角 | | ||
| children | object[] | - | 子元素 | | ||
可绘制的元素类型如下: | ||
### container | ||
```js | ||
const container = { | ||
type: 'container', | ||
// 其余属性,如下 | ||
}; | ||
``` | ||
| 字段名 | 类型 | 默认值 | 说明 | | ||
| --------------- | ---------------------- | --------- | --------------------------------------- | | ||
| left | number \| () => number | - | (必填)相对父元素x轴的偏移 | | ||
| top | number \| () => number | - | (必填)相对父元素y轴的偏移 | | ||
| width | number | - | (必填)容器宽度 | | ||
| height | number | - | (必填)容器高度 | | ||
| backgroundColor | string | - | 背景颜色 | | ||
| borderRadius | number | 0 | 边框圆角 | | ||
| overflow | 'visible' \| 'hidden' | 'visible' | 子元素溢出时的行为,可参考对应 CSS 属性 | | ||
| children | object[] | - | 子元素 | | ||
### image | ||
```js | ||
const image = { | ||
type: 'image', | ||
// 其余属性,如下 | ||
}; | ||
``` | ||
| 字段名 | 类型 | 默认值 | 说明 | | ||
| --------------- | ------------------------------ | ------ | ----------------------------------- | | ||
| left | number \| () => number | - | (必填)相对父元素x轴的偏移 | | ||
| top | number \| () => number | - | (必填)相对父元素y轴的偏移 | | ||
| width | number | - | (必填)图像宽度 | | ||
| height | number | - | (必填)图像高度 | | ||
| backgroundColor | string | - | 背景颜色 | | ||
| borderRadius | number | 0 | 边框圆角 | | ||
| objectFit | 'fill' \| 'contain' \| 'cover' | 'fill' | 图片的展示模式,可参考对应 CSS 属性 | | ||
### text | ||
```js | ||
const text = { | ||
type: 'text', | ||
// 其余属性,如下 | ||
}; | ||
``` | ||
| 字段名 | 类型 | 默认值 | 说明 | | ||
| -------------- | ----------------------------- | ------------------ | ---------------------------------------------------- | | ||
| id | string | - | 可以通过 getSize 方法获取对应的宽高信息 | | ||
| left | number \| () => number | - | (必填)相对父元素x轴的偏移 | | ||
| top | number \| () => number | - | (必填)相对父元素y轴的偏移 | | ||
| width | number | - | 文本宽度 | | ||
| content | string | - | 文本内容 | | ||
| fontSize | number | 14 | 字体大小 | | ||
| fontWeight | string | 'normal' | 字体的粗细程度,一些字体只提供 normal 和 bold 两种值 | | ||
| fontFamily | string | 'sans-serif' | 字体名称 | | ||
| fontSrc | string | - | 字体资源地址 | | ||
| lineClamp | number | - | 文本最大行数,超过即显示省略号,需设置文本宽度 | | ||
| lineHeight | number | 字体大小的 1.43 倍 | 文本行高 | | ||
| textAlign | 'left' \| 'center' \| 'right' | 'left' | 文本的水平对齐方式,需设置文本宽度 | | ||
| textDecoration | 'none' \| 'line-through' | 'none' | 文本上的装饰性线条的外观,可参考对应 CSS 属性 | | ||
## miniposter.getSize(id) | ||
获取指定元素的宽高信息 |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
23380
146
178