@obliczeniowo/elementary
Version:
Library made in Angular version 19
1 lines • 96 kB
Source Map (JSON)
{"version":3,"file":"obliczeniowo-elementary-drawing.mjs","sources":["../../../../projects/components/drawing/drawing-context-interface.ts","../../../../projects/components/drawing/drawing-canvas-interface.ts","../../../../projects/components/drawing/drawing-svg-interface.ts","../../../../projects/components/drawing/drawing-openscad-interface.ts","../../../../projects/components/drawing/drawing-zwcad-command-interface.ts","../../../../projects/components/drawing/rect.ts","../../../../projects/components/drawing/obliczeniowo-elementary-drawing.ts"],"sourcesContent":["/* eslint-disable @typescript-eslint/no-unsafe-argument */\r\nimport { ColorType, Point2D } from '@obliczeniowo/elementary/classes';\r\nimport { Size } from '@obliczeniowo/elementary/resize-window';\r\n\r\nexport enum TextAlign {\r\n LEFT = 'left',\r\n CENTER = 'center',\r\n RIGHT = 'right',\r\n JUSTIFY = 'justify',\r\n}\r\n\r\nexport enum TextBaseline {\r\n ALPHABETIC = 'alphabetic',\r\n IDEOGRAPHIC = 'ideographic',\r\n BOTTOM = 'bottom',\r\n TOP = 'top',\r\n MIDDLE = 'middle',\r\n HANGING = 'hanging',\r\n}\r\n\r\nexport enum LinePattern {\r\n NONE,\r\n DOTTED,\r\n DASHED,\r\n DASH_DOT,\r\n DISABLED,\r\n}\r\n\r\nexport interface LinePatternDef {\r\n strokeMiterlimit: number;\r\n strokeDasharray: number[];\r\n strokeDashoffset: number;\r\n}\r\n\r\nexport interface Drawable {\r\n draw: (ctx: DrawingContextInterface) => void;\r\n}\r\n\r\n/**\r\n * Abstract class to easy cooperate and switch drawing context logic\r\n */\r\nexport abstract class DrawingContextInterface {\r\n protected lastPoint: Point2D = new Point2D();\r\n protected arrowScale = 1;\r\n\r\n /** optional data that can be set after call draw method */\r\n lastSettings: { text?: Size } = {};\r\n\r\n /**\r\n * Some object required clear option before redraw (DrawingSvgInterface for example)\r\n */\r\n abstract clear(): void;\r\n\r\n abstract getTextDimension(text: string): { width: number; height: number };\r\n\r\n /**\r\n * Draw line\r\n * @param startPoint start point\r\n * @param endPoint end point\r\n * @param stroke stroke width\r\n * @param color stroke color as string in format #ffffff or Color object\r\n * @param options specific for object under the hood object\r\n */\r\n abstract drawLine(\r\n startPoint: Point2D,\r\n endPoint: Point2D,\r\n stroke: number,\r\n color: ColorType,\r\n options?: any\r\n ): DrawingContextInterface;\r\n\r\n /**\r\n * Draw text\r\n * @param text text to draw\r\n * @param handlePosition handle position\r\n * @param color color as string in format #ffffff or Color object\r\n * @param angle rotate angle in radians\r\n * @param options specific options for object under the hood\r\n */\r\n abstract drawText(\r\n text: string,\r\n handlePosition: Point2D,\r\n color: ColorType,\r\n angle: number,\r\n options?: any\r\n ): DrawingContextInterface;\r\n\r\n /**\r\n * Draw polyline using table of points\r\n * @param points table of points\r\n * @param stroke stroke width\r\n * @param color stroke color as string in format #ffffff or Color object\r\n * @param options specific for object under hood\r\n */\r\n abstract drawPolyline(\r\n points: Point2D[],\r\n stroke: number,\r\n color: ColorType,\r\n options?: { close?: boolean; [key: string]: any }\r\n ): DrawingContextInterface;\r\n\r\n abstract drawPolygon(\r\n points: Point2D[],\r\n stroke: number,\r\n color: ColorType,\r\n fill: ColorType,\r\n options?: any\r\n ): DrawingContextInterface;\r\n\r\n /**\r\n * Draw circle\r\n * @param point center point\r\n * @param ray ray\r\n * @param stroke stroke width\r\n * @param strokeColor stroke color as string in format #ffffff or Color object\r\n * @param fillColor fill color as string in format #ffffff or Color object\r\n * @param options options specific for object under the hood\r\n */\r\n abstract drawCircle(\r\n point: Point2D,\r\n ray: number,\r\n stroke: number,\r\n strokeColor: ColorType,\r\n fillColor: ColorType,\r\n options?: any\r\n ): DrawingContextInterface;\r\n\r\n /**\r\n * Draw pie\r\n * @param center center\r\n * @param rx rx\r\n * @param ry ry (some object could not draw this correctly when rx !== ry)\r\n * @param start start angle in radians\r\n * @param end end angle in radians\r\n * @param stroke stroke width\r\n * @param strokeColor stroke color as string in format #ffffff or Color object\r\n * @param fillColor fill color as string in format #ffffff or Color object\r\n * @param options options specific for object under the hood\r\n */\r\n abstract drawPie(\r\n center: Point2D,\r\n rx: number,\r\n ry: number,\r\n start: number,\r\n end: number,\r\n stroke: number,\r\n strokeColor: ColorType,\r\n fillColor: ColorType,\r\n options?: any\r\n ): DrawingContextInterface;\r\n\r\n /**\r\n * Draw ellipse\r\n * @param point center\r\n * @param xRay x ray\r\n * @param yRay y ray\r\n * @param stroke stroke width\r\n * @param strokeColor stroke color as string in format #ffffff or as Color object\r\n * @param fillColor fill color as string in format #ffffff or as Color object\r\n * @param options external options specific for different object under the hood\r\n */\r\n abstract drawEllipse(\r\n point: Point2D,\r\n xRay: number,\r\n yRay: number,\r\n stroke: number,\r\n strokeColor: ColorType,\r\n fillColor: ColorType,\r\n options?: any\r\n ): DrawingContextInterface;\r\n\r\n /**\r\n * Draw rectangle\r\n * @param x coordinate\r\n * @param y coordinate\r\n * @param width rect width\r\n * @param height rect height\r\n * @param stroke stroke width\r\n * @param strokeColor stroke color\r\n * @param fillColor fill color\r\n * @param options extra options (specific for object under the hood)\r\n */\r\n abstract drawRect(\r\n x: number,\r\n y: number,\r\n width: number,\r\n height: number,\r\n stroke: number,\r\n strokeColor: ColorType,\r\n fillColor: ColorType,\r\n options?: any\r\n ): DrawingContextInterface;\r\n\r\n abstract lineTo(\r\n point: Point2D,\r\n stroke: number,\r\n color: ColorType\r\n ): DrawingContextInterface;\r\n\r\n /**\r\n * Set font size\r\n * @param fontSize font size\r\n */\r\n abstract setFontSize(fontSize: number): DrawingContextInterface;\r\n\r\n /** */\r\n abstract getFontSize(): number;\r\n\r\n /**\r\n * Set text align flag\r\n * @param align text align enum\r\n */\r\n abstract setTextAlign(align: TextAlign): DrawingContextInterface;\r\n\r\n /**\r\n * Set text baseline\r\n * @param textBaseline text baseline enum\r\n */\r\n abstract setTextBaseline(textBaseline: TextBaseline): DrawingContextInterface;\r\n\r\n /**\r\n * Return text width of given text\r\n */\r\n abstract getTextWidth(text: string): number;\r\n\r\n group(options?: any): this {\r\n return this;\r\n }\r\n\r\n endGroup(): this {\r\n return this;\r\n }\r\n\r\n /**\r\n * Set line pattern\r\n * @param linePattern LinePattern enum or string or undefined to set default patterns\r\n * @param linePatternDef to set own pattern definition\r\n */\r\n abstract setLinePattern(\r\n linePattern: LinePattern | string | undefined,\r\n linePatternDef?: LinePatternDef\r\n ): DrawingContextInterface;\r\n\r\n moveTo(point: Point2D): this {\r\n this.lastPoint = point;\r\n return this;\r\n }\r\n\r\n drawArrow(\r\n startPoint: Point2D,\r\n endPoint: Point2D,\r\n stroke: number,\r\n color: ColorType,\r\n drawArrow: boolean = true,\r\n options?: any\r\n ): this {\r\n this.group({ name: 'arrow' });\r\n\r\n this.drawLine(startPoint, endPoint, stroke, color, options);\r\n\r\n if (drawArrow) {\r\n let arrowVector = startPoint.subtract(endPoint);\r\n\r\n arrowVector = arrowVector.multiply(\r\n 10 / arrowVector.length() * this.arrowScale\r\n );\r\n\r\n const firstArrowPoint = arrowVector\r\n .rotate(15 * Math.PI / 180)\r\n .add(endPoint);\r\n\r\n const secondArrowPoint = arrowVector\r\n .rotate(-15 * Math.PI / 180)\r\n .add(endPoint);\r\n\r\n this.drawPolyline(\r\n [firstArrowPoint, endPoint, secondArrowPoint],\r\n stroke,\r\n color,\r\n options\r\n );\r\n }\r\n\r\n this.endGroup();\r\n\r\n return this;\r\n }\r\n\r\n abstract getLineStrokeSize(size?: number): number;\r\n\r\n abstract save(fileName: string): void;\r\n\r\n /**\r\n * Draw all kind of object that implements Drawable interface\r\n * @param drawable table of object that implements Drawable interface\r\n */\r\n draw(drawable: Drawable[]): this {\r\n drawable.forEach((drawable) => drawable.draw(this));\r\n return this;\r\n }\r\n}\r\n","import { Point2D, ColorType } from '@obliczeniowo/elementary/classes';\r\nimport {\r\n DrawingContextInterface,\r\n LinePattern,\r\n LinePatternDef,\r\n TextAlign,\r\n TextBaseline,\r\n} from './drawing-context-interface';\r\n\r\nexport class DrawingCanvasInterface extends DrawingContextInterface {\r\n ctx!: CanvasRenderingContext2D;\r\n\r\n protected _stroke = 0;\r\n\r\n public get stroke(): number {\r\n return this._stroke;\r\n }\r\n\r\n public set stroke(value: number) {\r\n this.ctx.lineWidth = value;\r\n this._stroke = value;\r\n }\r\n\r\n constructor(ctx: CanvasRenderingContext2D) {\r\n super();\r\n\r\n this.ctx = ctx;\r\n }\r\n\r\n getTextDimension(text: string): { width: number; height: number } {\r\n const textMetrics = this.ctx.measureText(text);\r\n return {\r\n width: textMetrics.width,\r\n height:\r\n Math.abs(textMetrics.fontBoundingBoxAscent) +\r\n Math.abs(textMetrics.fontBoundingBoxDescent),\r\n };\r\n }\r\n\r\n clear(): void {\r\n const rect = this.ctx.canvas.getBoundingClientRect();\r\n const pt = this.transform(new Point2D());\r\n this.ctx.clearRect(pt.x, pt.y, rect.width, rect.height);\r\n }\r\n\r\n drawLine(\r\n startPoint: Point2D,\r\n endPoint: Point2D,\r\n stroke: number,\r\n color: ColorType,\r\n options?: any\r\n ): this {\r\n this.stroke = stroke;\r\n this.ctx.strokeStyle = color.toString();\r\n this.ctx.beginPath();\r\n this.ctx.moveTo(startPoint.x, startPoint.y);\r\n this.ctx.lineTo(endPoint.x, endPoint.y);\r\n this.ctx.stroke();\r\n\r\n return this;\r\n }\r\n\r\n drawText(\r\n text: string,\r\n handlePosition: Point2D,\r\n color: ColorType,\r\n angle: number,\r\n options?: any\r\n ): this {\r\n this.ctx.fillStyle = color.toString();\r\n this.ctx.save();\r\n this.ctx.rotate(angle);\r\n this.ctx.translate(handlePosition.x, handlePosition.y);\r\n this.ctx.fillText(text, 0, 0);\r\n this.ctx.restore();\r\n\r\n return this;\r\n }\r\n\r\n drawPolyline(\r\n points: Point2D[],\r\n stroke: number,\r\n color: ColorType,\r\n options?: { close?: boolean; [key: string]: any }\r\n ): this {\r\n this.stroke = stroke;\r\n this.ctx.strokeStyle = color.toString();\r\n this.ctx.beginPath();\r\n if (points.length > 1) {\r\n this.ctx.moveTo(points[0].x, points[0].y);\r\n let point;\r\n for (let i = 1; i < points.length; i++) {\r\n point = points[i];\r\n this.ctx.lineTo(point.x, point.y);\r\n }\r\n if (options?.close) {\r\n this.ctx.lineTo(points[0].x, points[0].y);\r\n }\r\n }\r\n this.ctx.stroke();\r\n\r\n return this;\r\n }\r\n\r\n drawPolygon(\r\n points: Point2D[],\r\n stroke: number,\r\n color: ColorType | 'none',\r\n fill: ColorType | 'none',\r\n options?: { close?: boolean; [key: string]: any }\r\n ): this {\r\n this.stroke = stroke;\r\n this.ctx.strokeStyle = color.toString();\r\n this.ctx.beginPath();\r\n if (points.length > 1) {\r\n this.ctx.moveTo(points[0].x, points[0].y);\r\n let point;\r\n for (let i = 1; i < points.length; i++) {\r\n point = points[i];\r\n this.ctx.lineTo(point.x, point.y);\r\n }\r\n if (options?.close) {\r\n this.ctx.lineTo(points[0].x, points[0].y);\r\n }\r\n }\r\n this.ctx.closePath();\r\n if (color !== 'none') {\r\n this.ctx.stroke();\r\n }\r\n if (fill !== 'none') {\r\n this.ctx.fill();\r\n }\r\n\r\n return this;\r\n }\r\n\r\n drawCircle(\r\n point: Point2D,\r\n ray: number,\r\n stroke: number,\r\n strokeColor: ColorType,\r\n fillColor: ColorType,\r\n options?: any\r\n ): this {\r\n this.ctx.strokeStyle = strokeColor.toString();\r\n this.stroke = stroke;\r\n this.ctx.fillStyle = fillColor.toString();\r\n\r\n const draw = () =>\r\n this.ctx.ellipse(point.x, point.y, ray, ray, 0, 0, Math.PI * 2);\r\n\r\n this.path(draw);\r\n this.path(draw, true);\r\n\r\n return this;\r\n }\r\n\r\n drawPie(\r\n center: Point2D,\r\n rx: number,\r\n ry: number,\r\n start: number,\r\n end: number,\r\n stroke: number,\r\n strokeColor: ColorType,\r\n fillColor: ColorType,\r\n options?: any\r\n ): this {\r\n if (rx !== ry) {\r\n console.warn(`Drawing pie with ${rx} !== ${ry} is imposable right now`);\r\n }\r\n\r\n this.stroke = stroke;\r\n this.ctx.strokeStyle = strokeColor.toString();\r\n this.ctx.fillStyle = fillColor.toString();\r\n const draw = () => {\r\n this.ctx.arc(center.x, center.y, rx, start, end);\r\n this.ctx.lineTo(center.x, center.y);\r\n };\r\n\r\n this.path(draw);\r\n this.path(draw, true);\r\n\r\n return this;\r\n }\r\n\r\n drawEllipse(\r\n point: Point2D,\r\n xRay: number,\r\n yRay: number,\r\n stroke: number,\r\n strokeColor: ColorType,\r\n fillColor: ColorType,\r\n options?: any\r\n ): this {\r\n this.ctx.strokeStyle = strokeColor.toString();\r\n this.stroke = stroke;\r\n this.ctx.fillStyle = fillColor.toString();\r\n\r\n const draw = () =>\r\n this.ctx.ellipse(point.x, point.y, xRay, yRay, 0, 0, Math.PI * 2);\r\n\r\n this.path(draw);\r\n this.path(draw, true);\r\n\r\n return this;\r\n }\r\n\r\n drawRect(\r\n x: number,\r\n y: number,\r\n width: number,\r\n height: number,\r\n stroke: number,\r\n strokeColor: ColorType,\r\n fillColor: ColorType,\r\n options?: any\r\n ): this {\r\n this.ctx.strokeStyle = strokeColor.toString();\r\n this.stroke = stroke;\r\n this.ctx.fillStyle = fillColor.toString();\r\n\r\n this.ctx.fillRect(x, y, x + width, y + height);\r\n\r\n return this;\r\n }\r\n\r\n lineTo(point: Point2D, stroke: number, color: ColorType): this {\r\n this.ctx.lineTo(point.x, point.y);\r\n\r\n return this;\r\n }\r\n\r\n setFontSize(fontSize: number): this {\r\n this.ctx.font = fontSize + 'px';\r\n\r\n return this;\r\n }\r\n\r\n getFontSize(): number {\r\n const metrics = this.ctx.measureText('Ty');\r\n return metrics.fontBoundingBoxAscent + metrics.fontBoundingBoxDescent;\r\n }\r\n\r\n setTextAlign(align: TextAlign): this {\r\n this.ctx.textAlign = align as CanvasTextAlign;\r\n\r\n return this;\r\n }\r\n\r\n setTextBaseline(textBaseline: TextBaseline): this {\r\n this.ctx.textBaseline = textBaseline as CanvasTextBaseline;\r\n\r\n return this;\r\n }\r\n\r\n getTextWidth(text: string): number {\r\n return this.ctx.measureText(text).width;\r\n }\r\n\r\n setLinePattern(\r\n linePattern: string | LinePattern | undefined,\r\n linePatternDef?: LinePatternDef\r\n ): this {\r\n switch (linePattern) {\r\n case LinePattern.DASHED:\r\n this.ctx.setLineDash([4, 2]);\r\n break;\r\n case LinePattern.DOTTED:\r\n this.ctx.setLineDash([2, 1]);\r\n break;\r\n case LinePattern.DASH_DOT:\r\n this.ctx.setLineDash([10, 2, 1, 2]);\r\n break;\r\n default:\r\n if (linePatternDef) {\r\n this.ctx.setLineDash(linePatternDef.strokeDasharray);\r\n }\r\n }\r\n return this;\r\n }\r\n\r\n getLineStrokeSize(size?: number): number {\r\n if (size) {\r\n this.stroke = size;\r\n }\r\n return this._stroke;\r\n }\r\n\r\n transform(point: Point2D): Point2D {\r\n const m = DOMMatrix.fromMatrix(this.ctx.getTransform());\r\n m.invertSelf();\r\n\r\n return new Point2D(\r\n m.a * point.x + m.b * point.y + m.e,\r\n m.c * point.x + m.d * point.y + m.f\r\n );\r\n }\r\n\r\n save(fileName: string) {\r\n const downloadLink = document.createElement('a');\r\n downloadLink.setAttribute('download', `${fileName}.png`);\r\n const canvas = this.ctx.canvas;\r\n const dataURL = canvas.toDataURL('image/png');\r\n const url = dataURL.replace(/^data:image\\/png/, 'data:application/octet-stream');\r\n downloadLink.setAttribute('href', url);\r\n downloadLink.click();\r\n }\r\n\r\n protected path(cb: () => void, fillOrStroke?: boolean): void {\r\n this.ctx.beginPath();\r\n cb();\r\n if (fillOrStroke) {\r\n this.ctx.fill();\r\n } else {\r\n this.ctx.stroke();\r\n }\r\n }\r\n}\r\n","/* eslint-disable @typescript-eslint/non-nullable-type-assertion-style */\r\n/* eslint-disable @typescript-eslint/no-unsafe-argument */\r\nimport { Point2D, ColorType } from '@obliczeniowo/elementary/classes';\r\nimport { ns } from '@obliczeniowo/elementary/svg';\r\nimport { keys } from '@obliczeniowo/elementary/objects';\r\nimport { Size } from '@obliczeniowo/elementary/resize-window';\r\nimport { TextSizePipe } from '@obliczeniowo/elementary/text-pipes';\r\n\r\nimport {\r\n DrawingContextInterface,\r\n LinePattern,\r\n LinePatternDef,\r\n TextAlign,\r\n TextBaseline,\r\n} from './drawing-context-interface';\r\nimport { Renderer2 } from '@angular/core';\r\n\r\ninterface SvgOnly {\r\n classes?: string[];\r\n events?: { [name: string]: (event: any) => void };\r\n attributes?: { [name: string]: any };\r\n}\r\n\r\ninterface SvgOptions {\r\n svgOnly?: SvgOnly;\r\n [key: string]: any;\r\n}\r\n\r\ninterface SvgPolylineOptions extends SvgOptions {\r\n close?: boolean;\r\n}\r\n\r\nexport class DrawingSvgInterface extends DrawingContextInterface {\r\n getTextDimension(text: string): Size {\r\n return new TextSizePipe().transform(text, this.fontSize) as any;\r\n }\r\n\r\n static archPath(\r\n center: Point2D,\r\n rx: number,\r\n ry: number,\r\n start: number,\r\n end: number,\r\n reverse: boolean = false\r\n ): string | undefined {\r\n if (Math.abs(start - end) < Math.PI * 2) {\r\n let flag = start > end ? 1 : 0;\r\n if (\r\n Math.abs(start - end) -\r\n Math.floor(Math.abs(start - end) / (Math.PI * 2)) >\r\n Math.PI\r\n ) {\r\n flag = start > end ? 0 : 1;\r\n }\r\n\r\n const fP = new Point2D(\r\n center.x + rx * Math.cos(start),\r\n center.y + ry * Math.sin(start)\r\n );\r\n const lP = new Point2D(\r\n center.x + rx * Math.cos(end),\r\n center.y + ry * Math.sin(end)\r\n );\r\n\r\n return (\r\n 'M ' +\r\n ((reverse ? -lP.x : fP.x) +\r\n ',' +\r\n (reverse ? lP.y : fP.y) +\r\n ' A ' +\r\n rx +\r\n ',' +\r\n ry +\r\n ' 0 ' +\r\n flag +\r\n ' 1 ' +\r\n (reverse ? -fP.x : lP.x) +\r\n ',' +\r\n (reverse ? fP.y : lP.y))\r\n );\r\n }\r\n\r\n return undefined;\r\n }\r\n\r\n private readonly svg: SVGSVGElement;\r\n\r\n private readonly root?: SVGGElement;\r\n\r\n private xMinimum: number | null = null;\r\n private xMaximum: number | null = null;\r\n private yMinimum: number | null = null;\r\n private yMaximum: number | null = null;\r\n\r\n private textAnchor: 'start' | 'middle' | 'end' = 'start';\r\n private linePatternName: string | LinePattern = LinePattern.NONE;\r\n\r\n private defs!: SVGDefsElement;\r\n\r\n private readonly linesDefs: Map<string | LinePattern, LinePatternDef> =\r\n new Map<string | LinePattern, LinePatternDef>();\r\n\r\n private readonly groups: SVGElement[] = [];\r\n\r\n private readonly renderer!: Renderer2;\r\n\r\n fontSize = 10;\r\n\r\n constructor(svg: SVGSVGElement, renderer: Renderer2) {\r\n super();\r\n\r\n this.renderer = renderer;\r\n\r\n this.svg = svg;\r\n\r\n const root = svg.querySelector('g.root') as SVGGElement;\r\n\r\n this.root = root || this.renderer.createElement('g', ns);\r\n\r\n if (!root) {\r\n this.svg.appendChild(this.root);\r\n }\r\n }\r\n\r\n private setXMin(value: number): void {\r\n if (this.xMinimum !== null) {\r\n if (this.xMinimum >= value) {\r\n this.xMinimum = value;\r\n }\r\n } else {\r\n this.xMinimum = value;\r\n }\r\n }\r\n\r\n private setYMin(value: number): void {\r\n if (this.yMinimum !== null) {\r\n if (this.yMinimum >= value) {\r\n this.yMinimum = value;\r\n }\r\n } else {\r\n this.yMinimum = value;\r\n }\r\n }\r\n\r\n private setXMax(value: number): void {\r\n if (this.xMaximum !== null) {\r\n if (this.xMaximum <= value) {\r\n this.xMaximum = value;\r\n }\r\n } else {\r\n this.xMaximum = value;\r\n }\r\n }\r\n\r\n private setYMax(value: number): void {\r\n if (this.yMaximum !== null) {\r\n if (this.yMaximum <= value) {\r\n this.yMaximum = value;\r\n }\r\n } else {\r\n this.yMaximum = value;\r\n }\r\n }\r\n\r\n private setMinMax(point: Point2D): void {\r\n this.setXMin(point.x);\r\n this.setXMax(point.x);\r\n\r\n this.setYMin(point.y);\r\n this.setYMax(point.y);\r\n }\r\n\r\n protected setSvgOptions(element: SVGElement, options?: any): void {\r\n if (options) {\r\n const { opacity, style } = options;\r\n const { attributes } = options?.svgOnly || {};\r\n\r\n if (opacity) {\r\n element.setAttribute('opacity', options.opacity.toString() || 1);\r\n }\r\n\r\n if (attributes) {\r\n keys(attributes).forEach((name) => {\r\n element.setAttribute(name, attributes[name]);\r\n });\r\n }\r\n\r\n if (style) {\r\n keys(options.style).forEach(\r\n (key: any) => (element.style[key] = options.style?.[key])\r\n );\r\n }\r\n }\r\n }\r\n\r\n protected createSvgPoint(point2d: Point2D): DOMPoint {\r\n const point = this.svg.createSVGPoint();\r\n\r\n point.x = point2d.x;\r\n point.y = point2d.y;\r\n\r\n return point;\r\n }\r\n\r\n protected setSvgElementLinePattern(element: SVGElement): void {\r\n const pattern: LinePatternDef | undefined = this.linesDefs.get(\r\n this.linePatternName\r\n );\r\n\r\n if (pattern) {\r\n element.style.strokeMiterlimit = pattern.strokeMiterlimit.toString();\r\n element.style.strokeDasharray = pattern.strokeDasharray.join(', ');\r\n element.style.strokeDashoffset = pattern.strokeDashoffset.toString();\r\n }\r\n }\r\n\r\n lineTo(point: Point2D, stroke: number, color: ColorType): this {\r\n this.drawLine(this.lastPoint, point, stroke, color);\r\n\r\n this.lastPoint = point;\r\n\r\n return this;\r\n }\r\n\r\n setFontSize(fontSize: number): this {\r\n this.fontSize = fontSize;\r\n\r\n return this;\r\n }\r\n\r\n getFontSize() {\r\n return this.fontSize;\r\n }\r\n\r\n setTextAlign(align: TextAlign): this {\r\n switch (align) {\r\n case TextAlign.LEFT:\r\n this.textAnchor = 'start';\r\n break;\r\n case TextAlign.CENTER:\r\n this.textAnchor = 'middle';\r\n break;\r\n case TextAlign.RIGHT:\r\n this.textAnchor = 'end';\r\n break;\r\n default:\r\n this.textAnchor = 'start';\r\n break;\r\n }\r\n\r\n return this;\r\n }\r\n\r\n setTextBaseline(textBaseline: TextBaseline): this {\r\n return this;\r\n }\r\n\r\n getTextWidth(text: string): number {\r\n const lDiv: HTMLDivElement = this.renderer.createElement('div');\r\n\r\n document.body.appendChild(lDiv);\r\n\r\n lDiv.style.fontSize = this.fontSize + 'px';\r\n lDiv.style.position = 'absolute';\r\n\r\n lDiv.innerHTML = text;\r\n\r\n const lResult = {\r\n width: lDiv.clientWidth,\r\n height: lDiv.clientHeight,\r\n };\r\n\r\n document.body.removeChild(lDiv);\r\n\r\n return lResult.width;\r\n }\r\n\r\n clear(): this {\r\n const container = this.svgContainer;\r\n if (this.defs) {\r\n while (this.defs.hasChildNodes()) {\r\n this.defs.removeChild(this.defs.firstChild as ChildNode);\r\n }\r\n }\r\n while (container.hasChildNodes()) {\r\n container.removeChild(container.firstChild as ChildNode);\r\n }\r\n return this;\r\n }\r\n\r\n setLinePattern(\r\n linePattern: string | LinePattern | undefined,\r\n linePatternDef?: LinePatternDef\r\n ): this {\r\n if (linePattern && !this.linesDefs.has(linePattern)) {\r\n switch (linePattern) {\r\n case LinePattern.DASHED:\r\n this.linesDefs.set(linePattern, {\r\n strokeMiterlimit: 4,\r\n strokeDasharray: [4, 2],\r\n strokeDashoffset: 0,\r\n });\r\n break;\r\n case LinePattern.DOTTED:\r\n this.linesDefs.set(linePattern, {\r\n strokeMiterlimit: 4,\r\n strokeDasharray: [2, 1],\r\n strokeDashoffset: 0,\r\n });\r\n break;\r\n case LinePattern.DASH_DOT:\r\n this.linesDefs.set(linePattern, {\r\n strokeMiterlimit: 0,\r\n strokeDasharray: [10, 2, 1, 2],\r\n strokeDashoffset: 0,\r\n });\r\n break;\r\n default:\r\n if (linePatternDef) {\r\n this.linesDefs.set(linePattern, linePatternDef);\r\n }\r\n }\r\n }\r\n\r\n this.linePatternName = linePattern || LinePattern.NONE;\r\n\r\n return this;\r\n }\r\n\r\n drawLine(\r\n startPoint: Point2D,\r\n endPoint: Point2D,\r\n stroke: number = 1,\r\n color: ColorType = '#000000',\r\n options?: SvgOptions\r\n ): this {\r\n this.setMinMax(startPoint);\r\n this.setMinMax(endPoint);\r\n\r\n const svgPolyline: SVGPolylineElement = this.renderer.createElement(\r\n 'polyline',\r\n ns\r\n );\r\n\r\n if (!this.setClasses(svgPolyline, options)) {\r\n this.setSvgElementLinePattern(svgPolyline);\r\n svgPolyline.style.strokeWidth = stroke.toString();\r\n svgPolyline.style.stroke = this.color(color);\r\n }\r\n\r\n svgPolyline.points.appendItem(this.createSvgPoint(startPoint));\r\n\r\n svgPolyline.points.appendItem(this.createSvgPoint(endPoint));\r\n\r\n this.setSvgOptions(svgPolyline, options);\r\n\r\n this.svgContainer.append(svgPolyline);\r\n\r\n return this;\r\n }\r\n\r\n drawCircle(\r\n point: Point2D,\r\n ray: number,\r\n stroke: number = 1,\r\n strokeColor: ColorType = '#000000',\r\n fillColor: ColorType = 'none',\r\n options?: SvgOptions\r\n ): this {\r\n const svgCircle: SVGCircleElement = this.renderer.createElement('circle', ns);\r\n\r\n svgCircle.setAttribute('cx', point.x.toString());\r\n svgCircle.setAttribute('cy', point.y.toString());\r\n svgCircle.setAttribute('r', ray.toString());\r\n\r\n if (!this.setClasses(svgCircle, options)) {\r\n this.setSvgElementLinePattern(svgCircle);\r\n svgCircle.style.strokeWidth = stroke.toString();\r\n svgCircle.style.stroke = this.color(strokeColor);\r\n svgCircle.style.fill = this.color(fillColor);\r\n }\r\n\r\n this.setSvgOptions(svgCircle, options);\r\n\r\n this.svgContainer.append(svgCircle);\r\n\r\n return this;\r\n }\r\n\r\n drawPie(\r\n center: Point2D,\r\n rx: number,\r\n ry: number,\r\n start: number,\r\n end: number,\r\n stroke: number,\r\n strokeColor: ColorType = '#000000',\r\n fillColor: ColorType = 'none',\r\n options?: SvgOptions\r\n ): this {\r\n let element;\r\n if (Math.abs(start - end) < Math.PI * 2) {\r\n let flag = 0;\r\n if (\r\n Math.abs(start - end) -\r\n Math.floor(Math.abs(start - end) / (Math.PI * 2)) >\r\n Math.PI\r\n ) {\r\n flag = 1;\r\n }\r\n\r\n element = this.renderer.createElement('path', ns);\r\n\r\n element.setAttribute(\r\n 'd',\r\n 'M ' +\r\n (center.x +\r\n rx * Math.cos(start) +\r\n ',' +\r\n (center.y + ry * Math.sin(start)) +\r\n ' A ' +\r\n rx +\r\n ',' +\r\n ry +\r\n ' 0 ' +\r\n flag +\r\n ' 1 ' +\r\n (center.x + rx * Math.cos(end)) +\r\n ',' +\r\n (center.y + ry * Math.sin(end)) +\r\n ' L ' +\r\n center.x +\r\n ',' +\r\n center.y +\r\n 'z')\r\n );\r\n } else {\r\n element = this.renderer.createElement('ellipse', ns);\r\n element.setAttribute('cx', center.x.toString());\r\n element.setAttribute('cy', center.y.toString());\r\n element.setAttribute('rx', rx.toString());\r\n element.setAttribute('ry', ry.toString());\r\n }\r\n\r\n this.setSvgElementLinePattern(element);\r\n\r\n if (!this.setClasses(element, options)) {\r\n element.style.strokeWidth = stroke.toString();\r\n element.style.stroke = this.color(strokeColor);\r\n element.style.fill = this.color(fillColor);\r\n }\r\n\r\n this.svgContainer.append(element);\r\n\r\n return this;\r\n }\r\n\r\n drawArch(\r\n center: Point2D,\r\n rx: number,\r\n ry: number,\r\n start: number,\r\n end: number,\r\n stroke: number,\r\n strokeColor: ColorType = '#000000',\r\n options?: SvgOptions\r\n ): this {\r\n let element;\r\n const path = DrawingSvgInterface.archPath(center, rx, ry, start, end);\r\n if (path) {\r\n element = this.renderer.createElement('path', ns);\r\n\r\n element.setAttribute('d', path);\r\n } else {\r\n element = this.renderer.createElement('ellipse', ns);\r\n element.setAttribute('cx', center.x.toString());\r\n element.setAttribute('cy', center.y.toString());\r\n element.setAttribute('rx', rx.toString());\r\n element.setAttribute('ry', ry.toString());\r\n }\r\n\r\n if (!this.setClasses(element, options)) {\r\n this.setSvgElementLinePattern(element);\r\n\r\n element.style.strokeWidth = stroke.toString();\r\n element.style.stroke = this.color(strokeColor);\r\n element.style.fill = 'none';\r\n }\r\n\r\n this.svgContainer.append(element);\r\n\r\n return this;\r\n }\r\n\r\n drawEllipse(\r\n point: Point2D,\r\n xRay: number,\r\n yRay: number,\r\n stroke: number,\r\n strokeColor: ColorType,\r\n fillColor: ColorType,\r\n options?: SvgOptions\r\n ): this {\r\n const svgEllipse: SVGEllipseElement = this.renderer.createElement(\r\n 'ellipse',\r\n ns\r\n );\r\n\r\n svgEllipse.setAttribute('cx', point.x.toString());\r\n svgEllipse.setAttribute('cy', point.y.toString());\r\n svgEllipse.setAttribute('rx', xRay.toString());\r\n svgEllipse.setAttribute('ry', yRay.toString());\r\n\r\n if (!this.setClasses(svgEllipse, options)) {\r\n this.setSvgElementLinePattern(svgEllipse);\r\n\r\n svgEllipse.style.strokeWidth = stroke.toString();\r\n svgEllipse.style.stroke = this.color(strokeColor);\r\n svgEllipse.style.fill = this.color(fillColor);\r\n }\r\n\r\n this.setSvgOptions(svgEllipse, options);\r\n\r\n this.svgContainer.append(svgEllipse);\r\n\r\n return this;\r\n }\r\n\r\n drawRect(\r\n x: number,\r\n y: number,\r\n width: number,\r\n height: number,\r\n stroke: number,\r\n strokeColor: ColorType,\r\n fillColor: ColorType,\r\n options?: SvgOptions\r\n ): this {\r\n const svgRect: SVGRectElement = this.renderer.createElement('rect', ns);\r\n\r\n svgRect.setAttribute('x', x.toString());\r\n svgRect.setAttribute('y', y.toString());\r\n svgRect.setAttribute('width', width.toString());\r\n svgRect.setAttribute('height', height.toString());\r\n\r\n if (!this.setClasses(svgRect, options)) {\r\n this.setSvgElementLinePattern(svgRect);\r\n\r\n svgRect.style.strokeWidth = stroke.toString();\r\n svgRect.style.stroke = this.color(strokeColor);\r\n svgRect.style.fill = this.color(fillColor);\r\n }\r\n\r\n this.setSvgOptions(svgRect, options);\r\n\r\n this.svgContainer.append(svgRect);\r\n\r\n return this;\r\n }\r\n\r\n get svgContainer(): SVGGElement {\r\n return ((this.groups.length && this.groups[this.groups.length - 1]) ||\r\n this.root) as SVGGElement;\r\n }\r\n\r\n drawText(\r\n text: string,\r\n handlePosition: Point2D,\r\n color: ColorType = '#000000',\r\n angle: number = 0,\r\n options?: SvgOptions\r\n ): this {\r\n text = text.replace(/ /g, ' ');\r\n this.setMinMax(handlePosition);\r\n const svgText: SVGTextElement = this.renderer.createElement('text', ns);\r\n\r\n svgText.innerHTML = text;\r\n\r\n svgText.setAttribute('x', handlePosition.x.toString());\r\n svgText.setAttribute('y', handlePosition.y.toString());\r\n svgText.setAttribute('text-anchor', this.textAnchor);\r\n\r\n svgText.style.fontSize = `${this.fontSize}px`;\r\n\r\n if (angle) {\r\n svgText.style.transform = `translate(${handlePosition.x}px, ${\r\n handlePosition.y\r\n }px) rotate(${-angle}deg) translate(${-handlePosition.x}px, ${-handlePosition.y}px)`;\r\n }\r\n\r\n if (!this.setClasses(svgText, options)) {\r\n svgText.style.fill = this.color(color);\r\n }\r\n\r\n this.setSvgOptions(svgText, options);\r\n\r\n this.svgContainer.append(svgText);\r\n\r\n return this;\r\n }\r\n\r\n drawPolyline(\r\n points: Point2D[],\r\n stroke: number = 1,\r\n color: ColorType = '#000000',\r\n options?: SvgPolylineOptions\r\n ): this {\r\n const svgPolyline: SVGPolylineElement = this.renderer.createElement(\r\n 'polyline',\r\n ns\r\n );\r\n\r\n if (!this.setClasses(svgPolyline, options as any)) {\r\n svgPolyline.style.strokeWidth = stroke.toString();\r\n svgPolyline.style.stroke = this.color(color);\r\n svgPolyline.style.fill = 'none';\r\n\r\n this.setSvgElementLinePattern(svgPolyline);\r\n }\r\n\r\n points.forEach((point) => {\r\n svgPolyline.points.appendItem(this.createSvgPoint(point));\r\n this.setMinMax(point);\r\n });\r\n\r\n if (points.length && options?.close) {\r\n svgPolyline.points.appendItem(this.createSvgPoint(points[0]));\r\n }\r\n\r\n this.setSvgOptions(svgPolyline, options);\r\n\r\n this.svgContainer.append(svgPolyline);\r\n\r\n return this;\r\n }\r\n\r\n drawPolygon(\r\n points: Point2D[],\r\n stroke: number,\r\n color: ColorType | 'none',\r\n fill: ColorType | 'none',\r\n options?: any\r\n ): this {\r\n const polygon: SVGPolygonElement = this.renderer.createElement('polygon', ns);\r\n\r\n if (!this.setClasses(polygon, options as any)) {\r\n polygon.style.strokeWidth = stroke.toString();\r\n polygon.style.stroke = this.color(color);\r\n polygon.style.fill = fill.toString();\r\n\r\n this.setSvgElementLinePattern(polygon);\r\n }\r\n\r\n points.forEach((point) => {\r\n polygon.points.appendItem(this.createSvgPoint(point));\r\n this.setMinMax(point);\r\n });\r\n\r\n this.setSvgOptions(polygon, options);\r\n\r\n this.svgContainer.append(polygon);\r\n\r\n return this;\r\n }\r\n\r\n get width(): number {\r\n if (this.xMaximum !== null && this.xMinimum !== null) {\r\n return this.xMaximum - this.xMinimum;\r\n }\r\n return 1000;\r\n }\r\n\r\n get height(): number {\r\n if (this.yMaximum !== null && this.yMinimum !== null) {\r\n return this.yMaximum - this.yMinimum;\r\n }\r\n return 1000;\r\n }\r\n\r\n getSvg(): string {\r\n return this.svg.innerHTML;\r\n }\r\n\r\n group(options?: {\r\n name?: string,\r\n data?: { [key: string]: string },\r\n clipRect?: {\r\n pos: Point2D,\r\n width: number,\r\n height: number\r\n },\r\n svgOnly?: SvgOnly,\r\n onMouseOver?: (g: SVGElement) => void,\r\n onMouseOut?: () => void\r\n }): this {\r\n const g = this.renderer.createElement('g', ns);\r\n const { svgOnly } = options || {};\r\n\r\n if (svgOnly) {\r\n const { events } = svgOnly;\r\n if (events) {\r\n keys(events).forEach((event) => {\r\n (g as any)[`on${event}`] = events[event];\r\n });\r\n }\r\n }\r\n\r\n if (g) {\r\n g.onmouseover = () => options?.onMouseOver && options?.onMouseOver(g);\r\n g.onmouseleave = () => options?.onMouseOut && options?.onMouseOut();\r\n }\r\n\r\n if (options?.data) {\r\n keys(options.data).forEach(\r\n (key) => (g.dataset[key] = options?.data?.[key] || '')\r\n );\r\n }\r\n\r\n if (options?.clipRect) {\r\n if (!this.defs) {\r\n this.defs = this.renderer.createElement('defs', ns);\r\n if (this.svgContainer.firstChild) {\r\n this.svgContainer.insertBefore(\r\n this.defs,\r\n this.svgContainer.firstChild\r\n );\r\n } else {\r\n this.svgContainer.appendChild(this.defs);\r\n }\r\n } else if (!this.svgContainer.querySelector('defs')) {\r\n this.svgContainer.appendChild(this.defs);\r\n }\r\n\r\n const clipPath = this.renderer.createElement('clipPath', ns);\r\n\r\n const rect = this.renderer.createElement('rect', ns);\r\n\r\n rect.setAttribute('x', options.clipRect.pos.x.toString());\r\n rect.setAttribute('y', options.clipRect.pos.y.toString());\r\n rect.setAttribute('width', options.clipRect.width.toString());\r\n rect.setAttribute('height', options.clipRect.height.toString());\r\n\r\n const id = 'ClipRect' + Math.floor(Math.random() * 10000000) + '-' + Math.floor(Math.random() * 10000000) + '-' + Math.floor(Math.random() * 10000000);\r\n\r\n clipPath.setAttribute('id', id);\r\n\r\n clipPath.appendChild(rect);\r\n\r\n this.defs.appendChild(clipPath);\r\n\r\n g.style.clipPath = 'url(#' + id + ')';\r\n }\r\n\r\n if (options?.name) {\r\n g.classList.add(options.name);\r\n }\r\n\r\n this.setSvgOptions(g, options);\r\n\r\n this.groups.push(g);\r\n\r\n return this;\r\n }\r\n\r\n endGroup(): this {\r\n if (this.groups.length) {\r\n const gr = this.groups.pop();\r\n if (gr) {\r\n this.svgContainer.appendChild(gr);\r\n }\r\n }\r\n\r\n return this;\r\n }\r\n\r\n getLineStrokeSize(size?: number): number {\r\n return size || 1;\r\n }\r\n\r\n save(fileName: string) {\r\n const file = new window.Blob([this.getSvg()], { type: 'image/svg+xml' });\r\n\r\n const downloadAnchor = document.createElement('a');\r\n const fileURL = URL.createObjectURL(file);\r\n\r\n downloadAnchor.href = fileURL;\r\n downloadAnchor.download = fileName + '.svg';\r\n downloadAnchor.click();\r\n }\r\n\r\n protected setClasses(element: SVGElement, options?: SvgOptions): boolean {\r\n const { svgOnly } = options || {};\r\n if (svgOnly) {\r\n const { events } = svgOnly;\r\n if (events) {\r\n keys(events).forEach((key) => {\r\n (element as any)['on' + key] = events[key];\r\n });\r\n }\r\n\r\n if (svgOnly?.classes?.length) {\r\n svgOnly.classes.forEach((cls) => element.classList.add(cls));\r\n return true;\r\n }\r\n }\r\n\r\n return false;\r\n }\r\n\r\n protected color(color: ColorType): string {\r\n return typeof color === 'string' ? color : color.html;\r\n }\r\n}\r\n","import { Point2D, ColorType, ColorRGB } from '@obliczeniowo/elementary/classes';\r\nimport { DrawingContextInterface, LinePattern, LinePatternDef, TextAlign, TextBaseline } from './drawing-context-interface';\r\nimport { keys } from '@obliczeniowo/elementary/objects';\r\nimport { ElementaryMath } from '@obliczeniowo/elementary/math';\r\n\r\ninterface ModuleDef {\r\n value: string;\r\n added: boolean;\r\n}\r\n\r\nexport class DrawingOpenScadInterface extends DrawingContextInterface {\r\n protected openScad: string = '';\r\n\r\n protected textAlign: TextAlign = TextAlign.LEFT;\r\n\r\n protected textBaseline: TextBaseline = TextBaseline.ALPHABETIC;\r\n\r\n protected fontSize: number = 10;\r\n\r\n protected groups: boolean[] = [];\r\n\r\n protected modules: { [module: string]: ModuleDef } = {\r\n line: {\r\n value: `module line(start, end, thickness = 1) {\r\nhull() {\r\n translate(start) circle(thickness);\r\n translate(end) circle(thickness);\r\n }\r\n}\\n\\n`,\r\n added: false\r\n },\r\n pie: {\r\n value: `module pieSlice(r=1.0, a=20) {\r\n polygon(points=[\r\n [0, 0],\r\n for(theta=0; theta<a; theta=theta+$fa)\r\n [r*cos(theta), r*sin(theta)],\r\n [r*cos(a), r*sin(a)]\r\n ]);\r\n}\\n\\n`,\r\n added: false\r\n }\r\n }\r\n\r\n clear(): void {\r\n this.openScad = '';\r\n keys(this.modules).forEach((key: string) => this.modules[key].added = false);\r\n }\r\n\r\n getTextDimension(text: string): { width: number; height: number } {\r\n return { width: this.getTextWidth(text), height: this.getFontSize() };\r\n }\r\n\r\n drawLine(startPoint: Point2D, endPoint: Point2D, stroke: number, color: ColorType, options?: any): DrawingContextInterface {\r\n if (!this.modules.line.added) {\r\n this.openScad += this.modules.line.value;\r\n this.modules.line.added = true;\r\n }\r\n this.setColor(color);\r\n this.openScad += `\\nline([${startPoint.x}, ${startPoint.y}], [${endPoint.x}, ${endPoint.y}], thickness=${stroke});\\n`;\r\n return this;\r\n }\r\n\r\n setColor(color: ColorType): DrawingContextInterface {\r\n const converted = ColorRGB.fromHex(typeof color === 'string' ? color : color.hex);\r\n this.openScad += `color([${converted.red / 255}, ${converted.green / 255}, ${converted.blue / 255}])\\n`;\r\n return this;\r\n }\r\n\r\n drawText(text: string, handlePosition: Point2D, color: ColorType, angle: number, options?: any): DrawingContextInterface {\r\n const mapping = {\r\n [TextAlign.CENTER]: 'center',\r\n [TextAlign.JUSTIFY]: 'left',\r\n [TextAlign.LEFT]: 'left',\r\n [TextAlign.RIGHT]: 'right'\r\n }\r\n this.setColor(color);\r\n this.openScad += `\r\ntranslate([${handlePosition.x}, ${handlePosition.y}])\r\nscale([-1, 1, 1])\r\nrotate(${angle + 180})\r\ntext(\"${text}\", size=${this.getFontSize()}, halign=\"${mapping[this.textAlign]}\");\r\n\\n`;\r\n return this;\r\n }\r\n\r\n drawPolyline(points: Point2D[], stroke: number, color: ColorType, options?: { close?: boolean;[key: string]: any }): DrawingContextInterface {\r\n this.setColor(color);\r\n this.openScad += '{\\n';\r\n points.forEach((point, index) => index > 0 ? this.drawLine(point, points[index - 1], stroke, color) : '');\r\n this.openScad += '\\n}';\r\n return this;\r\n }\r\n\r\n drawPolygon(points: Point2D[], stroke: number, color: ColorType, fill: ColorType, options?: any): DrawingContextInterface {\r\n this.setColor(fill);\r\n this.openScad += `polygon(points=${points.map(point => `[${point.x}, ${point.y}}]`).join(',')};\\n`;\r\n return this;\r\n }\r\n\r\n drawCircle(point: Point2D, ray: number, stroke: number, strokeColor: ColorType, fillColor: ColorType, options?: any): DrawingContextInterface {\r\n this.setColor(fillColor);\r\n this.openScad += `translate([${point.x}, ${point.y})\\n`;\r\n this.openScad += `circle(r=${ray});\\n`;\r\n\r\n return this;\r\n }\r\n\r\n drawPie(center: Point2D, rx: number, ry: number, start: number, end: number, stroke: number, strokeColor: ColorType, fillColor: ColorType, options?: any): DrawingContextInterface {\r\n if (!this.modules.pie.added) {\r\n this.openScad += this.modules.pie.value;\r\n this.modules.pie.added = true;\r\n }\r\n this.setColor(fillColor);\r\n this.openScad += `translate([${center.x}, ${center.y}]) \\n`;\r\n this.openScad += `scale([1, ${ry / rx}, 0])\\n`;\r\n this.openScad += `rotate([0, 0, ${ElementaryMath.radiansToDegrees(start)}]) \\n`;\r\n this.openScad += `pieSlice(r=${rx}, a=${ElementaryMath.radiansToDegrees(end - start)});\\n`;\r\n\r\n return this;\r\n }\r\n\r\n drawEllipse(point: Point2D, xRay: number, yRay: number, stroke: number, strokeColor: ColorType, fillColor: ColorType, options?: any): DrawingContextInterface {\r\n this.setColor(fillColor);\r\n this.openScad += `scale([1, ${yRay / xRay}, 0])\\n`;\r\n this.openScad += `translate([${point.x}, ${point.y}]) \\n`;\r\n this.openScad += `circle(r=${xRay});\\n`;\r\n\r\n return this;\r\n }\r\n\r\n drawRect(x: number, y: number, width: number, height: number, stroke: number, strokeColor: ColorType, fillColor: ColorType, options?: any): DrawingContextInterface {\r\n this.setColor(fillColor);\r\n this.openScad += `translate([${x}, ${y}]) \\n`;\r\n this.openScad += `square([${width}, ${height}]);\\n`;\r\n\r\n return this;\r\n }\r\n\r\n lineTo(point: Point2D, stroke: number, color: ColorType): DrawingContextInterface {\r\n this.drawLine(this.lastPoint, point, stroke, color);\r\n this.moveTo(point);\r\n return this;\r\n }\r\n\r\n setFontSize(fontSize: number): DrawingContextInterface {\r\n this.fontSize = fontSize;\r\n return this;\r\n }\r\n\r\n getFontSize(): number {\r\n return this.fontSize;\r\n }\r\n\r\n setTextAlign(align: TextAlign): DrawingContextInterface {\r\n this.textAlign = align;\r\n return this;\r\n }\r\n\r\n setTextBaseline(textBaseline: TextBaseline): DrawingContextInterface {\r\n this.textBaseline = textBaseline;\r\n return this;\r\n }\r\n\r\n getTextWidth(text: string): number {\r\n return text.length * this.fontSize * 0.8;\r\n }\r\n\r\n setLinePattern(linePattern: LinePattern | string | undefined, linePatternDef?: LinePatternDef): DrawingContextInterface {\r\n console.log('WARING: this method can not be implemented for OpenScad format')\r\n return this;\r\n }\r\n\r\n getLineStrokeSize(size?: number): number {\r\n return 0;\r\n }\r\n\r\n save(fileName: string): this {\r\n const file = new window.Blob([this.openScad], { type: 'text/plain' });\r\n\r\n const downloadAnchor = document.createElement('a');\r\n const fileURL = URL.createObjectURL(file);\r\n\r\n downloadAnchor.href = fileURL;\r\n downloadAnchor.download = fileName + '.scad';\r\n downloadAnchor.click();\r\n\r\n return this;\r\n }\r\n}\r\n","import { Point2D, ColorType } from '@obliczeniowo/elementary/classes';\r\nimport { DrawingContextInterface, LinePattern, LinePatternDef, TextAlign, TextBaseline } from './drawing-context-interface';\r\nimport { ElementaryMath } from '@obliczeniowo/elementary/math';\r\n\r\nexport class DrawingZwCadCommandInterface extends DrawingContextInterface {\r\n protected data: string = '';\r\n\r\n protected textAlign: TextAlign = TextAlign.LEFT;\r\n\r\n protected fontSize: number = 10;\r\n\r\n clear(): void {\r\n this.data = '';\r\n }\r\n\r\n getTextDimension(text: string): { width: number; height: number } {\r\n return { width: this.getTextWidth(text), height: this.getFontSize() };\r\n }\r\n\r\n drawLine(startPoint: Point2D, endPoint: Point2D, stroke: number, color: ColorType, options?: any): DrawingContextInterface {\r\n this.data += '_line\\r\\n' +\r\n `${startPoint.x},${startPoint.y}\\r\\n` +\r\n `${endPoint.x},${endPoint.y}\\r\\n\\r\\n`;\r\n return this;\r\n }\r\n\r\n drawText(text: string, handlePosition: Point2D, color: ColorType, angle: number, options?: any): DrawingContextInterface {\r\n console.warn('cant implement ZwCad command for adding text');\r\n return this;\r\n }\r\n\r\n drawPolyline(points: Point2D[], stroke: number, color: ColorType, options?: { close?: boolean;[key: string]: any }): DrawingContextInterface {\r\n this.data += 'polyline\\r\\n' +\r\n `${points.map(point => `${point.x},${point.y}`).join('\\r\\n')}\\r\\n\\r\\n`;\r\n return this;\r\n }\r\n\r\n drawPolygon(points: Point2D[], stroke: number, color: ColorType, fill: ColorType, options?: any): DrawingContextInterface {\r\n if (points.length) {\r\n this.data += 'polyline' +\r\n points.map(point => `${point.x},${point.y}`).join('\\r\\n') +\r\n `${points[0].x},${points[0].y}\\r\\n\\r\\n`;\r\n }\r\n return this;\r\n }\r\n\r\n drawCircle(point: Point2D, ray: number, stroke: number, strokeColor: ColorType, fillColor: ColorType, options?: any): DrawingContextInterface {\r\n this.data += '_circle\\r\\n' +\r\n `${point.x},${point.y}\\r\\n` +\r\n `${ray}\\r\\n`;\r\n return this;\r\n }\r\n\r\n drawPie(center: Point2D, rx: number, ry: number, start: number, end: number, stroke: number, strokeColor: ColorType, fillColor: ColorType, options?: any): DrawingContextInterface {\r\n const startCos = Math.cos(start);\r\n const startSin = Math.sin(start);\r\n const endCos = Math.cos(end);\r\n const endSin = Math.sin(end);\r\n\r\n const ellipseRay = (cos: number, sin: number) => {\r\n return Math.sqrt(rx * rx * ry * ry / (rx * rx * cos * cos + ry * ry * sin * sin));\r\n }\r\n\r\n const startRay = ellipseRay(startCos, startSin);\r\n const endRay = ellipseRay(endCos, endSin);\r\n\r\n this.data += '_ellipse\\r\\n' +\r\n '_a\\r\\n' +\r\n '_c\\r\\n' +\r\n `${center.x},${center.y}\\r\\n` +\r\n `${center.x + rx},${center.y}\\r\\n` +\r\n `${center.x},${center.y + ry}\\r\\n` +\r\n `${ElementaryMath.radiansToDegrees(start)}\\r\\n` +\r\n `${ElementaryMath.radian