@armniko/canvas
Advanced tools
| export declare enum ElementType { | ||
| Rectangle = "rectangle", | ||
| Ellipse = "ellipse", | ||
| Polygon = "polygon", | ||
| Line = "line", | ||
| Text = "text", | ||
| Image = "image" | ||
| } | ||
| export declare enum ColorType { | ||
| SolidColor = "solid-color", | ||
| LinearGradient = "linear-gradient", | ||
| RadialGradient = "radial-gradient" | ||
| } | ||
| export declare enum FontWeight { | ||
| Normal = "normal", | ||
| Bold = "bold" | ||
| } | ||
| export declare enum HorizontalAlign { | ||
| Left = "left", | ||
| Center = "center", | ||
| Right = "right" | ||
| } | ||
| export declare enum ImageFit { | ||
| Fill = "fill", | ||
| Cover = "cover" | ||
| } | ||
| export declare enum LineCap { | ||
| Round = "round", | ||
| Square = "square" | ||
| } | ||
| export declare enum PositionPoint { | ||
| LeftTop = "left-top", | ||
| RightTop = "right-top", | ||
| LeftBottom = "left-bottom", | ||
| RightBottom = "right-bottom", | ||
| Center = "center" | ||
| } | ||
| export declare enum VerticalAlign { | ||
| Top = "top", | ||
| Center = "center", | ||
| Bottom = "bottom" | ||
| } |
+15
-23
@@ -0,9 +1,4 @@ | ||
| /** Base */ | ||
| export { Canvas } from './libraries/canvas'; | ||
| export type { Color } from './libraries/colors/color'; | ||
| export type { Gradient } from './libraries/colors/gradient'; | ||
| export { GradientColor } from './libraries/colors/gradient-color'; | ||
| export { SolidColor } from './libraries/colors/solid-color'; | ||
| export { LinearGradient } from './libraries/colors/linear-gradient'; | ||
| export { RadialGradient } from './libraries/colors/radial-gradient'; | ||
| export type { Element } from './libraries/elements/element'; | ||
| /** Elements */ | ||
| export { RectangleElement } from './libraries/elements/rectangle-element'; | ||
@@ -15,18 +10,15 @@ export { EllipseElement } from './libraries/elements/ellipse-element'; | ||
| export { ImageElement } from './libraries/elements/image-element'; | ||
| /** Colors */ | ||
| export { GradientColor } from './libraries/colors/gradient-color'; | ||
| export { SolidColor } from './libraries/colors/solid-color'; | ||
| export { LinearGradient } from './libraries/colors/linear-gradient'; | ||
| export { RadialGradient } from './libraries/colors/radial-gradient'; | ||
| /** Other */ | ||
| export { Shape } from './libraries/shapes/shape'; | ||
| export { ColorType } from './enums/color-type'; | ||
| export { FontWeight } from './enums/font-weight'; | ||
| export { HorizontalAlign } from './enums/horizontal-align'; | ||
| export { ImageFit } from './enums/image-fit'; | ||
| export { LineCap } from './enums/line-cap'; | ||
| export { PositionPoint } from './enums/position-point'; | ||
| export { VerticalAlign } from './enums/vertical-align'; | ||
| export type { Position } from './interfaces/position.interface'; | ||
| export type { Size } from './interfaces/size.interface'; | ||
| export type { Scale } from './interfaces/scale.interface'; | ||
| export type { Rotation } from './interfaces/rotation.interface'; | ||
| export type { CropArea } from './interfaces/crop-area.interface'; | ||
| export type { TextBox } from './interfaces/text-box.interface'; | ||
| export type { TextLine } from './interfaces/text-line.interface'; | ||
| export type { RelativeProps } from './interfaces/relative-props.interface'; | ||
| export type { ResolvableAttribute, ImageSource } from './types'; | ||
| /** Enums */ | ||
| export { PositionPoint, HorizontalAlign, VerticalAlign, ColorType, FontWeight, ImageFit, LineCap } from './enums'; | ||
| /** Types */ | ||
| export { type Element } from './libraries/elements/element'; | ||
| export { type Color } from './libraries/colors/color'; | ||
| export { type Gradient } from './libraries/colors/gradient'; | ||
| export type { ResolvableAttribute, ImageSource, Position, Size, Scale, Rotation, CropArea, TextBox, TextLine, RelativeProps, } from './types'; |
+1880
-1
@@ -1,1 +0,1880 @@ | ||
| var t=/* @__PURE__ */function(t){return t.Rectangle="rectangle",t.Ellipse="ellipse",t.Polygon="polygon",t.Line="line",t.Text="text",t.Image="image",t}({}),i=/* @__PURE__ */function(t){return t.LeftTop="left-top",t.RightTop="right-top",t.LeftBottom="left-bottom",t.RightBottom="right-bottom",t.Center="center",t}({}),e=class t{static valueInRange(t,i){return t<i.min&&(t=i.min),t>i.max&&(t=i.max),t}static valueNotGreaterThan(t,i){return t>i&&(t=i),t}static valueNotLessThan(t,i){return t<i&&(t=i),t}static relativePositionValue(i,e,s){const o=t.extractedValue(i);let h;if("%"===o.unit)h=(s-e)/100*o.value;else h=Number(i);return h}static extractedValue(t){let i;if("string"==typeof t){const e=t.match(/^(\d+)(\D+)$/)??[];i=e.length>2?{value:parseFloat(e[1]??"0"),unit:e[2]??"px"}:{value:Number(t),unit:"px"}}else i={value:t,unit:"px"};return i}static degreesToRadians(t){return t/180*Math.PI}static distance(t,i){return Math.sqrt(Math.pow(Math.abs(i.x-t.x),2)+Math.pow(Math.abs(i.y-t.y),2))}static positionBetweenPoints(i,e,s){const o=s/t.distance(i,e);return{x:i.x+o*(e.x-i.x),y:i.y+o*(e.y-i.y)}}static shapeSize(t){const i=Math.min(...t.map(t=>t.x)),e=Math.min(...t.map(t=>t.y)),s=Math.max(...t.map(t=>t.x)),o=Math.max(...t.map(t=>t.y));return{width:Math.floor(s-i),height:Math.floor(o-e)}}static positionLeftTop(t,e,s){const o={x:t.x,y:t.y};switch(e){case i.RightTop:o.x-=s.width;break;case i.RightBottom:o.x-=s.width,o.y-=s.height;break;case i.LeftBottom:o.y-=s.height;break;case i.Center:o.x-=s.width/2,o.y-=s.height/2}return o}static positionCenter(i,e,s){const o=t.positionLeftTop(i,e,s);return o.x+=s.width/2,o.y+=s.height/2,o}static resizedSize(t,i,e){const s={width:i.width,height:i.height};if(e){const e=[{width:t.width*(i.height/t.height),height:i.height},{width:i.width,height:t.height*(i.width/t.width)}].find(t=>t.width<=i.width&&t.height<=i.height);e&&(s.width=e.width,s.height=e.height)}return s}static resolvedValue(t){let i;return i=t&&"function"==typeof t?t():t,i}static isObject(t){return"object"==typeof t&&t.constructor===Object&&!Array.isArray(t)}static hasMethod(t,i){return!!t[i]&&"function"==typeof t[i]}static hash(i){return i.map(i=>{let e;return e=null==i?"undefined":t.isObject(i)?JSON.stringify(i):Array.isArray(i)?"["+t.hash(i)+"]":t.hasMethod(i,"hash")?i.hash():i.toString(),e}).join("|")}},s=class{ctx;element;relativeProps;defaultBorderColor="#000000";constructor(t,i,e){this.ctx=t,this.element=i,this.relativeProps=e??{}}relativePosition(){return this.relativeProps.position??{x:0,y:0}}relativeScale(){return this.relativeProps.scale??{x:1,y:1}}rotations(){return[...this.relativeProps.rotations??[],...this.element.rotations()]}relativeOpacity(){return this.relativeProps.opacity??1}scale(){const t=this.element.scale(),i=this.relativeScale();return{x:t.x*i.x,y:t.y*i.y}}degrees(){return this.element.degrees()??0}anchor(){return this.element.anchor()??this.defaultAnchor()}opacity(){return this.element.opacity()*this.relativeOpacity()}startDrawing(){this.ctx.save();const t=this.scale(),i=this.opacity(),e=this.degrees(),s=this.rotations(),o=this.element.shadow();1===t.x&&1===t.y||this.ctx.scale(t.x,t.y),i<1&&(this.ctx.globalAlpha=i),0!==e&&this.rotate({degrees:e,pivot:this.anchor()}),s.length>0&&s.forEach(t=>{0!==t.degrees&&this.rotate(t)}),o&&o.blur>0&&(this.ctx.shadowColor=o.color,this.ctx.shadowBlur=o.blur,this.ctx.shadowOffsetX=o.x,this.ctx.shadowOffsetY=o.y)}endDrawing(){this.ctx.restore()}rotate(t){const i=this.position(),s=t.pivot??this.defaultAnchor(),o={x:s.x+i.x,y:s.y+i.y};this.ctx.translate(o.x,o.y),this.ctx.rotate(e.degreesToRadians(t.degrees)),this.ctx.translate(-o.x,-o.y)}},o=class{_originalSize;_sizeRatio;_shapePosition;constructor(t,i){this.ctx=t,this.shape=i}draw(){this.prepare(),this.ctx.save(),this.rotateContext(),this.shape.isCloseShape()?this.makeClosedShape():this.makeOpenedShape(),this.ctx.restore()}prepare(){this._originalSize=e.shapeSize(this.shape.points()),this._sizeRatio=this.sizeRatio(),this._shapePosition=this.shapePosition()}rotateContext(){const t=this.shape.degrees();0!==t&&this.rotate({degrees:t}),this.shape.rotations().forEach(t=>{this.rotate(t)})}makeClosedShape(){const t=this.shape.points();t.length>2&&t.forEach((i,s)=>{const o=0===s,h=s===t.length-1,n=t[s+1]??t[0],r=e.distance(i,n),a=this.radius(r),l=e.positionBetweenPoints(i,n,a),d=e.positionBetweenPoints(i,n,r-a);if(o&&this.moveToPoint(l),this.lineToPoint(d),a>0){const i=t[s+2]??(h?t[1]:t[0]);this.quadraticCurveToPoint(n,i)}})}makeOpenedShape(){const t=this.shape.points();t.length>1&&t.forEach((i,s)=>{const o=0===s,h=s===t.length-1,n=s===t.length-2;if(!h){const h=t[s+1],r=h?e.distance(i,h):0,a=this.radius(r),l=o?i:e.positionBetweenPoints(i,h,a),d=n?h:e.positionBetweenPoints(i,h,r-a);o&&this.moveToPoint(l),this.lineToPoint(d),!n&&a>0&&this.quadraticCurveToPoint(h,t[s+2])}})}radius(t){const i=Math.min(this._sizeRatio.width,this._sizeRatio.height),e=Math.floor(this.shape.jointsRadius()/i),s=Math.floor(.55*t);return e>s?s:e}moveToPoint(t){this.ctx.moveTo(this._shapePosition.x+t.x*this._sizeRatio.width,this._shapePosition.y+t.y*this._sizeRatio.height)}lineToPoint(t){this.ctx.lineTo(this._shapePosition.x+t.x*this._sizeRatio.width,this._shapePosition.y+t.y*this._sizeRatio.height)}quadraticCurveToPoint(t,i){const s=e.distance(t,i),o=this.radius(s),h=e.positionBetweenPoints(t,i,o);this.ctx.quadraticCurveTo(this._shapePosition.x+t.x*this._sizeRatio.width,this._shapePosition.y+t.y*this._sizeRatio.height,this._shapePosition.x+h.x*this._sizeRatio.width,this._shapePosition.y+h.y*this._sizeRatio.height)}shapePosition(){return e.positionLeftTop(this.shape.position(),this.shape.positionPoint(),this.shapeSize())}shapeSize(){return this.shape.size()??this._originalSize}sizeRatio(){const t=this.shapeSize();return{width:t.width/e.valueNotLessThan(this._originalSize.width,1),height:t.height/e.valueNotLessThan(this._originalSize.height,1)}}rotate(t){const i=this.shapePosition(),s=t.pivot??this.defaultAnchor();s.x+=i.x,s.y+=i.y,this.ctx.translate(s.x,s.y),this.ctx.rotate(e.degreesToRadians(t.degrees)),this.ctx.translate(-s.x,-s.y)}defaultAnchor(){const t=this.shapeSize();return{x:t.width/2,y:t.height/2}}},h=/* @__PURE__ */function(t){return t.SolidColor="solid-color",t.LinearGradient="linear-gradient",t.RadialGradient="radial-gradient",t}({}),n=class{constructor(t,i,e){this.ctx=t,this.elementPosition=i,this.elementSize=e}mixed(t){let i="";if("string"==typeof t)i=t;else switch(t.type()){case h.SolidColor:i=t.color();break;case h.LinearGradient:i=this.linearGradient(t);break;case h.RadialGradient:i=this.radialGradient(t)}return i}linearGradient(t){const i=e.degreesToRadians(t.degrees()),s=this.elementPosition.x+this.elementSize.width/2,o=this.elementPosition.y+this.elementSize.height/2,h=Math.sqrt(this.elementSize.width**2+this.elementSize.height**2)/2,n=Math.cos(i)*h,r=Math.sin(i)*h,a={x:s-n,y:o-r},l={x:s+n,y:o+r},d=this.ctx.createLinearGradient(a.x,a.y,l.x,l.y);return this.appendGradientColors(d,t.colors()),d}radialGradient(t){const i=t.position()??{x:this.elementSize.width/2,y:this.elementSize.height/2},e={x:this.elementPosition.x+i.x,y:this.elementPosition.y+i.y},s=Math.sqrt(this.elementSize.width*this.elementSize.width+this.elementSize.height*this.elementSize.height)/2,o=this.ctx.createRadialGradient(e.x,e.y,0,e.x,e.y,s*t.radiusScale());return this.appendGradientColors(o,t.colors()),o}appendGradientColors(t,i){i.forEach(i=>t.addColorStop(i.offset(),i.color()))}},r=class extends s{draw(){this.startDrawing(),this.makeShape(),this.drawFill(),this.drawBorder(),this.endDrawing()}makeShape(){const t=this.element.borderWidth(),i=this.position(),e=this.element.size(),s=this.element.cutOutShapes(),h={x:i.x+t/2,y:i.y+t/2},n={width:e.width-t,height:e.height-t};this.ctx.beginPath(),s.length>0&&(this.ctx.translate(h.x,h.y),s.forEach(t=>{new o(this.ctx,t).draw()}),this.ctx.translate(-h.x,-h.y),h.x+=n.width,n.width*=-1),this.ctx.rect(h.x,h.y,n.width,n.height),this.ctx.closePath()}drawFill(){const t=this.element.fillColor();t&&(this.ctx.fillStyle=new n(this.ctx,this.position(),this.element.size()).mixed(t),this.ctx.fill())}drawBorder(){const t=this.element.borderWidth();t>0&&(this.ctx.strokeStyle=new n(this.ctx,this.position(),this.element.size()).mixed(this.element.borderColor()??this.defaultBorderColor),this.ctx.lineWidth=t,this.ctx.stroke())}position(){const t=this.element.positionLeftTop(),i=this.relativePosition();return{x:t.x+i.x,y:t.y+i.y}}defaultAnchor(){const t=this.element.size();return{x:t.width/2,y:t.height/2}}},a=class extends s{draw(){this.startDrawing(),this.drawFill(),this.drawBorder(),this.endDrawing()}drawFill(){const t=this.element.fillColor();if(t){const i=this.element.borderWidth(),s=this.position(),o=this.element.size();this.ctx.beginPath(),this.ctx.ellipse(s.x,s.y,e.valueNotLessThan(o.width/2-i,1),e.valueNotLessThan(o.height/2-i,1),0,0,2*Math.PI),this.ctx.closePath(),this.ctx.fillStyle=new n(this.ctx,this.element.positionLeftTop(),this.element.size()).mixed(t),this.ctx.fill()}}drawBorder(){const t=this.element.borderWidth();if(t>0){const i=this.position(),s=this.element.size();this.ctx.beginPath(),this.ctx.ellipse(i.x,i.y,e.valueNotLessThan(s.width/2-t/2,1),e.valueNotLessThan(s.height/2-t/2,1),0,0,2*Math.PI),this.ctx.closePath(),this.ctx.strokeStyle=new n(this.ctx,this.element.positionLeftTop(),this.element.size()).mixed(this.element.borderColor()??this.defaultBorderColor),this.ctx.lineWidth=t,this.ctx.stroke()}}position(){const t=e.positionCenter(this.element.position(),this.element.positionPoint(),this.element.boundingClientSize()),i=this.relativePosition();return{x:t.x+i.x,y:t.y+i.y}}defaultAnchor(){return{x:0,y:0}}},l=class extends s{draw(){this.prepare(),this.startDrawing(),this.drawStroke(),this.drawFill(),this.endDrawing()}prepare(){this.ctx.font=this.element.font(),this.ctx.textBaseline="alphabetic"}drawFill(){const t=this.element.fillColor();if(t){const i=this.element.textBox(),e=this.position();this.ctx.fillStyle=new n(this.ctx,this.position(),i.size).mixed(t),i.lines.forEach(t=>{this.ctx.fillText(t.text,e.x+t.position.x,Math.round(e.y+t.position.y+.75*t.size.height))})}}drawStroke(){const t=this.element.strokeWidth();if(t>0){const i=this.element.textBox(),e=this.position(),s=this.element.fillColor();this.ctx.strokeStyle=new n(this.ctx,this.position(),i.size).mixed(this.element.strokeColor()??this.defaultBorderColor),this.ctx.lineWidth=s?2*t:t,i.lines.forEach(t=>{this.ctx.strokeText(t.text,e.x+t.position.x,Math.round(e.y+t.position.y+.75*t.size.height))})}}position(){const t=this.element.positionLeftTop(),i=this.relativePosition();return{x:t.x+i.x,y:t.y+i.y}}defaultAnchor(){const t=this.element.textBox();return{x:t.size.width/2,y:t.size.height/2}}},d=/* @__PURE__ */function(t){return t.Fill="fill",t.Cover="cover",t}({}),c=class extends s{draw(){this.startDrawing(),this.drawBorder(),this.drawImage(),this.endDrawing()}drawImage(){const t=this.element.image();if(t){const i=this.sourceDimensions(),e=this.flippedDimensions(this.elementDimensions());this.ctx.drawImage(t,i.position.x,i.position.y,i.size.width,i.size.height,e.position.x,e.position.y,e.size.width,e.size.height)}}drawBorder(){const t=this.element.borderWidth(),i=new n(this.ctx,this.position(),this.element.size()).mixed(this.element.borderColor()??this.defaultBorderColor),e=this.elementDimensions();this.ctx.strokeStyle=i,this.ctx.lineWidth=2*t,this.isRadiusSet()?(this.makeShape(e),this.ctx.lineJoin="round",this.ctx.fillStyle="#ffffff",this.ctx.fill(),t>0&&(this.ctx.strokeStyle=i,this.ctx.lineWidth=2*t,this.ctx.stroke()),this.ctx.clip()):t>0&&(this.ctx.strokeStyle=i,this.ctx.lineWidth=2*t,this.ctx.strokeRect(e.position.x,e.position.y,e.size.width,e.size.height))}sourceDimensions(){const t=this.element.image(),i=this.element.cropArea(),s={position:{x:0,y:0},size:i.size??{width:t.width,height:t.height}};switch(this.element.fit()){case d.Fill:i.position&&(s.position.x=i.position.x,s.position.y=i.position.y);break;case d.Cover:{const{size:o}=this.elementDimensions();o.width/o.height<s.size.width/s.size.height?s.size.width=s.size.height/(o.height/o.width):s.size.height=s.size.width/(o.width/o.height),i.position?(s.position.x=i.position.x,s.position.y=i.position.y):(s.position.x=e.relativePositionValue("50%",s.size.width,t.width),s.position.y=e.relativePositionValue("50%",s.size.height,t.height));break}}return s}elementDimensions(){const t=this.element.borderWidth(),i=this.position(),e=this.element.size();return{position:{x:i.x+t,y:i.y+t},size:{width:e.width-2*t,height:e.height-2*t}}}flippedDimensions(t){const i=this.element.flipX(),e=this.element.flipY();if(i||e){let s=1,o=1;i&&(s=-1,t.position.x=-(+t.position.x+t.size.width)),e&&(o=-1,t.position.y=-(+t.position.y+t.size.height)),this.ctx.scale(s,o)}return t}makeShape(t){const i=this.borderRadius();this.ctx.lineJoin="round",this.ctx.beginPath(),this.ctx.moveTo(t.position.x+i.topLeft,t.position.y),this.ctx.lineTo(t.position.x+t.size.width-i.topRight,t.position.y),this.ctx.quadraticCurveTo(t.position.x+t.size.width,t.position.y,t.position.x+t.size.width,t.position.y+i.topRight),this.ctx.lineTo(t.position.x+t.size.width,t.position.y+t.size.height-i.bottomRight),this.ctx.quadraticCurveTo(t.position.x+t.size.width,t.position.y+t.size.height,t.position.x+t.size.width-i.bottomRight,t.position.y+t.size.height),this.ctx.lineTo(t.position.x+i.bottomLeft,t.position.y+t.size.height),this.ctx.quadraticCurveTo(t.position.x,t.position.y+t.size.height,t.position.x,t.position.y+t.size.height-i.bottomLeft),this.ctx.lineTo(t.position.x,t.position.y+i.topLeft),this.ctx.quadraticCurveTo(t.position.x,t.position.y,t.position.x+i.topLeft,t.position.y),this.ctx.closePath()}borderRadius(){const t=this.element.borderRadius();if(this.isRadiusSet()){const{size:i}=this.elementDimensions();Object.keys(t).forEach(e=>{t[e]>i.width/2&&(t[e]=Math.floor(i.width/2)),t[e]>i.height/2&&(t[e]=Math.floor(i.height/2))})}return t}isRadiusSet(){const t=this.element.borderRadius();return t.topLeft>0||t.topRight>0||t.bottomLeft>0||t.bottomRight>0}position(){const t=this.element.positionLeftTop(),i=this.relativePosition();return{x:t.x+i.x,y:t.y+i.y}}defaultAnchor(){const t=this.element.size();return{x:t.width/2,y:t.height/2}}},u=class{_points=[];_isCloseShape;_position={x:0,y:0};_positionPoint=i.LeftTop;_size;_jointsRadius=0;_degrees=0;_anchor;_rotations=[];constructor(t,i=!0){this._points=t,this._isCloseShape=i}hash(){return e.hash([this.points(),this.isCloseShape(),this.position(),this.positionPoint(),this.size(),this.jointsRadius(),this.rotations(),this.degrees(),this.anchor()])}points(){return this._points}isCloseShape(){return this._isCloseShape}position(){return this._position}positionPoint(){return this._positionPoint}size(){return this._size}jointsRadius(){return this._jointsRadius}degrees(){return this._degrees}anchor(){return this._anchor}rotations(){return this._rotations}setPosition(t,i){return this._position=t,i&&(this._positionPoint=i),this}setSize(t){return this._size=t,this}setJointsRadius(t){return this._jointsRadius=t,this}setDegrees(t,i){return this._degrees=t,this._anchor=i,this}setRotations(t){return this._rotations=t,this}},p=class extends s{draw(){this.startDrawing(),this.makeShape(),this.drawBorder(),this.drawFill(),this.endDrawing()}makeShape(){const t=this.element.shapePoints();if(t.length>2){const i=new u(t).setSize(this.shapeSize()).setPosition(this.shapePosition()).setJointsRadius(this.element.borderRadius());this.ctx.beginPath(),new o(this.ctx,i).draw(),this.ctx.closePath()}}drawFill(){const t=this.element.fillColor();t&&(this.ctx.fillStyle=new n(this.ctx,this.position(),this.shapeSize()).mixed(t),this.ctx.fill())}drawBorder(){const t=this.element.borderWidth();if(t>0){const i=new n(this.ctx,this.position(),this.element.size()).mixed(this.element.borderColor()??this.defaultBorderColor),e=this.element.fillColor();this.element.borderRadius()>0&&(this.ctx.lineJoin="round"),this.ctx.strokeStyle=i,this.ctx.lineWidth=e?2*t:t,this.ctx.stroke()}}shapePosition(){const t=this.position(),i=this.element.borderWidth();return{x:t.x+i,y:t.y+i}}shapeSize(){const t=this.element.size(),i=this.element.borderWidth();return{width:t.width-2*i,height:t.height-2*i}}position(){const t=this.element.positionLeftTop(),i=this.relativePosition();return{x:t.x+i.x,y:t.y+i.y}}defaultAnchor(){const t=this.element.size();return{x:t.width/2,y:t.height/2}}},x=class extends s{draw(){this.startDrawing(),this.makeShape(),this.drawStroke(),this.endDrawing()}makeShape(){this.ctx.beginPath();const t=new u(this.element.shapePoints(),!1).setSize(this.shapeSize()).setPosition(this.shapePosition()).setJointsRadius(this.element.jointsRadius());new o(this.ctx,t).draw()}drawStroke(){const t=new n(this.ctx,this.position(),this.element.boundingClientSize()).mixed(this.element.color()??this.defaultBorderColor);this.ctx.lineCap=this.element.lineCap(),this.ctx.strokeStyle=t,this.ctx.lineWidth=this.element.calculatedThickness(),this.ctx.stroke()}shapeSize(){const t=this.element.boundingClientSize(),i=this.element.calculatedThickness();return{width:t.width-i,height:t.height-i/2}}shapePosition(){const t=this.position(),i=this.element.calculatedThickness();return{x:t.x+i/2,y:t.y}}position(){const t=this.element.positionLeftTop(),i=this.relativePosition();return{x:t.x+i.x,y:t.y+i.y}}defaultAnchor(){const t=this.shapeSize();return{x:t.width/2,y:t.height/2}}},g=class{_canvas;_ctx;constructor(t){this._canvas=t??document.createElement("canvas"),this._ctx=this._canvas.getContext("2d")}nativeElement(){return this._canvas}ctx(){return this._ctx}size(){return{width:this._canvas.width,height:this._canvas.height}}setSize(t){this._canvas.width=t.width,this._canvas.height=t.height}draw(i,e){switch(i.type()){case t.Rectangle:new r(this.ctx(),i,e).draw();break;case t.Ellipse:new a(this.ctx(),i,e).draw();break;case t.Polygon:new p(this.ctx(),i,e).draw();break;case t.Line:new x(this.ctx(),i,e).draw();break;case t.Text:new l(this.ctx(),i,e).draw();break;case t.Image:new c(this.ctx(),i,e).draw()}}clear(){this.ctx().clearRect(0,0,this._canvas.width,this._canvas.height)}},m=class{},f=class extends m{_color;constructor(t,i){super(),this.setColor(t,i)}type(){return h.SolidColor}color(){return this._color}hash(){return e.hash([this.color()])}setColor(t,i){const e=9===t.length?t.slice(-2):void 0,s=void 0!==i?this.alphaToHex(i):e??"ff";return this._color=t.slice(0,7)+s,this}alphaToHex(t){let i=Math.round(255*t).toString(16);return 1===i.length&&(i="0"+i),i}},_=class extends f{_offset;constructor(t,i,e=1){super(i,e),this.setOffset(t)}offset(){return this._offset}hash(){return e.hash([super.hash(),this.offset()])}setOffset(t){return this._offset=e.valueInRange(t,{min:0,max:1}),this}},w=class extends m{_colors;constructor(t){super(),this._colors=t}colors(){return this._colors}hash(){return e.hash([this.colors()])}},y=class extends w{_degrees=0;type(){return h.LinearGradient}degrees(){return this._degrees}setDegrees(t){return this._degrees=t,this}hash(){return e.hash([super.hash(),this.degrees()])}},z=class extends w{_position;_radiusScale;type(){return h.RadialGradient}position(){return this._position}radiusScale(){return this._radiusScale??1}setPosition(t){return this._position=t,this}setRadiusScale(t){return this._radiusScale=t,this}hash(){return e.hash([super.hash(),this.position(),this.radiusScale()])}},v=class{_position={x:0,y:0};_positionPoint;_scale={x:1,y:1};_opacity=1;_rotations=[];_degrees=0;_anchor;_shadow;hash(){return e.hash([this.position(),this.positionPoint(),this.scale(),this.opacity(),this.rotations(),this.degrees(),this.anchor(),this.shadow()])}position(){return e.resolvedValue(this._position)}positionLeftTop(){return e.positionLeftTop(this.position(),this.positionPoint(),this.boundingClientSize())}positionPoint(){return e.resolvedValue(this._positionPoint)??i.LeftTop}scale(){return e.resolvedValue(this._scale)}opacity(){return e.resolvedValue(this._opacity)}rotations(){return e.resolvedValue(this._rotations)}degrees(){return e.resolvedValue(this._degrees)}anchor(){return this._anchor?e.resolvedValue(this._anchor):void 0}shadow(){return this._shadow?e.resolvedValue(this._shadow):void 0}setPosition(t,i){return this._position=t,i&&(this._positionPoint=i),this}setScale(t){return this._scale=t,this}setOpacity(t){return this._opacity=t,this}setDegrees(t,i){return this._degrees=t,this._anchor=i,this}setRotations(t){return this._rotations=t,this}setShadow(t){return this._shadow=t,this}},S=class extends v{_size={width:1,height:1};_borderColor;_borderWidth=0;hash(){return e.hash([super.hash(),this.size(),this.borderColor(),this.borderWidth()])}size(){return e.resolvedValue(this._size)}borderColor(){return this._borderColor?e.resolvedValue(this._borderColor):void 0}borderWidth(){return e.resolvedValue(this._borderWidth)}setSize(t){return this._size=t,this}setBorder(t,i){return this._borderWidth=t,this._borderColor=i,this}},b=class extends S{_fillColor;_cutOutShapes=[];type(){return t.Rectangle}hash(){return e.hash([super.hash(),this.fillColor(),this.cutOutShapes()])}fillColor(){return this._fillColor?e.resolvedValue(this._fillColor):void 0}cutOutShapes(){return e.resolvedValue(this._cutOutShapes)}boundingClientSize(){return this.size()}setFillColor(t){return this._fillColor=t,this}setCutOutShapes(t){return this._cutOutShapes=t,this}},C=class extends S{_fillColor;type(){return t.Ellipse}hash(){return e.hash([super.hash(),this.fillColor()])}fillColor(){return this._fillColor?e.resolvedValue(this._fillColor):void 0}boundingClientSize(){return this.size()}setFillColor(t){return this._fillColor=t,this}},P=class extends S{_shapePoints=[];_borderRadius=0;_fillColor;type(){return t.Polygon}hash(){return e.hash([super.hash(),this.shapePoints(),this.borderRadius(),this.fillColor()])}shapePoints(){return e.resolvedValue(this._shapePoints)}borderRadius(){return e.resolvedValue(this._borderRadius)}fillColor(){return this._fillColor?e.resolvedValue(this._fillColor):void 0}boundingClientSize(){return this.size()}setShapePoints(t){return this._shapePoints=t,this}setBorderRadius(t){return this._borderRadius=t,this}setFillColor(t){return this._fillColor=t,this}},R=/* @__PURE__ */function(t){return t.Round="round",t.Square="square",t}({}),T=class extends v{_shapePoints=[];_size;_keepRatio;_thickness=1;_jointsRadius=0;_lineCap=R.Square;_color;type(){return t.Line}hash(){return e.hash([super.hash(),this.shapePoints(),this.size(),this.keepRatio(),this.thickness(),this.jointsRadius(),this.lineCap(),this.color()])}shapePoints(){return e.resolvedValue(this._shapePoints)}size(){return this._size?e.resolvedValue(this._size):void 0}keepRatio(){return e.resolvedValue(this._keepRatio)??!1}thickness(){return e.resolvedValue(this._thickness)}calculatedThickness(){const t=this.size();let i=this.thickness();if(t){const s=e.shapeSize(this.shapePoints()),o=e.resizedSize(s,t,this.keepRatio()),h=o.width/e.valueNotLessThan(s.width,1),n=o.height/e.valueNotLessThan(s.height,1);i=e.valueNotLessThan(i*Math.min(h,n),1)}return i}jointsRadius(){return e.resolvedValue(this._jointsRadius)}lineCap(){return e.resolvedValue(this._lineCap)}color(){return this._color?e.resolvedValue(this._color):void 0}boundingClientSize(){let t=this.size();const i=this.calculatedThickness(),s=e.shapeSize(this.shapePoints());if(t){const o=e.resizedSize(s,t,this.keepRatio());t={width:o.width,height:o.height+i/2}}else t={width:s.width,height:e.valueNotLessThan(s.height+i/2,0)};return t}setShapePoints(t){return this._shapePoints=t,this}setSize(t,i){return this._size=t,i&&(this._keepRatio=i),this}setThickness(t){return this._thickness=t,this}setJointsRadius(t){return this._jointsRadius=t,this}setLineCap(t){return this._lineCap=t,this}setColor(t){return this._color=t,this}},k=/* @__PURE__ */function(t){return t.Normal="normal",t.Bold="bold",t}({}),L=/* @__PURE__ */function(t){return t.Left="left",t.Center="center",t.Right="right",t}({}),V=/* @__PURE__ */function(t){return t.Top="top",t.Center="center",t.Bottom="bottom",t}({}),B=class{element;canvas;ctx;lastTextBox;lastTextBoxHash="";constructor(t){this.element=t,this.canvas=document.createElement("canvas"),this.ctx=this.canvas.getContext("2d")}textBox(){return this.elementChanged()&&(this.lastTextBox=void 0),this.lastTextBox||(this.lastTextBox=this.createTextBox()),this.lastTextBox}createTextBox(){const t={text:this.element.text(),lines:[],size:{width:1,height:1},position:{x:0,y:0}};return t.lines=this.textLines(),t.size=this.textBoxSize(t.lines),t.position=this.textBoxPosition(t.lines),t}textLines(){const t=this.element.bounds(),i=this.element.padding(),e=t?{width:t.width-i.left-i.right,height:t.height-i.top-i.bottom}:void 0,s=this.splitText().map(t=>({text:t,size:{width:this.textWidth(t),height:this.element.lineHeight()},position:{x:0,y:0}})).map((t,i,e)=>{const s=e[i-1];return t.position={x:0,y:s?s.position.y+s.size.height:0},t});return(void 0!==e?this.alignedLines(s,e):s).map(t=>(t.position.x+=i.left,t.position.y+=i.top,t))}alignedLines(t,i){const e=this.element.horizontalAlign(),s=this.element.verticalAlign(),o=t.reduce((t,i)=>t+i.size.height,0);return t.map(t=>(e===L.Center&&(t.position.x+=Math.round((i.width-t.size.width)/2)),e===L.Right&&(t.position.x+=i.width-t.size.width),s===V.Center&&(t.position.y+=Math.round((i.height-o)/2)),s===V.Bottom&&(t.position.y+=i.height-o),t))}textBoxSize(t){const i=this.element.padding(),e=this.element.bounds()??{width:1,height:1},s=Math.max(...t.map(t=>t.size.width));return{width:Math.max(s+i.left+i.right,e.width),height:Math.max(this.element.lineHeight()*t.length+i.top+i.bottom,e.height)}}textBoxPosition(t){const i=this.element.padding(),e=Math.min(...t.map(t=>t.position.x)),s=Math.min(...t.map(t=>t.position.y));return{x:e-i.left,y:s-i.top}}splitText(){const t=this.element.text().replaceAll("<BR>","<br>").split("<br>"),i=this.element.padding(),e=(this.element.bounds()??{width:999999,height:999999}).width-(i.left+i.right);let s=[];return t.forEach(t=>{t=t.trim();const i=this.textWidth(t);if(i>e){const o=Math.floor(e/(i/t.length))-3,h=t.split(" ");let n="";h.forEach((t,i)=>{n.length+t.length>o&&(s.push(n.trim()),n=""),n+=t+" ",i===h.length-1&&s.push(n.trim())}),s=s.map(t=>t.trim())}else s.push(t)}),s}textWidth(t){return this.ctx.font=this.element.font(),Math.ceil(this.ctx.measureText(t).width+this.element.strokeWidth())}elementChanged(){const t=[this.element.text(),this.element.bounds(),this.element.horizontalAlign(),this.element.verticalAlign(),this.element.padding(),this.element.lineHeight(),this.element.font(),this.element.strokeWidth()].join("|"),i=this.lastTextBoxHash!==t;return this.lastTextBoxHash=t,i}},A=class extends v{_text="";_bounds;_fontSize=12;_fontFamily="Tahoma";_fontWeight=k.Normal;_lineHeight;_fillColor;_strokeColor;_strokeWidth=0;_horizontalAlign=L.Left;_verticalAlign=V.Top;_padding={topOrVerticalOrDefault:0};_textSplitter=new B(this);type(){return t.Text}hash(){return e.hash([super.hash(),this.text(),this.bounds(),this.font(),this.lineHeight(),this.fillColor(),this.strokeColor(),this.strokeWidth(),this.horizontalAlign(),this.verticalAlign(),this.padding()])}text(){return e.resolvedValue(this._text).toString()}bounds(){return this._bounds?e.resolvedValue(this._bounds):void 0}fontSize(){return e.resolvedValue(this._fontSize)}fontFamily(){return e.resolvedValue(this._fontFamily)}fontWeight(){return e.resolvedValue(this._fontWeight)}lineHeight(){return e.resolvedValue(this._lineHeight??Math.round(1.2*this.fontSize()))}fillColor(){return this._fillColor?e.resolvedValue(this._fillColor):void 0}strokeColor(){return this._strokeColor?e.resolvedValue(this._strokeColor):void 0}strokeWidth(){return e.resolvedValue(this._strokeWidth)}horizontalAlign(){return e.resolvedValue(this._horizontalAlign)}verticalAlign(){return e.resolvedValue(this._verticalAlign)}padding(){let t;const i=e.resolvedValue(this._padding.topOrVerticalOrDefault),s=this._padding.rightOrHorizontal?e.resolvedValue(this._padding.rightOrHorizontal):void 0,o=this._padding.bottom?e.resolvedValue(this._padding.bottom):void 0,h=this._padding.left?e.resolvedValue(this._padding.left):void 0;return t=void 0===s?{top:i,right:i,bottom:i,left:i}:void 0===o&&void 0===h?{top:i,right:s,bottom:i,left:s}:{top:i,right:s,bottom:o??0,left:h??0},t}font(){const t=[this.fontSize()+"px",this.fontFamily()],i=this.fontWeight();return i&&t.unshift(i),t.join(" ")}textBox(){return this._textSplitter.textBox()}boundingClientSize(){return this.textBox().size}setText(t){return this._text=t,this}setBounds(t){return this._bounds=t,this}setFontSize(t){return this._fontSize=t,this}setFontFamily(t){return this._fontFamily=t,this}setFontWeight(t){return this._fontWeight=t,this}setLineHeight(t){return this._lineHeight=t,this}setFillColor(t){return this._fillColor=t,this}setStroke(t,i){return this._strokeWidth=t,this._strokeColor=i,this}setHorizontalAlign(t){return this._horizontalAlign=t,this}setVerticalAlign(t){return this._verticalAlign=t,this}setPadding(t,i,e,s){return this._padding={topOrVerticalOrDefault:t,rightOrHorizontal:i,bottom:e,left:s},this}},M=class extends S{_image;_fit=d.Fill;_borderRadius={topLeftOrDefault:0};_flipX;_flipY;_cropArea;type(){return t.Image}hash(){return e.hash([super.hash(),this.image()?.id,this.fit(),this.borderRadius(),this.flipX(),this.flipY(),this.cropArea()])}image(){return this._image?e.resolvedValue(this._image):void 0}fit(){return e.resolvedValue(this._fit)}borderRadius(){let t;const i=e.resolvedValue(this._borderRadius.topLeftOrDefault),s=this._borderRadius.topRight?e.resolvedValue(this._borderRadius.topRight):void 0,o=this._borderRadius.bottomRight?e.resolvedValue(this._borderRadius.bottomRight):void 0,h=this._borderRadius.bottomLeft?e.resolvedValue(this._borderRadius.bottomLeft):void 0;return t=void 0===s&&void 0===o&&void 0===h?{topLeft:i,topRight:i,bottomRight:i,bottomLeft:i}:{topLeft:i,topRight:s??0,bottomRight:o??0,bottomLeft:h??0},t}flipX(){return e.resolvedValue(this._flipX)??!1}flipY(){return e.resolvedValue(this._flipY)??!1}cropArea(){return e.resolvedValue(this._cropArea)??{}}boundingClientSize(){return this.size()}setImage(t){return this._image=t,this}setFit(t){return this._fit=t,this}setBorderRadius(t,i,e,s){return this._borderRadius={topLeftOrDefault:t,topRight:i,bottomRight:e,bottomLeft:s},this}setFlip(t){return this._flipX=t.horizontal??!1,this._flipY=t.vertical??!1,this}setCropArea(t){return this._cropArea=t,this}};export{g as Canvas,h as ColorType,C as EllipseElement,k as FontWeight,_ as GradientColor,L as HorizontalAlign,M as ImageElement,d as ImageFit,R as LineCap,T as LineElement,y as LinearGradient,P as PolygonElement,i as PositionPoint,z as RadialGradient,b as RectangleElement,u as Shape,f as SolidColor,A as TextElement,V as VerticalAlign}; | ||
| //#region src/enums.ts | ||
| var ElementType = /* @__PURE__ */ function(ElementType) { | ||
| ElementType["Rectangle"] = "rectangle"; | ||
| ElementType["Ellipse"] = "ellipse"; | ||
| ElementType["Polygon"] = "polygon"; | ||
| ElementType["Line"] = "line"; | ||
| ElementType["Text"] = "text"; | ||
| ElementType["Image"] = "image"; | ||
| return ElementType; | ||
| }({}); | ||
| var ColorType = /* @__PURE__ */ function(ColorType) { | ||
| ColorType["SolidColor"] = "solid-color"; | ||
| ColorType["LinearGradient"] = "linear-gradient"; | ||
| ColorType["RadialGradient"] = "radial-gradient"; | ||
| return ColorType; | ||
| }({}); | ||
| var FontWeight = /* @__PURE__ */ function(FontWeight) { | ||
| FontWeight["Normal"] = "normal"; | ||
| FontWeight["Bold"] = "bold"; | ||
| return FontWeight; | ||
| }({}); | ||
| var HorizontalAlign = /* @__PURE__ */ function(HorizontalAlign) { | ||
| HorizontalAlign["Left"] = "left"; | ||
| HorizontalAlign["Center"] = "center"; | ||
| HorizontalAlign["Right"] = "right"; | ||
| return HorizontalAlign; | ||
| }({}); | ||
| var ImageFit = /* @__PURE__ */ function(ImageFit) { | ||
| ImageFit["Fill"] = "fill"; | ||
| ImageFit["Cover"] = "cover"; | ||
| return ImageFit; | ||
| }({}); | ||
| var LineCap = /* @__PURE__ */ function(LineCap) { | ||
| LineCap["Round"] = "round"; | ||
| LineCap["Square"] = "square"; | ||
| return LineCap; | ||
| }({}); | ||
| var PositionPoint = /* @__PURE__ */ function(PositionPoint) { | ||
| PositionPoint["LeftTop"] = "left-top"; | ||
| PositionPoint["RightTop"] = "right-top"; | ||
| PositionPoint["LeftBottom"] = "left-bottom"; | ||
| PositionPoint["RightBottom"] = "right-bottom"; | ||
| PositionPoint["Center"] = "center"; | ||
| return PositionPoint; | ||
| }({}); | ||
| var VerticalAlign = /* @__PURE__ */ function(VerticalAlign) { | ||
| VerticalAlign["Top"] = "top"; | ||
| VerticalAlign["Center"] = "center"; | ||
| VerticalAlign["Bottom"] = "bottom"; | ||
| return VerticalAlign; | ||
| }({}); | ||
| //#endregion | ||
| //#region src/libraries/helper/helper.ts | ||
| var Helper = class Helper { | ||
| static valueInRange(value, range) { | ||
| if (value < range.min) value = range.min; | ||
| if (value > range.max) value = range.max; | ||
| return value; | ||
| } | ||
| static valueNotGreaterThan(value, max) { | ||
| if (value > max) value = max; | ||
| return value; | ||
| } | ||
| static valueNotLessThan(value, min) { | ||
| if (value < min) value = min; | ||
| return value; | ||
| } | ||
| static relativePositionValue(value, size, maxValue) { | ||
| const extractedValue = Helper.extractedValue(value); | ||
| let position; | ||
| switch (extractedValue.unit) { | ||
| case "%": | ||
| position = (maxValue - size) / 100 * extractedValue.value; | ||
| break; | ||
| default: position = Number(value); | ||
| } | ||
| return position; | ||
| } | ||
| static extractedValue(value) { | ||
| let result; | ||
| if (typeof value === "string") { | ||
| const match = value.match(/^(\d+)(\D+)$/) ?? []; | ||
| if (match.length > 2) result = { | ||
| value: parseFloat(match[1] ?? "0"), | ||
| unit: match[2] ?? "px" | ||
| }; | ||
| else result = { | ||
| value: Number(value), | ||
| unit: "px" | ||
| }; | ||
| } else result = { | ||
| value, | ||
| unit: "px" | ||
| }; | ||
| return result; | ||
| } | ||
| static degreesToRadians(degrees) { | ||
| return degrees / 180 * Math.PI; | ||
| } | ||
| static distance(firstPoint, secondPoint) { | ||
| return Math.sqrt(Math.pow(Math.abs(secondPoint.x - firstPoint.x), 2) + Math.pow(Math.abs(secondPoint.y - firstPoint.y), 2)); | ||
| } | ||
| static positionBetweenPoints(firstPoint, secondPoint, moveBy) { | ||
| const fraction = moveBy / Helper.distance(firstPoint, secondPoint); | ||
| return { | ||
| x: firstPoint.x + fraction * (secondPoint.x - firstPoint.x), | ||
| y: firstPoint.y + fraction * (secondPoint.y - firstPoint.y) | ||
| }; | ||
| } | ||
| static shapeSize(points) { | ||
| const leftTopCorner = { | ||
| x: Math.min(...points.map((point) => point.x)), | ||
| y: Math.min(...points.map((point) => point.y)) | ||
| }; | ||
| const rightBottomCorner = { | ||
| x: Math.max(...points.map((point) => point.x)), | ||
| y: Math.max(...points.map((point) => point.y)) | ||
| }; | ||
| return { | ||
| width: Math.floor(rightBottomCorner.x - leftTopCorner.x), | ||
| height: Math.floor(rightBottomCorner.y - leftTopCorner.y) | ||
| }; | ||
| } | ||
| static positionLeftTop(position, point, size) { | ||
| const positionLeftTop = { | ||
| x: position.x, | ||
| y: position.y | ||
| }; | ||
| switch (point) { | ||
| case PositionPoint.RightTop: | ||
| positionLeftTop.x -= size.width; | ||
| break; | ||
| case PositionPoint.RightBottom: | ||
| positionLeftTop.x -= size.width; | ||
| positionLeftTop.y -= size.height; | ||
| break; | ||
| case PositionPoint.LeftBottom: | ||
| positionLeftTop.y -= size.height; | ||
| break; | ||
| case PositionPoint.Center: | ||
| positionLeftTop.x -= size.width / 2; | ||
| positionLeftTop.y -= size.height / 2; | ||
| break; | ||
| } | ||
| return positionLeftTop; | ||
| } | ||
| static positionCenter(position, point, size) { | ||
| const positionLeftTop = Helper.positionLeftTop(position, point, size); | ||
| positionLeftTop.x += size.width / 2; | ||
| positionLeftTop.y += size.height / 2; | ||
| return positionLeftTop; | ||
| } | ||
| static resizedSize(originalSize, desiredSize, keepRatio) { | ||
| const result = { | ||
| width: desiredSize.width, | ||
| height: desiredSize.height | ||
| }; | ||
| if (keepRatio) { | ||
| const selectedVariant = [{ | ||
| width: originalSize.width * (desiredSize.height / originalSize.height), | ||
| height: desiredSize.height | ||
| }, { | ||
| width: desiredSize.width, | ||
| height: originalSize.height * (desiredSize.width / originalSize.width) | ||
| }].find((variant) => { | ||
| return variant.width <= desiredSize.width && variant.height <= desiredSize.height; | ||
| }); | ||
| if (selectedVariant) { | ||
| result.width = selectedVariant.width; | ||
| result.height = selectedVariant.height; | ||
| } | ||
| } | ||
| return result; | ||
| } | ||
| static resolvedValue(attribute) { | ||
| let result; | ||
| if (attribute) if (typeof attribute === "function") result = attribute(); | ||
| else result = attribute; | ||
| else result = attribute; | ||
| return result; | ||
| } | ||
| static isObject(value) { | ||
| return typeof value === "object" && value.constructor === Object && !Array.isArray(value); | ||
| } | ||
| static hasMethod(object, methodName) { | ||
| return !!object[methodName] && typeof object[methodName] === "function"; | ||
| } | ||
| static hash(attributes) { | ||
| return attributes.map((attribute) => { | ||
| let result; | ||
| if (attribute === void 0 || attribute === null) result = "undefined"; | ||
| else if (Helper.isObject(attribute)) result = JSON.stringify(attribute); | ||
| else if (Array.isArray(attribute)) result = "[" + Helper.hash(attribute) + "]"; | ||
| else if (Helper.hasMethod(attribute, "hash")) result = attribute.hash(); | ||
| else result = attribute.toString(); | ||
| return result; | ||
| }).join("|"); | ||
| } | ||
| }; | ||
| //#endregion | ||
| //#region src/libraries/artists/element-artist.ts | ||
| var ElementArtist = class { | ||
| ctx; | ||
| element; | ||
| relativeProps; | ||
| defaultBorderColor = "#000000"; | ||
| constructor(ctx, element, relativeProps) { | ||
| this.ctx = ctx; | ||
| this.element = element; | ||
| this.relativeProps = relativeProps ?? {}; | ||
| } | ||
| relativePosition() { | ||
| return this.relativeProps.position ?? { | ||
| x: 0, | ||
| y: 0 | ||
| }; | ||
| } | ||
| relativeScale() { | ||
| return this.relativeProps.scale ?? { | ||
| x: 1, | ||
| y: 1 | ||
| }; | ||
| } | ||
| rotations() { | ||
| return [...this.relativeProps.rotations ?? [], ...this.element.rotations()]; | ||
| } | ||
| relativeOpacity() { | ||
| return this.relativeProps.opacity ?? 1; | ||
| } | ||
| scale() { | ||
| const elementScale = this.element.scale(); | ||
| const relativeScale = this.relativeScale(); | ||
| return { | ||
| x: elementScale.x * relativeScale.x, | ||
| y: elementScale.y * relativeScale.y | ||
| }; | ||
| } | ||
| /** @deprecated Use Rotations instead */ | ||
| degrees() { | ||
| return this.element.degrees() ?? 0; | ||
| } | ||
| /** @deprecated Use Rotations instead */ | ||
| anchor() { | ||
| return this.element.anchor() ?? this.defaultAnchor(); | ||
| } | ||
| opacity() { | ||
| return this.element.opacity() * this.relativeOpacity(); | ||
| } | ||
| startDrawing() { | ||
| this.ctx.save(); | ||
| const scale = this.scale(); | ||
| const opacity = this.opacity(); | ||
| const degrees = this.degrees(); | ||
| const rotations = this.rotations(); | ||
| const shadow = this.element.shadow(); | ||
| if (scale.x !== 1 || scale.y !== 1) this.ctx.scale(scale.x, scale.y); | ||
| if (opacity < 1) this.ctx.globalAlpha = opacity; | ||
| if (degrees !== 0) this.rotate({ | ||
| degrees, | ||
| pivot: this.anchor() | ||
| }); | ||
| if (rotations.length > 0) rotations.forEach((rotation) => { | ||
| if (rotation.degrees !== 0) this.rotate(rotation); | ||
| }); | ||
| if (shadow && shadow.blur > 0) { | ||
| this.ctx.shadowColor = shadow.color; | ||
| this.ctx.shadowBlur = shadow.blur; | ||
| this.ctx.shadowOffsetX = shadow.x; | ||
| this.ctx.shadowOffsetY = shadow.y; | ||
| } | ||
| } | ||
| endDrawing() { | ||
| this.ctx.restore(); | ||
| } | ||
| rotate(rotation) { | ||
| const position = this.position(); | ||
| const pivot = rotation.pivot ?? this.defaultAnchor(); | ||
| const absolutePivot = { | ||
| x: pivot.x + position.x, | ||
| y: pivot.y + position.y | ||
| }; | ||
| this.ctx.translate(absolutePivot.x, absolutePivot.y); | ||
| this.ctx.rotate(Helper.degreesToRadians(rotation.degrees)); | ||
| this.ctx.translate(-absolutePivot.x, -absolutePivot.y); | ||
| } | ||
| }; | ||
| //#endregion | ||
| //#region src/libraries/shapes/shaper.ts | ||
| var Shaper = class { | ||
| _originalSize; | ||
| _sizeRatio; | ||
| _shapePosition; | ||
| constructor(ctx, shape) { | ||
| this.ctx = ctx; | ||
| this.shape = shape; | ||
| } | ||
| draw() { | ||
| this.prepare(); | ||
| this.ctx.save(); | ||
| this.rotateContext(); | ||
| if (this.shape.isCloseShape()) this.makeClosedShape(); | ||
| else this.makeOpenedShape(); | ||
| this.ctx.restore(); | ||
| } | ||
| prepare() { | ||
| this._originalSize = Helper.shapeSize(this.shape.points()); | ||
| this._sizeRatio = this.sizeRatio(); | ||
| this._shapePosition = this.shapePosition(); | ||
| } | ||
| rotateContext() { | ||
| const degrees = this.shape.degrees(); | ||
| if (degrees !== 0) this.rotate({ degrees }); | ||
| this.shape.rotations().forEach((rotation) => { | ||
| this.rotate(rotation); | ||
| }); | ||
| } | ||
| makeClosedShape() { | ||
| const points = this.shape.points(); | ||
| if (points.length > 2) points.forEach((point, index) => { | ||
| const isFirstPoint = index === 0; | ||
| const isLastPoint = index === points.length - 1; | ||
| const nextPoint = points[index + 1] ?? points[0]; | ||
| const lineLength = Helper.distance(point, nextPoint); | ||
| const currentRadius = this.radius(lineLength); | ||
| const currentStart = Helper.positionBetweenPoints(point, nextPoint, currentRadius); | ||
| const currentEnd = Helper.positionBetweenPoints(point, nextPoint, lineLength - currentRadius); | ||
| if (isFirstPoint) this.moveToPoint(currentStart); | ||
| this.lineToPoint(currentEnd); | ||
| if (currentRadius > 0) { | ||
| const afterNextPoint = points[index + 2] ?? (isLastPoint ? points[1] : points[0]); | ||
| this.quadraticCurveToPoint(nextPoint, afterNextPoint); | ||
| } | ||
| }); | ||
| } | ||
| makeOpenedShape() { | ||
| const points = this.shape.points(); | ||
| if (points.length > 1) points.forEach((point, index) => { | ||
| const isFirstPoint = index === 0; | ||
| const isLastPoint = index === points.length - 1; | ||
| const isBeforeLastPoint = index === points.length - 2; | ||
| if (!isLastPoint) { | ||
| const nextPoint = points[index + 1]; | ||
| const currentLineLength = nextPoint ? Helper.distance(point, nextPoint) : 0; | ||
| const currentRadius = this.radius(currentLineLength); | ||
| const currentStart = isFirstPoint ? point : Helper.positionBetweenPoints(point, nextPoint, currentRadius); | ||
| const currentEnd = isBeforeLastPoint ? nextPoint : Helper.positionBetweenPoints(point, nextPoint, currentLineLength - currentRadius); | ||
| if (isFirstPoint) this.moveToPoint(currentStart); | ||
| this.lineToPoint(currentEnd); | ||
| if (!isBeforeLastPoint && currentRadius > 0) this.quadraticCurveToPoint(nextPoint, points[index + 2]); | ||
| } | ||
| }); | ||
| } | ||
| radius(lineLength) { | ||
| const averageRatio = Math.min(this._sizeRatio.width, this._sizeRatio.height); | ||
| const radius = Math.floor(this.shape.jointsRadius() / averageRatio); | ||
| const maxRadius = Math.floor(lineLength * .55); | ||
| return radius > maxRadius ? maxRadius : radius; | ||
| } | ||
| moveToPoint(point) { | ||
| this.ctx.moveTo(this._shapePosition.x + point.x * this._sizeRatio.width, this._shapePosition.y + point.y * this._sizeRatio.height); | ||
| } | ||
| lineToPoint(point) { | ||
| this.ctx.lineTo(this._shapePosition.x + point.x * this._sizeRatio.width, this._shapePosition.y + point.y * this._sizeRatio.height); | ||
| } | ||
| quadraticCurveToPoint(point, nextPoint) { | ||
| const nextLineLength = Helper.distance(point, nextPoint); | ||
| const nextRadius = this.radius(nextLineLength); | ||
| const nextStart = Helper.positionBetweenPoints(point, nextPoint, nextRadius); | ||
| this.ctx.quadraticCurveTo(this._shapePosition.x + point.x * this._sizeRatio.width, this._shapePosition.y + point.y * this._sizeRatio.height, this._shapePosition.x + nextStart.x * this._sizeRatio.width, this._shapePosition.y + nextStart.y * this._sizeRatio.height); | ||
| } | ||
| shapePosition() { | ||
| return Helper.positionLeftTop(this.shape.position(), this.shape.positionPoint(), this.shapeSize()); | ||
| } | ||
| shapeSize() { | ||
| return this.shape.size() ?? this._originalSize; | ||
| } | ||
| sizeRatio() { | ||
| const size = this.shapeSize(); | ||
| return { | ||
| width: size.width / Helper.valueNotLessThan(this._originalSize.width, 1), | ||
| height: size.height / Helper.valueNotLessThan(this._originalSize.height, 1) | ||
| }; | ||
| } | ||
| rotate(rotation) { | ||
| const position = this.shapePosition(); | ||
| const anchor = rotation.pivot ?? this.defaultAnchor(); | ||
| anchor.x += position.x; | ||
| anchor.y += position.y; | ||
| this.ctx.translate(anchor.x, anchor.y); | ||
| this.ctx.rotate(Helper.degreesToRadians(rotation.degrees)); | ||
| this.ctx.translate(-anchor.x, -anchor.y); | ||
| } | ||
| defaultAnchor() { | ||
| const size = this.shapeSize(); | ||
| return { | ||
| x: size.width / 2, | ||
| y: size.height / 2 | ||
| }; | ||
| } | ||
| }; | ||
| //#endregion | ||
| //#region src/libraries/colors/color-mixer.ts | ||
| var ColorMixer = class { | ||
| constructor(ctx, elementPosition, elementSize) { | ||
| this.ctx = ctx; | ||
| this.elementPosition = elementPosition; | ||
| this.elementSize = elementSize; | ||
| } | ||
| mixed(color) { | ||
| let result = ""; | ||
| if (typeof color === "string") result = color; | ||
| else switch (color.type()) { | ||
| case ColorType.SolidColor: | ||
| result = color.color(); | ||
| break; | ||
| case ColorType.LinearGradient: | ||
| result = this.linearGradient(color); | ||
| break; | ||
| case ColorType.RadialGradient: | ||
| result = this.radialGradient(color); | ||
| break; | ||
| } | ||
| return result; | ||
| } | ||
| linearGradient(gradient) { | ||
| const angleRadians = Helper.degreesToRadians(gradient.degrees()); | ||
| const centerPosition = { | ||
| x: this.elementPosition.x + this.elementSize.width / 2, | ||
| y: this.elementPosition.y + this.elementSize.height / 2 | ||
| }; | ||
| const halfLength = Math.sqrt(this.elementSize.width ** 2 + this.elementSize.height ** 2) / 2; | ||
| const offset = { | ||
| x: Math.cos(angleRadians) * halfLength, | ||
| y: Math.sin(angleRadians) * halfLength | ||
| }; | ||
| const startPosition = { | ||
| x: centerPosition.x - offset.x, | ||
| y: centerPosition.y - offset.y | ||
| }; | ||
| const endPosition = { | ||
| x: centerPosition.x + offset.x, | ||
| y: centerPosition.y + offset.y | ||
| }; | ||
| const canvasGradient = this.ctx.createLinearGradient(startPosition.x, startPosition.y, endPosition.x, endPosition.y); | ||
| this.appendGradientColors(canvasGradient, gradient.colors()); | ||
| return canvasGradient; | ||
| } | ||
| radialGradient(gradient) { | ||
| const localPosition = gradient.position() ?? { | ||
| x: this.elementSize.width / 2, | ||
| y: this.elementSize.height / 2 | ||
| }; | ||
| const centerPosition = { | ||
| x: this.elementPosition.x + localPosition.x, | ||
| y: this.elementPosition.y + localPosition.y | ||
| }; | ||
| const radius = Math.sqrt(this.elementSize.width * this.elementSize.width + this.elementSize.height * this.elementSize.height) / 2; | ||
| const canvasGradient = this.ctx.createRadialGradient(centerPosition.x, centerPosition.y, 0, centerPosition.x, centerPosition.y, radius * gradient.radiusScale()); | ||
| this.appendGradientColors(canvasGradient, gradient.colors()); | ||
| return canvasGradient; | ||
| } | ||
| appendGradientColors(gradient, colors) { | ||
| colors.forEach((color) => gradient.addColorStop(color.offset(), color.color())); | ||
| } | ||
| }; | ||
| //#endregion | ||
| //#region src/libraries/artists/rectangle-artist.ts | ||
| var RectangleArtist = class extends ElementArtist { | ||
| draw() { | ||
| this.startDrawing(); | ||
| this.makeShape(); | ||
| this.drawFill(); | ||
| this.drawBorder(); | ||
| this.endDrawing(); | ||
| } | ||
| makeShape() { | ||
| const borderWidth = this.element.borderWidth(); | ||
| const position = this.position(); | ||
| const size = this.element.size(); | ||
| const shapes = this.element.cutOutShapes(); | ||
| const drawPosition = { | ||
| x: position.x + borderWidth / 2, | ||
| y: position.y + borderWidth / 2 | ||
| }; | ||
| const drawSize = { | ||
| width: size.width - borderWidth, | ||
| height: size.height - borderWidth | ||
| }; | ||
| this.ctx.beginPath(); | ||
| if (shapes.length > 0) { | ||
| this.ctx.translate(drawPosition.x, drawPosition.y); | ||
| shapes.forEach((shape) => { | ||
| new Shaper(this.ctx, shape).draw(); | ||
| }); | ||
| this.ctx.translate(-drawPosition.x, -drawPosition.y); | ||
| drawPosition.x += drawSize.width; | ||
| drawSize.width *= -1; | ||
| } | ||
| this.ctx.rect(drawPosition.x, drawPosition.y, drawSize.width, drawSize.height); | ||
| this.ctx.closePath(); | ||
| } | ||
| drawFill() { | ||
| const color = this.element.fillColor(); | ||
| if (color) { | ||
| this.ctx.fillStyle = new ColorMixer(this.ctx, this.position(), this.element.size()).mixed(color); | ||
| this.ctx.fill(); | ||
| } | ||
| } | ||
| drawBorder() { | ||
| const borderWidth = this.element.borderWidth(); | ||
| if (borderWidth > 0) { | ||
| this.ctx.strokeStyle = new ColorMixer(this.ctx, this.position(), this.element.size()).mixed(this.element.borderColor() ?? this.defaultBorderColor); | ||
| this.ctx.lineWidth = borderWidth; | ||
| this.ctx.stroke(); | ||
| } | ||
| } | ||
| position() { | ||
| const elementPosition = this.element.positionLeftTop(); | ||
| const relativePosition = this.relativePosition(); | ||
| return { | ||
| x: elementPosition.x + relativePosition.x, | ||
| y: elementPosition.y + relativePosition.y | ||
| }; | ||
| } | ||
| defaultAnchor() { | ||
| const size = this.element.size(); | ||
| return { | ||
| x: size.width / 2, | ||
| y: size.height / 2 | ||
| }; | ||
| } | ||
| }; | ||
| //#endregion | ||
| //#region src/libraries/artists/ellipse-artist.ts | ||
| var EllipseArtist = class extends ElementArtist { | ||
| draw() { | ||
| this.startDrawing(); | ||
| this.drawFill(); | ||
| this.drawBorder(); | ||
| this.endDrawing(); | ||
| } | ||
| drawFill() { | ||
| const color = this.element.fillColor(); | ||
| if (color) { | ||
| const borderWidth = this.element.borderWidth(); | ||
| const position = this.position(); | ||
| const size = this.element.size(); | ||
| this.ctx.beginPath(); | ||
| this.ctx.ellipse(position.x, position.y, Helper.valueNotLessThan(size.width / 2 - borderWidth, 1), Helper.valueNotLessThan(size.height / 2 - borderWidth, 1), 0, 0, Math.PI * 2); | ||
| this.ctx.closePath(); | ||
| this.ctx.fillStyle = new ColorMixer(this.ctx, this.element.positionLeftTop(), this.element.size()).mixed(color); | ||
| this.ctx.fill(); | ||
| } | ||
| } | ||
| drawBorder() { | ||
| const borderWidth = this.element.borderWidth(); | ||
| if (borderWidth > 0) { | ||
| const position = this.position(); | ||
| const size = this.element.size(); | ||
| this.ctx.beginPath(); | ||
| this.ctx.ellipse(position.x, position.y, Helper.valueNotLessThan(size.width / 2 - borderWidth / 2, 1), Helper.valueNotLessThan(size.height / 2 - borderWidth / 2, 1), 0, 0, Math.PI * 2); | ||
| this.ctx.closePath(); | ||
| this.ctx.strokeStyle = new ColorMixer(this.ctx, this.element.positionLeftTop(), this.element.size()).mixed(this.element.borderColor() ?? this.defaultBorderColor); | ||
| this.ctx.lineWidth = borderWidth; | ||
| this.ctx.stroke(); | ||
| } | ||
| } | ||
| position() { | ||
| const elementPosition = Helper.positionCenter(this.element.position(), this.element.positionPoint(), this.element.boundingClientSize()); | ||
| const relativePosition = this.relativePosition(); | ||
| return { | ||
| x: elementPosition.x + relativePosition.x, | ||
| y: elementPosition.y + relativePosition.y | ||
| }; | ||
| } | ||
| defaultAnchor() { | ||
| return { | ||
| x: 0, | ||
| y: 0 | ||
| }; | ||
| } | ||
| }; | ||
| //#endregion | ||
| //#region src/libraries/artists/text-artist.ts | ||
| var TextArtist = class extends ElementArtist { | ||
| draw() { | ||
| this.prepare(); | ||
| this.startDrawing(); | ||
| this.drawStroke(); | ||
| this.drawFill(); | ||
| this.endDrawing(); | ||
| } | ||
| prepare() { | ||
| this.ctx.font = this.element.font(); | ||
| this.ctx.textBaseline = "alphabetic"; | ||
| } | ||
| drawFill() { | ||
| const color = this.element.fillColor(); | ||
| if (color) { | ||
| const textBox = this.element.textBox(); | ||
| const position = this.position(); | ||
| this.ctx.fillStyle = new ColorMixer(this.ctx, this.position(), textBox.size).mixed(color); | ||
| textBox.lines.forEach((textLine) => { | ||
| this.ctx.fillText(textLine.text, position.x + textLine.position.x, Math.round(position.y + textLine.position.y + textLine.size.height * .75)); | ||
| }); | ||
| } | ||
| } | ||
| drawStroke() { | ||
| const width = this.element.strokeWidth(); | ||
| if (width > 0) { | ||
| const textBox = this.element.textBox(); | ||
| const position = this.position(); | ||
| const fillColor = this.element.fillColor(); | ||
| this.ctx.strokeStyle = new ColorMixer(this.ctx, this.position(), textBox.size).mixed(this.element.strokeColor() ?? this.defaultBorderColor); | ||
| this.ctx.lineWidth = fillColor ? width * 2 : width; | ||
| textBox.lines.forEach((textLine) => { | ||
| this.ctx.strokeText(textLine.text, position.x + textLine.position.x, Math.round(position.y + textLine.position.y + textLine.size.height * .75)); | ||
| }); | ||
| } | ||
| } | ||
| position() { | ||
| const elementPosition = this.element.positionLeftTop(); | ||
| const relativePosition = this.relativePosition(); | ||
| return { | ||
| x: elementPosition.x + relativePosition.x, | ||
| y: elementPosition.y + relativePosition.y | ||
| }; | ||
| } | ||
| defaultAnchor() { | ||
| const textBox = this.element.textBox(); | ||
| return { | ||
| x: textBox.size.width / 2, | ||
| y: textBox.size.height / 2 | ||
| }; | ||
| } | ||
| }; | ||
| //#endregion | ||
| //#region src/libraries/artists/image-artist.ts | ||
| var ImageArtist = class extends ElementArtist { | ||
| draw() { | ||
| this.startDrawing(); | ||
| this.drawBorder(); | ||
| this.drawImage(); | ||
| this.endDrawing(); | ||
| } | ||
| drawImage() { | ||
| const image = this.element.image(); | ||
| if (image) { | ||
| const source = this.sourceDimensions(); | ||
| const destination = this.flippedDimensions(this.elementDimensions()); | ||
| this.ctx.drawImage(image, source.position.x, source.position.y, source.size.width, source.size.height, destination.position.x, destination.position.y, destination.size.width, destination.size.height); | ||
| } | ||
| } | ||
| drawBorder() { | ||
| const width = this.element.borderWidth(); | ||
| const color = new ColorMixer(this.ctx, this.position(), this.element.size()).mixed(this.element.borderColor() ?? this.defaultBorderColor); | ||
| const dimensions = this.elementDimensions(); | ||
| this.ctx.strokeStyle = color; | ||
| this.ctx.lineWidth = width * 2; | ||
| if (this.isRadiusSet()) { | ||
| this.makeShape(dimensions); | ||
| this.ctx.lineJoin = "round"; | ||
| this.ctx.fillStyle = "#ffffff"; | ||
| this.ctx.fill(); | ||
| if (width > 0) { | ||
| this.ctx.strokeStyle = color; | ||
| this.ctx.lineWidth = width * 2; | ||
| this.ctx.stroke(); | ||
| } | ||
| this.ctx.clip(); | ||
| } else if (width > 0) { | ||
| this.ctx.strokeStyle = color; | ||
| this.ctx.lineWidth = width * 2; | ||
| this.ctx.strokeRect(dimensions.position.x, dimensions.position.y, dimensions.size.width, dimensions.size.height); | ||
| } | ||
| } | ||
| sourceDimensions() { | ||
| const image = this.element.image(); | ||
| const cropArea = this.element.cropArea(); | ||
| const result = { | ||
| position: { | ||
| x: 0, | ||
| y: 0 | ||
| }, | ||
| size: cropArea.size ?? { | ||
| width: image.width, | ||
| height: image.height | ||
| } | ||
| }; | ||
| switch (this.element.fit()) { | ||
| case ImageFit.Fill: | ||
| if (cropArea.position) { | ||
| result.position.x = cropArea.position.x; | ||
| result.position.y = cropArea.position.y; | ||
| } | ||
| break; | ||
| case ImageFit.Cover: { | ||
| const { size } = this.elementDimensions(); | ||
| if (size.width / size.height < result.size.width / result.size.height) result.size.width = result.size.height / (size.height / size.width); | ||
| else result.size.height = result.size.width / (size.width / size.height); | ||
| if (!cropArea.position) { | ||
| result.position.x = Helper.relativePositionValue("50%", result.size.width, image.width); | ||
| result.position.y = Helper.relativePositionValue("50%", result.size.height, image.height); | ||
| } else { | ||
| result.position.x = cropArea.position.x; | ||
| result.position.y = cropArea.position.y; | ||
| } | ||
| break; | ||
| } | ||
| } | ||
| return result; | ||
| } | ||
| elementDimensions() { | ||
| const borderWidth = this.element.borderWidth(); | ||
| const position = this.position(); | ||
| const size = this.element.size(); | ||
| return { | ||
| position: { | ||
| x: position.x + borderWidth, | ||
| y: position.y + borderWidth | ||
| }, | ||
| size: { | ||
| width: size.width - borderWidth * 2, | ||
| height: size.height - borderWidth * 2 | ||
| } | ||
| }; | ||
| } | ||
| flippedDimensions(dimensions) { | ||
| const flipX = this.element.flipX(); | ||
| const flipY = this.element.flipY(); | ||
| if (flipX || flipY) { | ||
| let scaleX = 1; | ||
| let scaleY = 1; | ||
| if (flipX) { | ||
| scaleX = -1; | ||
| dimensions.position.x = -(+dimensions.position.x + dimensions.size.width); | ||
| } | ||
| if (flipY) { | ||
| scaleY = -1; | ||
| dimensions.position.y = -(+dimensions.position.y + dimensions.size.height); | ||
| } | ||
| this.ctx.scale(scaleX, scaleY); | ||
| } | ||
| return dimensions; | ||
| } | ||
| makeShape(dimensions) { | ||
| const radius = this.borderRadius(); | ||
| this.ctx.lineJoin = "round"; | ||
| this.ctx.beginPath(); | ||
| this.ctx.moveTo(dimensions.position.x + radius.topLeft, dimensions.position.y); | ||
| this.ctx.lineTo(dimensions.position.x + dimensions.size.width - radius.topRight, dimensions.position.y); | ||
| this.ctx.quadraticCurveTo(dimensions.position.x + dimensions.size.width, dimensions.position.y, dimensions.position.x + dimensions.size.width, dimensions.position.y + radius.topRight); | ||
| this.ctx.lineTo(dimensions.position.x + dimensions.size.width, dimensions.position.y + dimensions.size.height - radius.bottomRight); | ||
| this.ctx.quadraticCurveTo(dimensions.position.x + dimensions.size.width, dimensions.position.y + dimensions.size.height, dimensions.position.x + dimensions.size.width - radius.bottomRight, dimensions.position.y + dimensions.size.height); | ||
| this.ctx.lineTo(dimensions.position.x + radius.bottomLeft, dimensions.position.y + dimensions.size.height); | ||
| this.ctx.quadraticCurveTo(dimensions.position.x, dimensions.position.y + dimensions.size.height, dimensions.position.x, dimensions.position.y + dimensions.size.height - radius.bottomLeft); | ||
| this.ctx.lineTo(dimensions.position.x, dimensions.position.y + radius.topLeft); | ||
| this.ctx.quadraticCurveTo(dimensions.position.x, dimensions.position.y, dimensions.position.x + radius.topLeft, dimensions.position.y); | ||
| this.ctx.closePath(); | ||
| } | ||
| borderRadius() { | ||
| const radius = this.element.borderRadius(); | ||
| if (this.isRadiusSet()) { | ||
| const { size } = this.elementDimensions(); | ||
| Object.keys(radius).forEach((key) => { | ||
| if (radius[key] > size.width / 2) radius[key] = Math.floor(size.width / 2); | ||
| if (radius[key] > size.height / 2) radius[key] = Math.floor(size.height / 2); | ||
| }); | ||
| } | ||
| return radius; | ||
| } | ||
| isRadiusSet() { | ||
| const radius = this.element.borderRadius(); | ||
| return radius.topLeft > 0 || radius.topRight > 0 || radius.bottomLeft > 0 || radius.bottomRight > 0; | ||
| } | ||
| position() { | ||
| const elementPosition = this.element.positionLeftTop(); | ||
| const relativePosition = this.relativePosition(); | ||
| return { | ||
| x: elementPosition.x + relativePosition.x, | ||
| y: elementPosition.y + relativePosition.y | ||
| }; | ||
| } | ||
| defaultAnchor() { | ||
| const size = this.element.size(); | ||
| return { | ||
| x: size.width / 2, | ||
| y: size.height / 2 | ||
| }; | ||
| } | ||
| }; | ||
| //#endregion | ||
| //#region src/libraries/shapes/shape.ts | ||
| var Shape = class { | ||
| _points = []; | ||
| _isCloseShape; | ||
| _position = { | ||
| x: 0, | ||
| y: 0 | ||
| }; | ||
| _positionPoint = PositionPoint.LeftTop; | ||
| _size; | ||
| _jointsRadius = 0; | ||
| _degrees = 0; | ||
| _anchor; | ||
| _rotations = []; | ||
| constructor(points, isCloseShape = true) { | ||
| this._points = points; | ||
| this._isCloseShape = isCloseShape; | ||
| } | ||
| hash() { | ||
| return Helper.hash([ | ||
| this.points(), | ||
| this.isCloseShape(), | ||
| this.position(), | ||
| this.positionPoint(), | ||
| this.size(), | ||
| this.jointsRadius(), | ||
| this.rotations(), | ||
| this.degrees(), | ||
| this.anchor() | ||
| ]); | ||
| } | ||
| points() { | ||
| return this._points; | ||
| } | ||
| isCloseShape() { | ||
| return this._isCloseShape; | ||
| } | ||
| position() { | ||
| return this._position; | ||
| } | ||
| positionPoint() { | ||
| return this._positionPoint; | ||
| } | ||
| size() { | ||
| return this._size; | ||
| } | ||
| jointsRadius() { | ||
| return this._jointsRadius; | ||
| } | ||
| /** @deprecated Use Rotations instead */ | ||
| degrees() { | ||
| return this._degrees; | ||
| } | ||
| /** @deprecated Use Rotations instead */ | ||
| anchor() { | ||
| return this._anchor; | ||
| } | ||
| rotations() { | ||
| return this._rotations; | ||
| } | ||
| setPosition(position, point) { | ||
| this._position = position; | ||
| if (point) this._positionPoint = point; | ||
| return this; | ||
| } | ||
| setSize(value) { | ||
| this._size = value; | ||
| return this; | ||
| } | ||
| setJointsRadius(value) { | ||
| this._jointsRadius = value; | ||
| return this; | ||
| } | ||
| /** @deprecated Use Rotations instead */ | ||
| setDegrees(degrees, anchor) { | ||
| this._degrees = degrees; | ||
| this._anchor = anchor; | ||
| return this; | ||
| } | ||
| setRotations(rotations) { | ||
| this._rotations = rotations; | ||
| return this; | ||
| } | ||
| }; | ||
| //#endregion | ||
| //#region src/libraries/artists/polygon-artist.ts | ||
| var PolygonArtist = class extends ElementArtist { | ||
| draw() { | ||
| this.startDrawing(); | ||
| this.makeShape(); | ||
| this.drawBorder(); | ||
| this.drawFill(); | ||
| this.endDrawing(); | ||
| } | ||
| makeShape() { | ||
| const points = this.element.shapePoints(); | ||
| if (points.length > 2) { | ||
| const shape = new Shape(points).setSize(this.shapeSize()).setPosition(this.shapePosition()).setJointsRadius(this.element.borderRadius()); | ||
| this.ctx.beginPath(); | ||
| new Shaper(this.ctx, shape).draw(); | ||
| this.ctx.closePath(); | ||
| } | ||
| } | ||
| drawFill() { | ||
| const color = this.element.fillColor(); | ||
| if (color) { | ||
| this.ctx.fillStyle = new ColorMixer(this.ctx, this.position(), this.shapeSize()).mixed(color); | ||
| this.ctx.fill(); | ||
| } | ||
| } | ||
| drawBorder() { | ||
| const width = this.element.borderWidth(); | ||
| if (width > 0) { | ||
| const color = new ColorMixer(this.ctx, this.position(), this.element.size()).mixed(this.element.borderColor() ?? this.defaultBorderColor); | ||
| const fillColor = this.element.fillColor(); | ||
| if (this.element.borderRadius() > 0) this.ctx.lineJoin = "round"; | ||
| this.ctx.strokeStyle = color; | ||
| this.ctx.lineWidth = fillColor ? width * 2 : width; | ||
| this.ctx.stroke(); | ||
| } | ||
| } | ||
| shapePosition() { | ||
| const position = this.position(); | ||
| const borderWidth = this.element.borderWidth(); | ||
| return { | ||
| x: position.x + borderWidth, | ||
| y: position.y + borderWidth | ||
| }; | ||
| } | ||
| shapeSize() { | ||
| const size = this.element.size(); | ||
| const borderWidth = this.element.borderWidth(); | ||
| return { | ||
| width: size.width - borderWidth * 2, | ||
| height: size.height - borderWidth * 2 | ||
| }; | ||
| } | ||
| position() { | ||
| const elementPosition = this.element.positionLeftTop(); | ||
| const relativePosition = this.relativePosition(); | ||
| return { | ||
| x: elementPosition.x + relativePosition.x, | ||
| y: elementPosition.y + relativePosition.y | ||
| }; | ||
| } | ||
| defaultAnchor() { | ||
| const size = this.element.size(); | ||
| return { | ||
| x: size.width / 2, | ||
| y: size.height / 2 | ||
| }; | ||
| } | ||
| }; | ||
| //#endregion | ||
| //#region src/libraries/artists/line-artist.ts | ||
| var LineArtist = class extends ElementArtist { | ||
| draw() { | ||
| this.startDrawing(); | ||
| this.makeShape(); | ||
| this.drawStroke(); | ||
| this.endDrawing(); | ||
| } | ||
| makeShape() { | ||
| this.ctx.beginPath(); | ||
| const shape = new Shape(this.element.shapePoints(), false).setSize(this.shapeSize()).setPosition(this.shapePosition()).setJointsRadius(this.element.jointsRadius()); | ||
| new Shaper(this.ctx, shape).draw(); | ||
| } | ||
| drawStroke() { | ||
| const color = new ColorMixer(this.ctx, this.position(), this.element.boundingClientSize()).mixed(this.element.color() ?? this.defaultBorderColor); | ||
| this.ctx.lineCap = this.element.lineCap(); | ||
| this.ctx.strokeStyle = color; | ||
| this.ctx.lineWidth = this.element.calculatedThickness(); | ||
| this.ctx.stroke(); | ||
| } | ||
| shapeSize() { | ||
| const size = this.element.boundingClientSize(); | ||
| const thickness = this.element.calculatedThickness(); | ||
| return { | ||
| width: size.width - thickness, | ||
| height: size.height - thickness / 2 | ||
| }; | ||
| } | ||
| shapePosition() { | ||
| const position = this.position(); | ||
| const thickness = this.element.calculatedThickness(); | ||
| return { | ||
| x: position.x + thickness / 2, | ||
| y: position.y | ||
| }; | ||
| } | ||
| position() { | ||
| const elementPosition = this.element.positionLeftTop(); | ||
| const relativePosition = this.relativePosition(); | ||
| return { | ||
| x: elementPosition.x + relativePosition.x, | ||
| y: elementPosition.y + relativePosition.y | ||
| }; | ||
| } | ||
| defaultAnchor() { | ||
| const size = this.shapeSize(); | ||
| return { | ||
| x: size.width / 2, | ||
| y: size.height / 2 | ||
| }; | ||
| } | ||
| }; | ||
| //#endregion | ||
| //#region src/libraries/canvas.ts | ||
| var Canvas = class { | ||
| _canvas; | ||
| _ctx; | ||
| constructor(canvas) { | ||
| this._canvas = canvas ?? document.createElement("canvas"); | ||
| this._ctx = this._canvas.getContext("2d"); | ||
| } | ||
| nativeElement() { | ||
| return this._canvas; | ||
| } | ||
| ctx() { | ||
| return this._ctx; | ||
| } | ||
| size() { | ||
| return { | ||
| width: this._canvas.width, | ||
| height: this._canvas.height | ||
| }; | ||
| } | ||
| setSize(size) { | ||
| this._canvas.width = size.width; | ||
| this._canvas.height = size.height; | ||
| } | ||
| draw(element, relativeProps) { | ||
| switch (element.type()) { | ||
| case ElementType.Rectangle: | ||
| new RectangleArtist(this.ctx(), element, relativeProps).draw(); | ||
| break; | ||
| case ElementType.Ellipse: | ||
| new EllipseArtist(this.ctx(), element, relativeProps).draw(); | ||
| break; | ||
| case ElementType.Polygon: | ||
| new PolygonArtist(this.ctx(), element, relativeProps).draw(); | ||
| break; | ||
| case ElementType.Line: | ||
| new LineArtist(this.ctx(), element, relativeProps).draw(); | ||
| break; | ||
| case ElementType.Text: | ||
| new TextArtist(this.ctx(), element, relativeProps).draw(); | ||
| break; | ||
| case ElementType.Image: | ||
| new ImageArtist(this.ctx(), element, relativeProps).draw(); | ||
| break; | ||
| } | ||
| } | ||
| clear() { | ||
| this.ctx().clearRect(0, 0, this._canvas.width, this._canvas.height); | ||
| } | ||
| }; | ||
| //#endregion | ||
| //#region src/libraries/elements/element.ts | ||
| var Element = class { | ||
| _position = { | ||
| x: 0, | ||
| y: 0 | ||
| }; | ||
| _positionPoint; | ||
| _scale = { | ||
| x: 1, | ||
| y: 1 | ||
| }; | ||
| _opacity = 1; | ||
| _rotations = []; | ||
| _degrees = 0; | ||
| _anchor; | ||
| _shadow; | ||
| hash() { | ||
| return Helper.hash([ | ||
| this.position(), | ||
| this.positionPoint(), | ||
| this.scale(), | ||
| this.opacity(), | ||
| this.rotations(), | ||
| this.degrees(), | ||
| this.anchor(), | ||
| this.shadow() | ||
| ]); | ||
| } | ||
| position() { | ||
| return Helper.resolvedValue(this._position); | ||
| } | ||
| positionLeftTop() { | ||
| return Helper.positionLeftTop(this.position(), this.positionPoint(), this.boundingClientSize()); | ||
| } | ||
| positionPoint() { | ||
| return Helper.resolvedValue(this._positionPoint) ?? PositionPoint.LeftTop; | ||
| } | ||
| scale() { | ||
| return Helper.resolvedValue(this._scale); | ||
| } | ||
| opacity() { | ||
| return Helper.resolvedValue(this._opacity); | ||
| } | ||
| rotations() { | ||
| return Helper.resolvedValue(this._rotations); | ||
| } | ||
| /** @deprecated Use Rotations instead */ | ||
| degrees() { | ||
| return Helper.resolvedValue(this._degrees); | ||
| } | ||
| /** @deprecated Use Rotations instead */ | ||
| anchor() { | ||
| return this._anchor ? Helper.resolvedValue(this._anchor) : void 0; | ||
| } | ||
| shadow() { | ||
| return this._shadow ? Helper.resolvedValue(this._shadow) : void 0; | ||
| } | ||
| setPosition(position, point) { | ||
| this._position = position; | ||
| if (point) this._positionPoint = point; | ||
| return this; | ||
| } | ||
| setScale(value) { | ||
| this._scale = value; | ||
| return this; | ||
| } | ||
| setOpacity(value) { | ||
| this._opacity = value; | ||
| return this; | ||
| } | ||
| /** @deprecated Use Rotations instead */ | ||
| setDegrees(degrees, anchor) { | ||
| this._degrees = degrees; | ||
| this._anchor = anchor; | ||
| return this; | ||
| } | ||
| setRotations(rotations) { | ||
| this._rotations = rotations; | ||
| return this; | ||
| } | ||
| setShadow(shadow) { | ||
| this._shadow = shadow; | ||
| return this; | ||
| } | ||
| }; | ||
| //#endregion | ||
| //#region src/libraries/elements/geometric-element.ts | ||
| var GeometricElement = class extends Element { | ||
| _size = { | ||
| width: 1, | ||
| height: 1 | ||
| }; | ||
| _borderColor; | ||
| _borderWidth = 0; | ||
| hash() { | ||
| return Helper.hash([ | ||
| super.hash(), | ||
| this.size(), | ||
| this.borderColor(), | ||
| this.borderWidth() | ||
| ]); | ||
| } | ||
| size() { | ||
| return Helper.resolvedValue(this._size); | ||
| } | ||
| borderColor() { | ||
| return this._borderColor ? Helper.resolvedValue(this._borderColor) : void 0; | ||
| } | ||
| borderWidth() { | ||
| return Helper.resolvedValue(this._borderWidth); | ||
| } | ||
| setSize(value) { | ||
| this._size = value; | ||
| return this; | ||
| } | ||
| setBorder(width, color) { | ||
| this._borderWidth = width; | ||
| this._borderColor = color; | ||
| return this; | ||
| } | ||
| }; | ||
| //#endregion | ||
| //#region src/libraries/elements/rectangle-element.ts | ||
| var RectangleElement = class extends GeometricElement { | ||
| _fillColor; | ||
| _cutOutShapes = []; | ||
| type() { | ||
| return ElementType.Rectangle; | ||
| } | ||
| hash() { | ||
| return Helper.hash([ | ||
| super.hash(), | ||
| this.fillColor(), | ||
| this.cutOutShapes() | ||
| ]); | ||
| } | ||
| fillColor() { | ||
| return this._fillColor ? Helper.resolvedValue(this._fillColor) : void 0; | ||
| } | ||
| cutOutShapes() { | ||
| return Helper.resolvedValue(this._cutOutShapes); | ||
| } | ||
| boundingClientSize() { | ||
| return this.size(); | ||
| } | ||
| setFillColor(value) { | ||
| this._fillColor = value; | ||
| return this; | ||
| } | ||
| setCutOutShapes(value) { | ||
| this._cutOutShapes = value; | ||
| return this; | ||
| } | ||
| }; | ||
| //#endregion | ||
| //#region src/libraries/elements/ellipse-element.ts | ||
| var EllipseElement = class extends GeometricElement { | ||
| _fillColor; | ||
| type() { | ||
| return ElementType.Ellipse; | ||
| } | ||
| hash() { | ||
| return Helper.hash([super.hash(), this.fillColor()]); | ||
| } | ||
| fillColor() { | ||
| return this._fillColor ? Helper.resolvedValue(this._fillColor) : void 0; | ||
| } | ||
| boundingClientSize() { | ||
| return this.size(); | ||
| } | ||
| setFillColor(value) { | ||
| this._fillColor = value; | ||
| return this; | ||
| } | ||
| }; | ||
| //#endregion | ||
| //#region src/libraries/elements/polygon-element.ts | ||
| var PolygonElement = class extends GeometricElement { | ||
| _shapePoints = []; | ||
| _borderRadius = 0; | ||
| _fillColor; | ||
| type() { | ||
| return ElementType.Polygon; | ||
| } | ||
| hash() { | ||
| return Helper.hash([ | ||
| super.hash(), | ||
| this.shapePoints(), | ||
| this.borderRadius(), | ||
| this.fillColor() | ||
| ]); | ||
| } | ||
| shapePoints() { | ||
| return Helper.resolvedValue(this._shapePoints); | ||
| } | ||
| borderRadius() { | ||
| return Helper.resolvedValue(this._borderRadius); | ||
| } | ||
| fillColor() { | ||
| return this._fillColor ? Helper.resolvedValue(this._fillColor) : void 0; | ||
| } | ||
| boundingClientSize() { | ||
| return this.size(); | ||
| } | ||
| setShapePoints(value) { | ||
| this._shapePoints = value; | ||
| return this; | ||
| } | ||
| setBorderRadius(value) { | ||
| this._borderRadius = value; | ||
| return this; | ||
| } | ||
| setFillColor(value) { | ||
| this._fillColor = value; | ||
| return this; | ||
| } | ||
| }; | ||
| //#endregion | ||
| //#region src/libraries/elements/line-element.ts | ||
| var LineElement = class extends Element { | ||
| _shapePoints = []; | ||
| _size; | ||
| _keepRatio; | ||
| _thickness = 1; | ||
| _jointsRadius = 0; | ||
| _lineCap = LineCap.Square; | ||
| _color; | ||
| type() { | ||
| return ElementType.Line; | ||
| } | ||
| hash() { | ||
| return Helper.hash([ | ||
| super.hash(), | ||
| this.shapePoints(), | ||
| this.size(), | ||
| this.keepRatio(), | ||
| this.thickness(), | ||
| this.jointsRadius(), | ||
| this.lineCap(), | ||
| this.color() | ||
| ]); | ||
| } | ||
| shapePoints() { | ||
| return Helper.resolvedValue(this._shapePoints); | ||
| } | ||
| size() { | ||
| return this._size ? Helper.resolvedValue(this._size) : void 0; | ||
| } | ||
| keepRatio() { | ||
| return Helper.resolvedValue(this._keepRatio) ?? false; | ||
| } | ||
| thickness() { | ||
| return Helper.resolvedValue(this._thickness); | ||
| } | ||
| calculatedThickness() { | ||
| const size = this.size(); | ||
| let thickness = this.thickness(); | ||
| if (size) { | ||
| const originalSize = Helper.shapeSize(this.shapePoints()); | ||
| const resized = Helper.resizedSize(originalSize, size, this.keepRatio()); | ||
| const ratioWidth = resized.width / Helper.valueNotLessThan(originalSize.width, 1); | ||
| const ratioHeight = resized.height / Helper.valueNotLessThan(originalSize.height, 1); | ||
| thickness = Helper.valueNotLessThan(thickness * Math.min(ratioWidth, ratioHeight), 1); | ||
| } | ||
| return thickness; | ||
| } | ||
| jointsRadius() { | ||
| return Helper.resolvedValue(this._jointsRadius); | ||
| } | ||
| lineCap() { | ||
| return Helper.resolvedValue(this._lineCap); | ||
| } | ||
| color() { | ||
| return this._color ? Helper.resolvedValue(this._color) : void 0; | ||
| } | ||
| boundingClientSize() { | ||
| let size = this.size(); | ||
| const thickness = this.calculatedThickness(); | ||
| const originalSize = Helper.shapeSize(this.shapePoints()); | ||
| if (size) { | ||
| const resized = Helper.resizedSize(originalSize, size, this.keepRatio()); | ||
| size = { | ||
| width: resized.width, | ||
| height: resized.height + thickness / 2 | ||
| }; | ||
| } else size = { | ||
| width: originalSize.width, | ||
| height: Helper.valueNotLessThan(originalSize.height + thickness / 2, 0) | ||
| }; | ||
| return size; | ||
| } | ||
| setShapePoints(value) { | ||
| this._shapePoints = value; | ||
| return this; | ||
| } | ||
| setSize(value, keepRatio) { | ||
| this._size = value; | ||
| if (keepRatio) this._keepRatio = keepRatio; | ||
| return this; | ||
| } | ||
| setThickness(value) { | ||
| this._thickness = value; | ||
| return this; | ||
| } | ||
| setJointsRadius(value) { | ||
| this._jointsRadius = value; | ||
| return this; | ||
| } | ||
| setLineCap(value) { | ||
| this._lineCap = value; | ||
| return this; | ||
| } | ||
| setColor(value) { | ||
| this._color = value; | ||
| return this; | ||
| } | ||
| }; | ||
| //#endregion | ||
| //#region src/libraries/texts/text-splitter.ts | ||
| var TextSplitter = class { | ||
| element; | ||
| canvas; | ||
| ctx; | ||
| lastTextBox; | ||
| lastTextBoxHash = ""; | ||
| constructor(element) { | ||
| this.element = element; | ||
| this.canvas = document.createElement("canvas"); | ||
| this.ctx = this.canvas.getContext("2d"); | ||
| } | ||
| textBox() { | ||
| if (this.elementChanged()) this.lastTextBox = void 0; | ||
| if (!this.lastTextBox) this.lastTextBox = this.createTextBox(); | ||
| return this.lastTextBox; | ||
| } | ||
| createTextBox() { | ||
| const result = { | ||
| text: this.element.text(), | ||
| lines: [], | ||
| size: { | ||
| width: 1, | ||
| height: 1 | ||
| }, | ||
| position: { | ||
| x: 0, | ||
| y: 0 | ||
| } | ||
| }; | ||
| result.lines = this.textLines(); | ||
| result.size = this.textBoxSize(result.lines); | ||
| result.position = this.textBoxPosition(result.lines); | ||
| return result; | ||
| } | ||
| textLines() { | ||
| const bounds = this.element.bounds(); | ||
| const padding = this.element.padding(); | ||
| const boundsWithoutPadding = bounds ? { | ||
| width: bounds.width - padding.left - padding.right, | ||
| height: bounds.height - padding.top - padding.bottom | ||
| } : void 0; | ||
| const textLines = this.splitText().map((line) => { | ||
| return { | ||
| text: line, | ||
| size: { | ||
| width: this.textWidth(line), | ||
| height: this.element.lineHeight() | ||
| }, | ||
| position: { | ||
| x: 0, | ||
| y: 0 | ||
| } | ||
| }; | ||
| }).map((line, index, transformed) => { | ||
| const previousLine = transformed[index - 1]; | ||
| line.position = { | ||
| x: 0, | ||
| y: previousLine ? previousLine.position.y + previousLine.size.height : 0 | ||
| }; | ||
| return line; | ||
| }); | ||
| return (boundsWithoutPadding !== void 0 ? this.alignedLines(textLines, boundsWithoutPadding) : textLines).map((line) => { | ||
| line.position.x += padding.left; | ||
| line.position.y += padding.top; | ||
| return line; | ||
| }); | ||
| } | ||
| alignedLines(textLines, bounds) { | ||
| const horizontalAlign = this.element.horizontalAlign(); | ||
| const verticalAlign = this.element.verticalAlign(); | ||
| const totalLinesHeight = textLines.reduce((sum, line) => sum + line.size.height, 0); | ||
| return textLines.map((line) => { | ||
| if (horizontalAlign === HorizontalAlign.Center) line.position.x += Math.round((bounds.width - line.size.width) / 2); | ||
| if (horizontalAlign === HorizontalAlign.Right) line.position.x += bounds.width - line.size.width; | ||
| if (verticalAlign === VerticalAlign.Center) line.position.y += Math.round((bounds.height - totalLinesHeight) / 2); | ||
| if (verticalAlign === VerticalAlign.Bottom) line.position.y += bounds.height - totalLinesHeight; | ||
| return line; | ||
| }); | ||
| } | ||
| textBoxSize(textLines) { | ||
| const padding = this.element.padding(); | ||
| const bounds = this.element.bounds() ?? { | ||
| width: 1, | ||
| height: 1 | ||
| }; | ||
| const longestWidth = Math.max(...textLines.map((textLine) => textLine.size.width)); | ||
| return { | ||
| width: Math.max(longestWidth + padding.left + padding.right, bounds.width), | ||
| height: Math.max(this.element.lineHeight() * textLines.length + padding.top + padding.bottom, bounds.height) | ||
| }; | ||
| } | ||
| textBoxPosition(textLines) { | ||
| const padding = this.element.padding(); | ||
| const smallestX = Math.min(...textLines.map((textLine) => textLine.position.x)); | ||
| const smallestY = Math.min(...textLines.map((textLine) => textLine.position.y)); | ||
| return { | ||
| x: smallestX - padding.left, | ||
| y: smallestY - padding.top | ||
| }; | ||
| } | ||
| splitText() { | ||
| const lines = this.element.text().replaceAll("<BR>", "<br>").split("<br>"); | ||
| const padding = this.element.padding(); | ||
| const maxWidth = (this.element.bounds() ?? { | ||
| width: 999999, | ||
| height: 999999 | ||
| }).width - (padding.left + padding.right); | ||
| let result = []; | ||
| lines.forEach((line) => { | ||
| line = line.trim(); | ||
| const textWidth = this.textWidth(line); | ||
| if (textWidth > maxWidth) { | ||
| const splitEvery = Math.floor(maxWidth / (textWidth / line.length)) - 3; | ||
| const words = line.split(" "); | ||
| let tmp = ""; | ||
| words.forEach((word, index) => { | ||
| if (tmp.length + word.length > splitEvery) { | ||
| result.push(tmp.trim()); | ||
| tmp = ""; | ||
| } | ||
| tmp += word + " "; | ||
| if (index === words.length - 1) result.push(tmp.trim()); | ||
| }); | ||
| result = result.map((line) => line.trim()); | ||
| } else result.push(line); | ||
| }); | ||
| return result; | ||
| } | ||
| textWidth(text) { | ||
| this.ctx.font = this.element.font(); | ||
| return Math.ceil(this.ctx.measureText(text).width + this.element.strokeWidth()); | ||
| } | ||
| elementChanged() { | ||
| const hash = [ | ||
| this.element.text(), | ||
| this.element.bounds(), | ||
| this.element.horizontalAlign(), | ||
| this.element.verticalAlign(), | ||
| this.element.padding(), | ||
| this.element.lineHeight(), | ||
| this.element.font(), | ||
| this.element.strokeWidth() | ||
| ].join("|"); | ||
| const changed = this.lastTextBoxHash !== hash; | ||
| this.lastTextBoxHash = hash; | ||
| return changed; | ||
| } | ||
| }; | ||
| //#endregion | ||
| //#region src/libraries/elements/text-element.ts | ||
| var TextElement = class extends Element { | ||
| _text = ""; | ||
| _bounds; | ||
| _fontSize = 12; | ||
| _fontFamily = "Tahoma"; | ||
| _fontWeight = FontWeight.Normal; | ||
| _lineHeight; | ||
| _fillColor; | ||
| _strokeColor; | ||
| _strokeWidth = 0; | ||
| _horizontalAlign = HorizontalAlign.Left; | ||
| _verticalAlign = VerticalAlign.Top; | ||
| _padding = { topOrVerticalOrDefault: 0 }; | ||
| _textSplitter = new TextSplitter(this); | ||
| type() { | ||
| return ElementType.Text; | ||
| } | ||
| hash() { | ||
| return Helper.hash([ | ||
| super.hash(), | ||
| this.text(), | ||
| this.bounds(), | ||
| this.font(), | ||
| this.lineHeight(), | ||
| this.fillColor(), | ||
| this.strokeColor(), | ||
| this.strokeWidth(), | ||
| this.horizontalAlign(), | ||
| this.verticalAlign(), | ||
| this.padding() | ||
| ]); | ||
| } | ||
| text() { | ||
| return Helper.resolvedValue(this._text).toString(); | ||
| } | ||
| bounds() { | ||
| return this._bounds ? Helper.resolvedValue(this._bounds) : void 0; | ||
| } | ||
| fontSize() { | ||
| return Helper.resolvedValue(this._fontSize); | ||
| } | ||
| fontFamily() { | ||
| return Helper.resolvedValue(this._fontFamily); | ||
| } | ||
| fontWeight() { | ||
| return Helper.resolvedValue(this._fontWeight); | ||
| } | ||
| lineHeight() { | ||
| return Helper.resolvedValue(this._lineHeight ?? Math.round(this.fontSize() * 1.2)); | ||
| } | ||
| fillColor() { | ||
| return this._fillColor ? Helper.resolvedValue(this._fillColor) : void 0; | ||
| } | ||
| strokeColor() { | ||
| return this._strokeColor ? Helper.resolvedValue(this._strokeColor) : void 0; | ||
| } | ||
| strokeWidth() { | ||
| return Helper.resolvedValue(this._strokeWidth); | ||
| } | ||
| horizontalAlign() { | ||
| return Helper.resolvedValue(this._horizontalAlign); | ||
| } | ||
| verticalAlign() { | ||
| return Helper.resolvedValue(this._verticalAlign); | ||
| } | ||
| padding() { | ||
| let result; | ||
| const topOrVerticalOrDefault = Helper.resolvedValue(this._padding.topOrVerticalOrDefault); | ||
| const rightOrHorizontal = this._padding.rightOrHorizontal ? Helper.resolvedValue(this._padding.rightOrHorizontal) : void 0; | ||
| const bottom = this._padding.bottom ? Helper.resolvedValue(this._padding.bottom) : void 0; | ||
| const left = this._padding.left ? Helper.resolvedValue(this._padding.left) : void 0; | ||
| if (rightOrHorizontal === void 0) result = { | ||
| top: topOrVerticalOrDefault, | ||
| right: topOrVerticalOrDefault, | ||
| bottom: topOrVerticalOrDefault, | ||
| left: topOrVerticalOrDefault | ||
| }; | ||
| else if (bottom === void 0 && left === void 0) result = { | ||
| top: topOrVerticalOrDefault, | ||
| right: rightOrHorizontal, | ||
| bottom: topOrVerticalOrDefault, | ||
| left: rightOrHorizontal | ||
| }; | ||
| else result = { | ||
| top: topOrVerticalOrDefault, | ||
| right: rightOrHorizontal, | ||
| bottom: bottom ?? 0, | ||
| left: left ?? 0 | ||
| }; | ||
| return result; | ||
| } | ||
| font() { | ||
| const font = [this.fontSize() + "px", this.fontFamily()]; | ||
| const weight = this.fontWeight(); | ||
| if (weight) font.unshift(weight); | ||
| return font.join(" "); | ||
| } | ||
| textBox() { | ||
| return this._textSplitter.textBox(); | ||
| } | ||
| boundingClientSize() { | ||
| return this.textBox().size; | ||
| } | ||
| setText(value) { | ||
| this._text = value; | ||
| return this; | ||
| } | ||
| setBounds(value) { | ||
| this._bounds = value; | ||
| return this; | ||
| } | ||
| setFontSize(value) { | ||
| this._fontSize = value; | ||
| return this; | ||
| } | ||
| setFontFamily(value) { | ||
| this._fontFamily = value; | ||
| return this; | ||
| } | ||
| setFontWeight(value) { | ||
| this._fontWeight = value; | ||
| return this; | ||
| } | ||
| setLineHeight(value) { | ||
| this._lineHeight = value; | ||
| return this; | ||
| } | ||
| setFillColor(value) { | ||
| this._fillColor = value; | ||
| return this; | ||
| } | ||
| setStroke(width, color) { | ||
| this._strokeWidth = width; | ||
| this._strokeColor = color; | ||
| return this; | ||
| } | ||
| setHorizontalAlign(value) { | ||
| this._horizontalAlign = value; | ||
| return this; | ||
| } | ||
| setVerticalAlign(value) { | ||
| this._verticalAlign = value; | ||
| return this; | ||
| } | ||
| setPadding(topOrVerticalOrDefault, rightOrHorizontal, bottom, left) { | ||
| this._padding = { | ||
| topOrVerticalOrDefault, | ||
| rightOrHorizontal, | ||
| bottom, | ||
| left | ||
| }; | ||
| return this; | ||
| } | ||
| }; | ||
| //#endregion | ||
| //#region src/libraries/elements/image-element.ts | ||
| var ImageElement = class extends GeometricElement { | ||
| _image; | ||
| _fit = ImageFit.Fill; | ||
| _borderRadius = { topLeftOrDefault: 0 }; | ||
| _flipX; | ||
| _flipY; | ||
| _cropArea; | ||
| type() { | ||
| return ElementType.Image; | ||
| } | ||
| hash() { | ||
| return Helper.hash([ | ||
| super.hash(), | ||
| this.image()?.id, | ||
| this.fit(), | ||
| this.borderRadius(), | ||
| this.flipX(), | ||
| this.flipY(), | ||
| this.cropArea() | ||
| ]); | ||
| } | ||
| image() { | ||
| return this._image ? Helper.resolvedValue(this._image) : void 0; | ||
| } | ||
| fit() { | ||
| return Helper.resolvedValue(this._fit); | ||
| } | ||
| borderRadius() { | ||
| let result; | ||
| const topLeftOrDefault = Helper.resolvedValue(this._borderRadius.topLeftOrDefault); | ||
| const topRight = this._borderRadius.topRight ? Helper.resolvedValue(this._borderRadius.topRight) : void 0; | ||
| const bottomRight = this._borderRadius.bottomRight ? Helper.resolvedValue(this._borderRadius.bottomRight) : void 0; | ||
| const bottomLeft = this._borderRadius.bottomLeft ? Helper.resolvedValue(this._borderRadius.bottomLeft) : void 0; | ||
| if (topRight === void 0 && bottomRight === void 0 && bottomLeft === void 0) result = { | ||
| topLeft: topLeftOrDefault, | ||
| topRight: topLeftOrDefault, | ||
| bottomRight: topLeftOrDefault, | ||
| bottomLeft: topLeftOrDefault | ||
| }; | ||
| else result = { | ||
| topLeft: topLeftOrDefault, | ||
| topRight: topRight ?? 0, | ||
| bottomRight: bottomRight ?? 0, | ||
| bottomLeft: bottomLeft ?? 0 | ||
| }; | ||
| return result; | ||
| } | ||
| flipX() { | ||
| return Helper.resolvedValue(this._flipX) ?? false; | ||
| } | ||
| flipY() { | ||
| return Helper.resolvedValue(this._flipY) ?? false; | ||
| } | ||
| cropArea() { | ||
| return Helper.resolvedValue(this._cropArea) ?? {}; | ||
| } | ||
| boundingClientSize() { | ||
| return this.size(); | ||
| } | ||
| setImage(value) { | ||
| this._image = value; | ||
| return this; | ||
| } | ||
| setFit(value) { | ||
| this._fit = value; | ||
| return this; | ||
| } | ||
| setBorderRadius(topLeftOrDefault, topRight, bottomRight, bottomLeft) { | ||
| this._borderRadius = { | ||
| topLeftOrDefault, | ||
| topRight, | ||
| bottomRight, | ||
| bottomLeft | ||
| }; | ||
| return this; | ||
| } | ||
| setFlip(value) { | ||
| this._flipX = value.horizontal ?? false; | ||
| this._flipY = value.vertical ?? false; | ||
| return this; | ||
| } | ||
| setCropArea(value) { | ||
| this._cropArea = value; | ||
| return this; | ||
| } | ||
| }; | ||
| //#endregion | ||
| //#region src/libraries/colors/color.ts | ||
| var Color = class {}; | ||
| //#endregion | ||
| //#region src/libraries/colors/solid-color.ts | ||
| var SolidColor = class extends Color { | ||
| _color; | ||
| constructor(hexColor, opacity) { | ||
| super(); | ||
| this.setColor(hexColor, opacity); | ||
| } | ||
| type() { | ||
| return ColorType.SolidColor; | ||
| } | ||
| color() { | ||
| return this._color; | ||
| } | ||
| hash() { | ||
| return Helper.hash([this.color()]); | ||
| } | ||
| setColor(hexColor, opacity) { | ||
| const alphaFromHex = hexColor.length === 9 ? hexColor.slice(-2) : void 0; | ||
| const alpha = opacity !== void 0 ? this.alphaToHex(opacity) : alphaFromHex ?? "ff"; | ||
| this._color = hexColor.slice(0, 7) + alpha; | ||
| return this; | ||
| } | ||
| alphaToHex(alpha) { | ||
| let hex = Math.round(alpha * 255).toString(16); | ||
| if (hex.length === 1) hex = "0" + hex; | ||
| return hex; | ||
| } | ||
| }; | ||
| //#endregion | ||
| //#region src/libraries/colors/gradient-color.ts | ||
| var GradientColor = class extends SolidColor { | ||
| _offset; | ||
| constructor(offset, hexColor, opacity = 1) { | ||
| super(hexColor, opacity); | ||
| this.setOffset(offset); | ||
| } | ||
| offset() { | ||
| return this._offset; | ||
| } | ||
| hash() { | ||
| return Helper.hash([super.hash(), this.offset()]); | ||
| } | ||
| setOffset(offset) { | ||
| this._offset = Helper.valueInRange(offset, { | ||
| min: 0, | ||
| max: 1 | ||
| }); | ||
| return this; | ||
| } | ||
| }; | ||
| //#endregion | ||
| //#region src/libraries/colors/gradient.ts | ||
| var Gradient = class extends Color { | ||
| _colors; | ||
| constructor(colors) { | ||
| super(); | ||
| this._colors = colors; | ||
| } | ||
| colors() { | ||
| return this._colors; | ||
| } | ||
| hash() { | ||
| return Helper.hash([this.colors()]); | ||
| } | ||
| }; | ||
| //#endregion | ||
| //#region src/libraries/colors/linear-gradient.ts | ||
| var LinearGradient = class extends Gradient { | ||
| _degrees = 0; | ||
| type() { | ||
| return ColorType.LinearGradient; | ||
| } | ||
| degrees() { | ||
| return this._degrees; | ||
| } | ||
| setDegrees(value) { | ||
| this._degrees = value; | ||
| return this; | ||
| } | ||
| hash() { | ||
| return Helper.hash([super.hash(), this.degrees()]); | ||
| } | ||
| }; | ||
| //#endregion | ||
| //#region src/libraries/colors/radial-gradient.ts | ||
| var RadialGradient = class extends Gradient { | ||
| _position; | ||
| _radiusScale; | ||
| type() { | ||
| return ColorType.RadialGradient; | ||
| } | ||
| position() { | ||
| return this._position; | ||
| } | ||
| radiusScale() { | ||
| return this._radiusScale ?? 1; | ||
| } | ||
| setPosition(value) { | ||
| this._position = value; | ||
| return this; | ||
| } | ||
| setRadiusScale(value) { | ||
| this._radiusScale = value; | ||
| return this; | ||
| } | ||
| hash() { | ||
| return Helper.hash([ | ||
| super.hash(), | ||
| this.position(), | ||
| this.radiusScale() | ||
| ]); | ||
| } | ||
| }; | ||
| //#endregion | ||
| export { Canvas, ColorType, EllipseElement, FontWeight, GradientColor, HorizontalAlign, ImageElement, ImageFit, LineCap, LineElement, LinearGradient, PolygonElement, PositionPoint, RadialGradient, RectangleElement, Shape, SolidColor, TextElement, VerticalAlign }; |
+1903
-1
@@ -1,1 +0,1903 @@ | ||
| !function(t,e){"object"==typeof exports&&"undefined"!=typeof module?e(exports):"function"==typeof define&&define.amd?define(["exports"],e):e((t="undefined"!=typeof globalThis?globalThis:t||self).Canvas={})}(this,function(t){Object.defineProperty(t,Symbol.toStringTag,{value:"Module"});var e=function(t){return t.Rectangle="rectangle",t.Ellipse="ellipse",t.Polygon="polygon",t.Line="line",t.Text="text",t.Image="image",t}({}),i=function(t){return t.LeftTop="left-top",t.RightTop="right-top",t.LeftBottom="left-bottom",t.RightBottom="right-bottom",t.Center="center",t}({}),s=class t{static valueInRange(t,e){return t<e.min&&(t=e.min),t>e.max&&(t=e.max),t}static valueNotGreaterThan(t,e){return t>e&&(t=e),t}static valueNotLessThan(t,e){return t<e&&(t=e),t}static relativePositionValue(e,i,s){const o=t.extractedValue(e);let h;if("%"===o.unit)h=(s-i)/100*o.value;else h=Number(e);return h}static extractedValue(t){let e;if("string"==typeof t){const i=t.match(/^(\d+)(\D+)$/)??[];e=i.length>2?{value:parseFloat(i[1]??"0"),unit:i[2]??"px"}:{value:Number(t),unit:"px"}}else e={value:t,unit:"px"};return e}static degreesToRadians(t){return t/180*Math.PI}static distance(t,e){return Math.sqrt(Math.pow(Math.abs(e.x-t.x),2)+Math.pow(Math.abs(e.y-t.y),2))}static positionBetweenPoints(e,i,s){const o=s/t.distance(e,i);return{x:e.x+o*(i.x-e.x),y:e.y+o*(i.y-e.y)}}static shapeSize(t){const e=Math.min(...t.map(t=>t.x)),i=Math.min(...t.map(t=>t.y)),s=Math.max(...t.map(t=>t.x)),o=Math.max(...t.map(t=>t.y));return{width:Math.floor(s-e),height:Math.floor(o-i)}}static positionLeftTop(t,e,s){const o={x:t.x,y:t.y};switch(e){case i.RightTop:o.x-=s.width;break;case i.RightBottom:o.x-=s.width,o.y-=s.height;break;case i.LeftBottom:o.y-=s.height;break;case i.Center:o.x-=s.width/2,o.y-=s.height/2}return o}static positionCenter(e,i,s){const o=t.positionLeftTop(e,i,s);return o.x+=s.width/2,o.y+=s.height/2,o}static resizedSize(t,e,i){const s={width:e.width,height:e.height};if(i){const i=[{width:t.width*(e.height/t.height),height:e.height},{width:e.width,height:t.height*(e.width/t.width)}].find(t=>t.width<=e.width&&t.height<=e.height);i&&(s.width=i.width,s.height=i.height)}return s}static resolvedValue(t){let e;return e=t&&"function"==typeof t?t():t,e}static isObject(t){return"object"==typeof t&&t.constructor===Object&&!Array.isArray(t)}static hasMethod(t,e){return!!t[e]&&"function"==typeof t[e]}static hash(e){return e.map(e=>{let i;return i=null==e?"undefined":t.isObject(e)?JSON.stringify(e):Array.isArray(e)?"["+t.hash(e)+"]":t.hasMethod(e,"hash")?e.hash():e.toString(),i}).join("|")}},o=class{ctx;element;relativeProps;defaultBorderColor="#000000";constructor(t,e,i){this.ctx=t,this.element=e,this.relativeProps=i??{}}relativePosition(){return this.relativeProps.position??{x:0,y:0}}relativeScale(){return this.relativeProps.scale??{x:1,y:1}}rotations(){return[...this.relativeProps.rotations??[],...this.element.rotations()]}relativeOpacity(){return this.relativeProps.opacity??1}scale(){const t=this.element.scale(),e=this.relativeScale();return{x:t.x*e.x,y:t.y*e.y}}degrees(){return this.element.degrees()??0}anchor(){return this.element.anchor()??this.defaultAnchor()}opacity(){return this.element.opacity()*this.relativeOpacity()}startDrawing(){this.ctx.save();const t=this.scale(),e=this.opacity(),i=this.degrees(),s=this.rotations(),o=this.element.shadow();1===t.x&&1===t.y||this.ctx.scale(t.x,t.y),e<1&&(this.ctx.globalAlpha=e),0!==i&&this.rotate({degrees:i,pivot:this.anchor()}),s.length>0&&s.forEach(t=>{0!==t.degrees&&this.rotate(t)}),o&&o.blur>0&&(this.ctx.shadowColor=o.color,this.ctx.shadowBlur=o.blur,this.ctx.shadowOffsetX=o.x,this.ctx.shadowOffsetY=o.y)}endDrawing(){this.ctx.restore()}rotate(t){const e=this.position(),i=t.pivot??this.defaultAnchor(),o={x:i.x+e.x,y:i.y+e.y};this.ctx.translate(o.x,o.y),this.ctx.rotate(s.degreesToRadians(t.degrees)),this.ctx.translate(-o.x,-o.y)}},h=class{_originalSize;_sizeRatio;_shapePosition;constructor(t,e){this.ctx=t,this.shape=e}draw(){this.prepare(),this.ctx.save(),this.rotateContext(),this.shape.isCloseShape()?this.makeClosedShape():this.makeOpenedShape(),this.ctx.restore()}prepare(){this._originalSize=s.shapeSize(this.shape.points()),this._sizeRatio=this.sizeRatio(),this._shapePosition=this.shapePosition()}rotateContext(){const t=this.shape.degrees();0!==t&&this.rotate({degrees:t}),this.shape.rotations().forEach(t=>{this.rotate(t)})}makeClosedShape(){const t=this.shape.points();t.length>2&&t.forEach((e,i)=>{const o=0===i,h=i===t.length-1,n=t[i+1]??t[0],r=s.distance(e,n),a=this.radius(r),l=s.positionBetweenPoints(e,n,a),d=s.positionBetweenPoints(e,n,r-a);if(o&&this.moveToPoint(l),this.lineToPoint(d),a>0){const e=t[i+2]??(h?t[1]:t[0]);this.quadraticCurveToPoint(n,e)}})}makeOpenedShape(){const t=this.shape.points();t.length>1&&t.forEach((e,i)=>{const o=0===i,h=i===t.length-1,n=i===t.length-2;if(!h){const h=t[i+1],r=h?s.distance(e,h):0,a=this.radius(r),l=o?e:s.positionBetweenPoints(e,h,a),d=n?h:s.positionBetweenPoints(e,h,r-a);o&&this.moveToPoint(l),this.lineToPoint(d),!n&&a>0&&this.quadraticCurveToPoint(h,t[i+2])}})}radius(t){const e=Math.min(this._sizeRatio.width,this._sizeRatio.height),i=Math.floor(this.shape.jointsRadius()/e),s=Math.floor(.55*t);return i>s?s:i}moveToPoint(t){this.ctx.moveTo(this._shapePosition.x+t.x*this._sizeRatio.width,this._shapePosition.y+t.y*this._sizeRatio.height)}lineToPoint(t){this.ctx.lineTo(this._shapePosition.x+t.x*this._sizeRatio.width,this._shapePosition.y+t.y*this._sizeRatio.height)}quadraticCurveToPoint(t,e){const i=s.distance(t,e),o=this.radius(i),h=s.positionBetweenPoints(t,e,o);this.ctx.quadraticCurveTo(this._shapePosition.x+t.x*this._sizeRatio.width,this._shapePosition.y+t.y*this._sizeRatio.height,this._shapePosition.x+h.x*this._sizeRatio.width,this._shapePosition.y+h.y*this._sizeRatio.height)}shapePosition(){return s.positionLeftTop(this.shape.position(),this.shape.positionPoint(),this.shapeSize())}shapeSize(){return this.shape.size()??this._originalSize}sizeRatio(){const t=this.shapeSize();return{width:t.width/s.valueNotLessThan(this._originalSize.width,1),height:t.height/s.valueNotLessThan(this._originalSize.height,1)}}rotate(t){const e=this.shapePosition(),i=t.pivot??this.defaultAnchor();i.x+=e.x,i.y+=e.y,this.ctx.translate(i.x,i.y),this.ctx.rotate(s.degreesToRadians(t.degrees)),this.ctx.translate(-i.x,-i.y)}defaultAnchor(){const t=this.shapeSize();return{x:t.width/2,y:t.height/2}}},n=function(t){return t.SolidColor="solid-color",t.LinearGradient="linear-gradient",t.RadialGradient="radial-gradient",t}({}),r=class{constructor(t,e,i){this.ctx=t,this.elementPosition=e,this.elementSize=i}mixed(t){let e="";if("string"==typeof t)e=t;else switch(t.type()){case n.SolidColor:e=t.color();break;case n.LinearGradient:e=this.linearGradient(t);break;case n.RadialGradient:e=this.radialGradient(t)}return e}linearGradient(t){const e=s.degreesToRadians(t.degrees()),i=this.elementPosition.x+this.elementSize.width/2,o=this.elementPosition.y+this.elementSize.height/2,h=Math.sqrt(this.elementSize.width**2+this.elementSize.height**2)/2,n=Math.cos(e)*h,r=Math.sin(e)*h,a={x:i-n,y:o-r},l={x:i+n,y:o+r},d=this.ctx.createLinearGradient(a.x,a.y,l.x,l.y);return this.appendGradientColors(d,t.colors()),d}radialGradient(t){const e=t.position()??{x:this.elementSize.width/2,y:this.elementSize.height/2},i={x:this.elementPosition.x+e.x,y:this.elementPosition.y+e.y},s=Math.sqrt(this.elementSize.width*this.elementSize.width+this.elementSize.height*this.elementSize.height)/2,o=this.ctx.createRadialGradient(i.x,i.y,0,i.x,i.y,s*t.radiusScale());return this.appendGradientColors(o,t.colors()),o}appendGradientColors(t,e){e.forEach(e=>t.addColorStop(e.offset(),e.color()))}},a=class extends o{draw(){this.startDrawing(),this.makeShape(),this.drawFill(),this.drawBorder(),this.endDrawing()}makeShape(){const t=this.element.borderWidth(),e=this.position(),i=this.element.size(),s=this.element.cutOutShapes(),o={x:e.x+t/2,y:e.y+t/2},n={width:i.width-t,height:i.height-t};this.ctx.beginPath(),s.length>0&&(this.ctx.translate(o.x,o.y),s.forEach(t=>{new h(this.ctx,t).draw()}),this.ctx.translate(-o.x,-o.y),o.x+=n.width,n.width*=-1),this.ctx.rect(o.x,o.y,n.width,n.height),this.ctx.closePath()}drawFill(){const t=this.element.fillColor();t&&(this.ctx.fillStyle=new r(this.ctx,this.position(),this.element.size()).mixed(t),this.ctx.fill())}drawBorder(){const t=this.element.borderWidth();t>0&&(this.ctx.strokeStyle=new r(this.ctx,this.position(),this.element.size()).mixed(this.element.borderColor()??this.defaultBorderColor),this.ctx.lineWidth=t,this.ctx.stroke())}position(){const t=this.element.positionLeftTop(),e=this.relativePosition();return{x:t.x+e.x,y:t.y+e.y}}defaultAnchor(){const t=this.element.size();return{x:t.width/2,y:t.height/2}}},l=class extends o{draw(){this.startDrawing(),this.drawFill(),this.drawBorder(),this.endDrawing()}drawFill(){const t=this.element.fillColor();if(t){const e=this.element.borderWidth(),i=this.position(),o=this.element.size();this.ctx.beginPath(),this.ctx.ellipse(i.x,i.y,s.valueNotLessThan(o.width/2-e,1),s.valueNotLessThan(o.height/2-e,1),0,0,2*Math.PI),this.ctx.closePath(),this.ctx.fillStyle=new r(this.ctx,this.element.positionLeftTop(),this.element.size()).mixed(t),this.ctx.fill()}}drawBorder(){const t=this.element.borderWidth();if(t>0){const e=this.position(),i=this.element.size();this.ctx.beginPath(),this.ctx.ellipse(e.x,e.y,s.valueNotLessThan(i.width/2-t/2,1),s.valueNotLessThan(i.height/2-t/2,1),0,0,2*Math.PI),this.ctx.closePath(),this.ctx.strokeStyle=new r(this.ctx,this.element.positionLeftTop(),this.element.size()).mixed(this.element.borderColor()??this.defaultBorderColor),this.ctx.lineWidth=t,this.ctx.stroke()}}position(){const t=s.positionCenter(this.element.position(),this.element.positionPoint(),this.element.boundingClientSize()),e=this.relativePosition();return{x:t.x+e.x,y:t.y+e.y}}defaultAnchor(){return{x:0,y:0}}},d=class extends o{draw(){this.prepare(),this.startDrawing(),this.drawStroke(),this.drawFill(),this.endDrawing()}prepare(){this.ctx.font=this.element.font(),this.ctx.textBaseline="alphabetic"}drawFill(){const t=this.element.fillColor();if(t){const e=this.element.textBox(),i=this.position();this.ctx.fillStyle=new r(this.ctx,this.position(),e.size).mixed(t),e.lines.forEach(t=>{this.ctx.fillText(t.text,i.x+t.position.x,Math.round(i.y+t.position.y+.75*t.size.height))})}}drawStroke(){const t=this.element.strokeWidth();if(t>0){const e=this.element.textBox(),i=this.position(),s=this.element.fillColor();this.ctx.strokeStyle=new r(this.ctx,this.position(),e.size).mixed(this.element.strokeColor()??this.defaultBorderColor),this.ctx.lineWidth=s?2*t:t,e.lines.forEach(t=>{this.ctx.strokeText(t.text,i.x+t.position.x,Math.round(i.y+t.position.y+.75*t.size.height))})}}position(){const t=this.element.positionLeftTop(),e=this.relativePosition();return{x:t.x+e.x,y:t.y+e.y}}defaultAnchor(){const t=this.element.textBox();return{x:t.size.width/2,y:t.size.height/2}}},c=function(t){return t.Fill="fill",t.Cover="cover",t}({}),u=class extends o{draw(){this.startDrawing(),this.drawBorder(),this.drawImage(),this.endDrawing()}drawImage(){const t=this.element.image();if(t){const e=this.sourceDimensions(),i=this.flippedDimensions(this.elementDimensions());this.ctx.drawImage(t,e.position.x,e.position.y,e.size.width,e.size.height,i.position.x,i.position.y,i.size.width,i.size.height)}}drawBorder(){const t=this.element.borderWidth(),e=new r(this.ctx,this.position(),this.element.size()).mixed(this.element.borderColor()??this.defaultBorderColor),i=this.elementDimensions();this.ctx.strokeStyle=e,this.ctx.lineWidth=2*t,this.isRadiusSet()?(this.makeShape(i),this.ctx.lineJoin="round",this.ctx.fillStyle="#ffffff",this.ctx.fill(),t>0&&(this.ctx.strokeStyle=e,this.ctx.lineWidth=2*t,this.ctx.stroke()),this.ctx.clip()):t>0&&(this.ctx.strokeStyle=e,this.ctx.lineWidth=2*t,this.ctx.strokeRect(i.position.x,i.position.y,i.size.width,i.size.height))}sourceDimensions(){const t=this.element.image(),e=this.element.cropArea(),i={position:{x:0,y:0},size:e.size??{width:t.width,height:t.height}};switch(this.element.fit()){case c.Fill:e.position&&(i.position.x=e.position.x,i.position.y=e.position.y);break;case c.Cover:{const{size:o}=this.elementDimensions();o.width/o.height<i.size.width/i.size.height?i.size.width=i.size.height/(o.height/o.width):i.size.height=i.size.width/(o.width/o.height),e.position?(i.position.x=e.position.x,i.position.y=e.position.y):(i.position.x=s.relativePositionValue("50%",i.size.width,t.width),i.position.y=s.relativePositionValue("50%",i.size.height,t.height));break}}return i}elementDimensions(){const t=this.element.borderWidth(),e=this.position(),i=this.element.size();return{position:{x:e.x+t,y:e.y+t},size:{width:i.width-2*t,height:i.height-2*t}}}flippedDimensions(t){const e=this.element.flipX(),i=this.element.flipY();if(e||i){let s=1,o=1;e&&(s=-1,t.position.x=-(+t.position.x+t.size.width)),i&&(o=-1,t.position.y=-(+t.position.y+t.size.height)),this.ctx.scale(s,o)}return t}makeShape(t){const e=this.borderRadius();this.ctx.lineJoin="round",this.ctx.beginPath(),this.ctx.moveTo(t.position.x+e.topLeft,t.position.y),this.ctx.lineTo(t.position.x+t.size.width-e.topRight,t.position.y),this.ctx.quadraticCurveTo(t.position.x+t.size.width,t.position.y,t.position.x+t.size.width,t.position.y+e.topRight),this.ctx.lineTo(t.position.x+t.size.width,t.position.y+t.size.height-e.bottomRight),this.ctx.quadraticCurveTo(t.position.x+t.size.width,t.position.y+t.size.height,t.position.x+t.size.width-e.bottomRight,t.position.y+t.size.height),this.ctx.lineTo(t.position.x+e.bottomLeft,t.position.y+t.size.height),this.ctx.quadraticCurveTo(t.position.x,t.position.y+t.size.height,t.position.x,t.position.y+t.size.height-e.bottomLeft),this.ctx.lineTo(t.position.x,t.position.y+e.topLeft),this.ctx.quadraticCurveTo(t.position.x,t.position.y,t.position.x+e.topLeft,t.position.y),this.ctx.closePath()}borderRadius(){const t=this.element.borderRadius();if(this.isRadiusSet()){const{size:e}=this.elementDimensions();Object.keys(t).forEach(i=>{t[i]>e.width/2&&(t[i]=Math.floor(e.width/2)),t[i]>e.height/2&&(t[i]=Math.floor(e.height/2))})}return t}isRadiusSet(){const t=this.element.borderRadius();return t.topLeft>0||t.topRight>0||t.bottomLeft>0||t.bottomRight>0}position(){const t=this.element.positionLeftTop(),e=this.relativePosition();return{x:t.x+e.x,y:t.y+e.y}}defaultAnchor(){const t=this.element.size();return{x:t.width/2,y:t.height/2}}},p=class{_points=[];_isCloseShape;_position={x:0,y:0};_positionPoint=i.LeftTop;_size;_jointsRadius=0;_degrees=0;_anchor;_rotations=[];constructor(t,e=!0){this._points=t,this._isCloseShape=e}hash(){return s.hash([this.points(),this.isCloseShape(),this.position(),this.positionPoint(),this.size(),this.jointsRadius(),this.rotations(),this.degrees(),this.anchor()])}points(){return this._points}isCloseShape(){return this._isCloseShape}position(){return this._position}positionPoint(){return this._positionPoint}size(){return this._size}jointsRadius(){return this._jointsRadius}degrees(){return this._degrees}anchor(){return this._anchor}rotations(){return this._rotations}setPosition(t,e){return this._position=t,e&&(this._positionPoint=e),this}setSize(t){return this._size=t,this}setJointsRadius(t){return this._jointsRadius=t,this}setDegrees(t,e){return this._degrees=t,this._anchor=e,this}setRotations(t){return this._rotations=t,this}},x=class extends o{draw(){this.startDrawing(),this.makeShape(),this.drawBorder(),this.drawFill(),this.endDrawing()}makeShape(){const t=this.element.shapePoints();if(t.length>2){const e=new p(t).setSize(this.shapeSize()).setPosition(this.shapePosition()).setJointsRadius(this.element.borderRadius());this.ctx.beginPath(),new h(this.ctx,e).draw(),this.ctx.closePath()}}drawFill(){const t=this.element.fillColor();t&&(this.ctx.fillStyle=new r(this.ctx,this.position(),this.shapeSize()).mixed(t),this.ctx.fill())}drawBorder(){const t=this.element.borderWidth();if(t>0){const e=new r(this.ctx,this.position(),this.element.size()).mixed(this.element.borderColor()??this.defaultBorderColor),i=this.element.fillColor();this.element.borderRadius()>0&&(this.ctx.lineJoin="round"),this.ctx.strokeStyle=e,this.ctx.lineWidth=i?2*t:t,this.ctx.stroke()}}shapePosition(){const t=this.position(),e=this.element.borderWidth();return{x:t.x+e,y:t.y+e}}shapeSize(){const t=this.element.size(),e=this.element.borderWidth();return{width:t.width-2*e,height:t.height-2*e}}position(){const t=this.element.positionLeftTop(),e=this.relativePosition();return{x:t.x+e.x,y:t.y+e.y}}defaultAnchor(){const t=this.element.size();return{x:t.width/2,y:t.height/2}}},g=class extends o{draw(){this.startDrawing(),this.makeShape(),this.drawStroke(),this.endDrawing()}makeShape(){this.ctx.beginPath();const t=new p(this.element.shapePoints(),!1).setSize(this.shapeSize()).setPosition(this.shapePosition()).setJointsRadius(this.element.jointsRadius());new h(this.ctx,t).draw()}drawStroke(){const t=new r(this.ctx,this.position(),this.element.boundingClientSize()).mixed(this.element.color()??this.defaultBorderColor);this.ctx.lineCap=this.element.lineCap(),this.ctx.strokeStyle=t,this.ctx.lineWidth=this.element.calculatedThickness(),this.ctx.stroke()}shapeSize(){const t=this.element.boundingClientSize(),e=this.element.calculatedThickness();return{width:t.width-e,height:t.height-e/2}}shapePosition(){const t=this.position(),e=this.element.calculatedThickness();return{x:t.x+e/2,y:t.y}}position(){const t=this.element.positionLeftTop(),e=this.relativePosition();return{x:t.x+e.x,y:t.y+e.y}}defaultAnchor(){const t=this.shapeSize();return{x:t.width/2,y:t.height/2}}},m=class{},f=class extends m{_color;constructor(t,e){super(),this.setColor(t,e)}type(){return n.SolidColor}color(){return this._color}hash(){return s.hash([this.color()])}setColor(t,e){const i=9===t.length?t.slice(-2):void 0,s=void 0!==e?this.alphaToHex(e):i??"ff";return this._color=t.slice(0,7)+s,this}alphaToHex(t){let e=Math.round(255*t).toString(16);return 1===e.length&&(e="0"+e),e}},_=class extends m{_colors;constructor(t){super(),this._colors=t}colors(){return this._colors}hash(){return s.hash([this.colors()])}},w=class{_position={x:0,y:0};_positionPoint;_scale={x:1,y:1};_opacity=1;_rotations=[];_degrees=0;_anchor;_shadow;hash(){return s.hash([this.position(),this.positionPoint(),this.scale(),this.opacity(),this.rotations(),this.degrees(),this.anchor(),this.shadow()])}position(){return s.resolvedValue(this._position)}positionLeftTop(){return s.positionLeftTop(this.position(),this.positionPoint(),this.boundingClientSize())}positionPoint(){return s.resolvedValue(this._positionPoint)??i.LeftTop}scale(){return s.resolvedValue(this._scale)}opacity(){return s.resolvedValue(this._opacity)}rotations(){return s.resolvedValue(this._rotations)}degrees(){return s.resolvedValue(this._degrees)}anchor(){return this._anchor?s.resolvedValue(this._anchor):void 0}shadow(){return this._shadow?s.resolvedValue(this._shadow):void 0}setPosition(t,e){return this._position=t,e&&(this._positionPoint=e),this}setScale(t){return this._scale=t,this}setOpacity(t){return this._opacity=t,this}setDegrees(t,e){return this._degrees=t,this._anchor=e,this}setRotations(t){return this._rotations=t,this}setShadow(t){return this._shadow=t,this}},y=class extends w{_size={width:1,height:1};_borderColor;_borderWidth=0;hash(){return s.hash([super.hash(),this.size(),this.borderColor(),this.borderWidth()])}size(){return s.resolvedValue(this._size)}borderColor(){return this._borderColor?s.resolvedValue(this._borderColor):void 0}borderWidth(){return s.resolvedValue(this._borderWidth)}setSize(t){return this._size=t,this}setBorder(t,e){return this._borderWidth=t,this._borderColor=e,this}},z=function(t){return t.Round="round",t.Square="square",t}({}),v=function(t){return t.Normal="normal",t.Bold="bold",t}({}),S=function(t){return t.Left="left",t.Center="center",t.Right="right",t}({}),b=function(t){return t.Top="top",t.Center="center",t.Bottom="bottom",t}({}),C=class{element;canvas;ctx;lastTextBox;lastTextBoxHash="";constructor(t){this.element=t,this.canvas=document.createElement("canvas"),this.ctx=this.canvas.getContext("2d")}textBox(){return this.elementChanged()&&(this.lastTextBox=void 0),this.lastTextBox||(this.lastTextBox=this.createTextBox()),this.lastTextBox}createTextBox(){const t={text:this.element.text(),lines:[],size:{width:1,height:1},position:{x:0,y:0}};return t.lines=this.textLines(),t.size=this.textBoxSize(t.lines),t.position=this.textBoxPosition(t.lines),t}textLines(){const t=this.element.bounds(),e=this.element.padding(),i=t?{width:t.width-e.left-e.right,height:t.height-e.top-e.bottom}:void 0,s=this.splitText().map(t=>({text:t,size:{width:this.textWidth(t),height:this.element.lineHeight()},position:{x:0,y:0}})).map((t,e,i)=>{const s=i[e-1];return t.position={x:0,y:s?s.position.y+s.size.height:0},t});return(void 0!==i?this.alignedLines(s,i):s).map(t=>(t.position.x+=e.left,t.position.y+=e.top,t))}alignedLines(t,e){const i=this.element.horizontalAlign(),s=this.element.verticalAlign(),o=t.reduce((t,e)=>t+e.size.height,0);return t.map(t=>(i===S.Center&&(t.position.x+=Math.round((e.width-t.size.width)/2)),i===S.Right&&(t.position.x+=e.width-t.size.width),s===b.Center&&(t.position.y+=Math.round((e.height-o)/2)),s===b.Bottom&&(t.position.y+=e.height-o),t))}textBoxSize(t){const e=this.element.padding(),i=this.element.bounds()??{width:1,height:1},s=Math.max(...t.map(t=>t.size.width));return{width:Math.max(s+e.left+e.right,i.width),height:Math.max(this.element.lineHeight()*t.length+e.top+e.bottom,i.height)}}textBoxPosition(t){const e=this.element.padding(),i=Math.min(...t.map(t=>t.position.x)),s=Math.min(...t.map(t=>t.position.y));return{x:i-e.left,y:s-e.top}}splitText(){const t=this.element.text().replaceAll("<BR>","<br>").split("<br>"),e=this.element.padding(),i=(this.element.bounds()??{width:999999,height:999999}).width-(e.left+e.right);let s=[];return t.forEach(t=>{t=t.trim();const e=this.textWidth(t);if(e>i){const o=Math.floor(i/(e/t.length))-3,h=t.split(" ");let n="";h.forEach((t,e)=>{n.length+t.length>o&&(s.push(n.trim()),n=""),n+=t+" ",e===h.length-1&&s.push(n.trim())}),s=s.map(t=>t.trim())}else s.push(t)}),s}textWidth(t){return this.ctx.font=this.element.font(),Math.ceil(this.ctx.measureText(t).width+this.element.strokeWidth())}elementChanged(){const t=[this.element.text(),this.element.bounds(),this.element.horizontalAlign(),this.element.verticalAlign(),this.element.padding(),this.element.lineHeight(),this.element.font(),this.element.strokeWidth()].join("|"),e=this.lastTextBoxHash!==t;return this.lastTextBoxHash=t,e}};t.Canvas=class{_canvas;_ctx;constructor(t){this._canvas=t??document.createElement("canvas"),this._ctx=this._canvas.getContext("2d")}nativeElement(){return this._canvas}ctx(){return this._ctx}size(){return{width:this._canvas.width,height:this._canvas.height}}setSize(t){this._canvas.width=t.width,this._canvas.height=t.height}draw(t,i){switch(t.type()){case e.Rectangle:new a(this.ctx(),t,i).draw();break;case e.Ellipse:new l(this.ctx(),t,i).draw();break;case e.Polygon:new x(this.ctx(),t,i).draw();break;case e.Line:new g(this.ctx(),t,i).draw();break;case e.Text:new d(this.ctx(),t,i).draw();break;case e.Image:new u(this.ctx(),t,i).draw()}}clear(){this.ctx().clearRect(0,0,this._canvas.width,this._canvas.height)}},t.ColorType=n,t.EllipseElement=class extends y{_fillColor;type(){return e.Ellipse}hash(){return s.hash([super.hash(),this.fillColor()])}fillColor(){return this._fillColor?s.resolvedValue(this._fillColor):void 0}boundingClientSize(){return this.size()}setFillColor(t){return this._fillColor=t,this}},t.FontWeight=v,t.GradientColor=class extends f{_offset;constructor(t,e,i=1){super(e,i),this.setOffset(t)}offset(){return this._offset}hash(){return s.hash([super.hash(),this.offset()])}setOffset(t){return this._offset=s.valueInRange(t,{min:0,max:1}),this}},t.HorizontalAlign=S,t.ImageElement=class extends y{_image;_fit=c.Fill;_borderRadius={topLeftOrDefault:0};_flipX;_flipY;_cropArea;type(){return e.Image}hash(){return s.hash([super.hash(),this.image()?.id,this.fit(),this.borderRadius(),this.flipX(),this.flipY(),this.cropArea()])}image(){return this._image?s.resolvedValue(this._image):void 0}fit(){return s.resolvedValue(this._fit)}borderRadius(){let t;const e=s.resolvedValue(this._borderRadius.topLeftOrDefault),i=this._borderRadius.topRight?s.resolvedValue(this._borderRadius.topRight):void 0,o=this._borderRadius.bottomRight?s.resolvedValue(this._borderRadius.bottomRight):void 0,h=this._borderRadius.bottomLeft?s.resolvedValue(this._borderRadius.bottomLeft):void 0;return t=void 0===i&&void 0===o&&void 0===h?{topLeft:e,topRight:e,bottomRight:e,bottomLeft:e}:{topLeft:e,topRight:i??0,bottomRight:o??0,bottomLeft:h??0},t}flipX(){return s.resolvedValue(this._flipX)??!1}flipY(){return s.resolvedValue(this._flipY)??!1}cropArea(){return s.resolvedValue(this._cropArea)??{}}boundingClientSize(){return this.size()}setImage(t){return this._image=t,this}setFit(t){return this._fit=t,this}setBorderRadius(t,e,i,s){return this._borderRadius={topLeftOrDefault:t,topRight:e,bottomRight:i,bottomLeft:s},this}setFlip(t){return this._flipX=t.horizontal??!1,this._flipY=t.vertical??!1,this}setCropArea(t){return this._cropArea=t,this}},t.ImageFit=c,t.LineCap=z,t.LineElement=class extends w{_shapePoints=[];_size;_keepRatio;_thickness=1;_jointsRadius=0;_lineCap=z.Square;_color;type(){return e.Line}hash(){return s.hash([super.hash(),this.shapePoints(),this.size(),this.keepRatio(),this.thickness(),this.jointsRadius(),this.lineCap(),this.color()])}shapePoints(){return s.resolvedValue(this._shapePoints)}size(){return this._size?s.resolvedValue(this._size):void 0}keepRatio(){return s.resolvedValue(this._keepRatio)??!1}thickness(){return s.resolvedValue(this._thickness)}calculatedThickness(){const t=this.size();let e=this.thickness();if(t){const i=s.shapeSize(this.shapePoints()),o=s.resizedSize(i,t,this.keepRatio()),h=o.width/s.valueNotLessThan(i.width,1),n=o.height/s.valueNotLessThan(i.height,1);e=s.valueNotLessThan(e*Math.min(h,n),1)}return e}jointsRadius(){return s.resolvedValue(this._jointsRadius)}lineCap(){return s.resolvedValue(this._lineCap)}color(){return this._color?s.resolvedValue(this._color):void 0}boundingClientSize(){let t=this.size();const e=this.calculatedThickness(),i=s.shapeSize(this.shapePoints());if(t){const o=s.resizedSize(i,t,this.keepRatio());t={width:o.width,height:o.height+e/2}}else t={width:i.width,height:s.valueNotLessThan(i.height+e/2,0)};return t}setShapePoints(t){return this._shapePoints=t,this}setSize(t,e){return this._size=t,e&&(this._keepRatio=e),this}setThickness(t){return this._thickness=t,this}setJointsRadius(t){return this._jointsRadius=t,this}setLineCap(t){return this._lineCap=t,this}setColor(t){return this._color=t,this}},t.LinearGradient=class extends _{_degrees=0;type(){return n.LinearGradient}degrees(){return this._degrees}setDegrees(t){return this._degrees=t,this}hash(){return s.hash([super.hash(),this.degrees()])}},t.PolygonElement=class extends y{_shapePoints=[];_borderRadius=0;_fillColor;type(){return e.Polygon}hash(){return s.hash([super.hash(),this.shapePoints(),this.borderRadius(),this.fillColor()])}shapePoints(){return s.resolvedValue(this._shapePoints)}borderRadius(){return s.resolvedValue(this._borderRadius)}fillColor(){return this._fillColor?s.resolvedValue(this._fillColor):void 0}boundingClientSize(){return this.size()}setShapePoints(t){return this._shapePoints=t,this}setBorderRadius(t){return this._borderRadius=t,this}setFillColor(t){return this._fillColor=t,this}},t.PositionPoint=i,t.RadialGradient=class extends _{_position;_radiusScale;type(){return n.RadialGradient}position(){return this._position}radiusScale(){return this._radiusScale??1}setPosition(t){return this._position=t,this}setRadiusScale(t){return this._radiusScale=t,this}hash(){return s.hash([super.hash(),this.position(),this.radiusScale()])}},t.RectangleElement=class extends y{_fillColor;_cutOutShapes=[];type(){return e.Rectangle}hash(){return s.hash([super.hash(),this.fillColor(),this.cutOutShapes()])}fillColor(){return this._fillColor?s.resolvedValue(this._fillColor):void 0}cutOutShapes(){return s.resolvedValue(this._cutOutShapes)}boundingClientSize(){return this.size()}setFillColor(t){return this._fillColor=t,this}setCutOutShapes(t){return this._cutOutShapes=t,this}},t.Shape=p,t.SolidColor=f,t.TextElement=class extends w{_text="";_bounds;_fontSize=12;_fontFamily="Tahoma";_fontWeight=v.Normal;_lineHeight;_fillColor;_strokeColor;_strokeWidth=0;_horizontalAlign=S.Left;_verticalAlign=b.Top;_padding={topOrVerticalOrDefault:0};_textSplitter=new C(this);type(){return e.Text}hash(){return s.hash([super.hash(),this.text(),this.bounds(),this.font(),this.lineHeight(),this.fillColor(),this.strokeColor(),this.strokeWidth(),this.horizontalAlign(),this.verticalAlign(),this.padding()])}text(){return s.resolvedValue(this._text).toString()}bounds(){return this._bounds?s.resolvedValue(this._bounds):void 0}fontSize(){return s.resolvedValue(this._fontSize)}fontFamily(){return s.resolvedValue(this._fontFamily)}fontWeight(){return s.resolvedValue(this._fontWeight)}lineHeight(){return s.resolvedValue(this._lineHeight??Math.round(1.2*this.fontSize()))}fillColor(){return this._fillColor?s.resolvedValue(this._fillColor):void 0}strokeColor(){return this._strokeColor?s.resolvedValue(this._strokeColor):void 0}strokeWidth(){return s.resolvedValue(this._strokeWidth)}horizontalAlign(){return s.resolvedValue(this._horizontalAlign)}verticalAlign(){return s.resolvedValue(this._verticalAlign)}padding(){let t;const e=s.resolvedValue(this._padding.topOrVerticalOrDefault),i=this._padding.rightOrHorizontal?s.resolvedValue(this._padding.rightOrHorizontal):void 0,o=this._padding.bottom?s.resolvedValue(this._padding.bottom):void 0,h=this._padding.left?s.resolvedValue(this._padding.left):void 0;return t=void 0===i?{top:e,right:e,bottom:e,left:e}:void 0===o&&void 0===h?{top:e,right:i,bottom:e,left:i}:{top:e,right:i,bottom:o??0,left:h??0},t}font(){const t=[this.fontSize()+"px",this.fontFamily()],e=this.fontWeight();return e&&t.unshift(e),t.join(" ")}textBox(){return this._textSplitter.textBox()}boundingClientSize(){return this.textBox().size}setText(t){return this._text=t,this}setBounds(t){return this._bounds=t,this}setFontSize(t){return this._fontSize=t,this}setFontFamily(t){return this._fontFamily=t,this}setFontWeight(t){return this._fontWeight=t,this}setLineHeight(t){return this._lineHeight=t,this}setFillColor(t){return this._fillColor=t,this}setStroke(t,e){return this._strokeWidth=t,this._strokeColor=e,this}setHorizontalAlign(t){return this._horizontalAlign=t,this}setVerticalAlign(t){return this._verticalAlign=t,this}setPadding(t,e,i,s){return this._padding={topOrVerticalOrDefault:t,rightOrHorizontal:e,bottom:i,left:s},this}},t.VerticalAlign=b}); | ||
| (function(global, factory) { | ||
| typeof exports === "object" && typeof module !== "undefined" ? factory(exports) : typeof define === "function" && define.amd ? define(["exports"], factory) : (global = typeof globalThis !== "undefined" ? globalThis : global || self, factory(global.Canvas = {})); | ||
| })(this, function(exports) { | ||
| Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" }); | ||
| //#region src/enums.ts | ||
| var ElementType = /* @__PURE__ */ function(ElementType) { | ||
| ElementType["Rectangle"] = "rectangle"; | ||
| ElementType["Ellipse"] = "ellipse"; | ||
| ElementType["Polygon"] = "polygon"; | ||
| ElementType["Line"] = "line"; | ||
| ElementType["Text"] = "text"; | ||
| ElementType["Image"] = "image"; | ||
| return ElementType; | ||
| }({}); | ||
| var ColorType = /* @__PURE__ */ function(ColorType) { | ||
| ColorType["SolidColor"] = "solid-color"; | ||
| ColorType["LinearGradient"] = "linear-gradient"; | ||
| ColorType["RadialGradient"] = "radial-gradient"; | ||
| return ColorType; | ||
| }({}); | ||
| var FontWeight = /* @__PURE__ */ function(FontWeight) { | ||
| FontWeight["Normal"] = "normal"; | ||
| FontWeight["Bold"] = "bold"; | ||
| return FontWeight; | ||
| }({}); | ||
| var HorizontalAlign = /* @__PURE__ */ function(HorizontalAlign) { | ||
| HorizontalAlign["Left"] = "left"; | ||
| HorizontalAlign["Center"] = "center"; | ||
| HorizontalAlign["Right"] = "right"; | ||
| return HorizontalAlign; | ||
| }({}); | ||
| var ImageFit = /* @__PURE__ */ function(ImageFit) { | ||
| ImageFit["Fill"] = "fill"; | ||
| ImageFit["Cover"] = "cover"; | ||
| return ImageFit; | ||
| }({}); | ||
| var LineCap = /* @__PURE__ */ function(LineCap) { | ||
| LineCap["Round"] = "round"; | ||
| LineCap["Square"] = "square"; | ||
| return LineCap; | ||
| }({}); | ||
| var PositionPoint = /* @__PURE__ */ function(PositionPoint) { | ||
| PositionPoint["LeftTop"] = "left-top"; | ||
| PositionPoint["RightTop"] = "right-top"; | ||
| PositionPoint["LeftBottom"] = "left-bottom"; | ||
| PositionPoint["RightBottom"] = "right-bottom"; | ||
| PositionPoint["Center"] = "center"; | ||
| return PositionPoint; | ||
| }({}); | ||
| var VerticalAlign = /* @__PURE__ */ function(VerticalAlign) { | ||
| VerticalAlign["Top"] = "top"; | ||
| VerticalAlign["Center"] = "center"; | ||
| VerticalAlign["Bottom"] = "bottom"; | ||
| return VerticalAlign; | ||
| }({}); | ||
| //#endregion | ||
| //#region src/libraries/helper/helper.ts | ||
| var Helper = class Helper { | ||
| static valueInRange(value, range) { | ||
| if (value < range.min) value = range.min; | ||
| if (value > range.max) value = range.max; | ||
| return value; | ||
| } | ||
| static valueNotGreaterThan(value, max) { | ||
| if (value > max) value = max; | ||
| return value; | ||
| } | ||
| static valueNotLessThan(value, min) { | ||
| if (value < min) value = min; | ||
| return value; | ||
| } | ||
| static relativePositionValue(value, size, maxValue) { | ||
| const extractedValue = Helper.extractedValue(value); | ||
| let position; | ||
| switch (extractedValue.unit) { | ||
| case "%": | ||
| position = (maxValue - size) / 100 * extractedValue.value; | ||
| break; | ||
| default: position = Number(value); | ||
| } | ||
| return position; | ||
| } | ||
| static extractedValue(value) { | ||
| let result; | ||
| if (typeof value === "string") { | ||
| const match = value.match(/^(\d+)(\D+)$/) ?? []; | ||
| if (match.length > 2) result = { | ||
| value: parseFloat(match[1] ?? "0"), | ||
| unit: match[2] ?? "px" | ||
| }; | ||
| else result = { | ||
| value: Number(value), | ||
| unit: "px" | ||
| }; | ||
| } else result = { | ||
| value, | ||
| unit: "px" | ||
| }; | ||
| return result; | ||
| } | ||
| static degreesToRadians(degrees) { | ||
| return degrees / 180 * Math.PI; | ||
| } | ||
| static distance(firstPoint, secondPoint) { | ||
| return Math.sqrt(Math.pow(Math.abs(secondPoint.x - firstPoint.x), 2) + Math.pow(Math.abs(secondPoint.y - firstPoint.y), 2)); | ||
| } | ||
| static positionBetweenPoints(firstPoint, secondPoint, moveBy) { | ||
| const fraction = moveBy / Helper.distance(firstPoint, secondPoint); | ||
| return { | ||
| x: firstPoint.x + fraction * (secondPoint.x - firstPoint.x), | ||
| y: firstPoint.y + fraction * (secondPoint.y - firstPoint.y) | ||
| }; | ||
| } | ||
| static shapeSize(points) { | ||
| const leftTopCorner = { | ||
| x: Math.min(...points.map((point) => point.x)), | ||
| y: Math.min(...points.map((point) => point.y)) | ||
| }; | ||
| const rightBottomCorner = { | ||
| x: Math.max(...points.map((point) => point.x)), | ||
| y: Math.max(...points.map((point) => point.y)) | ||
| }; | ||
| return { | ||
| width: Math.floor(rightBottomCorner.x - leftTopCorner.x), | ||
| height: Math.floor(rightBottomCorner.y - leftTopCorner.y) | ||
| }; | ||
| } | ||
| static positionLeftTop(position, point, size) { | ||
| const positionLeftTop = { | ||
| x: position.x, | ||
| y: position.y | ||
| }; | ||
| switch (point) { | ||
| case PositionPoint.RightTop: | ||
| positionLeftTop.x -= size.width; | ||
| break; | ||
| case PositionPoint.RightBottom: | ||
| positionLeftTop.x -= size.width; | ||
| positionLeftTop.y -= size.height; | ||
| break; | ||
| case PositionPoint.LeftBottom: | ||
| positionLeftTop.y -= size.height; | ||
| break; | ||
| case PositionPoint.Center: | ||
| positionLeftTop.x -= size.width / 2; | ||
| positionLeftTop.y -= size.height / 2; | ||
| break; | ||
| } | ||
| return positionLeftTop; | ||
| } | ||
| static positionCenter(position, point, size) { | ||
| const positionLeftTop = Helper.positionLeftTop(position, point, size); | ||
| positionLeftTop.x += size.width / 2; | ||
| positionLeftTop.y += size.height / 2; | ||
| return positionLeftTop; | ||
| } | ||
| static resizedSize(originalSize, desiredSize, keepRatio) { | ||
| const result = { | ||
| width: desiredSize.width, | ||
| height: desiredSize.height | ||
| }; | ||
| if (keepRatio) { | ||
| const selectedVariant = [{ | ||
| width: originalSize.width * (desiredSize.height / originalSize.height), | ||
| height: desiredSize.height | ||
| }, { | ||
| width: desiredSize.width, | ||
| height: originalSize.height * (desiredSize.width / originalSize.width) | ||
| }].find((variant) => { | ||
| return variant.width <= desiredSize.width && variant.height <= desiredSize.height; | ||
| }); | ||
| if (selectedVariant) { | ||
| result.width = selectedVariant.width; | ||
| result.height = selectedVariant.height; | ||
| } | ||
| } | ||
| return result; | ||
| } | ||
| static resolvedValue(attribute) { | ||
| let result; | ||
| if (attribute) if (typeof attribute === "function") result = attribute(); | ||
| else result = attribute; | ||
| else result = attribute; | ||
| return result; | ||
| } | ||
| static isObject(value) { | ||
| return typeof value === "object" && value.constructor === Object && !Array.isArray(value); | ||
| } | ||
| static hasMethod(object, methodName) { | ||
| return !!object[methodName] && typeof object[methodName] === "function"; | ||
| } | ||
| static hash(attributes) { | ||
| return attributes.map((attribute) => { | ||
| let result; | ||
| if (attribute === void 0 || attribute === null) result = "undefined"; | ||
| else if (Helper.isObject(attribute)) result = JSON.stringify(attribute); | ||
| else if (Array.isArray(attribute)) result = "[" + Helper.hash(attribute) + "]"; | ||
| else if (Helper.hasMethod(attribute, "hash")) result = attribute.hash(); | ||
| else result = attribute.toString(); | ||
| return result; | ||
| }).join("|"); | ||
| } | ||
| }; | ||
| //#endregion | ||
| //#region src/libraries/artists/element-artist.ts | ||
| var ElementArtist = class { | ||
| ctx; | ||
| element; | ||
| relativeProps; | ||
| defaultBorderColor = "#000000"; | ||
| constructor(ctx, element, relativeProps) { | ||
| this.ctx = ctx; | ||
| this.element = element; | ||
| this.relativeProps = relativeProps ?? {}; | ||
| } | ||
| relativePosition() { | ||
| return this.relativeProps.position ?? { | ||
| x: 0, | ||
| y: 0 | ||
| }; | ||
| } | ||
| relativeScale() { | ||
| return this.relativeProps.scale ?? { | ||
| x: 1, | ||
| y: 1 | ||
| }; | ||
| } | ||
| rotations() { | ||
| return [...this.relativeProps.rotations ?? [], ...this.element.rotations()]; | ||
| } | ||
| relativeOpacity() { | ||
| return this.relativeProps.opacity ?? 1; | ||
| } | ||
| scale() { | ||
| const elementScale = this.element.scale(); | ||
| const relativeScale = this.relativeScale(); | ||
| return { | ||
| x: elementScale.x * relativeScale.x, | ||
| y: elementScale.y * relativeScale.y | ||
| }; | ||
| } | ||
| /** @deprecated Use Rotations instead */ | ||
| degrees() { | ||
| return this.element.degrees() ?? 0; | ||
| } | ||
| /** @deprecated Use Rotations instead */ | ||
| anchor() { | ||
| return this.element.anchor() ?? this.defaultAnchor(); | ||
| } | ||
| opacity() { | ||
| return this.element.opacity() * this.relativeOpacity(); | ||
| } | ||
| startDrawing() { | ||
| this.ctx.save(); | ||
| const scale = this.scale(); | ||
| const opacity = this.opacity(); | ||
| const degrees = this.degrees(); | ||
| const rotations = this.rotations(); | ||
| const shadow = this.element.shadow(); | ||
| if (scale.x !== 1 || scale.y !== 1) this.ctx.scale(scale.x, scale.y); | ||
| if (opacity < 1) this.ctx.globalAlpha = opacity; | ||
| if (degrees !== 0) this.rotate({ | ||
| degrees, | ||
| pivot: this.anchor() | ||
| }); | ||
| if (rotations.length > 0) rotations.forEach((rotation) => { | ||
| if (rotation.degrees !== 0) this.rotate(rotation); | ||
| }); | ||
| if (shadow && shadow.blur > 0) { | ||
| this.ctx.shadowColor = shadow.color; | ||
| this.ctx.shadowBlur = shadow.blur; | ||
| this.ctx.shadowOffsetX = shadow.x; | ||
| this.ctx.shadowOffsetY = shadow.y; | ||
| } | ||
| } | ||
| endDrawing() { | ||
| this.ctx.restore(); | ||
| } | ||
| rotate(rotation) { | ||
| const position = this.position(); | ||
| const pivot = rotation.pivot ?? this.defaultAnchor(); | ||
| const absolutePivot = { | ||
| x: pivot.x + position.x, | ||
| y: pivot.y + position.y | ||
| }; | ||
| this.ctx.translate(absolutePivot.x, absolutePivot.y); | ||
| this.ctx.rotate(Helper.degreesToRadians(rotation.degrees)); | ||
| this.ctx.translate(-absolutePivot.x, -absolutePivot.y); | ||
| } | ||
| }; | ||
| //#endregion | ||
| //#region src/libraries/shapes/shaper.ts | ||
| var Shaper = class { | ||
| _originalSize; | ||
| _sizeRatio; | ||
| _shapePosition; | ||
| constructor(ctx, shape) { | ||
| this.ctx = ctx; | ||
| this.shape = shape; | ||
| } | ||
| draw() { | ||
| this.prepare(); | ||
| this.ctx.save(); | ||
| this.rotateContext(); | ||
| if (this.shape.isCloseShape()) this.makeClosedShape(); | ||
| else this.makeOpenedShape(); | ||
| this.ctx.restore(); | ||
| } | ||
| prepare() { | ||
| this._originalSize = Helper.shapeSize(this.shape.points()); | ||
| this._sizeRatio = this.sizeRatio(); | ||
| this._shapePosition = this.shapePosition(); | ||
| } | ||
| rotateContext() { | ||
| const degrees = this.shape.degrees(); | ||
| if (degrees !== 0) this.rotate({ degrees }); | ||
| this.shape.rotations().forEach((rotation) => { | ||
| this.rotate(rotation); | ||
| }); | ||
| } | ||
| makeClosedShape() { | ||
| const points = this.shape.points(); | ||
| if (points.length > 2) points.forEach((point, index) => { | ||
| const isFirstPoint = index === 0; | ||
| const isLastPoint = index === points.length - 1; | ||
| const nextPoint = points[index + 1] ?? points[0]; | ||
| const lineLength = Helper.distance(point, nextPoint); | ||
| const currentRadius = this.radius(lineLength); | ||
| const currentStart = Helper.positionBetweenPoints(point, nextPoint, currentRadius); | ||
| const currentEnd = Helper.positionBetweenPoints(point, nextPoint, lineLength - currentRadius); | ||
| if (isFirstPoint) this.moveToPoint(currentStart); | ||
| this.lineToPoint(currentEnd); | ||
| if (currentRadius > 0) { | ||
| const afterNextPoint = points[index + 2] ?? (isLastPoint ? points[1] : points[0]); | ||
| this.quadraticCurveToPoint(nextPoint, afterNextPoint); | ||
| } | ||
| }); | ||
| } | ||
| makeOpenedShape() { | ||
| const points = this.shape.points(); | ||
| if (points.length > 1) points.forEach((point, index) => { | ||
| const isFirstPoint = index === 0; | ||
| const isLastPoint = index === points.length - 1; | ||
| const isBeforeLastPoint = index === points.length - 2; | ||
| if (!isLastPoint) { | ||
| const nextPoint = points[index + 1]; | ||
| const currentLineLength = nextPoint ? Helper.distance(point, nextPoint) : 0; | ||
| const currentRadius = this.radius(currentLineLength); | ||
| const currentStart = isFirstPoint ? point : Helper.positionBetweenPoints(point, nextPoint, currentRadius); | ||
| const currentEnd = isBeforeLastPoint ? nextPoint : Helper.positionBetweenPoints(point, nextPoint, currentLineLength - currentRadius); | ||
| if (isFirstPoint) this.moveToPoint(currentStart); | ||
| this.lineToPoint(currentEnd); | ||
| if (!isBeforeLastPoint && currentRadius > 0) this.quadraticCurveToPoint(nextPoint, points[index + 2]); | ||
| } | ||
| }); | ||
| } | ||
| radius(lineLength) { | ||
| const averageRatio = Math.min(this._sizeRatio.width, this._sizeRatio.height); | ||
| const radius = Math.floor(this.shape.jointsRadius() / averageRatio); | ||
| const maxRadius = Math.floor(lineLength * .55); | ||
| return radius > maxRadius ? maxRadius : radius; | ||
| } | ||
| moveToPoint(point) { | ||
| this.ctx.moveTo(this._shapePosition.x + point.x * this._sizeRatio.width, this._shapePosition.y + point.y * this._sizeRatio.height); | ||
| } | ||
| lineToPoint(point) { | ||
| this.ctx.lineTo(this._shapePosition.x + point.x * this._sizeRatio.width, this._shapePosition.y + point.y * this._sizeRatio.height); | ||
| } | ||
| quadraticCurveToPoint(point, nextPoint) { | ||
| const nextLineLength = Helper.distance(point, nextPoint); | ||
| const nextRadius = this.radius(nextLineLength); | ||
| const nextStart = Helper.positionBetweenPoints(point, nextPoint, nextRadius); | ||
| this.ctx.quadraticCurveTo(this._shapePosition.x + point.x * this._sizeRatio.width, this._shapePosition.y + point.y * this._sizeRatio.height, this._shapePosition.x + nextStart.x * this._sizeRatio.width, this._shapePosition.y + nextStart.y * this._sizeRatio.height); | ||
| } | ||
| shapePosition() { | ||
| return Helper.positionLeftTop(this.shape.position(), this.shape.positionPoint(), this.shapeSize()); | ||
| } | ||
| shapeSize() { | ||
| return this.shape.size() ?? this._originalSize; | ||
| } | ||
| sizeRatio() { | ||
| const size = this.shapeSize(); | ||
| return { | ||
| width: size.width / Helper.valueNotLessThan(this._originalSize.width, 1), | ||
| height: size.height / Helper.valueNotLessThan(this._originalSize.height, 1) | ||
| }; | ||
| } | ||
| rotate(rotation) { | ||
| const position = this.shapePosition(); | ||
| const anchor = rotation.pivot ?? this.defaultAnchor(); | ||
| anchor.x += position.x; | ||
| anchor.y += position.y; | ||
| this.ctx.translate(anchor.x, anchor.y); | ||
| this.ctx.rotate(Helper.degreesToRadians(rotation.degrees)); | ||
| this.ctx.translate(-anchor.x, -anchor.y); | ||
| } | ||
| defaultAnchor() { | ||
| const size = this.shapeSize(); | ||
| return { | ||
| x: size.width / 2, | ||
| y: size.height / 2 | ||
| }; | ||
| } | ||
| }; | ||
| //#endregion | ||
| //#region src/libraries/colors/color-mixer.ts | ||
| var ColorMixer = class { | ||
| constructor(ctx, elementPosition, elementSize) { | ||
| this.ctx = ctx; | ||
| this.elementPosition = elementPosition; | ||
| this.elementSize = elementSize; | ||
| } | ||
| mixed(color) { | ||
| let result = ""; | ||
| if (typeof color === "string") result = color; | ||
| else switch (color.type()) { | ||
| case ColorType.SolidColor: | ||
| result = color.color(); | ||
| break; | ||
| case ColorType.LinearGradient: | ||
| result = this.linearGradient(color); | ||
| break; | ||
| case ColorType.RadialGradient: | ||
| result = this.radialGradient(color); | ||
| break; | ||
| } | ||
| return result; | ||
| } | ||
| linearGradient(gradient) { | ||
| const angleRadians = Helper.degreesToRadians(gradient.degrees()); | ||
| const centerPosition = { | ||
| x: this.elementPosition.x + this.elementSize.width / 2, | ||
| y: this.elementPosition.y + this.elementSize.height / 2 | ||
| }; | ||
| const halfLength = Math.sqrt(this.elementSize.width ** 2 + this.elementSize.height ** 2) / 2; | ||
| const offset = { | ||
| x: Math.cos(angleRadians) * halfLength, | ||
| y: Math.sin(angleRadians) * halfLength | ||
| }; | ||
| const startPosition = { | ||
| x: centerPosition.x - offset.x, | ||
| y: centerPosition.y - offset.y | ||
| }; | ||
| const endPosition = { | ||
| x: centerPosition.x + offset.x, | ||
| y: centerPosition.y + offset.y | ||
| }; | ||
| const canvasGradient = this.ctx.createLinearGradient(startPosition.x, startPosition.y, endPosition.x, endPosition.y); | ||
| this.appendGradientColors(canvasGradient, gradient.colors()); | ||
| return canvasGradient; | ||
| } | ||
| radialGradient(gradient) { | ||
| const localPosition = gradient.position() ?? { | ||
| x: this.elementSize.width / 2, | ||
| y: this.elementSize.height / 2 | ||
| }; | ||
| const centerPosition = { | ||
| x: this.elementPosition.x + localPosition.x, | ||
| y: this.elementPosition.y + localPosition.y | ||
| }; | ||
| const radius = Math.sqrt(this.elementSize.width * this.elementSize.width + this.elementSize.height * this.elementSize.height) / 2; | ||
| const canvasGradient = this.ctx.createRadialGradient(centerPosition.x, centerPosition.y, 0, centerPosition.x, centerPosition.y, radius * gradient.radiusScale()); | ||
| this.appendGradientColors(canvasGradient, gradient.colors()); | ||
| return canvasGradient; | ||
| } | ||
| appendGradientColors(gradient, colors) { | ||
| colors.forEach((color) => gradient.addColorStop(color.offset(), color.color())); | ||
| } | ||
| }; | ||
| //#endregion | ||
| //#region src/libraries/artists/rectangle-artist.ts | ||
| var RectangleArtist = class extends ElementArtist { | ||
| draw() { | ||
| this.startDrawing(); | ||
| this.makeShape(); | ||
| this.drawFill(); | ||
| this.drawBorder(); | ||
| this.endDrawing(); | ||
| } | ||
| makeShape() { | ||
| const borderWidth = this.element.borderWidth(); | ||
| const position = this.position(); | ||
| const size = this.element.size(); | ||
| const shapes = this.element.cutOutShapes(); | ||
| const drawPosition = { | ||
| x: position.x + borderWidth / 2, | ||
| y: position.y + borderWidth / 2 | ||
| }; | ||
| const drawSize = { | ||
| width: size.width - borderWidth, | ||
| height: size.height - borderWidth | ||
| }; | ||
| this.ctx.beginPath(); | ||
| if (shapes.length > 0) { | ||
| this.ctx.translate(drawPosition.x, drawPosition.y); | ||
| shapes.forEach((shape) => { | ||
| new Shaper(this.ctx, shape).draw(); | ||
| }); | ||
| this.ctx.translate(-drawPosition.x, -drawPosition.y); | ||
| drawPosition.x += drawSize.width; | ||
| drawSize.width *= -1; | ||
| } | ||
| this.ctx.rect(drawPosition.x, drawPosition.y, drawSize.width, drawSize.height); | ||
| this.ctx.closePath(); | ||
| } | ||
| drawFill() { | ||
| const color = this.element.fillColor(); | ||
| if (color) { | ||
| this.ctx.fillStyle = new ColorMixer(this.ctx, this.position(), this.element.size()).mixed(color); | ||
| this.ctx.fill(); | ||
| } | ||
| } | ||
| drawBorder() { | ||
| const borderWidth = this.element.borderWidth(); | ||
| if (borderWidth > 0) { | ||
| this.ctx.strokeStyle = new ColorMixer(this.ctx, this.position(), this.element.size()).mixed(this.element.borderColor() ?? this.defaultBorderColor); | ||
| this.ctx.lineWidth = borderWidth; | ||
| this.ctx.stroke(); | ||
| } | ||
| } | ||
| position() { | ||
| const elementPosition = this.element.positionLeftTop(); | ||
| const relativePosition = this.relativePosition(); | ||
| return { | ||
| x: elementPosition.x + relativePosition.x, | ||
| y: elementPosition.y + relativePosition.y | ||
| }; | ||
| } | ||
| defaultAnchor() { | ||
| const size = this.element.size(); | ||
| return { | ||
| x: size.width / 2, | ||
| y: size.height / 2 | ||
| }; | ||
| } | ||
| }; | ||
| //#endregion | ||
| //#region src/libraries/artists/ellipse-artist.ts | ||
| var EllipseArtist = class extends ElementArtist { | ||
| draw() { | ||
| this.startDrawing(); | ||
| this.drawFill(); | ||
| this.drawBorder(); | ||
| this.endDrawing(); | ||
| } | ||
| drawFill() { | ||
| const color = this.element.fillColor(); | ||
| if (color) { | ||
| const borderWidth = this.element.borderWidth(); | ||
| const position = this.position(); | ||
| const size = this.element.size(); | ||
| this.ctx.beginPath(); | ||
| this.ctx.ellipse(position.x, position.y, Helper.valueNotLessThan(size.width / 2 - borderWidth, 1), Helper.valueNotLessThan(size.height / 2 - borderWidth, 1), 0, 0, Math.PI * 2); | ||
| this.ctx.closePath(); | ||
| this.ctx.fillStyle = new ColorMixer(this.ctx, this.element.positionLeftTop(), this.element.size()).mixed(color); | ||
| this.ctx.fill(); | ||
| } | ||
| } | ||
| drawBorder() { | ||
| const borderWidth = this.element.borderWidth(); | ||
| if (borderWidth > 0) { | ||
| const position = this.position(); | ||
| const size = this.element.size(); | ||
| this.ctx.beginPath(); | ||
| this.ctx.ellipse(position.x, position.y, Helper.valueNotLessThan(size.width / 2 - borderWidth / 2, 1), Helper.valueNotLessThan(size.height / 2 - borderWidth / 2, 1), 0, 0, Math.PI * 2); | ||
| this.ctx.closePath(); | ||
| this.ctx.strokeStyle = new ColorMixer(this.ctx, this.element.positionLeftTop(), this.element.size()).mixed(this.element.borderColor() ?? this.defaultBorderColor); | ||
| this.ctx.lineWidth = borderWidth; | ||
| this.ctx.stroke(); | ||
| } | ||
| } | ||
| position() { | ||
| const elementPosition = Helper.positionCenter(this.element.position(), this.element.positionPoint(), this.element.boundingClientSize()); | ||
| const relativePosition = this.relativePosition(); | ||
| return { | ||
| x: elementPosition.x + relativePosition.x, | ||
| y: elementPosition.y + relativePosition.y | ||
| }; | ||
| } | ||
| defaultAnchor() { | ||
| return { | ||
| x: 0, | ||
| y: 0 | ||
| }; | ||
| } | ||
| }; | ||
| //#endregion | ||
| //#region src/libraries/artists/text-artist.ts | ||
| var TextArtist = class extends ElementArtist { | ||
| draw() { | ||
| this.prepare(); | ||
| this.startDrawing(); | ||
| this.drawStroke(); | ||
| this.drawFill(); | ||
| this.endDrawing(); | ||
| } | ||
| prepare() { | ||
| this.ctx.font = this.element.font(); | ||
| this.ctx.textBaseline = "alphabetic"; | ||
| } | ||
| drawFill() { | ||
| const color = this.element.fillColor(); | ||
| if (color) { | ||
| const textBox = this.element.textBox(); | ||
| const position = this.position(); | ||
| this.ctx.fillStyle = new ColorMixer(this.ctx, this.position(), textBox.size).mixed(color); | ||
| textBox.lines.forEach((textLine) => { | ||
| this.ctx.fillText(textLine.text, position.x + textLine.position.x, Math.round(position.y + textLine.position.y + textLine.size.height * .75)); | ||
| }); | ||
| } | ||
| } | ||
| drawStroke() { | ||
| const width = this.element.strokeWidth(); | ||
| if (width > 0) { | ||
| const textBox = this.element.textBox(); | ||
| const position = this.position(); | ||
| const fillColor = this.element.fillColor(); | ||
| this.ctx.strokeStyle = new ColorMixer(this.ctx, this.position(), textBox.size).mixed(this.element.strokeColor() ?? this.defaultBorderColor); | ||
| this.ctx.lineWidth = fillColor ? width * 2 : width; | ||
| textBox.lines.forEach((textLine) => { | ||
| this.ctx.strokeText(textLine.text, position.x + textLine.position.x, Math.round(position.y + textLine.position.y + textLine.size.height * .75)); | ||
| }); | ||
| } | ||
| } | ||
| position() { | ||
| const elementPosition = this.element.positionLeftTop(); | ||
| const relativePosition = this.relativePosition(); | ||
| return { | ||
| x: elementPosition.x + relativePosition.x, | ||
| y: elementPosition.y + relativePosition.y | ||
| }; | ||
| } | ||
| defaultAnchor() { | ||
| const textBox = this.element.textBox(); | ||
| return { | ||
| x: textBox.size.width / 2, | ||
| y: textBox.size.height / 2 | ||
| }; | ||
| } | ||
| }; | ||
| //#endregion | ||
| //#region src/libraries/artists/image-artist.ts | ||
| var ImageArtist = class extends ElementArtist { | ||
| draw() { | ||
| this.startDrawing(); | ||
| this.drawBorder(); | ||
| this.drawImage(); | ||
| this.endDrawing(); | ||
| } | ||
| drawImage() { | ||
| const image = this.element.image(); | ||
| if (image) { | ||
| const source = this.sourceDimensions(); | ||
| const destination = this.flippedDimensions(this.elementDimensions()); | ||
| this.ctx.drawImage(image, source.position.x, source.position.y, source.size.width, source.size.height, destination.position.x, destination.position.y, destination.size.width, destination.size.height); | ||
| } | ||
| } | ||
| drawBorder() { | ||
| const width = this.element.borderWidth(); | ||
| const color = new ColorMixer(this.ctx, this.position(), this.element.size()).mixed(this.element.borderColor() ?? this.defaultBorderColor); | ||
| const dimensions = this.elementDimensions(); | ||
| this.ctx.strokeStyle = color; | ||
| this.ctx.lineWidth = width * 2; | ||
| if (this.isRadiusSet()) { | ||
| this.makeShape(dimensions); | ||
| this.ctx.lineJoin = "round"; | ||
| this.ctx.fillStyle = "#ffffff"; | ||
| this.ctx.fill(); | ||
| if (width > 0) { | ||
| this.ctx.strokeStyle = color; | ||
| this.ctx.lineWidth = width * 2; | ||
| this.ctx.stroke(); | ||
| } | ||
| this.ctx.clip(); | ||
| } else if (width > 0) { | ||
| this.ctx.strokeStyle = color; | ||
| this.ctx.lineWidth = width * 2; | ||
| this.ctx.strokeRect(dimensions.position.x, dimensions.position.y, dimensions.size.width, dimensions.size.height); | ||
| } | ||
| } | ||
| sourceDimensions() { | ||
| const image = this.element.image(); | ||
| const cropArea = this.element.cropArea(); | ||
| const result = { | ||
| position: { | ||
| x: 0, | ||
| y: 0 | ||
| }, | ||
| size: cropArea.size ?? { | ||
| width: image.width, | ||
| height: image.height | ||
| } | ||
| }; | ||
| switch (this.element.fit()) { | ||
| case ImageFit.Fill: | ||
| if (cropArea.position) { | ||
| result.position.x = cropArea.position.x; | ||
| result.position.y = cropArea.position.y; | ||
| } | ||
| break; | ||
| case ImageFit.Cover: { | ||
| const { size } = this.elementDimensions(); | ||
| if (size.width / size.height < result.size.width / result.size.height) result.size.width = result.size.height / (size.height / size.width); | ||
| else result.size.height = result.size.width / (size.width / size.height); | ||
| if (!cropArea.position) { | ||
| result.position.x = Helper.relativePositionValue("50%", result.size.width, image.width); | ||
| result.position.y = Helper.relativePositionValue("50%", result.size.height, image.height); | ||
| } else { | ||
| result.position.x = cropArea.position.x; | ||
| result.position.y = cropArea.position.y; | ||
| } | ||
| break; | ||
| } | ||
| } | ||
| return result; | ||
| } | ||
| elementDimensions() { | ||
| const borderWidth = this.element.borderWidth(); | ||
| const position = this.position(); | ||
| const size = this.element.size(); | ||
| return { | ||
| position: { | ||
| x: position.x + borderWidth, | ||
| y: position.y + borderWidth | ||
| }, | ||
| size: { | ||
| width: size.width - borderWidth * 2, | ||
| height: size.height - borderWidth * 2 | ||
| } | ||
| }; | ||
| } | ||
| flippedDimensions(dimensions) { | ||
| const flipX = this.element.flipX(); | ||
| const flipY = this.element.flipY(); | ||
| if (flipX || flipY) { | ||
| let scaleX = 1; | ||
| let scaleY = 1; | ||
| if (flipX) { | ||
| scaleX = -1; | ||
| dimensions.position.x = -(+dimensions.position.x + dimensions.size.width); | ||
| } | ||
| if (flipY) { | ||
| scaleY = -1; | ||
| dimensions.position.y = -(+dimensions.position.y + dimensions.size.height); | ||
| } | ||
| this.ctx.scale(scaleX, scaleY); | ||
| } | ||
| return dimensions; | ||
| } | ||
| makeShape(dimensions) { | ||
| const radius = this.borderRadius(); | ||
| this.ctx.lineJoin = "round"; | ||
| this.ctx.beginPath(); | ||
| this.ctx.moveTo(dimensions.position.x + radius.topLeft, dimensions.position.y); | ||
| this.ctx.lineTo(dimensions.position.x + dimensions.size.width - radius.topRight, dimensions.position.y); | ||
| this.ctx.quadraticCurveTo(dimensions.position.x + dimensions.size.width, dimensions.position.y, dimensions.position.x + dimensions.size.width, dimensions.position.y + radius.topRight); | ||
| this.ctx.lineTo(dimensions.position.x + dimensions.size.width, dimensions.position.y + dimensions.size.height - radius.bottomRight); | ||
| this.ctx.quadraticCurveTo(dimensions.position.x + dimensions.size.width, dimensions.position.y + dimensions.size.height, dimensions.position.x + dimensions.size.width - radius.bottomRight, dimensions.position.y + dimensions.size.height); | ||
| this.ctx.lineTo(dimensions.position.x + radius.bottomLeft, dimensions.position.y + dimensions.size.height); | ||
| this.ctx.quadraticCurveTo(dimensions.position.x, dimensions.position.y + dimensions.size.height, dimensions.position.x, dimensions.position.y + dimensions.size.height - radius.bottomLeft); | ||
| this.ctx.lineTo(dimensions.position.x, dimensions.position.y + radius.topLeft); | ||
| this.ctx.quadraticCurveTo(dimensions.position.x, dimensions.position.y, dimensions.position.x + radius.topLeft, dimensions.position.y); | ||
| this.ctx.closePath(); | ||
| } | ||
| borderRadius() { | ||
| const radius = this.element.borderRadius(); | ||
| if (this.isRadiusSet()) { | ||
| const { size } = this.elementDimensions(); | ||
| Object.keys(radius).forEach((key) => { | ||
| if (radius[key] > size.width / 2) radius[key] = Math.floor(size.width / 2); | ||
| if (radius[key] > size.height / 2) radius[key] = Math.floor(size.height / 2); | ||
| }); | ||
| } | ||
| return radius; | ||
| } | ||
| isRadiusSet() { | ||
| const radius = this.element.borderRadius(); | ||
| return radius.topLeft > 0 || radius.topRight > 0 || radius.bottomLeft > 0 || radius.bottomRight > 0; | ||
| } | ||
| position() { | ||
| const elementPosition = this.element.positionLeftTop(); | ||
| const relativePosition = this.relativePosition(); | ||
| return { | ||
| x: elementPosition.x + relativePosition.x, | ||
| y: elementPosition.y + relativePosition.y | ||
| }; | ||
| } | ||
| defaultAnchor() { | ||
| const size = this.element.size(); | ||
| return { | ||
| x: size.width / 2, | ||
| y: size.height / 2 | ||
| }; | ||
| } | ||
| }; | ||
| //#endregion | ||
| //#region src/libraries/shapes/shape.ts | ||
| var Shape = class { | ||
| _points = []; | ||
| _isCloseShape; | ||
| _position = { | ||
| x: 0, | ||
| y: 0 | ||
| }; | ||
| _positionPoint = PositionPoint.LeftTop; | ||
| _size; | ||
| _jointsRadius = 0; | ||
| _degrees = 0; | ||
| _anchor; | ||
| _rotations = []; | ||
| constructor(points, isCloseShape = true) { | ||
| this._points = points; | ||
| this._isCloseShape = isCloseShape; | ||
| } | ||
| hash() { | ||
| return Helper.hash([ | ||
| this.points(), | ||
| this.isCloseShape(), | ||
| this.position(), | ||
| this.positionPoint(), | ||
| this.size(), | ||
| this.jointsRadius(), | ||
| this.rotations(), | ||
| this.degrees(), | ||
| this.anchor() | ||
| ]); | ||
| } | ||
| points() { | ||
| return this._points; | ||
| } | ||
| isCloseShape() { | ||
| return this._isCloseShape; | ||
| } | ||
| position() { | ||
| return this._position; | ||
| } | ||
| positionPoint() { | ||
| return this._positionPoint; | ||
| } | ||
| size() { | ||
| return this._size; | ||
| } | ||
| jointsRadius() { | ||
| return this._jointsRadius; | ||
| } | ||
| /** @deprecated Use Rotations instead */ | ||
| degrees() { | ||
| return this._degrees; | ||
| } | ||
| /** @deprecated Use Rotations instead */ | ||
| anchor() { | ||
| return this._anchor; | ||
| } | ||
| rotations() { | ||
| return this._rotations; | ||
| } | ||
| setPosition(position, point) { | ||
| this._position = position; | ||
| if (point) this._positionPoint = point; | ||
| return this; | ||
| } | ||
| setSize(value) { | ||
| this._size = value; | ||
| return this; | ||
| } | ||
| setJointsRadius(value) { | ||
| this._jointsRadius = value; | ||
| return this; | ||
| } | ||
| /** @deprecated Use Rotations instead */ | ||
| setDegrees(degrees, anchor) { | ||
| this._degrees = degrees; | ||
| this._anchor = anchor; | ||
| return this; | ||
| } | ||
| setRotations(rotations) { | ||
| this._rotations = rotations; | ||
| return this; | ||
| } | ||
| }; | ||
| //#endregion | ||
| //#region src/libraries/artists/polygon-artist.ts | ||
| var PolygonArtist = class extends ElementArtist { | ||
| draw() { | ||
| this.startDrawing(); | ||
| this.makeShape(); | ||
| this.drawBorder(); | ||
| this.drawFill(); | ||
| this.endDrawing(); | ||
| } | ||
| makeShape() { | ||
| const points = this.element.shapePoints(); | ||
| if (points.length > 2) { | ||
| const shape = new Shape(points).setSize(this.shapeSize()).setPosition(this.shapePosition()).setJointsRadius(this.element.borderRadius()); | ||
| this.ctx.beginPath(); | ||
| new Shaper(this.ctx, shape).draw(); | ||
| this.ctx.closePath(); | ||
| } | ||
| } | ||
| drawFill() { | ||
| const color = this.element.fillColor(); | ||
| if (color) { | ||
| this.ctx.fillStyle = new ColorMixer(this.ctx, this.position(), this.shapeSize()).mixed(color); | ||
| this.ctx.fill(); | ||
| } | ||
| } | ||
| drawBorder() { | ||
| const width = this.element.borderWidth(); | ||
| if (width > 0) { | ||
| const color = new ColorMixer(this.ctx, this.position(), this.element.size()).mixed(this.element.borderColor() ?? this.defaultBorderColor); | ||
| const fillColor = this.element.fillColor(); | ||
| if (this.element.borderRadius() > 0) this.ctx.lineJoin = "round"; | ||
| this.ctx.strokeStyle = color; | ||
| this.ctx.lineWidth = fillColor ? width * 2 : width; | ||
| this.ctx.stroke(); | ||
| } | ||
| } | ||
| shapePosition() { | ||
| const position = this.position(); | ||
| const borderWidth = this.element.borderWidth(); | ||
| return { | ||
| x: position.x + borderWidth, | ||
| y: position.y + borderWidth | ||
| }; | ||
| } | ||
| shapeSize() { | ||
| const size = this.element.size(); | ||
| const borderWidth = this.element.borderWidth(); | ||
| return { | ||
| width: size.width - borderWidth * 2, | ||
| height: size.height - borderWidth * 2 | ||
| }; | ||
| } | ||
| position() { | ||
| const elementPosition = this.element.positionLeftTop(); | ||
| const relativePosition = this.relativePosition(); | ||
| return { | ||
| x: elementPosition.x + relativePosition.x, | ||
| y: elementPosition.y + relativePosition.y | ||
| }; | ||
| } | ||
| defaultAnchor() { | ||
| const size = this.element.size(); | ||
| return { | ||
| x: size.width / 2, | ||
| y: size.height / 2 | ||
| }; | ||
| } | ||
| }; | ||
| //#endregion | ||
| //#region src/libraries/artists/line-artist.ts | ||
| var LineArtist = class extends ElementArtist { | ||
| draw() { | ||
| this.startDrawing(); | ||
| this.makeShape(); | ||
| this.drawStroke(); | ||
| this.endDrawing(); | ||
| } | ||
| makeShape() { | ||
| this.ctx.beginPath(); | ||
| const shape = new Shape(this.element.shapePoints(), false).setSize(this.shapeSize()).setPosition(this.shapePosition()).setJointsRadius(this.element.jointsRadius()); | ||
| new Shaper(this.ctx, shape).draw(); | ||
| } | ||
| drawStroke() { | ||
| const color = new ColorMixer(this.ctx, this.position(), this.element.boundingClientSize()).mixed(this.element.color() ?? this.defaultBorderColor); | ||
| this.ctx.lineCap = this.element.lineCap(); | ||
| this.ctx.strokeStyle = color; | ||
| this.ctx.lineWidth = this.element.calculatedThickness(); | ||
| this.ctx.stroke(); | ||
| } | ||
| shapeSize() { | ||
| const size = this.element.boundingClientSize(); | ||
| const thickness = this.element.calculatedThickness(); | ||
| return { | ||
| width: size.width - thickness, | ||
| height: size.height - thickness / 2 | ||
| }; | ||
| } | ||
| shapePosition() { | ||
| const position = this.position(); | ||
| const thickness = this.element.calculatedThickness(); | ||
| return { | ||
| x: position.x + thickness / 2, | ||
| y: position.y | ||
| }; | ||
| } | ||
| position() { | ||
| const elementPosition = this.element.positionLeftTop(); | ||
| const relativePosition = this.relativePosition(); | ||
| return { | ||
| x: elementPosition.x + relativePosition.x, | ||
| y: elementPosition.y + relativePosition.y | ||
| }; | ||
| } | ||
| defaultAnchor() { | ||
| const size = this.shapeSize(); | ||
| return { | ||
| x: size.width / 2, | ||
| y: size.height / 2 | ||
| }; | ||
| } | ||
| }; | ||
| //#endregion | ||
| //#region src/libraries/canvas.ts | ||
| var Canvas = class { | ||
| _canvas; | ||
| _ctx; | ||
| constructor(canvas) { | ||
| this._canvas = canvas ?? document.createElement("canvas"); | ||
| this._ctx = this._canvas.getContext("2d"); | ||
| } | ||
| nativeElement() { | ||
| return this._canvas; | ||
| } | ||
| ctx() { | ||
| return this._ctx; | ||
| } | ||
| size() { | ||
| return { | ||
| width: this._canvas.width, | ||
| height: this._canvas.height | ||
| }; | ||
| } | ||
| setSize(size) { | ||
| this._canvas.width = size.width; | ||
| this._canvas.height = size.height; | ||
| } | ||
| draw(element, relativeProps) { | ||
| switch (element.type()) { | ||
| case ElementType.Rectangle: | ||
| new RectangleArtist(this.ctx(), element, relativeProps).draw(); | ||
| break; | ||
| case ElementType.Ellipse: | ||
| new EllipseArtist(this.ctx(), element, relativeProps).draw(); | ||
| break; | ||
| case ElementType.Polygon: | ||
| new PolygonArtist(this.ctx(), element, relativeProps).draw(); | ||
| break; | ||
| case ElementType.Line: | ||
| new LineArtist(this.ctx(), element, relativeProps).draw(); | ||
| break; | ||
| case ElementType.Text: | ||
| new TextArtist(this.ctx(), element, relativeProps).draw(); | ||
| break; | ||
| case ElementType.Image: | ||
| new ImageArtist(this.ctx(), element, relativeProps).draw(); | ||
| break; | ||
| } | ||
| } | ||
| clear() { | ||
| this.ctx().clearRect(0, 0, this._canvas.width, this._canvas.height); | ||
| } | ||
| }; | ||
| //#endregion | ||
| //#region src/libraries/elements/element.ts | ||
| var Element = class { | ||
| _position = { | ||
| x: 0, | ||
| y: 0 | ||
| }; | ||
| _positionPoint; | ||
| _scale = { | ||
| x: 1, | ||
| y: 1 | ||
| }; | ||
| _opacity = 1; | ||
| _rotations = []; | ||
| _degrees = 0; | ||
| _anchor; | ||
| _shadow; | ||
| hash() { | ||
| return Helper.hash([ | ||
| this.position(), | ||
| this.positionPoint(), | ||
| this.scale(), | ||
| this.opacity(), | ||
| this.rotations(), | ||
| this.degrees(), | ||
| this.anchor(), | ||
| this.shadow() | ||
| ]); | ||
| } | ||
| position() { | ||
| return Helper.resolvedValue(this._position); | ||
| } | ||
| positionLeftTop() { | ||
| return Helper.positionLeftTop(this.position(), this.positionPoint(), this.boundingClientSize()); | ||
| } | ||
| positionPoint() { | ||
| return Helper.resolvedValue(this._positionPoint) ?? PositionPoint.LeftTop; | ||
| } | ||
| scale() { | ||
| return Helper.resolvedValue(this._scale); | ||
| } | ||
| opacity() { | ||
| return Helper.resolvedValue(this._opacity); | ||
| } | ||
| rotations() { | ||
| return Helper.resolvedValue(this._rotations); | ||
| } | ||
| /** @deprecated Use Rotations instead */ | ||
| degrees() { | ||
| return Helper.resolvedValue(this._degrees); | ||
| } | ||
| /** @deprecated Use Rotations instead */ | ||
| anchor() { | ||
| return this._anchor ? Helper.resolvedValue(this._anchor) : void 0; | ||
| } | ||
| shadow() { | ||
| return this._shadow ? Helper.resolvedValue(this._shadow) : void 0; | ||
| } | ||
| setPosition(position, point) { | ||
| this._position = position; | ||
| if (point) this._positionPoint = point; | ||
| return this; | ||
| } | ||
| setScale(value) { | ||
| this._scale = value; | ||
| return this; | ||
| } | ||
| setOpacity(value) { | ||
| this._opacity = value; | ||
| return this; | ||
| } | ||
| /** @deprecated Use Rotations instead */ | ||
| setDegrees(degrees, anchor) { | ||
| this._degrees = degrees; | ||
| this._anchor = anchor; | ||
| return this; | ||
| } | ||
| setRotations(rotations) { | ||
| this._rotations = rotations; | ||
| return this; | ||
| } | ||
| setShadow(shadow) { | ||
| this._shadow = shadow; | ||
| return this; | ||
| } | ||
| }; | ||
| //#endregion | ||
| //#region src/libraries/elements/geometric-element.ts | ||
| var GeometricElement = class extends Element { | ||
| _size = { | ||
| width: 1, | ||
| height: 1 | ||
| }; | ||
| _borderColor; | ||
| _borderWidth = 0; | ||
| hash() { | ||
| return Helper.hash([ | ||
| super.hash(), | ||
| this.size(), | ||
| this.borderColor(), | ||
| this.borderWidth() | ||
| ]); | ||
| } | ||
| size() { | ||
| return Helper.resolvedValue(this._size); | ||
| } | ||
| borderColor() { | ||
| return this._borderColor ? Helper.resolvedValue(this._borderColor) : void 0; | ||
| } | ||
| borderWidth() { | ||
| return Helper.resolvedValue(this._borderWidth); | ||
| } | ||
| setSize(value) { | ||
| this._size = value; | ||
| return this; | ||
| } | ||
| setBorder(width, color) { | ||
| this._borderWidth = width; | ||
| this._borderColor = color; | ||
| return this; | ||
| } | ||
| }; | ||
| //#endregion | ||
| //#region src/libraries/elements/rectangle-element.ts | ||
| var RectangleElement = class extends GeometricElement { | ||
| _fillColor; | ||
| _cutOutShapes = []; | ||
| type() { | ||
| return ElementType.Rectangle; | ||
| } | ||
| hash() { | ||
| return Helper.hash([ | ||
| super.hash(), | ||
| this.fillColor(), | ||
| this.cutOutShapes() | ||
| ]); | ||
| } | ||
| fillColor() { | ||
| return this._fillColor ? Helper.resolvedValue(this._fillColor) : void 0; | ||
| } | ||
| cutOutShapes() { | ||
| return Helper.resolvedValue(this._cutOutShapes); | ||
| } | ||
| boundingClientSize() { | ||
| return this.size(); | ||
| } | ||
| setFillColor(value) { | ||
| this._fillColor = value; | ||
| return this; | ||
| } | ||
| setCutOutShapes(value) { | ||
| this._cutOutShapes = value; | ||
| return this; | ||
| } | ||
| }; | ||
| //#endregion | ||
| //#region src/libraries/elements/ellipse-element.ts | ||
| var EllipseElement = class extends GeometricElement { | ||
| _fillColor; | ||
| type() { | ||
| return ElementType.Ellipse; | ||
| } | ||
| hash() { | ||
| return Helper.hash([super.hash(), this.fillColor()]); | ||
| } | ||
| fillColor() { | ||
| return this._fillColor ? Helper.resolvedValue(this._fillColor) : void 0; | ||
| } | ||
| boundingClientSize() { | ||
| return this.size(); | ||
| } | ||
| setFillColor(value) { | ||
| this._fillColor = value; | ||
| return this; | ||
| } | ||
| }; | ||
| //#endregion | ||
| //#region src/libraries/elements/polygon-element.ts | ||
| var PolygonElement = class extends GeometricElement { | ||
| _shapePoints = []; | ||
| _borderRadius = 0; | ||
| _fillColor; | ||
| type() { | ||
| return ElementType.Polygon; | ||
| } | ||
| hash() { | ||
| return Helper.hash([ | ||
| super.hash(), | ||
| this.shapePoints(), | ||
| this.borderRadius(), | ||
| this.fillColor() | ||
| ]); | ||
| } | ||
| shapePoints() { | ||
| return Helper.resolvedValue(this._shapePoints); | ||
| } | ||
| borderRadius() { | ||
| return Helper.resolvedValue(this._borderRadius); | ||
| } | ||
| fillColor() { | ||
| return this._fillColor ? Helper.resolvedValue(this._fillColor) : void 0; | ||
| } | ||
| boundingClientSize() { | ||
| return this.size(); | ||
| } | ||
| setShapePoints(value) { | ||
| this._shapePoints = value; | ||
| return this; | ||
| } | ||
| setBorderRadius(value) { | ||
| this._borderRadius = value; | ||
| return this; | ||
| } | ||
| setFillColor(value) { | ||
| this._fillColor = value; | ||
| return this; | ||
| } | ||
| }; | ||
| //#endregion | ||
| //#region src/libraries/elements/line-element.ts | ||
| var LineElement = class extends Element { | ||
| _shapePoints = []; | ||
| _size; | ||
| _keepRatio; | ||
| _thickness = 1; | ||
| _jointsRadius = 0; | ||
| _lineCap = LineCap.Square; | ||
| _color; | ||
| type() { | ||
| return ElementType.Line; | ||
| } | ||
| hash() { | ||
| return Helper.hash([ | ||
| super.hash(), | ||
| this.shapePoints(), | ||
| this.size(), | ||
| this.keepRatio(), | ||
| this.thickness(), | ||
| this.jointsRadius(), | ||
| this.lineCap(), | ||
| this.color() | ||
| ]); | ||
| } | ||
| shapePoints() { | ||
| return Helper.resolvedValue(this._shapePoints); | ||
| } | ||
| size() { | ||
| return this._size ? Helper.resolvedValue(this._size) : void 0; | ||
| } | ||
| keepRatio() { | ||
| return Helper.resolvedValue(this._keepRatio) ?? false; | ||
| } | ||
| thickness() { | ||
| return Helper.resolvedValue(this._thickness); | ||
| } | ||
| calculatedThickness() { | ||
| const size = this.size(); | ||
| let thickness = this.thickness(); | ||
| if (size) { | ||
| const originalSize = Helper.shapeSize(this.shapePoints()); | ||
| const resized = Helper.resizedSize(originalSize, size, this.keepRatio()); | ||
| const ratioWidth = resized.width / Helper.valueNotLessThan(originalSize.width, 1); | ||
| const ratioHeight = resized.height / Helper.valueNotLessThan(originalSize.height, 1); | ||
| thickness = Helper.valueNotLessThan(thickness * Math.min(ratioWidth, ratioHeight), 1); | ||
| } | ||
| return thickness; | ||
| } | ||
| jointsRadius() { | ||
| return Helper.resolvedValue(this._jointsRadius); | ||
| } | ||
| lineCap() { | ||
| return Helper.resolvedValue(this._lineCap); | ||
| } | ||
| color() { | ||
| return this._color ? Helper.resolvedValue(this._color) : void 0; | ||
| } | ||
| boundingClientSize() { | ||
| let size = this.size(); | ||
| const thickness = this.calculatedThickness(); | ||
| const originalSize = Helper.shapeSize(this.shapePoints()); | ||
| if (size) { | ||
| const resized = Helper.resizedSize(originalSize, size, this.keepRatio()); | ||
| size = { | ||
| width: resized.width, | ||
| height: resized.height + thickness / 2 | ||
| }; | ||
| } else size = { | ||
| width: originalSize.width, | ||
| height: Helper.valueNotLessThan(originalSize.height + thickness / 2, 0) | ||
| }; | ||
| return size; | ||
| } | ||
| setShapePoints(value) { | ||
| this._shapePoints = value; | ||
| return this; | ||
| } | ||
| setSize(value, keepRatio) { | ||
| this._size = value; | ||
| if (keepRatio) this._keepRatio = keepRatio; | ||
| return this; | ||
| } | ||
| setThickness(value) { | ||
| this._thickness = value; | ||
| return this; | ||
| } | ||
| setJointsRadius(value) { | ||
| this._jointsRadius = value; | ||
| return this; | ||
| } | ||
| setLineCap(value) { | ||
| this._lineCap = value; | ||
| return this; | ||
| } | ||
| setColor(value) { | ||
| this._color = value; | ||
| return this; | ||
| } | ||
| }; | ||
| //#endregion | ||
| //#region src/libraries/texts/text-splitter.ts | ||
| var TextSplitter = class { | ||
| element; | ||
| canvas; | ||
| ctx; | ||
| lastTextBox; | ||
| lastTextBoxHash = ""; | ||
| constructor(element) { | ||
| this.element = element; | ||
| this.canvas = document.createElement("canvas"); | ||
| this.ctx = this.canvas.getContext("2d"); | ||
| } | ||
| textBox() { | ||
| if (this.elementChanged()) this.lastTextBox = void 0; | ||
| if (!this.lastTextBox) this.lastTextBox = this.createTextBox(); | ||
| return this.lastTextBox; | ||
| } | ||
| createTextBox() { | ||
| const result = { | ||
| text: this.element.text(), | ||
| lines: [], | ||
| size: { | ||
| width: 1, | ||
| height: 1 | ||
| }, | ||
| position: { | ||
| x: 0, | ||
| y: 0 | ||
| } | ||
| }; | ||
| result.lines = this.textLines(); | ||
| result.size = this.textBoxSize(result.lines); | ||
| result.position = this.textBoxPosition(result.lines); | ||
| return result; | ||
| } | ||
| textLines() { | ||
| const bounds = this.element.bounds(); | ||
| const padding = this.element.padding(); | ||
| const boundsWithoutPadding = bounds ? { | ||
| width: bounds.width - padding.left - padding.right, | ||
| height: bounds.height - padding.top - padding.bottom | ||
| } : void 0; | ||
| const textLines = this.splitText().map((line) => { | ||
| return { | ||
| text: line, | ||
| size: { | ||
| width: this.textWidth(line), | ||
| height: this.element.lineHeight() | ||
| }, | ||
| position: { | ||
| x: 0, | ||
| y: 0 | ||
| } | ||
| }; | ||
| }).map((line, index, transformed) => { | ||
| const previousLine = transformed[index - 1]; | ||
| line.position = { | ||
| x: 0, | ||
| y: previousLine ? previousLine.position.y + previousLine.size.height : 0 | ||
| }; | ||
| return line; | ||
| }); | ||
| return (boundsWithoutPadding !== void 0 ? this.alignedLines(textLines, boundsWithoutPadding) : textLines).map((line) => { | ||
| line.position.x += padding.left; | ||
| line.position.y += padding.top; | ||
| return line; | ||
| }); | ||
| } | ||
| alignedLines(textLines, bounds) { | ||
| const horizontalAlign = this.element.horizontalAlign(); | ||
| const verticalAlign = this.element.verticalAlign(); | ||
| const totalLinesHeight = textLines.reduce((sum, line) => sum + line.size.height, 0); | ||
| return textLines.map((line) => { | ||
| if (horizontalAlign === HorizontalAlign.Center) line.position.x += Math.round((bounds.width - line.size.width) / 2); | ||
| if (horizontalAlign === HorizontalAlign.Right) line.position.x += bounds.width - line.size.width; | ||
| if (verticalAlign === VerticalAlign.Center) line.position.y += Math.round((bounds.height - totalLinesHeight) / 2); | ||
| if (verticalAlign === VerticalAlign.Bottom) line.position.y += bounds.height - totalLinesHeight; | ||
| return line; | ||
| }); | ||
| } | ||
| textBoxSize(textLines) { | ||
| const padding = this.element.padding(); | ||
| const bounds = this.element.bounds() ?? { | ||
| width: 1, | ||
| height: 1 | ||
| }; | ||
| const longestWidth = Math.max(...textLines.map((textLine) => textLine.size.width)); | ||
| return { | ||
| width: Math.max(longestWidth + padding.left + padding.right, bounds.width), | ||
| height: Math.max(this.element.lineHeight() * textLines.length + padding.top + padding.bottom, bounds.height) | ||
| }; | ||
| } | ||
| textBoxPosition(textLines) { | ||
| const padding = this.element.padding(); | ||
| const smallestX = Math.min(...textLines.map((textLine) => textLine.position.x)); | ||
| const smallestY = Math.min(...textLines.map((textLine) => textLine.position.y)); | ||
| return { | ||
| x: smallestX - padding.left, | ||
| y: smallestY - padding.top | ||
| }; | ||
| } | ||
| splitText() { | ||
| const lines = this.element.text().replaceAll("<BR>", "<br>").split("<br>"); | ||
| const padding = this.element.padding(); | ||
| const maxWidth = (this.element.bounds() ?? { | ||
| width: 999999, | ||
| height: 999999 | ||
| }).width - (padding.left + padding.right); | ||
| let result = []; | ||
| lines.forEach((line) => { | ||
| line = line.trim(); | ||
| const textWidth = this.textWidth(line); | ||
| if (textWidth > maxWidth) { | ||
| const splitEvery = Math.floor(maxWidth / (textWidth / line.length)) - 3; | ||
| const words = line.split(" "); | ||
| let tmp = ""; | ||
| words.forEach((word, index) => { | ||
| if (tmp.length + word.length > splitEvery) { | ||
| result.push(tmp.trim()); | ||
| tmp = ""; | ||
| } | ||
| tmp += word + " "; | ||
| if (index === words.length - 1) result.push(tmp.trim()); | ||
| }); | ||
| result = result.map((line) => line.trim()); | ||
| } else result.push(line); | ||
| }); | ||
| return result; | ||
| } | ||
| textWidth(text) { | ||
| this.ctx.font = this.element.font(); | ||
| return Math.ceil(this.ctx.measureText(text).width + this.element.strokeWidth()); | ||
| } | ||
| elementChanged() { | ||
| const hash = [ | ||
| this.element.text(), | ||
| this.element.bounds(), | ||
| this.element.horizontalAlign(), | ||
| this.element.verticalAlign(), | ||
| this.element.padding(), | ||
| this.element.lineHeight(), | ||
| this.element.font(), | ||
| this.element.strokeWidth() | ||
| ].join("|"); | ||
| const changed = this.lastTextBoxHash !== hash; | ||
| this.lastTextBoxHash = hash; | ||
| return changed; | ||
| } | ||
| }; | ||
| //#endregion | ||
| //#region src/libraries/elements/text-element.ts | ||
| var TextElement = class extends Element { | ||
| _text = ""; | ||
| _bounds; | ||
| _fontSize = 12; | ||
| _fontFamily = "Tahoma"; | ||
| _fontWeight = FontWeight.Normal; | ||
| _lineHeight; | ||
| _fillColor; | ||
| _strokeColor; | ||
| _strokeWidth = 0; | ||
| _horizontalAlign = HorizontalAlign.Left; | ||
| _verticalAlign = VerticalAlign.Top; | ||
| _padding = { topOrVerticalOrDefault: 0 }; | ||
| _textSplitter = new TextSplitter(this); | ||
| type() { | ||
| return ElementType.Text; | ||
| } | ||
| hash() { | ||
| return Helper.hash([ | ||
| super.hash(), | ||
| this.text(), | ||
| this.bounds(), | ||
| this.font(), | ||
| this.lineHeight(), | ||
| this.fillColor(), | ||
| this.strokeColor(), | ||
| this.strokeWidth(), | ||
| this.horizontalAlign(), | ||
| this.verticalAlign(), | ||
| this.padding() | ||
| ]); | ||
| } | ||
| text() { | ||
| return Helper.resolvedValue(this._text).toString(); | ||
| } | ||
| bounds() { | ||
| return this._bounds ? Helper.resolvedValue(this._bounds) : void 0; | ||
| } | ||
| fontSize() { | ||
| return Helper.resolvedValue(this._fontSize); | ||
| } | ||
| fontFamily() { | ||
| return Helper.resolvedValue(this._fontFamily); | ||
| } | ||
| fontWeight() { | ||
| return Helper.resolvedValue(this._fontWeight); | ||
| } | ||
| lineHeight() { | ||
| return Helper.resolvedValue(this._lineHeight ?? Math.round(this.fontSize() * 1.2)); | ||
| } | ||
| fillColor() { | ||
| return this._fillColor ? Helper.resolvedValue(this._fillColor) : void 0; | ||
| } | ||
| strokeColor() { | ||
| return this._strokeColor ? Helper.resolvedValue(this._strokeColor) : void 0; | ||
| } | ||
| strokeWidth() { | ||
| return Helper.resolvedValue(this._strokeWidth); | ||
| } | ||
| horizontalAlign() { | ||
| return Helper.resolvedValue(this._horizontalAlign); | ||
| } | ||
| verticalAlign() { | ||
| return Helper.resolvedValue(this._verticalAlign); | ||
| } | ||
| padding() { | ||
| let result; | ||
| const topOrVerticalOrDefault = Helper.resolvedValue(this._padding.topOrVerticalOrDefault); | ||
| const rightOrHorizontal = this._padding.rightOrHorizontal ? Helper.resolvedValue(this._padding.rightOrHorizontal) : void 0; | ||
| const bottom = this._padding.bottom ? Helper.resolvedValue(this._padding.bottom) : void 0; | ||
| const left = this._padding.left ? Helper.resolvedValue(this._padding.left) : void 0; | ||
| if (rightOrHorizontal === void 0) result = { | ||
| top: topOrVerticalOrDefault, | ||
| right: topOrVerticalOrDefault, | ||
| bottom: topOrVerticalOrDefault, | ||
| left: topOrVerticalOrDefault | ||
| }; | ||
| else if (bottom === void 0 && left === void 0) result = { | ||
| top: topOrVerticalOrDefault, | ||
| right: rightOrHorizontal, | ||
| bottom: topOrVerticalOrDefault, | ||
| left: rightOrHorizontal | ||
| }; | ||
| else result = { | ||
| top: topOrVerticalOrDefault, | ||
| right: rightOrHorizontal, | ||
| bottom: bottom ?? 0, | ||
| left: left ?? 0 | ||
| }; | ||
| return result; | ||
| } | ||
| font() { | ||
| const font = [this.fontSize() + "px", this.fontFamily()]; | ||
| const weight = this.fontWeight(); | ||
| if (weight) font.unshift(weight); | ||
| return font.join(" "); | ||
| } | ||
| textBox() { | ||
| return this._textSplitter.textBox(); | ||
| } | ||
| boundingClientSize() { | ||
| return this.textBox().size; | ||
| } | ||
| setText(value) { | ||
| this._text = value; | ||
| return this; | ||
| } | ||
| setBounds(value) { | ||
| this._bounds = value; | ||
| return this; | ||
| } | ||
| setFontSize(value) { | ||
| this._fontSize = value; | ||
| return this; | ||
| } | ||
| setFontFamily(value) { | ||
| this._fontFamily = value; | ||
| return this; | ||
| } | ||
| setFontWeight(value) { | ||
| this._fontWeight = value; | ||
| return this; | ||
| } | ||
| setLineHeight(value) { | ||
| this._lineHeight = value; | ||
| return this; | ||
| } | ||
| setFillColor(value) { | ||
| this._fillColor = value; | ||
| return this; | ||
| } | ||
| setStroke(width, color) { | ||
| this._strokeWidth = width; | ||
| this._strokeColor = color; | ||
| return this; | ||
| } | ||
| setHorizontalAlign(value) { | ||
| this._horizontalAlign = value; | ||
| return this; | ||
| } | ||
| setVerticalAlign(value) { | ||
| this._verticalAlign = value; | ||
| return this; | ||
| } | ||
| setPadding(topOrVerticalOrDefault, rightOrHorizontal, bottom, left) { | ||
| this._padding = { | ||
| topOrVerticalOrDefault, | ||
| rightOrHorizontal, | ||
| bottom, | ||
| left | ||
| }; | ||
| return this; | ||
| } | ||
| }; | ||
| //#endregion | ||
| //#region src/libraries/elements/image-element.ts | ||
| var ImageElement = class extends GeometricElement { | ||
| _image; | ||
| _fit = ImageFit.Fill; | ||
| _borderRadius = { topLeftOrDefault: 0 }; | ||
| _flipX; | ||
| _flipY; | ||
| _cropArea; | ||
| type() { | ||
| return ElementType.Image; | ||
| } | ||
| hash() { | ||
| return Helper.hash([ | ||
| super.hash(), | ||
| this.image()?.id, | ||
| this.fit(), | ||
| this.borderRadius(), | ||
| this.flipX(), | ||
| this.flipY(), | ||
| this.cropArea() | ||
| ]); | ||
| } | ||
| image() { | ||
| return this._image ? Helper.resolvedValue(this._image) : void 0; | ||
| } | ||
| fit() { | ||
| return Helper.resolvedValue(this._fit); | ||
| } | ||
| borderRadius() { | ||
| let result; | ||
| const topLeftOrDefault = Helper.resolvedValue(this._borderRadius.topLeftOrDefault); | ||
| const topRight = this._borderRadius.topRight ? Helper.resolvedValue(this._borderRadius.topRight) : void 0; | ||
| const bottomRight = this._borderRadius.bottomRight ? Helper.resolvedValue(this._borderRadius.bottomRight) : void 0; | ||
| const bottomLeft = this._borderRadius.bottomLeft ? Helper.resolvedValue(this._borderRadius.bottomLeft) : void 0; | ||
| if (topRight === void 0 && bottomRight === void 0 && bottomLeft === void 0) result = { | ||
| topLeft: topLeftOrDefault, | ||
| topRight: topLeftOrDefault, | ||
| bottomRight: topLeftOrDefault, | ||
| bottomLeft: topLeftOrDefault | ||
| }; | ||
| else result = { | ||
| topLeft: topLeftOrDefault, | ||
| topRight: topRight ?? 0, | ||
| bottomRight: bottomRight ?? 0, | ||
| bottomLeft: bottomLeft ?? 0 | ||
| }; | ||
| return result; | ||
| } | ||
| flipX() { | ||
| return Helper.resolvedValue(this._flipX) ?? false; | ||
| } | ||
| flipY() { | ||
| return Helper.resolvedValue(this._flipY) ?? false; | ||
| } | ||
| cropArea() { | ||
| return Helper.resolvedValue(this._cropArea) ?? {}; | ||
| } | ||
| boundingClientSize() { | ||
| return this.size(); | ||
| } | ||
| setImage(value) { | ||
| this._image = value; | ||
| return this; | ||
| } | ||
| setFit(value) { | ||
| this._fit = value; | ||
| return this; | ||
| } | ||
| setBorderRadius(topLeftOrDefault, topRight, bottomRight, bottomLeft) { | ||
| this._borderRadius = { | ||
| topLeftOrDefault, | ||
| topRight, | ||
| bottomRight, | ||
| bottomLeft | ||
| }; | ||
| return this; | ||
| } | ||
| setFlip(value) { | ||
| this._flipX = value.horizontal ?? false; | ||
| this._flipY = value.vertical ?? false; | ||
| return this; | ||
| } | ||
| setCropArea(value) { | ||
| this._cropArea = value; | ||
| return this; | ||
| } | ||
| }; | ||
| //#endregion | ||
| //#region src/libraries/colors/color.ts | ||
| var Color = class {}; | ||
| //#endregion | ||
| //#region src/libraries/colors/solid-color.ts | ||
| var SolidColor = class extends Color { | ||
| _color; | ||
| constructor(hexColor, opacity) { | ||
| super(); | ||
| this.setColor(hexColor, opacity); | ||
| } | ||
| type() { | ||
| return ColorType.SolidColor; | ||
| } | ||
| color() { | ||
| return this._color; | ||
| } | ||
| hash() { | ||
| return Helper.hash([this.color()]); | ||
| } | ||
| setColor(hexColor, opacity) { | ||
| const alphaFromHex = hexColor.length === 9 ? hexColor.slice(-2) : void 0; | ||
| const alpha = opacity !== void 0 ? this.alphaToHex(opacity) : alphaFromHex ?? "ff"; | ||
| this._color = hexColor.slice(0, 7) + alpha; | ||
| return this; | ||
| } | ||
| alphaToHex(alpha) { | ||
| let hex = Math.round(alpha * 255).toString(16); | ||
| if (hex.length === 1) hex = "0" + hex; | ||
| return hex; | ||
| } | ||
| }; | ||
| //#endregion | ||
| //#region src/libraries/colors/gradient-color.ts | ||
| var GradientColor = class extends SolidColor { | ||
| _offset; | ||
| constructor(offset, hexColor, opacity = 1) { | ||
| super(hexColor, opacity); | ||
| this.setOffset(offset); | ||
| } | ||
| offset() { | ||
| return this._offset; | ||
| } | ||
| hash() { | ||
| return Helper.hash([super.hash(), this.offset()]); | ||
| } | ||
| setOffset(offset) { | ||
| this._offset = Helper.valueInRange(offset, { | ||
| min: 0, | ||
| max: 1 | ||
| }); | ||
| return this; | ||
| } | ||
| }; | ||
| //#endregion | ||
| //#region src/libraries/colors/gradient.ts | ||
| var Gradient = class extends Color { | ||
| _colors; | ||
| constructor(colors) { | ||
| super(); | ||
| this._colors = colors; | ||
| } | ||
| colors() { | ||
| return this._colors; | ||
| } | ||
| hash() { | ||
| return Helper.hash([this.colors()]); | ||
| } | ||
| }; | ||
| //#endregion | ||
| //#region src/libraries/colors/linear-gradient.ts | ||
| var LinearGradient = class extends Gradient { | ||
| _degrees = 0; | ||
| type() { | ||
| return ColorType.LinearGradient; | ||
| } | ||
| degrees() { | ||
| return this._degrees; | ||
| } | ||
| setDegrees(value) { | ||
| this._degrees = value; | ||
| return this; | ||
| } | ||
| hash() { | ||
| return Helper.hash([super.hash(), this.degrees()]); | ||
| } | ||
| }; | ||
| //#endregion | ||
| //#region src/libraries/colors/radial-gradient.ts | ||
| var RadialGradient = class extends Gradient { | ||
| _position; | ||
| _radiusScale; | ||
| type() { | ||
| return ColorType.RadialGradient; | ||
| } | ||
| position() { | ||
| return this._position; | ||
| } | ||
| radiusScale() { | ||
| return this._radiusScale ?? 1; | ||
| } | ||
| setPosition(value) { | ||
| this._position = value; | ||
| return this; | ||
| } | ||
| setRadiusScale(value) { | ||
| this._radiusScale = value; | ||
| return this; | ||
| } | ||
| hash() { | ||
| return Helper.hash([ | ||
| super.hash(), | ||
| this.position(), | ||
| this.radiusScale() | ||
| ]); | ||
| } | ||
| }; | ||
| //#endregion | ||
| exports.Canvas = Canvas; | ||
| exports.ColorType = ColorType; | ||
| exports.EllipseElement = EllipseElement; | ||
| exports.FontWeight = FontWeight; | ||
| exports.GradientColor = GradientColor; | ||
| exports.HorizontalAlign = HorizontalAlign; | ||
| exports.ImageElement = ImageElement; | ||
| exports.ImageFit = ImageFit; | ||
| exports.LineCap = LineCap; | ||
| exports.LineElement = LineElement; | ||
| exports.LinearGradient = LinearGradient; | ||
| exports.PolygonElement = PolygonElement; | ||
| exports.PositionPoint = PositionPoint; | ||
| exports.RadialGradient = RadialGradient; | ||
| exports.RectangleElement = RectangleElement; | ||
| exports.Shape = Shape; | ||
| exports.SolidColor = SolidColor; | ||
| exports.TextElement = TextElement; | ||
| exports.VerticalAlign = VerticalAlign; | ||
| }); |
| import { Element } from '../elements/element'; | ||
| import { Position } from '../../interfaces/position.interface'; | ||
| import { RelativeProps } from '../../interfaces/relative-props.interface'; | ||
| import { Scale } from '../../interfaces/scale.interface'; | ||
| import { Rotation } from '../../interfaces/rotation.interface'; | ||
| import { Position, RelativeProps, Scale, Rotation } from '../../types'; | ||
| export declare abstract class ElementArtist<AnyCanvasElement extends Element> { | ||
@@ -7,0 +4,0 @@ protected readonly ctx: CanvasRenderingContext2D; |
| import { ElementArtist } from './element-artist'; | ||
| import { Position } from '../../interfaces/position.interface'; | ||
| import { Position } from '../../types'; | ||
| import { EllipseElement } from '../elements/ellipse-element'; | ||
@@ -4,0 +4,0 @@ export declare class EllipseArtist extends ElementArtist<EllipseElement> { |
| import { ElementArtist } from './element-artist'; | ||
| import { Position } from '../../interfaces/position.interface'; | ||
| import { ImageElement } from '../elements/image-element'; | ||
| import { Position } from '../../types'; | ||
| export declare class ImageArtist extends ElementArtist<ImageElement> { | ||
@@ -5,0 +5,0 @@ draw(): void; |
| import { ElementArtist } from './element-artist'; | ||
| import { Position } from '../../interfaces/position.interface'; | ||
| import { Position } from '../../types'; | ||
| import { LineElement } from '../elements/line-element'; | ||
@@ -4,0 +4,0 @@ export declare class LineArtist extends ElementArtist<LineElement> { |
| import { ElementArtist } from './element-artist'; | ||
| import { Position } from '../../interfaces/position.interface'; | ||
| import { Position } from '../../types'; | ||
| import { PolygonElement } from '../elements/polygon-element'; | ||
@@ -4,0 +4,0 @@ export declare class PolygonArtist extends ElementArtist<PolygonElement> { |
| import { ElementArtist } from './element-artist'; | ||
| import { Position } from '../../interfaces/position.interface'; | ||
| import { Position } from '../../types'; | ||
| import { RectangleElement } from '../elements/rectangle-element'; | ||
@@ -4,0 +4,0 @@ export declare class RectangleArtist extends ElementArtist<RectangleElement> { |
| import { ElementArtist } from './element-artist'; | ||
| import { TextElement } from '../elements/text-element'; | ||
| import { Position } from '../../interfaces/position.interface'; | ||
| import { Position } from '../../types'; | ||
| export declare class TextArtist extends ElementArtist<TextElement> { | ||
@@ -5,0 +5,0 @@ draw(): void; |
@@ -1,4 +0,3 @@ | ||
| import { Size } from '../interfaces/size.interface'; | ||
| import { Element } from './elements/element'; | ||
| import { RelativeProps } from '../interfaces/relative-props.interface'; | ||
| import { RelativeProps, Size } from '../types'; | ||
| export declare class Canvas { | ||
@@ -5,0 +4,0 @@ private readonly _canvas; |
| import { Color } from './color'; | ||
| import { Size } from '../../interfaces/size.interface'; | ||
| import { Position } from '../../interfaces/position.interface'; | ||
| import { Position, Size } from '../../types'; | ||
| export declare class ColorMixer { | ||
@@ -5,0 +4,0 @@ private ctx; |
@@ -1,2 +0,2 @@ | ||
| import { ColorType } from '../../enums/color-type'; | ||
| import { ColorType } from '../../enums'; | ||
| export declare abstract class Color { | ||
@@ -3,0 +3,0 @@ abstract type(): ColorType; |
@@ -1,2 +0,2 @@ | ||
| import { ColorType } from '../../enums/color-type'; | ||
| import { ColorType } from '../../enums'; | ||
| import { Gradient } from './gradient'; | ||
@@ -3,0 +3,0 @@ export declare class LinearGradient extends Gradient { |
@@ -1,4 +0,4 @@ | ||
| import { ColorType } from '../../enums/color-type'; | ||
| import { ColorType } from '../../enums'; | ||
| import { Gradient } from './gradient'; | ||
| import { Position } from '../../interfaces/position.interface'; | ||
| import { Position } from '../../types'; | ||
| export declare class RadialGradient extends Gradient { | ||
@@ -5,0 +5,0 @@ private _position; |
@@ -1,2 +0,2 @@ | ||
| import { ColorType } from '../../enums/color-type'; | ||
| import { ColorType } from '../../enums'; | ||
| import { Color } from './color'; | ||
@@ -3,0 +3,0 @@ export declare class SolidColor extends Color { |
@@ -1,9 +0,3 @@ | ||
| import { Position } from '../../interfaces/position.interface'; | ||
| import { ElementType } from '../../enums/element-type'; | ||
| import { Shadow } from '../../interfaces/shadow.interface'; | ||
| import { PositionPoint } from '../../enums/position-point'; | ||
| import { Size } from '../../interfaces/size.interface'; | ||
| import { ResolvableAttribute } from '../../types'; | ||
| import { Scale } from '../../interfaces/scale.interface'; | ||
| import { Rotation } from '../../interfaces/rotation.interface'; | ||
| import { ElementType, PositionPoint } from '../../enums'; | ||
| import { Shadow, Position, Size, Scale, Rotation, ResolvableAttribute } from '../../types'; | ||
| export declare abstract class Element { | ||
@@ -10,0 +4,0 @@ private _position; |
@@ -1,6 +0,5 @@ | ||
| import { ElementType } from '../../enums/element-type'; | ||
| import { ElementType } from '../../enums'; | ||
| import { GeometricElement } from './geometric-element'; | ||
| import { Color } from '../colors/color'; | ||
| import { Size } from '../../interfaces/size.interface'; | ||
| import { ResolvableAttribute } from '../../types'; | ||
| import { Size, ResolvableAttribute } from '../../types'; | ||
| export declare class EllipseElement extends GeometricElement { | ||
@@ -7,0 +6,0 @@ private _fillColor?; |
| import { Element } from './element'; | ||
| import { Size } from '../../interfaces/size.interface'; | ||
| import { Color } from '../colors/color'; | ||
| import { ResolvableAttribute } from '../../types'; | ||
| import { Size, ResolvableAttribute } from '../../types'; | ||
| export declare abstract class GeometricElement extends Element { | ||
@@ -6,0 +5,0 @@ private _size; |
@@ -1,8 +0,4 @@ | ||
| import { ElementType } from '../../enums/element-type'; | ||
| import { ImageFit } from '../../enums/image-fit'; | ||
| import { BorderRadius } from '../../interfaces/border-radius.interface'; | ||
| import { ElementType, ImageFit } from '../../enums'; | ||
| import { GeometricElement } from './geometric-element'; | ||
| import { ImageSource, ResolvableAttribute } from '../../types'; | ||
| import { CropArea } from '../../interfaces/crop-area.interface'; | ||
| import { Size } from '../../interfaces/size.interface'; | ||
| import { Size, BorderRadius, CropArea, ImageSource, ResolvableAttribute } from '../../types'; | ||
| export declare class ImageElement extends GeometricElement { | ||
@@ -9,0 +5,0 @@ private _image?; |
@@ -1,8 +0,5 @@ | ||
| import { ElementType } from '../../enums/element-type'; | ||
| import { Position } from '../../interfaces/position.interface'; | ||
| import { ElementType, LineCap } from '../../enums'; | ||
| import { Element } from './element'; | ||
| import { Size } from '../../interfaces/size.interface'; | ||
| import { LineCap } from '../../enums/line-cap'; | ||
| import { Color } from '../colors/color'; | ||
| import { ResolvableAttribute } from '../../types'; | ||
| import { Size, Position, ResolvableAttribute } from '../../types'; | ||
| export declare class LineElement extends Element { | ||
@@ -9,0 +6,0 @@ private _shapePoints; |
@@ -1,7 +0,5 @@ | ||
| import { ElementType } from '../../enums/element-type'; | ||
| import { Position } from '../../interfaces/position.interface'; | ||
| import { ElementType } from '../../enums'; | ||
| import { GeometricElement } from './geometric-element'; | ||
| import { Color } from '../colors/color'; | ||
| import { Size } from '../../interfaces/size.interface'; | ||
| import { ResolvableAttribute } from '../../types'; | ||
| import { Size, Position, ResolvableAttribute } from '../../types'; | ||
| export declare class PolygonElement extends GeometricElement { | ||
@@ -8,0 +6,0 @@ private _shapePoints; |
@@ -1,7 +0,6 @@ | ||
| import { ElementType } from '../../enums/element-type'; | ||
| import { ElementType } from '../../enums'; | ||
| import { GeometricElement } from './geometric-element'; | ||
| import { Shape } from '../shapes/shape'; | ||
| import { GeometricElement } from './geometric-element'; | ||
| import { Color } from '../colors/color'; | ||
| import { Size } from '../../interfaces/size.interface'; | ||
| import { ResolvableAttribute } from '../../types'; | ||
| import { Size, ResolvableAttribute } from '../../types'; | ||
| export declare class RectangleElement extends GeometricElement { | ||
@@ -8,0 +7,0 @@ private _fillColor?; |
| import { Element } from './element'; | ||
| import { Size } from '../../interfaces/size.interface'; | ||
| import { ElementType } from '../../enums/element-type'; | ||
| import { FontWeight } from '../../enums/font-weight'; | ||
| import { HorizontalAlign } from '../../enums/horizontal-align'; | ||
| import { Padding } from '../../interfaces/padding.interface'; | ||
| import { VerticalAlign } from '../../enums/vertical-align'; | ||
| import { ElementType, FontWeight, HorizontalAlign, VerticalAlign } from '../../enums'; | ||
| import { Color } from '../colors/color'; | ||
| import { TextBox } from '../../interfaces/text-box.interface'; | ||
| import { ResolvableAttribute } from '../../types'; | ||
| import { Size, Padding, TextBox, ResolvableAttribute } from '../../types'; | ||
| export declare class TextElement extends Element { | ||
@@ -12,0 +6,0 @@ private _text; |
@@ -1,6 +0,3 @@ | ||
| import { Position } from '../../interfaces/position.interface'; | ||
| import { Size } from '../../interfaces/size.interface'; | ||
| import { PositionPoint } from '../../enums/position-point'; | ||
| import { ExtractedValue } from '../../interfaces/extracted-value.interface'; | ||
| import { ResolvableAttribute } from '../../types'; | ||
| import { PositionPoint } from '../../enums'; | ||
| import { Position, Size, ExtractedValue, ResolvableAttribute } from '../../types'; | ||
| export declare class Helper { | ||
@@ -7,0 +4,0 @@ static valueInRange(value: number, range: { |
@@ -1,5 +0,3 @@ | ||
| import { Position } from '../../interfaces/position.interface'; | ||
| import { Size } from '../../interfaces/size.interface'; | ||
| import { PositionPoint } from '../../enums/position-point'; | ||
| import { Rotation } from '../../interfaces/rotation.interface'; | ||
| import { PositionPoint } from '../../enums'; | ||
| import { Position, Size, Rotation } from '../../types'; | ||
| export declare class Shape { | ||
@@ -6,0 +4,0 @@ private readonly _points; |
| import { TextElement } from '../elements/text-element'; | ||
| import { TextBox } from '../../interfaces/text-box.interface'; | ||
| import { TextBox } from '../../types'; | ||
| export declare class TextSplitter { | ||
@@ -4,0 +4,0 @@ private element; |
+63
-0
@@ -6,1 +6,64 @@ export type ImageSource = HTMLCanvasElement | HTMLImageElement | HTMLVideoElement; | ||
| }; | ||
| export interface BorderRadius { | ||
| topRight: number; | ||
| topLeft: number; | ||
| bottomLeft: number; | ||
| bottomRight: number; | ||
| } | ||
| export interface CropArea { | ||
| size?: Size; | ||
| position?: Position; | ||
| } | ||
| export interface Dimensions { | ||
| position: Position; | ||
| size: Size; | ||
| } | ||
| export interface ExtractedValue { | ||
| value: number; | ||
| unit: 'px' | '%'; | ||
| } | ||
| export interface Padding { | ||
| top: number; | ||
| right: number; | ||
| bottom: number; | ||
| left: number; | ||
| } | ||
| export interface Position { | ||
| x: number; | ||
| y: number; | ||
| } | ||
| export interface Size { | ||
| width: number; | ||
| height: number; | ||
| } | ||
| export interface Scale { | ||
| x: number; | ||
| y: number; | ||
| } | ||
| export interface RelativeProps { | ||
| position?: Position; | ||
| scale?: Scale; | ||
| opacity?: number; | ||
| rotations?: Rotation[]; | ||
| } | ||
| export interface Rotation { | ||
| degrees: number; | ||
| pivot?: Position; | ||
| } | ||
| export interface Shadow { | ||
| color: string; | ||
| blur: number; | ||
| x: number; | ||
| y: number; | ||
| } | ||
| export interface TextBox { | ||
| text: string; | ||
| lines: TextLine[]; | ||
| size: Size; | ||
| position: Position; | ||
| } | ||
| export interface TextLine { | ||
| text: string; | ||
| size: Size; | ||
| position: Position; | ||
| } |
+34
-14
| { | ||
| "name": "@armniko/canvas", | ||
| "version": "2.0.0", | ||
| "description": "Simplified and object-based drawing on HTML canvas!", | ||
| "version": "2.1.0", | ||
| "description": "A lightweight, object-based abstraction over the HTML Canvas 2D API. Compose drawings declaratively with chainable element builders.", | ||
| "author": "Armīns Nikolajevs <armins.nikolajevs@gmail.com>", | ||
@@ -12,10 +12,31 @@ "license": "MIT", | ||
| "canvas", | ||
| "html5-canvas", | ||
| "canvas-2d", | ||
| "2d-graphics", | ||
| "2d-drawing", | ||
| "drawing", | ||
| "html canvas", | ||
| "object-based" | ||
| "graphics", | ||
| "rendering", | ||
| "typescript", | ||
| "object-oriented", | ||
| "gradient", | ||
| "shapes", | ||
| "text-rendering", | ||
| "reactive", | ||
| "change-detection" | ||
| ], | ||
| "type": "module", | ||
| "sideEffects": false, | ||
| "main": "./dist/index.umd.js", | ||
| "module": "./dist/index.esm.js", | ||
| "typings": "./dist/index.d.ts", | ||
| "type": "module", | ||
| "types": "./dist/index.d.ts", | ||
| "exports": { | ||
| ".": { | ||
| "types": "./dist/index.d.ts", | ||
| "import": "./dist/index.esm.js", | ||
| "require": "./dist/index.umd.js", | ||
| "default": "./dist/index.umd.js" | ||
| }, | ||
| "./package.json": "./package.json" | ||
| }, | ||
| "files": [ | ||
@@ -39,5 +60,5 @@ "dist" | ||
| "@types/node": "^24.12.2", | ||
| "@typescript-eslint/eslint-plugin": "^8.58.2", | ||
| "@typescript-eslint/parser": "^8.58.2", | ||
| "@vitest/coverage-v8": "^4.1.4", | ||
| "@typescript-eslint/eslint-plugin": "^8.59.0", | ||
| "@typescript-eslint/parser": "^8.59.0", | ||
| "@vitest/coverage-v8": "^4.1.5", | ||
| "eslint": "^10.2.1", | ||
@@ -49,10 +70,9 @@ "eslint-config-prettier": "^10.1.8", | ||
| "prettier": "^3.8.3", | ||
| "puppeteer": "^24.41.0", | ||
| "terser": "^5.46.1", | ||
| "puppeteer": "^24.42.0", | ||
| "typescript": "^6.0.3", | ||
| "typescript-eslint": "^8.58.2", | ||
| "vite": "^8.0.8", | ||
| "typescript-eslint": "^8.59.0", | ||
| "vite": "^8.0.10", | ||
| "vite-plugin-dts": "^4.5.4", | ||
| "vitest": "^4.1.4" | ||
| "vitest": "^4.1.5" | ||
| } | ||
| } |
+21
-2
@@ -8,8 +8,18 @@ # Canvas | ||
| Simplified and object-based drawing on HTML canvas! | ||
| A lightweight, object-based abstraction over the HTML Canvas 2D API. Compose drawings declaratively with chainable | ||
| element builders. | ||
| <hr> | ||
| ### Why Canvas? | ||
| - 🧩 **Object-based** — describe what to draw, not how to draw it | ||
| - 🧮 **Reactive-friendly** — pass functions (or signals) into any setter; attributes update with your app state, no | ||
| manual rewiring | ||
| - 🔍 **Built-in change detection** — every element exposes `getHash()`, making memoization and dirty-checking trivial | ||
| - 🌐 **Framework-agnostic** — drop into Vue, Angular, React, or vanilla TS | ||
| - 📦 **Zero dependencies** and fully typed | ||
| ### Installation | ||
| ``` | ||
| ```bash | ||
| npm install @armniko/canvas | ||
@@ -59,2 +69,11 @@ ``` | ||
| <tr> | ||
| <td>v2.1.0</td> | ||
| <td> | ||
| <b>Technical:</b><br> | ||
| - removed minification of build<br> | ||
| - added <code>exports</code> field for proper module resolution and types<br> | ||
| - marked package as side-effect free | ||
| </td> | ||
| </tr> | ||
| <tr> | ||
| <td>v2.0.0</td> | ||
@@ -61,0 +80,0 @@ <td> |
| export declare enum ColorType { | ||
| SolidColor = "solid-color", | ||
| LinearGradient = "linear-gradient", | ||
| RadialGradient = "radial-gradient" | ||
| } |
| export declare enum ElementType { | ||
| Rectangle = "rectangle", | ||
| Ellipse = "ellipse", | ||
| Polygon = "polygon", | ||
| Line = "line", | ||
| Text = "text", | ||
| Image = "image" | ||
| } |
| export declare enum FontWeight { | ||
| Normal = "normal", | ||
| Bold = "bold" | ||
| } |
| export declare enum HorizontalAlign { | ||
| Left = "left", | ||
| Center = "center", | ||
| Right = "right" | ||
| } |
| export declare enum ImageFit { | ||
| Fill = "fill", | ||
| Cover = "cover" | ||
| } |
| export declare enum LineCap { | ||
| Round = "round", | ||
| Square = "square" | ||
| } |
| export declare enum PositionPoint { | ||
| LeftTop = "left-top", | ||
| RightTop = "right-top", | ||
| LeftBottom = "left-bottom", | ||
| RightBottom = "right-bottom", | ||
| Center = "center" | ||
| } |
| export declare enum VerticalAlign { | ||
| Top = "top", | ||
| Center = "center", | ||
| Bottom = "bottom" | ||
| } |
| export interface BorderRadius { | ||
| topRight: number; | ||
| topLeft: number; | ||
| bottomLeft: number; | ||
| bottomRight: number; | ||
| } |
| import { Size } from './size.interface'; | ||
| import { Position } from './position.interface'; | ||
| export interface CropArea { | ||
| size?: Size; | ||
| position?: Position; | ||
| } |
| import { Size } from './size.interface'; | ||
| import { Position } from './position.interface'; | ||
| export interface Dimensions { | ||
| position: Position; | ||
| size: Size; | ||
| } |
| export interface ExtractedValue { | ||
| value: number; | ||
| unit: 'px' | '%'; | ||
| } |
| export interface Padding { | ||
| top: number; | ||
| right: number; | ||
| bottom: number; | ||
| left: number; | ||
| } |
| export interface Position { | ||
| x: number; | ||
| y: number; | ||
| } | ||
| /** | ||
| * @deprecated This interface is deprecated. Use `Position` instead. | ||
| */ | ||
| export type PositionInterface = Position; |
| import { Position } from './position.interface'; | ||
| import { Scale } from './scale.interface'; | ||
| import { Rotation } from './rotation.interface'; | ||
| export interface RelativeProps { | ||
| position?: Position; | ||
| scale?: Scale; | ||
| opacity?: number; | ||
| rotations?: Rotation[]; | ||
| } |
| import { Position } from './position.interface'; | ||
| export interface Rotation { | ||
| degrees: number; | ||
| pivot?: Position; | ||
| } |
| export interface Scale { | ||
| x: number; | ||
| y: number; | ||
| } |
| export interface Shadow { | ||
| color: string; | ||
| blur: number; | ||
| x: number; | ||
| y: number; | ||
| } |
| export interface Size { | ||
| width: number; | ||
| height: number; | ||
| } | ||
| /** | ||
| * @deprecated This interface is deprecated. Use `Size` instead. | ||
| */ | ||
| export type SizeInterface = Size; |
| import { Size } from './size.interface'; | ||
| import { Position } from './position.interface'; | ||
| import { TextLine } from './text-line.interface'; | ||
| export interface TextBox { | ||
| text: string; | ||
| lines: TextLine[]; | ||
| size: Size; | ||
| position: Position; | ||
| } |
| import { Size } from './size.interface'; | ||
| import { Position } from './position.interface'; | ||
| export interface TextLine { | ||
| text: string; | ||
| size: Size; | ||
| position: Position; | ||
| } |
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Minified code
QualityThis package contains minified code. This may be harmless in some cases where minified code is included in packaged libraries, however packages on npm should not minify code.
135971
47.46%18
-5.26%4388
556.89%1
-66.67%125
17.92%35
-36.36%