react-rough-fiber
Version:
A React renderer for rendering hand-drawn SVGs.
1 lines • 158 kB
Source Map (JSON)
{"version":3,"sources":["../src/context.tsx","../src/roughSVG.tsx","../src/renderer.ts","../src/constants.ts","../../../node_modules/.pnpm/roughjs@4.5.2_patch_hash=ccgdz2z4p5772c6hci3dehxkyq/node_modules/roughjs/bin/geometry.js","../../../node_modules/.pnpm/roughjs@4.5.2_patch_hash=ccgdz2z4p5772c6hci3dehxkyq/node_modules/roughjs/bin/fillers/scan-line-hachure.js","../../../node_modules/.pnpm/roughjs@4.5.2_patch_hash=ccgdz2z4p5772c6hci3dehxkyq/node_modules/roughjs/bin/fillers/hachure-filler.js","../../../node_modules/.pnpm/roughjs@4.5.2_patch_hash=ccgdz2z4p5772c6hci3dehxkyq/node_modules/roughjs/bin/fillers/zigzag-filler.js","../../../node_modules/.pnpm/roughjs@4.5.2_patch_hash=ccgdz2z4p5772c6hci3dehxkyq/node_modules/roughjs/bin/fillers/hatch-filler.js","../../../node_modules/.pnpm/roughjs@4.5.2_patch_hash=ccgdz2z4p5772c6hci3dehxkyq/node_modules/roughjs/bin/fillers/dot-filler.js","../../../node_modules/.pnpm/roughjs@4.5.2_patch_hash=ccgdz2z4p5772c6hci3dehxkyq/node_modules/roughjs/bin/fillers/dashed-filler.js","../../../node_modules/.pnpm/roughjs@4.5.2_patch_hash=ccgdz2z4p5772c6hci3dehxkyq/node_modules/roughjs/bin/fillers/zigzag-line-filler.js","../../../node_modules/.pnpm/roughjs@4.5.2_patch_hash=ccgdz2z4p5772c6hci3dehxkyq/node_modules/roughjs/bin/fillers/filler.js","../../../node_modules/.pnpm/roughjs@4.5.2_patch_hash=ccgdz2z4p5772c6hci3dehxkyq/node_modules/roughjs/bin/math.js","../../../node_modules/.pnpm/path-data-parser@0.1.0_patch_hash=4rhp5kl5f7kwcibkmwkhuggxfy/node_modules/path-data-parser/lib/parser.js","../../../node_modules/.pnpm/path-data-parser@0.1.0_patch_hash=4rhp5kl5f7kwcibkmwkhuggxfy/node_modules/path-data-parser/lib/absolutize.js","../../../node_modules/.pnpm/path-data-parser@0.1.0_patch_hash=4rhp5kl5f7kwcibkmwkhuggxfy/node_modules/path-data-parser/lib/normalize.js","../../../node_modules/.pnpm/roughjs@4.5.2_patch_hash=ccgdz2z4p5772c6hci3dehxkyq/node_modules/roughjs/bin/renderer.js","../../../node_modules/.pnpm/points-on-curve@0.2.0/node_modules/points-on-curve/lib/curve-to-bezier.js","../../../node_modules/.pnpm/points-on-curve@0.2.0/node_modules/points-on-curve/lib/index.js","../../../node_modules/.pnpm/points-on-path@0.2.1/node_modules/points-on-path/lib/index.js","../../../node_modules/.pnpm/roughjs@4.5.2_patch_hash=ccgdz2z4p5772c6hci3dehxkyq/node_modules/roughjs/bin/generator.js","../src/utils.ts","../src/shape.ts","../src/props.ts"],"sourcesContent":["import type { ContextBridge } from 'its-fine'\nimport type { RoughSVGProps } from './types'\nimport { FiberProvider, useContextBridge } from 'its-fine'\nimport { RoughSVG } from './roughSVG'\n\nfunction WithBridgeRoughSVG({ children, ...restProps }: RoughSVGProps) {\n const Bridge: ContextBridge = useContextBridge()\n return (\n <RoughSVG {...restProps}>\n <Bridge>{children}</Bridge>\n </RoughSVG>\n )\n}\n\nexport function WCRoughSVG(props: RoughSVGProps) {\n return (\n <FiberProvider>\n <WithBridgeRoughSVG {...props} />\n </FiberProvider>\n )\n}\n","import type { Options, RoughSVGProps } from './types'\nimport { createElement, useEffect, useRef, useState } from 'react'\nimport { LegacyRoot } from 'react-reconciler/constants.js'\nimport { createReconciler } from './renderer'\n\nexport function RoughSVG({\n containerType = 'div',\n children,\n options = {},\n ...restProps\n}: RoughSVGProps) {\n const containerRef = useRef<HTMLDivElement>(null)\n const mountNodeRef = useRef<any>(null)\n const optionsRef = useRef<Options>(options)\n const [reconciler] = useState(() => createReconciler(optionsRef))\n\n useEffect(() => {\n if (containerRef.current && !mountNodeRef.current) {\n mountNodeRef.current = reconciler.createContainer(\n containerRef.current,\n LegacyRoot,\n null,\n false,\n false,\n '',\n () => {},\n null,\n )\n }\n optionsRef.current = options || {}\n if (mountNodeRef.current) {\n reconciler.updateContainer(children, mountNodeRef.current, null)\n }\n }, [children, reconciler, options])\n\n useEffect(() => {\n return () => {\n reconciler.updateContainer(null, mountNodeRef.current, null)\n }\n }, [reconciler])\n\n return createElement(containerType, { ref: containerRef, ...restProps })\n}\n","import type { MutableRefObject } from 'react'\nimport type {\n HostConfig,\n HostContext,\n Instance,\n InstanceProps,\n InstanceWithRRF,\n Options,\n} from './types'\nimport Reconciler from 'react-reconciler'\nimport * as ReactReconcilerConstants from 'react-reconciler/constants.js'\nimport {\n DATA_RRF_GROUP,\n HTML_NAMESPACE,\n SVG_NAMESPACE,\n SVG_SHAPE_MAP,\n} from './constants'\nimport { diffProps } from './props'\nimport { isFun, isReact19 } from './utils'\n\nconst UPDATE_SIGNAL = {}\n\n// @ts-expect-error for React 19\nlet currentUpdatePriority: number = ReactReconcilerConstants.NoEventPriority\n\nfunction createInstance(type: string, _props: InstanceProps, root: Element, hostContext: HostContext) {\n const ownerDocument = root.ownerDocument\n const { namespace, inDefs } = hostContext\n let domElement: Instance\n if (namespace === SVG_NAMESPACE || type === 'svg') {\n // roughjs renders a shape as a fill path(if fill is not none) and a stroke path(if stroke is not none)\n // so we need to wrap the shape in a g element\n if (!inDefs && type in SVG_SHAPE_MAP) {\n domElement = ownerDocument.createElementNS(SVG_NAMESPACE, 'g')\n domElement.setAttribute(DATA_RRF_GROUP, '')\n }\n else {\n domElement = ownerDocument.createElementNS(SVG_NAMESPACE, type)\n }\n }\n else {\n domElement = ownerDocument.createElement(type)\n }\n (domElement as InstanceWithRRF)._rrf_inDefs = inDefs\n return domElement\n}\n\nfunction removeChild(parent: Instance, child: Instance) {\n return parent.removeChild(child)\n}\n\nfunction appendChild(parent: Instance, child: Instance) {\n return child && parent.appendChild(child)\n}\n\nfunction insertBefore(parent: Instance, child: Instance, beforeChild: Instance) {\n return parent.insertBefore(child, beforeChild)\n}\n\nexport function createReconciler(optionsRef: MutableRefObject<Options>): Reconciler.Reconciler<Instance, Instance, void, Instance, Instance> {\n return Reconciler<\n HostConfig['type'],\n HostConfig['props'],\n HostConfig['container'],\n HostConfig['instance'],\n HostConfig['textInstance'],\n HostConfig['suspenseInstance'],\n HostConfig['hydratableInstance'],\n HostConfig['publicInstance'],\n HostConfig['hostContext'],\n HostConfig['updatePayload'],\n HostConfig['childSet'],\n HostConfig['timeoutHandle'],\n HostConfig['noTimeout']\n >({\n createInstance,\n removeChild,\n appendChild,\n appendInitialChild: appendChild,\n insertBefore,\n supportsMutation: true,\n isPrimaryRenderer: false,\n supportsPersistence: false,\n supportsHydration: false,\n noTimeout: -1,\n appendChildToContainer: (container, child) => {\n container.appendChild(child as Instance)\n },\n removeChildFromContainer: (container, child) => {\n container.removeChild(child as Instance)\n },\n insertInContainerBefore: (container, child, beforeChild) => {\n container.insertBefore(child as Instance, beforeChild as Instance)\n },\n getRootHostContext: root => ({\n namespace: root.namespaceURI || '',\n inDefs: false,\n }),\n getChildHostContext: (parentHostContext, type) => {\n const { namespace, inDefs } = parentHostContext\n if (type === 'svg') {\n return { namespace: SVG_NAMESPACE, inDefs }\n }\n if (namespace === SVG_NAMESPACE && type === 'foreignObject') {\n return { namespace: HTML_NAMESPACE, inDefs }\n }\n if (type === 'defs') {\n return { namespace, inDefs: true }\n }\n return parentHostContext\n },\n finalizeInitialChildren(instance, type, props) {\n diffProps(\n type,\n instance as InstanceWithRRF,\n props,\n null,\n optionsRef.current,\n )\n return false\n },\n prepareUpdate(_instance, _type, _oldProps, _newProps, _rootContainer) {\n return UPDATE_SIGNAL\n },\n commitUpdate(...args) {\n let instance\n let type\n let oldProps\n let newProps\n let _\n if (isReact19()) {\n [instance, type, oldProps, newProps] = args\n }\n else {\n [instance, _, type, oldProps, newProps] = args\n }\n diffProps(\n type as string,\n instance as InstanceWithRRF,\n newProps,\n oldProps as InstanceProps | null,\n optionsRef.current,\n )\n },\n commitTextUpdate(textInstance, _oldText: string, newText: string): void {\n (<any>textInstance).nodeValue = newText\n },\n commitMount() {},\n getPublicInstance: (instance) => {\n if (instance?.hasAttribute(DATA_RRF_GROUP)) {\n const firstChild = instance.children[0]\n if (firstChild?.tagName === 'path') {\n return firstChild as SVGElement\n }\n }\n return instance!\n },\n prepareForCommit: () => null,\n preparePortalMount: () => {},\n resetAfterCommit: () => {},\n shouldSetTextContent: () => false,\n clearContainer: () => false,\n hideInstance() {},\n unhideInstance() {},\n createTextInstance: (text, container) =>\n container.ownerDocument.createTextNode(text),\n hideTextInstance: () => {},\n unhideTextInstance: () => {},\n getCurrentEventPriority: () => {\n return typeof currentUpdatePriority === 'number'\n ? currentUpdatePriority\n : ReactReconcilerConstants.DefaultEventPriority\n },\n beforeActiveInstanceBlur: () => {},\n afterActiveInstanceBlur: () => {},\n detachDeletedInstance: () => {},\n scheduleTimeout: (isFun(setTimeout) ? setTimeout : undefined) as any,\n cancelTimeout: (isFun(clearTimeout) ? clearTimeout : undefined) as any,\n getInstanceFromNode: () => null,\n prepareScopeUpdate: () => {},\n getInstanceFromScope: () => null,\n // @ts-expect-error these hooks are undocumented, remove @ts-ignore in the future\n setCurrentUpdatePriority: (newPriority: number) => {\n currentUpdatePriority = newPriority\n },\n getCurrentUpdatePriority: () => {\n return currentUpdatePriority\n },\n resolveUpdatePriority: () => {\n return (\n currentUpdatePriority || ReactReconcilerConstants.DefaultEventPriority\n )\n },\n shouldAttemptEagerTransition: () => false,\n requestPostPaintCallback: () => {},\n maySuspendCommit: () => false,\n preloadInstance: () => true,\n startSuspendingCommit: () => {},\n suspendInstance: () => {},\n waitForCommitToBeReady: () => null,\n NotPendingTransition: null,\n resetFormInstance: () => {},\n })\n}\n","import type { SVGShape } from './types'\n\nexport const SVG_NAMESPACE = 'http://www.w3.org/2000/svg'\nexport const HTML_NAMESPACE = 'http://www.w3.org/1999/xhtml'\n\nexport const IS_NON_DIMENSIONAL\n = /acit|ex(?:[sgnp]|$)|rph|grid|ows|mnc|ntw|ine[ch]|zoo|^ord|itera/i\nexport const ON_ANI = /^on(Ani|Tra|Tou|BeforeInp|Compo)/\nexport const CAMEL_REPLACE = /[A-Z0-9]/g\nexport const CAMEL_PROPS\n = /^(?:accent|alignment|arabic|baseline|cap|clip(?!PathU)|color|dominant|fill|flood|font|glyph(?!R)|horiz|image|letter|lighting|marker(?![HWU])|overline|paint|pointer|shape|stop|strikethrough|stroke|text(?!L)|transform|underline|unicode|units|v|vector|vert|word|writing|x(?!C))[A-Z]/\n\nexport const SVG_PATH_PROPS: SVGShape = {\n type: 'path',\n d: '',\n}\nexport const SVG_CIRCLE_PROPS: SVGShape = {\n type: 'circle',\n cx: 0,\n cy: 0,\n r: 0,\n}\nexport const SVG_LINE_PROPS: SVGShape = {\n type: 'line',\n x1: 0,\n y1: 0,\n x2: 0,\n y2: 0,\n}\nexport const SVG_RECT_PROPS: SVGShape = {\n type: 'rect',\n x: 0,\n y: 0,\n width: 0,\n height: 0,\n}\nexport const SVG_ELLIPSE_PROPS: SVGShape = {\n type: 'ellipse',\n cx: 0,\n cy: 0,\n rx: 0,\n ry: 0,\n}\nexport const SVG_POLYGON_PROPS: SVGShape = {\n type: 'polygon',\n points: '',\n}\nexport const SVG_POLYLINE_PROPS: SVGShape = {\n type: 'polyline',\n points: '',\n}\n\nexport const SVG_SHAPE_MAP: { [key in SVGShape['type']]: SVGShape } = {\n path: SVG_PATH_PROPS,\n circle: SVG_CIRCLE_PROPS,\n line: SVG_LINE_PROPS,\n rect: SVG_RECT_PROPS,\n ellipse: SVG_ELLIPSE_PROPS,\n polygon: SVG_POLYGON_PROPS,\n polyline: SVG_POLYLINE_PROPS,\n}\n\nexport const FILL_CSS_VARIABLE = '--rrf-fill-color'\nexport const FILL_OPACITY_CSS_VARIABLE = '--rrf-fill-opacity'\n\nexport const DATA_RRF_GROUP = 'data-rrf-group'\n","export function rotatePoints(points, center, degrees) {\n if (points && points.length) {\n const [cx, cy] = center;\n const angle = (Math.PI / 180) * degrees;\n const cos = Math.cos(angle);\n const sin = Math.sin(angle);\n points.forEach((p) => {\n const [x, y] = p;\n p[0] = ((x - cx) * cos) - ((y - cy) * sin) + cx;\n p[1] = ((x - cx) * sin) + ((y - cy) * cos) + cy;\n });\n }\n}\nexport function rotateLines(lines, center, degrees) {\n const points = [];\n lines.forEach((line) => points.push(...line));\n rotatePoints(points, center, degrees);\n}\nexport function lineLength(line) {\n const p1 = line[0];\n const p2 = line[1];\n return Math.sqrt(Math.pow(p1[0] - p2[0], 2) + Math.pow(p1[1] - p2[1], 2));\n}\n","import { rotatePoints, rotateLines } from '../geometry';\nexport function polygonHachureLines(polygonList, o) {\n const angle = o.hachureAngle + 90;\n let gap = o.hachureGap;\n if (gap < 0) {\n gap = o.strokeWidth * 4;\n }\n gap = Math.max(gap, 0.1);\n const rotationCenter = [0, 0];\n if (angle) {\n for (const polygon of polygonList) {\n rotatePoints(polygon, rotationCenter, angle);\n }\n }\n const lines = straightHachureLines(polygonList, gap);\n if (angle) {\n for (const polygon of polygonList) {\n rotatePoints(polygon, rotationCenter, -angle);\n }\n rotateLines(lines, rotationCenter, -angle);\n }\n return lines;\n}\nfunction straightHachureLines(polygonList, gap) {\n const vertexArray = [];\n for (const polygon of polygonList) {\n const vertices = [...polygon];\n if (vertices[0].join(',') !== vertices[vertices.length - 1].join(',')) {\n vertices.push([vertices[0][0], vertices[0][1]]);\n }\n if (vertices.length > 2) {\n vertexArray.push(vertices);\n }\n }\n const lines = [];\n gap = Math.max(gap, 0.1);\n // Create sorted edges table\n const edges = [];\n for (const vertices of vertexArray) {\n for (let i = 0; i < vertices.length - 1; i++) {\n const p1 = vertices[i];\n const p2 = vertices[i + 1];\n if (p1[1] !== p2[1]) {\n const ymin = Math.min(p1[1], p2[1]);\n edges.push({\n ymin,\n ymax: Math.max(p1[1], p2[1]),\n x: ymin === p1[1] ? p1[0] : p2[0],\n islope: (p2[0] - p1[0]) / (p2[1] - p1[1]),\n });\n }\n }\n }\n edges.sort((e1, e2) => {\n if (e1.ymin < e2.ymin) {\n return -1;\n }\n if (e1.ymin > e2.ymin) {\n return 1;\n }\n if (e1.x < e2.x) {\n return -1;\n }\n if (e1.x > e2.x) {\n return 1;\n }\n if (e1.ymax === e2.ymax) {\n return 0;\n }\n return (e1.ymax - e2.ymax) / Math.abs((e1.ymax - e2.ymax));\n });\n if (!edges.length) {\n return lines;\n }\n // Start scanning\n let activeEdges = [];\n let y = edges[0].ymin;\n while (activeEdges.length || edges.length) {\n if (edges.length) {\n let ix = -1;\n for (let i = 0; i < edges.length; i++) {\n if (edges[i].ymin > y) {\n break;\n }\n ix = i;\n }\n const removed = edges.splice(0, ix + 1);\n removed.forEach((edge) => {\n activeEdges.push({ s: y, edge });\n });\n }\n activeEdges = activeEdges.filter((ae) => {\n if (ae.edge.ymax <= y) {\n return false;\n }\n return true;\n });\n activeEdges.sort((ae1, ae2) => {\n if (ae1.edge.x === ae2.edge.x) {\n return 0;\n }\n return (ae1.edge.x - ae2.edge.x) / Math.abs((ae1.edge.x - ae2.edge.x));\n });\n // fill between the edges\n if (activeEdges.length > 1) {\n for (let i = 0; i < activeEdges.length; i = i + 2) {\n const nexti = i + 1;\n if (nexti >= activeEdges.length) {\n break;\n }\n const ce = activeEdges[i].edge;\n const ne = activeEdges[nexti].edge;\n lines.push([\n [Math.round(ce.x), y],\n [Math.round(ne.x), y],\n ]);\n }\n }\n y += gap;\n activeEdges.forEach((ae) => {\n ae.edge.x = ae.edge.x + (gap * ae.edge.islope);\n });\n }\n return lines;\n}\n","import { polygonHachureLines } from './scan-line-hachure';\nexport class HachureFiller {\n constructor(helper) {\n this.helper = helper;\n }\n fillPolygons(polygonList, o) {\n return this._fillPolygons(polygonList, o);\n }\n _fillPolygons(polygonList, o) {\n const lines = polygonHachureLines(polygonList, o);\n const ops = this.renderLines(lines, o);\n return { type: 'fillSketch', ops };\n }\n renderLines(lines, o) {\n const ops = [];\n for (const line of lines) {\n ops.push(...this.helper.doubleLineOps(line[0][0], line[0][1], line[1][0], line[1][1], o));\n }\n return ops;\n }\n}\n","import { HachureFiller } from './hachure-filler';\nimport { polygonHachureLines } from './scan-line-hachure';\nimport { lineLength } from '../geometry';\nexport class ZigZagFiller extends HachureFiller {\n fillPolygons(polygonList, o) {\n let gap = o.hachureGap;\n if (gap < 0) {\n gap = o.strokeWidth * 4;\n }\n gap = Math.max(gap, 0.1);\n const o2 = Object.assign({}, o, { hachureGap: gap });\n const lines = polygonHachureLines(polygonList, o2);\n const zigZagAngle = (Math.PI / 180) * o.hachureAngle;\n const zigzagLines = [];\n const dgx = gap * 0.5 * Math.cos(zigZagAngle);\n const dgy = gap * 0.5 * Math.sin(zigZagAngle);\n for (const [p1, p2] of lines) {\n if (lineLength([p1, p2])) {\n zigzagLines.push([\n [p1[0] - dgx, p1[1] + dgy],\n [...p2],\n ], [\n [p1[0] + dgx, p1[1] - dgy],\n [...p2],\n ]);\n }\n }\n const ops = this.renderLines(zigzagLines, o);\n return { type: 'fillSketch', ops };\n }\n}\n","import { HachureFiller } from './hachure-filler';\nexport class HatchFiller extends HachureFiller {\n fillPolygons(polygonList, o) {\n const set = this._fillPolygons(polygonList, o);\n const o2 = Object.assign({}, o, { hachureAngle: o.hachureAngle + 90 });\n const set2 = this._fillPolygons(polygonList, o2);\n set.ops = set.ops.concat(set2.ops);\n return set;\n }\n}\n","import { lineLength } from '../geometry';\nimport { polygonHachureLines } from './scan-line-hachure';\nexport class DotFiller {\n constructor(helper) {\n this.helper = helper;\n }\n fillPolygons(polygonList, o) {\n o = Object.assign({}, o, { hachureAngle: 0 });\n const lines = polygonHachureLines(polygonList, o);\n return this.dotsOnLines(lines, o);\n }\n dotsOnLines(lines, o) {\n const ops = [];\n let gap = o.hachureGap;\n if (gap < 0) {\n gap = o.strokeWidth * 4;\n }\n gap = Math.max(gap, 0.1);\n let fweight = o.fillWeight;\n if (fweight < 0) {\n fweight = o.strokeWidth / 2;\n }\n const ro = gap / 4;\n for (const line of lines) {\n const length = lineLength(line);\n const dl = length / gap;\n const count = Math.ceil(dl) - 1;\n const offset = length - (count * gap);\n const x = ((line[0][0] + line[1][0]) / 2) - (gap / 4);\n const minY = Math.min(line[0][1], line[1][1]);\n for (let i = 0; i < count; i++) {\n const y = minY + offset + (i * gap);\n const cx = (x - ro) + Math.random() * 2 * ro;\n const cy = (y - ro) + Math.random() * 2 * ro;\n const el = this.helper.ellipse(cx, cy, fweight, fweight, o);\n ops.push(...el.ops);\n }\n }\n return { type: 'fillSketch', ops };\n }\n}\n","import { lineLength } from '../geometry';\nimport { polygonHachureLines } from './scan-line-hachure';\nexport class DashedFiller {\n constructor(helper) {\n this.helper = helper;\n }\n fillPolygons(polygonList, o) {\n const lines = polygonHachureLines(polygonList, o);\n return { type: 'fillSketch', ops: this.dashedLine(lines, o) };\n }\n dashedLine(lines, o) {\n const offset = o.dashOffset < 0 ? (o.hachureGap < 0 ? (o.strokeWidth * 4) : o.hachureGap) : o.dashOffset;\n const gap = o.dashGap < 0 ? (o.hachureGap < 0 ? (o.strokeWidth * 4) : o.hachureGap) : o.dashGap;\n const ops = [];\n lines.forEach((line) => {\n const length = lineLength(line);\n const count = Math.floor(length / (offset + gap));\n const startOffset = (length + gap - (count * (offset + gap))) / 2;\n let p1 = line[0];\n let p2 = line[1];\n if (p1[0] > p2[0]) {\n p1 = line[1];\n p2 = line[0];\n }\n const alpha = Math.atan((p2[1] - p1[1]) / (p2[0] - p1[0]));\n for (let i = 0; i < count; i++) {\n const lstart = i * (offset + gap);\n const lend = lstart + offset;\n const start = [p1[0] + (lstart * Math.cos(alpha)) + (startOffset * Math.cos(alpha)), p1[1] + lstart * Math.sin(alpha) + (startOffset * Math.sin(alpha))];\n const end = [p1[0] + (lend * Math.cos(alpha)) + (startOffset * Math.cos(alpha)), p1[1] + (lend * Math.sin(alpha)) + (startOffset * Math.sin(alpha))];\n ops.push(...this.helper.doubleLineOps(start[0], start[1], end[0], end[1], o));\n }\n });\n return ops;\n }\n}\n","import { lineLength } from '../geometry';\nimport { polygonHachureLines } from './scan-line-hachure';\nexport class ZigZagLineFiller {\n constructor(helper) {\n this.helper = helper;\n }\n fillPolygons(polygonList, o) {\n const gap = o.hachureGap < 0 ? (o.strokeWidth * 4) : o.hachureGap;\n const zo = o.zigzagOffset < 0 ? gap : o.zigzagOffset;\n o = Object.assign({}, o, { hachureGap: gap + zo });\n const lines = polygonHachureLines(polygonList, o);\n return { type: 'fillSketch', ops: this.zigzagLines(lines, zo, o) };\n }\n zigzagLines(lines, zo, o) {\n const ops = [];\n lines.forEach((line) => {\n const length = lineLength(line);\n const count = Math.round(length / (2 * zo));\n let p1 = line[0];\n let p2 = line[1];\n if (p1[0] > p2[0]) {\n p1 = line[1];\n p2 = line[0];\n }\n const alpha = Math.atan((p2[1] - p1[1]) / (p2[0] - p1[0]));\n for (let i = 0; i < count; i++) {\n const lstart = i * 2 * zo;\n const lend = (i + 1) * 2 * zo;\n const dz = Math.sqrt(2 * Math.pow(zo, 2));\n const start = [p1[0] + (lstart * Math.cos(alpha)), p1[1] + lstart * Math.sin(alpha)];\n const end = [p1[0] + (lend * Math.cos(alpha)), p1[1] + (lend * Math.sin(alpha))];\n const middle = [start[0] + dz * Math.cos(alpha + Math.PI / 4), start[1] + dz * Math.sin(alpha + Math.PI / 4)];\n ops.push(...this.helper.doubleLineOps(start[0], start[1], middle[0], middle[1], o), ...this.helper.doubleLineOps(middle[0], middle[1], end[0], end[1], o));\n }\n });\n return ops;\n }\n}\n","import { HachureFiller } from './hachure-filler';\nimport { ZigZagFiller } from './zigzag-filler';\nimport { HatchFiller } from './hatch-filler';\nimport { DotFiller } from './dot-filler';\nimport { DashedFiller } from './dashed-filler';\nimport { ZigZagLineFiller } from './zigzag-line-filler';\nconst fillers = {};\nexport function getFiller(o, helper) {\n let fillerName = o.fillStyle || 'hachure';\n if (!fillers[fillerName]) {\n switch (fillerName) {\n case 'zigzag':\n if (!fillers[fillerName]) {\n fillers[fillerName] = new ZigZagFiller(helper);\n }\n break;\n case 'cross-hatch':\n if (!fillers[fillerName]) {\n fillers[fillerName] = new HatchFiller(helper);\n }\n break;\n case 'dots':\n if (!fillers[fillerName]) {\n fillers[fillerName] = new DotFiller(helper);\n }\n break;\n case 'dashed':\n if (!fillers[fillerName]) {\n fillers[fillerName] = new DashedFiller(helper);\n }\n break;\n case 'zigzag-line':\n if (!fillers[fillerName]) {\n fillers[fillerName] = new ZigZagLineFiller(helper);\n }\n break;\n case 'hachure':\n default:\n fillerName = 'hachure';\n if (!fillers[fillerName]) {\n fillers[fillerName] = new HachureFiller(helper);\n }\n break;\n }\n }\n return fillers[fillerName];\n}\n","export function randomSeed() {\n return Math.floor(Math.random() * 2 ** 31);\n}\nexport class Random {\n constructor(seed) {\n this.seed = seed;\n }\n next() {\n if (this.seed) {\n return ((2 ** 31 - 1) & (this.seed = Math.imul(48271, this.seed))) / 2 ** 31;\n }\n else {\n return Math.random();\n }\n }\n}\n","const PARAMS = { A: 7, a: 7, C: 6, c: 6, H: 1, h: 1, L: 2, l: 2, M: 2, m: 2, Q: 4, q: 4, S: 4, s: 4, T: 2, t: 2, V: 1, v: 1, Z: 0, z: 0 };\n\nconst isWsp = (c) => {\n const codePoint = c.codePointAt(0);\n return (\n codePoint === 0x20 ||\n codePoint === 0x9 ||\n codePoint === 0xd ||\n codePoint === 0xa\n );\n};\n\nconst isDigit = (c) => {\n const codePoint = c.codePointAt(0);\n if (codePoint === null || codePoint === undefined) {\n return false;\n }\n return 48 <= codePoint && codePoint <= 57;\n};\n\nconst readNumber = (string, cursor) => {\n let i = cursor;\n let value = '';\n let state = 'none';\n for (; i < string.length; i += 1) {\n const c = string[i];\n if (c === '+' || c === '-') {\n if (state === 'none') {\n state = 'sign';\n value += c;\n continue;\n }\n if (state === 'e') {\n state = 'exponent_sign';\n value += c;\n continue;\n }\n }\n if (isDigit(c)) {\n if (state === 'none' || state === 'sign' || state === 'whole') {\n state = 'whole';\n value += c;\n continue;\n }\n if (state === 'decimal_point' || state === 'decimal') {\n state = 'decimal';\n value += c;\n continue;\n }\n if (state === 'e' || state === 'exponent_sign' || state === 'exponent') {\n state = 'exponent';\n value += c;\n continue;\n }\n }\n if (c === '.') {\n if (state === 'none' || state === 'sign' || state === 'whole') {\n state = 'decimal_point';\n value += c;\n continue;\n }\n }\n if (c === 'E' || c === 'e') {\n if (\n state === 'whole' ||\n state === 'decimal_point' ||\n state === 'decimal'\n ) {\n state = 'e';\n value += c;\n continue;\n }\n }\n break;\n }\n const number = Number.parseFloat(value);\n if (Number.isNaN(number)) {\n return [cursor, null];\n } else {\n // step back to delegate iteration to parent loop\n return [i - 1, number];\n }\n};\n \nconst isKey = (c) => {\n return c in PARAMS;\n};\n\nexport function parsePath(d) {\n const pathData = [];\n let key = null;\n let data = [];\n let argsCount = 0;\n let canHaveComma = false;\n let hadComma = false;\n for (let i = 0; i < d.length; i += 1) {\n const c = d.charAt(i);\n if (isWsp(c)) {\n continue;\n }\n // allow comma only between arguments\n if (canHaveComma && c === ',') {\n if (hadComma) {\n break;\n }\n hadComma = true;\n continue;\n }\n if (isKey(c)) {\n if (hadComma) {\n return pathData;\n }\n if (key === null || key === undefined) {\n // moveto should be leading key\n if (c !== 'M' && c !== 'm') {\n return pathData;\n }\n } else {\n // stop if previous key arguments are not flushed\n if (data.length !== 0) {\n return pathData;\n }\n }\n key = c;\n data = [];\n argsCount = PARAMS[key];\n canHaveComma = false;\n // flush key without arguments\n if (argsCount === 0) {\n pathData.push({ key, data });\n }\n continue;\n }\n // avoid parsing arguments if no key detected\n if (key === null || key === undefined) {\n return pathData;\n }\n // read next argument\n let newCursor = i;\n let number = null;\n if (key === 'A' || key === 'a') {\n const position = data.length;\n if (position === 0 || position === 1) {\n // allow only positive number without sign as first two arguments\n if (c !== '+' && c !== '-') {\n [newCursor, number] = readNumber(d, i);\n }\n }\n if (position === 2 || position === 5 || position === 6) {\n [newCursor, number] = readNumber(d, i);\n }\n if (position === 3 || position === 4) {\n // read flags\n if (c === '0') {\n number = 0;\n }\n if (c === '1') {\n number = 1;\n }\n }\n } else {\n [newCursor, number] = readNumber(d, i);\n }\n if (number === null || number === undefined) {\n return pathData;\n }\n data.push(number);\n canHaveComma = true;\n hadComma = false;\n i = newCursor;\n // flush arguments when necessary count is reached\n if (data.length === argsCount) {\n pathData.push({ key, data });\n // subsequent moveto coordinates are threated as implicit lineto commands\n if (key === 'M') {\n key = 'L';\n }\n if (key === 'm') {\n key = 'l';\n }\n data = [];\n }\n }\n return pathData;\n}\nexport function serialize(segments) {\n const tokens = [];\n for (const { key, data } of segments) {\n tokens.push(key);\n switch (key) {\n case 'C':\n case 'c':\n tokens.push(data[0], `${data[1]},`, data[2], `${data[3]},`, data[4], data[5]);\n break;\n case 'S':\n case 's':\n case 'Q':\n case 'q':\n tokens.push(data[0], `${data[1]},`, data[2], data[3]);\n break;\n default:\n tokens.push(...data);\n break;\n }\n }\n return tokens.join(' ');\n}\n","// Translate relative commands to absolute commands\nexport function absolutize(segments) {\n let cx = 0, cy = 0;\n let subx = 0, suby = 0;\n const out = [];\n for (const { key, data } of segments) {\n switch (key) {\n case 'M':\n out.push({ key: 'M', data: [...data] });\n [cx, cy] = data;\n [subx, suby] = data;\n break;\n case 'm':\n cx += data[0];\n cy += data[1];\n out.push({ key: 'M', data: [cx, cy] });\n subx = cx;\n suby = cy;\n break;\n case 'L':\n out.push({ key: 'L', data: [...data] });\n [cx, cy] = data;\n break;\n case 'l':\n cx += data[0];\n cy += data[1];\n out.push({ key: 'L', data: [cx, cy] });\n break;\n case 'C':\n out.push({ key: 'C', data: [...data] });\n cx = data[4];\n cy = data[5];\n break;\n case 'c': {\n const newdata = data.map((d, i) => (i % 2) ? (d + cy) : (d + cx));\n out.push({ key: 'C', data: newdata });\n cx = newdata[4];\n cy = newdata[5];\n break;\n }\n case 'Q':\n out.push({ key: 'Q', data: [...data] });\n cx = data[2];\n cy = data[3];\n break;\n case 'q': {\n const newdata = data.map((d, i) => (i % 2) ? (d + cy) : (d + cx));\n out.push({ key: 'Q', data: newdata });\n cx = newdata[2];\n cy = newdata[3];\n break;\n }\n case 'A':\n out.push({ key: 'A', data: [...data] });\n cx = data[5];\n cy = data[6];\n break;\n case 'a':\n cx += data[5];\n cy += data[6];\n out.push({ key: 'A', data: [data[0], data[1], data[2], data[3], data[4], cx, cy] });\n break;\n case 'H':\n out.push({ key: 'H', data: [...data] });\n cx = data[0];\n break;\n case 'h':\n cx += data[0];\n out.push({ key: 'H', data: [cx] });\n break;\n case 'V':\n out.push({ key: 'V', data: [...data] });\n cy = data[0];\n break;\n case 'v':\n cy += data[0];\n out.push({ key: 'V', data: [cy] });\n break;\n case 'S':\n out.push({ key: 'S', data: [...data] });\n cx = data[2];\n cy = data[3];\n break;\n case 's': {\n const newdata = data.map((d, i) => (i % 2) ? (d + cy) : (d + cx));\n out.push({ key: 'S', data: newdata });\n cx = newdata[2];\n cy = newdata[3];\n break;\n }\n case 'T':\n out.push({ key: 'T', data: [...data] });\n cx = data[0];\n cy = data[1];\n break;\n case 't':\n cx += data[0];\n cy += data[1];\n out.push({ key: 'T', data: [cx, cy] });\n break;\n case 'Z':\n case 'z':\n out.push({ key: 'Z', data: [] });\n cx = subx;\n cy = suby;\n break;\n }\n }\n return out;\n}\n","// Normalize path to include only M, L, C, and Z commands\nexport function normalize(segments) {\n const out = [];\n let lastType = '';\n let cx = 0, cy = 0;\n let subx = 0, suby = 0;\n let lcx = 0, lcy = 0;\n for (const { key, data } of segments) {\n switch (key) {\n case 'M':\n out.push({ key: 'M', data: [...data] });\n [cx, cy] = data;\n [subx, suby] = data;\n break;\n case 'C':\n out.push({ key: 'C', data: [...data] });\n cx = data[4];\n cy = data[5];\n lcx = data[2];\n lcy = data[3];\n break;\n case 'L':\n out.push({ key: 'L', data: [...data] });\n [cx, cy] = data;\n break;\n case 'H':\n cx = data[0];\n out.push({ key: 'L', data: [cx, cy] });\n break;\n case 'V':\n cy = data[0];\n out.push({ key: 'L', data: [cx, cy] });\n break;\n case 'S': {\n let cx1 = 0, cy1 = 0;\n if (lastType === 'C' || lastType === 'S') {\n cx1 = cx + (cx - lcx);\n cy1 = cy + (cy - lcy);\n }\n else {\n cx1 = cx;\n cy1 = cy;\n }\n out.push({ key: 'C', data: [cx1, cy1, ...data] });\n lcx = data[0];\n lcy = data[1];\n cx = data[2];\n cy = data[3];\n break;\n }\n case 'T': {\n const [x, y] = data;\n let x1 = 0, y1 = 0;\n if (lastType === 'Q' || lastType === 'T') {\n x1 = cx + (cx - lcx);\n y1 = cy + (cy - lcy);\n }\n else {\n x1 = cx;\n y1 = cy;\n }\n const cx1 = cx + 2 * (x1 - cx) / 3;\n const cy1 = cy + 2 * (y1 - cy) / 3;\n const cx2 = x + 2 * (x1 - x) / 3;\n const cy2 = y + 2 * (y1 - y) / 3;\n out.push({ key: 'C', data: [cx1, cy1, cx2, cy2, x, y] });\n lcx = x1;\n lcy = y1;\n cx = x;\n cy = y;\n break;\n }\n case 'Q': {\n const [x1, y1, x, y] = data;\n const cx1 = cx + 2 * (x1 - cx) / 3;\n const cy1 = cy + 2 * (y1 - cy) / 3;\n const cx2 = x + 2 * (x1 - x) / 3;\n const cy2 = y + 2 * (y1 - y) / 3;\n out.push({ key: 'C', data: [cx1, cy1, cx2, cy2, x, y] });\n lcx = x1;\n lcy = y1;\n cx = x;\n cy = y;\n break;\n }\n case 'A': {\n const r1 = Math.abs(data[0]);\n const r2 = Math.abs(data[1]);\n const angle = data[2];\n const largeArcFlag = data[3];\n const sweepFlag = data[4];\n const x = data[5];\n const y = data[6];\n if (r1 === 0 || r2 === 0) {\n out.push({ key: 'C', data: [cx, cy, x, y, x, y] });\n cx = x;\n cy = y;\n }\n else {\n if (cx !== x || cy !== y) {\n const curves = arcToCubicCurves(cx, cy, x, y, r1, r2, angle, largeArcFlag, sweepFlag);\n curves.forEach(function (curve) {\n out.push({ key: 'C', data: curve });\n });\n cx = x;\n cy = y;\n }\n }\n break;\n }\n case 'Z':\n out.push({ key: 'Z', data: [] });\n cx = subx;\n cy = suby;\n break;\n }\n lastType = key;\n }\n return out;\n}\nfunction degToRad(degrees) {\n return (Math.PI * degrees) / 180;\n}\nfunction rotate(x, y, angleRad) {\n const X = x * Math.cos(angleRad) - y * Math.sin(angleRad);\n const Y = x * Math.sin(angleRad) + y * Math.cos(angleRad);\n return [X, Y];\n}\nfunction arcToCubicCurves(x1, y1, x2, y2, r1, r2, angle, largeArcFlag, sweepFlag, recursive) {\n const angleRad = degToRad(angle);\n let params = [];\n let f1 = 0, f2 = 0, cx = 0, cy = 0;\n if (recursive) {\n [f1, f2, cx, cy] = recursive;\n }\n else {\n [x1, y1] = rotate(x1, y1, -angleRad);\n [x2, y2] = rotate(x2, y2, -angleRad);\n const x = (x1 - x2) / 2;\n const y = (y1 - y2) / 2;\n let h = (x * x) / (r1 * r1) + (y * y) / (r2 * r2);\n if (h > 1) {\n h = Math.sqrt(h);\n r1 = h * r1;\n r2 = h * r2;\n }\n const sign = (largeArcFlag === sweepFlag) ? -1 : 1;\n const r1Pow = r1 * r1;\n const r2Pow = r2 * r2;\n const left = r1Pow * r2Pow - r1Pow * y * y - r2Pow * x * x;\n const right = r1Pow * y * y + r2Pow * x * x;\n const k = sign * Math.sqrt(Math.abs(left / right));\n cx = k * r1 * y / r2 + (x1 + x2) / 2;\n cy = k * -r2 * x / r1 + (y1 + y2) / 2;\n f1 = Math.asin(parseFloat(((y1 - cy) / r2).toFixed(9)));\n f2 = Math.asin(parseFloat(((y2 - cy) / r2).toFixed(9)));\n if (x1 < cx) {\n f1 = Math.PI - f1;\n }\n if (x2 < cx) {\n f2 = Math.PI - f2;\n }\n if (f1 < 0) {\n f1 = Math.PI * 2 + f1;\n }\n if (f2 < 0) {\n f2 = Math.PI * 2 + f2;\n }\n if (sweepFlag && f1 > f2) {\n f1 = f1 - Math.PI * 2;\n }\n if (!sweepFlag && f2 > f1) {\n f2 = f2 - Math.PI * 2;\n }\n }\n let df = f2 - f1;\n if (Math.abs(df) > (Math.PI * 120 / 180)) {\n const f2old = f2;\n const x2old = x2;\n const y2old = y2;\n if (sweepFlag && f2 > f1) {\n f2 = f1 + (Math.PI * 120 / 180) * (1);\n }\n else {\n f2 = f1 + (Math.PI * 120 / 180) * (-1);\n }\n x2 = cx + r1 * Math.cos(f2);\n y2 = cy + r2 * Math.sin(f2);\n params = arcToCubicCurves(x2, y2, x2old, y2old, r1, r2, angle, 0, sweepFlag, [f2, f2old, cx, cy]);\n }\n df = f2 - f1;\n const c1 = Math.cos(f1);\n const s1 = Math.sin(f1);\n const c2 = Math.cos(f2);\n const s2 = Math.sin(f2);\n const t = Math.tan(df / 4);\n const hx = 4 / 3 * r1 * t;\n const hy = 4 / 3 * r2 * t;\n const m1 = [x1, y1];\n const m2 = [x1 + hx * s1, y1 - hy * c1];\n const m3 = [x2 + hx * s2, y2 - hy * c2];\n const m4 = [x2, y2];\n m2[0] = 2 * m1[0] - m2[0];\n m2[1] = 2 * m1[1] - m2[1];\n if (recursive) {\n return [m2, m3, m4].concat(params);\n }\n else {\n params = [m2, m3, m4].concat(params);\n const curves = [];\n for (let i = 0; i < params.length; i += 3) {\n const r1 = rotate(params[i][0], params[i][1], angleRad);\n const r2 = rotate(params[i + 1][0], params[i + 1][1], angleRad);\n const r3 = rotate(params[i + 2][0], params[i + 2][1], angleRad);\n curves.push([r1[0], r1[1], r2[0], r2[1], r3[0], r3[1]]);\n }\n return curves;\n }\n}\n","import { getFiller } from './fillers/filler.js';\nimport { Random } from './math.js';\nimport { parsePath, normalize, absolutize } from 'path-data-parser';\nconst helper = {\n randOffset,\n randOffsetWithRange,\n ellipse,\n doubleLineOps: doubleLineFillOps,\n};\nexport function line(x1, y1, x2, y2, o) {\n return { type: 'path', ops: _doubleLine(x1, y1, x2, y2, o) };\n}\nexport function linearPath(points, close, o) {\n const len = (points || []).length;\n if (len > 2) {\n const ops = [];\n for (let i = 0; i < (len - 1); i++) {\n ops.push(..._doubleLine(points[i][0], points[i][1], points[i + 1][0], points[i + 1][1], o));\n }\n if (close) {\n ops.push(..._doubleLine(points[len - 1][0], points[len - 1][1], points[0][0], points[0][1], o));\n }\n return { type: 'path', ops };\n }\n else if (len === 2) {\n return line(points[0][0], points[0][1], points[1][0], points[1][1], o);\n }\n return { type: 'path', ops: [] };\n}\nexport function polygon(points, o) {\n return linearPath(points, true, o);\n}\nexport function rectangle(x, y, width, height, o) {\n const points = [\n [x, y],\n [x + width, y],\n [x + width, y + height],\n [x, y + height],\n ];\n return polygon(points, o);\n}\nexport function curve(points, o) {\n let o1 = _curveWithOffset(points, 1 * (1 + o.roughness * 0.2), o);\n if (!o.disableMultiStroke) {\n const o2 = _curveWithOffset(points, 1.5 * (1 + o.roughness * 0.22), cloneOptionsAlterSeed(o));\n o1 = o1.concat(o2);\n }\n return { type: 'path', ops: o1 };\n}\nexport function ellipse(x, y, width, height, o) {\n const params = generateEllipseParams(width, height, o);\n return ellipseWithParams(x, y, o, params).opset;\n}\nexport function generateEllipseParams(width, height, o) {\n const psq = Math.sqrt(Math.PI * 2 * Math.sqrt((Math.pow(width / 2, 2) + Math.pow(height / 2, 2)) / 2));\n const stepCount = Math.ceil(Math.max(o.curveStepCount, (o.curveStepCount / Math.sqrt(200)) * psq));\n const increment = (Math.PI * 2) / stepCount;\n let rx = Math.abs(width / 2);\n let ry = Math.abs(height / 2);\n const curveFitRandomness = 1 - o.curveFitting;\n rx += _offsetOpt(rx * curveFitRandomness, o);\n ry += _offsetOpt(ry * curveFitRandomness, o);\n return { increment, rx, ry };\n}\nexport function ellipseWithParams(x, y, o, ellipseParams) {\n const [ap1, cp1] = _computeEllipsePoints(ellipseParams.increment, x, y, ellipseParams.rx, ellipseParams.ry, 1, ellipseParams.increment * _offset(0.1, _offset(0.4, 1, o), o), o);\n let o1 = _curve(ap1, null, o);\n if ((!o.disableMultiStroke) && (o.roughness !== 0)) {\n const [ap2] = _computeEllipsePoints(ellipseParams.increment, x, y, ellipseParams.rx, ellipseParams.ry, 1.5, 0, o);\n const o2 = _curve(ap2, null, o);\n o1 = o1.concat(o2);\n }\n return {\n estimatedPoints: cp1,\n opset: { type: 'path', ops: o1 },\n };\n}\nexport function arc(x, y, width, height, start, stop, closed, roughClosure, o) {\n const cx = x;\n const cy = y;\n let rx = Math.abs(width / 2);\n let ry = Math.abs(height / 2);\n rx += _offsetOpt(rx * 0.01, o);\n ry += _offsetOpt(ry * 0.01, o);\n let strt = start;\n let stp = stop;\n while (strt < 0) {\n strt += Math.PI * 2;\n stp += Math.PI * 2;\n }\n if ((stp - strt) > (Math.PI * 2)) {\n strt = 0;\n stp = Math.PI * 2;\n }\n const ellipseInc = (Math.PI * 2) / o.curveStepCount;\n const arcInc = Math.min(ellipseInc / 2, (stp - strt) / 2);\n const ops = _arc(arcInc, cx, cy, rx, ry, strt, stp, 1, o);\n if (!o.disableMultiStroke) {\n const o2 = _arc(arcInc, cx, cy, rx, ry, strt, stp, 1.5, o);\n ops.push(...o2);\n }\n if (closed) {\n if (roughClosure) {\n ops.push(..._doubleLine(cx, cy, cx + rx * Math.cos(strt), cy + ry * Math.sin(strt), o), ..._doubleLine(cx, cy, cx + rx * Math.cos(stp), cy + ry * Math.sin(stp), o));\n }\n else {\n ops.push({ op: 'lineTo', data: [cx, cy] }, { op: 'lineTo', data: [cx + rx * Math.cos(strt), cy + ry * Math.sin(strt)] });\n }\n }\n return { type: 'path', ops };\n}\nexport function svgPath(path, o) {\n const segments = normalize(absolutize(parsePath(path)));\n const ops = [];\n let first = [0, 0];\n let current = [0, 0];\n for (const { key, data } of segments) {\n switch (key) {\n case 'M': {\n const ro = 1 * (o.maxRandomnessOffset || 0);\n const pv = o.preserveVertices;\n ops.push({ op: 'move', data: data.map((d) => d + (pv ? 0 : _offsetOpt(ro, o))) });\n current = [data[0], data[1]];\n first = [data[0], data[1]];\n break;\n }\n case 'L':\n ops.push(..._doubleLine(current[0], current[1], data[0], data[1], o));\n current = [data[0], data[1]];\n break;\n case 'C': {\n const [x1, y1, x2, y2, x, y] = data;\n ops.push(..._bezierTo(x1, y1, x2, y2, x, y, current, o));\n current = [x, y];\n break;\n }\n case 'Z':\n ops.push(..._doubleLine(current[0], current[1], first[0], first[1], o));\n current = [first[0], first[1]];\n break;\n }\n }\n return { type: 'path', ops };\n}\n// Fills\nexport function solidFillPolygon(polygonList, o) {\n const ops = [];\n for (const points of polygonList) {\n if (points.length) {\n const offset = o.maxRandomnessOffset || 0;\n const len