UNPKG

@obliczeniowo/elementary

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