{"version":3,"file":"hanzi-writer.min.js","sources":["../src/utils.ts","../src/RenderState.ts","../src/geometry.ts","../src/models/Stroke.ts","../src/models/Character.ts","../src/parseCharData.ts","../src/Positioner.ts","../src/strokeMatches.ts","../src/models/UserStroke.ts","../src/Mutation.ts","../src/characterActions.ts","../src/quizActions.ts","../src/Quiz.ts","../src/renderers/svg/svgUtils.ts","../src/renderers/StrokeRendererBase.ts","../src/renderers/svg/StrokeRenderer.ts","../src/renderers/svg/CharacterRenderer.ts","../src/renderers/svg/UserStrokeRenderer.ts","../src/renderers/RenderTargetBase.ts","../src/renderers/svg/RenderTarget.ts","../src/renderers/svg/index.ts","../src/renderers/svg/HanziWriterRenderer.ts","../src/renderers/canvas/canvasUtils.ts","../src/renderers/canvas/StrokeRenderer.ts","../src/renderers/canvas/CharacterRenderer.ts","../src/renderers/canvas/renderUserStroke.ts","../src/renderers/canvas/RenderTarget.ts","../src/renderers/canvas/index.ts","../src/renderers/canvas/HanziWriterRenderer.ts","../src/defaultCharDataLoader.ts","../src/defaultOptions.ts","../src/LoadingManager.ts","../src/HanziWriter.ts"],"sourcesContent":["import { ColorObject, RecursivePartial } from './typings/types';\n\n// hacky way to get around rollup not properly setting `global` to `window` in browser\nconst globalObj = typeof window === 'undefined' ? global : window;\n\nexport const performanceNow =\n (globalObj.performance && (() => globalObj.performance.now())) || (() => Date.now());\nexport const requestAnimationFrame =\n globalObj.requestAnimationFrame ||\n ((callback) => setTimeout(() => callback(performanceNow()), 1000 / 60));\nexport const cancelAnimationFrame = globalObj.cancelAnimationFrame || clearTimeout;\n\n// Object.assign polyfill, because IE :/\nexport const _assign = function (target: any, ...overrides: any[]) {\n const overrideTarget = Object(target);\n overrides.forEach((override) => {\n if (override != null) {\n for (const key in override) {\n if (Object.prototype.hasOwnProperty.call(override, key)) {\n overrideTarget[key] = override[key];\n }\n }\n }\n });\n return overrideTarget;\n};\n\nexport const assign = Object.assign || _assign;\n\nexport function arrLast(arr: Array) {\n return arr[arr.length - 1];\n}\n\nexport const fixIndex = (index: number, length: number) => {\n // helper to handle negative indexes in array indices\n if (index < 0) {\n return length + index;\n }\n return index;\n};\n\nexport const selectIndex = (arr: Array, index: number) => {\n // helper to select item from array at index, supporting negative indexes\n return arr[fixIndex(index, arr.length)];\n};\n\nexport function copyAndMergeDeep(base: T, override: RecursivePartial | undefined) {\n const output = { ...base };\n for (const key in override) {\n const baseVal = base[key];\n const overrideVal = override[key];\n if (baseVal === overrideVal) {\n continue;\n }\n if (\n baseVal &&\n overrideVal &&\n typeof baseVal === 'object' &&\n typeof overrideVal === 'object' &&\n !Array.isArray(overrideVal)\n ) {\n output[key] = copyAndMergeDeep(baseVal, overrideVal);\n } else {\n // @ts-ignore\n output[key] = overrideVal;\n }\n }\n return output;\n}\n\n/** basically a simplified version of lodash.get, selects a key out of an object like 'a.b' from {a: {b: 7}} */\nexport function inflate(scope: string, obj: any): any {\n const parts = scope.split('.');\n const final: any = {};\n let current = final;\n for (let i = 0; i < parts.length; i++) {\n const cap = i === parts.length - 1 ? obj : {};\n current[parts[i]] = cap;\n current = cap;\n }\n return final;\n}\n\nlet count = 0;\n\nexport function counter() {\n count++;\n return count;\n}\n\nexport function average(arr: number[]) {\n const sum = arr.reduce((acc, val) => val + acc, 0);\n return sum / arr.length;\n}\n\nexport function timeout(duration = 0) {\n return new Promise((resolve) => setTimeout(resolve, duration));\n}\n\nexport function colorStringToVals(colorString: string): ColorObject {\n const normalizedColor = colorString.toUpperCase().trim();\n // based on https://stackoverflow.com/a/21648508\n if (/^#([A-F0-9]{3}){1,2}$/.test(normalizedColor)) {\n let hexParts = normalizedColor.substring(1).split('');\n if (hexParts.length === 3) {\n hexParts = [\n hexParts[0],\n hexParts[0],\n hexParts[1],\n hexParts[1],\n hexParts[2],\n hexParts[2],\n ];\n }\n const hexStr = `${hexParts.join('')}`;\n return {\n r: parseInt(hexStr.slice(0, 2), 16),\n g: parseInt(hexStr.slice(2, 4), 16),\n b: parseInt(hexStr.slice(4, 6), 16),\n a: 1,\n };\n }\n const rgbMatch = normalizedColor.match(\n /^RGBA?\\((\\d+)\\s*,\\s*(\\d+)\\s*,\\s*(\\d+)(?:\\s*,\\s*(\\d*\\.?\\d+))?\\)$/,\n );\n if (rgbMatch) {\n return {\n r: parseInt(rgbMatch[1], 10),\n g: parseInt(rgbMatch[2], 10),\n b: parseInt(rgbMatch[3], 10),\n // @ts-expect-error ts-migrate(2554) FIXME: Expected 1 arguments, but got 2.\n a: parseFloat(rgbMatch[4] || 1, 10),\n };\n }\n throw new Error(`Invalid color: ${colorString}`);\n}\n\nexport const trim = (string: string) => string.replace(/^\\s+/, '').replace(/\\s+$/, '');\n\n// return a new array-like object with int keys where each key is item\n// ex: objRepeat({x: 8}, 3) === {0: {x: 8}, 1: {x: 8}, 2: {x: 8}}\nexport function objRepeat(item: T, times: number) {\n const obj: Record = {};\n for (let i = 0; i < times; i++) {\n obj[i] = item;\n }\n return obj;\n}\n\n// similar to objRepeat, but takes in a callback which is called for each index in the object\nexport function objRepeatCb(times: number, cb: (i: number) => T) {\n const obj: Record = {};\n for (let i = 0; i < times; i++) {\n obj[i] = cb(i);\n }\n return obj;\n}\n\nconst ua = globalObj.navigator?.userAgent || '';\n\nexport const isMsBrowser =\n ua.indexOf('MSIE ') > 0 || ua.indexOf('Trident/') > 0 || ua.indexOf('Edge/') > 0;\n\n// eslint-disable-next-line @typescript-eslint/no-empty-function\nexport const noop = () => {};\n","import Character from './models/Character';\nimport { GenericMutation } from './Mutation';\nimport {\n ColorObject,\n OnCompleteFunction,\n Point,\n RecursivePartial,\n} from './typings/types';\nimport { copyAndMergeDeep, colorStringToVals, noop } from './utils';\n\nexport type StrokeRenderState = {\n opacity: number;\n displayPortion: number;\n};\n\nexport type CharacterRenderState = {\n opacity: number;\n strokes: Record;\n};\n\nexport type RenderStateObject = {\n options: {\n drawingFadeDuration: number;\n drawingWidth: number;\n drawingColor: ColorObject;\n strokeColor: ColorObject;\n outlineColor: ColorObject;\n radicalColor: ColorObject;\n highlightColor: ColorObject;\n };\n character: {\n main: CharacterRenderState;\n outline: CharacterRenderState;\n highlight: CharacterRenderState;\n };\n userStrokes: Record<\n string,\n | {\n points: Point[];\n opacity: number;\n }\n | undefined\n > | null;\n};\n\nexport type CharacterName = keyof RenderStateObject['character'];\n\ntype OnStateChangeCallback = (\n nextState: RenderStateObject,\n currentState: RenderStateObject,\n) => void;\n\ntype MutationChain = {\n _isActive: boolean;\n _index: number;\n _resolve: OnCompleteFunction;\n _mutations: GenericMutation[];\n _loop: boolean | undefined;\n _scopes: string[];\n};\n\nexport type RenderStateOptions = {\n strokeColor: string;\n radicalColor: string | null;\n highlightColor: string;\n outlineColor: string;\n drawingColor: string;\n drawingFadeDuration: number;\n drawingWidth: number;\n outlineWidth: number;\n showCharacter: boolean;\n showOutline: boolean;\n};\n\nexport default class RenderState {\n _mutationChains: MutationChain[] = [];\n _onStateChange: OnStateChangeCallback;\n\n state: RenderStateObject;\n\n constructor(\n character: Character,\n options: RenderStateOptions,\n onStateChange: OnStateChangeCallback = noop,\n ) {\n this._onStateChange = onStateChange;\n\n this.state = {\n options: {\n drawingFadeDuration: options.drawingFadeDuration,\n drawingWidth: options.drawingWidth,\n drawingColor: colorStringToVals(options.drawingColor),\n strokeColor: colorStringToVals(options.strokeColor),\n outlineColor: colorStringToVals(options.outlineColor),\n radicalColor: colorStringToVals(options.radicalColor || options.strokeColor),\n highlightColor: colorStringToVals(options.highlightColor),\n },\n character: {\n main: {\n opacity: options.showCharacter ? 1 : 0,\n strokes: {},\n },\n outline: {\n opacity: options.showOutline ? 1 : 0,\n strokes: {},\n },\n highlight: {\n opacity: 1,\n strokes: {},\n },\n },\n userStrokes: null,\n };\n\n for (let i = 0; i < character.strokes.length; i++) {\n this.state.character.main.strokes[i] = {\n opacity: 1,\n displayPortion: 1,\n };\n\n this.state.character.outline.strokes[i] = {\n opacity: 1,\n displayPortion: 1,\n };\n\n this.state.character.highlight.strokes[i] = {\n opacity: 0,\n displayPortion: 1,\n };\n }\n }\n\n overwriteOnStateChange(onStateChange: OnStateChangeCallback) {\n this._onStateChange = onStateChange;\n }\n\n updateState(stateChanges: RecursivePartial) {\n const nextState = copyAndMergeDeep(this.state, stateChanges);\n this._onStateChange(nextState, this.state);\n this.state = nextState;\n }\n\n run(\n mutations: GenericMutation[],\n options: {\n loop?: boolean;\n } = {},\n ) {\n const scopes = mutations.map((mut) => mut.scope);\n\n this.cancelMutations(scopes);\n\n return new Promise((resolve: OnCompleteFunction) => {\n const mutationChain: MutationChain = {\n _isActive: true,\n _index: 0,\n _resolve: resolve,\n _mutations: mutations,\n _loop: options.loop,\n _scopes: scopes,\n };\n this._mutationChains.push(mutationChain);\n this._run(mutationChain);\n });\n }\n\n _run(mutationChain: MutationChain) {\n if (!mutationChain._isActive) {\n return;\n }\n\n const mutations = mutationChain._mutations;\n if (mutationChain._index >= mutations.length) {\n if (mutationChain._loop) {\n mutationChain._index = 0; // eslint-disable-line no-param-reassign\n } else {\n mutationChain._isActive = false; // eslint-disable-line no-param-reassign\n this._mutationChains = this._mutationChains.filter(\n (chain) => chain !== mutationChain,\n );\n // The chain is done - resolve the promise to signal it finished successfully\n mutationChain._resolve({ canceled: false });\n return;\n }\n }\n\n const activeMutation = mutationChain._mutations[mutationChain._index];\n\n activeMutation.run(this).then(() => {\n if (mutationChain._isActive) {\n mutationChain._index++; // eslint-disable-line no-param-reassign\n this._run(mutationChain);\n }\n });\n }\n\n _getActiveMutations() {\n return this._mutationChains.map((chain) => chain._mutations[chain._index]);\n }\n\n pauseAll() {\n this._getActiveMutations().forEach((mutation) => mutation.pause());\n }\n\n resumeAll() {\n this._getActiveMutations().forEach((mutation) => mutation.resume());\n }\n\n cancelMutations(scopesToCancel: string[]) {\n for (const chain of this._mutationChains) {\n for (const chainId of chain._scopes) {\n for (const scopeToCancel of scopesToCancel) {\n if (chainId.startsWith(scopeToCancel) || scopeToCancel.startsWith(chainId)) {\n this._cancelMutationChain(chain);\n }\n }\n }\n }\n }\n\n cancelAll() {\n this.cancelMutations(['']);\n }\n\n _cancelMutationChain(mutationChain: MutationChain) {\n mutationChain._isActive = false;\n for (let i = mutationChain._index; i < mutationChain._mutations.length; i++) {\n mutationChain._mutations[i].cancel(this);\n }\n\n mutationChain._resolve?.({ canceled: true });\n\n this._mutationChains = this._mutationChains.filter(\n (chain) => chain !== mutationChain,\n );\n }\n}\n","import { Point } from './typings/types';\nimport { average, arrLast } from './utils';\n\nexport const subtract = (p1: Point, p2: Point) => ({ x: p1.x - p2.x, y: p1.y - p2.y });\n\nexport const magnitude = (point: Point) =>\n Math.sqrt(Math.pow(point.x, 2) + Math.pow(point.y, 2));\n\nexport const distance = (point1: Point, point2: Point) =>\n magnitude(subtract(point1, point2));\n\nexport const equals = (point1: Point, point2: Point) =>\n point1.x === point2.x && point1.y === point2.y;\n\nexport const round = (point: Point, precision = 1) => {\n const multiplier = precision * 10;\n return {\n x: Math.round(multiplier * point.x) / multiplier,\n y: Math.round(multiplier * point.y) / multiplier,\n };\n};\n\nexport const length = (points: Point[]) => {\n let lastPoint = points[0];\n const pointsSansFirst = points.slice(1);\n return pointsSansFirst.reduce((acc, point) => {\n const dist = distance(point, lastPoint);\n lastPoint = point;\n return acc + dist;\n }, 0);\n};\n\nexport const cosineSimilarity = (point1: Point, point2: Point) => {\n const rawDotProduct = point1.x * point2.x + point1.y * point2.y;\n return rawDotProduct / magnitude(point1) / magnitude(point2);\n};\n\n/**\n * return a new point, p3, which is on the same line as p1 and p2, but distance away\n * from p2. p1, p2, p3 will always lie on the line in that order\n */\nexport const _extendPointOnLine = (p1: Point, p2: Point, dist: number) => {\n const vect = subtract(p2, p1);\n const norm = dist / magnitude(vect);\n return { x: p2.x + norm * vect.x, y: p2.y + norm * vect.y };\n};\n\n/** based on http://www.kr.tuwien.ac.at/staff/eiter/et-archive/cdtr9464.pdf */\nexport const frechetDist = (curve1: Point[], curve2: Point[]) => {\n const longCurve = curve1.length >= curve2.length ? curve1 : curve2;\n const shortCurve = curve1.length >= curve2.length ? curve2 : curve1;\n\n const calcVal = (\n i: number,\n j: number,\n prevResultsCol: number[],\n curResultsCol: number[],\n ): number => {\n if (i === 0 && j === 0) {\n return distance(longCurve[0], shortCurve[0]);\n }\n\n if (i > 0 && j === 0) {\n return Math.max(prevResultsCol[0], distance(longCurve[i], shortCurve[0]));\n }\n\n const lastResult = curResultsCol[curResultsCol.length - 1];\n\n if (i === 0 && j > 0) {\n return Math.max(lastResult, distance(longCurve[0], shortCurve[j]));\n }\n\n return Math.max(\n Math.min(prevResultsCol[j], prevResultsCol[j - 1], lastResult),\n distance(longCurve[i], shortCurve[j]),\n );\n };\n\n let prevResultsCol: number[] = [];\n for (let i = 0; i < longCurve.length; i++) {\n const curResultsCol: number[] = [];\n for (let j = 0; j < shortCurve.length; j++) {\n // we only need the results from i - 1 and j - 1 to continue the calculation\n // so we only need to hold onto the last column of calculated results\n // prevResultsCol is results[i-1][:] in the original algorithm\n // curResultsCol is results[i][:j-1] in the original algorithm\n curResultsCol.push(calcVal(i, j, prevResultsCol, curResultsCol));\n }\n prevResultsCol = curResultsCol;\n }\n\n return prevResultsCol[shortCurve.length - 1];\n};\n\n/** break up long segments in the curve into smaller segments of len maxLen or smaller */\nexport const subdivideCurve = (curve: Point[], maxLen = 0.05) => {\n const newCurve = curve.slice(0, 1);\n\n for (const point of curve.slice(1)) {\n const prevPoint = newCurve[newCurve.length - 1];\n const segLen = distance(point, prevPoint);\n if (segLen > maxLen) {\n const numNewPoints = Math.ceil(segLen / maxLen);\n const newSegLen = segLen / numNewPoints;\n for (let i = 0; i < numNewPoints; i++) {\n newCurve.push(_extendPointOnLine(point, prevPoint, -1 * newSegLen * (i + 1)));\n }\n } else {\n newCurve.push(point);\n }\n }\n\n return newCurve;\n};\n\n/** redraw the curve using numPoints equally spaced out along the length of the curve */\nexport const outlineCurve = (curve: Point[], numPoints = 30) => {\n const curveLen = length(curve);\n const segmentLen = curveLen / (numPoints - 1);\n const outlinePoints = [curve[0]];\n const endPoint = arrLast(curve);\n const remainingCurvePoints = curve.slice(1);\n\n for (let i = 0; i < numPoints - 2; i++) {\n let lastPoint: Point = arrLast(outlinePoints);\n let remainingDist = segmentLen;\n let outlinePointFound = false;\n while (!outlinePointFound) {\n const nextPointDist = distance(lastPoint, remainingCurvePoints[0]);\n if (nextPointDist < remainingDist) {\n remainingDist -= nextPointDist;\n lastPoint = remainingCurvePoints.shift()!;\n } else {\n const nextPoint = _extendPointOnLine(\n lastPoint,\n remainingCurvePoints[0],\n remainingDist - nextPointDist,\n );\n outlinePoints.push(nextPoint);\n outlinePointFound = true;\n }\n }\n }\n\n outlinePoints.push(endPoint);\n\n return outlinePoints;\n};\n\n/** translate and scale from https://en.wikipedia.org/wiki/Procrustes_analysis */\nexport const normalizeCurve = (curve: Point[]) => {\n const outlinedCurve = outlineCurve(curve);\n const meanX = average(outlinedCurve.map((point) => point.x));\n const meanY = average(outlinedCurve.map((point) => point.y));\n const mean = { x: meanX, y: meanY };\n const translatedCurve = outlinedCurve.map((point) => subtract(point, mean));\n const scale = Math.sqrt(\n average([\n Math.pow(translatedCurve[0].x, 2) + Math.pow(translatedCurve[0].y, 2),\n Math.pow(arrLast(translatedCurve).x, 2) + Math.pow(arrLast(translatedCurve).y, 2),\n ]),\n );\n const scaledCurve = translatedCurve.map((point) => ({\n x: point.x / scale,\n y: point.y / scale,\n }));\n return subdivideCurve(scaledCurve);\n};\n\n// rotate around the origin\nexport const rotate = (curve: Point[], theta: number) => {\n return curve.map((point) => ({\n x: Math.cos(theta) * point.x - Math.sin(theta) * point.y,\n y: Math.sin(theta) * point.x + Math.cos(theta) * point.y,\n }));\n};\n\n// remove intermediate points that are on the same line as the points to either side\nexport const _filterParallelPoints = (points: Point[]) => {\n if (points.length < 3) return points;\n const filteredPoints = [points[0], points[1]];\n points.slice(2).forEach((point) => {\n const numFilteredPoints = filteredPoints.length;\n const curVect = subtract(point, filteredPoints[numFilteredPoints - 1]);\n const prevVect = subtract(\n filteredPoints[numFilteredPoints - 1],\n filteredPoints[numFilteredPoints - 2],\n );\n // this is the z coord of the cross-product. If this is 0 then they're parallel\n const isParallel = curVect.y * prevVect.x - curVect.x * prevVect.y === 0;\n if (isParallel) {\n filteredPoints.pop();\n }\n filteredPoints.push(point);\n });\n return filteredPoints;\n};\n\nexport function getPathString(points: Point[], close = false) {\n const start = round(points[0]);\n const remainingPoints = points.slice(1);\n let pathString = `M ${start.x} ${start.y}`;\n remainingPoints.forEach((point) => {\n const roundedPoint = round(point);\n pathString += ` L ${roundedPoint.x} ${roundedPoint.y}`;\n });\n if (close) {\n pathString += 'Z';\n }\n return pathString;\n}\n\n/** take points on a path and move their start point backwards by distance */\nexport const extendStart = (points: Point[], dist: number) => {\n const filteredPoints = _filterParallelPoints(points);\n if (filteredPoints.length < 2) return filteredPoints;\n const p1 = filteredPoints[1];\n const p2 = filteredPoints[0];\n const newStart = _extendPointOnLine(p1, p2, dist);\n const extendedPoints = filteredPoints.slice(1);\n extendedPoints.unshift(newStart);\n return extendedPoints;\n};\n","import { subtract, distance, length } from '../geometry';\nimport { Point } from '../typings/types';\n\nexport default class Stroke {\n path: string;\n points: Point[];\n strokeNum: number;\n isInRadical: boolean;\n\n constructor(path: string, points: Point[], strokeNum: number, isInRadical = false) {\n this.path = path;\n this.points = points;\n this.strokeNum = strokeNum;\n this.isInRadical = isInRadical;\n }\n\n getStartingPoint() {\n return this.points[0];\n }\n\n getEndingPoint() {\n return this.points[this.points.length - 1];\n }\n\n getLength(): number {\n return length(this.points);\n }\n\n getVectors() {\n let lastPoint = this.points[0];\n const pointsSansFirst = this.points.slice(1);\n return pointsSansFirst.map((point) => {\n const vector = subtract(point, lastPoint);\n lastPoint = point;\n return vector;\n });\n }\n\n getDistance(point: Point) {\n const distances = this.points.map((strokePoint) => distance(strokePoint, point));\n return Math.min(...distances);\n }\n\n getAverageDistance(points: Point[]) {\n const totalDist = points.reduce((acc, point) => acc + this.getDistance(point), 0);\n return totalDist / points.length;\n }\n}\n","import Stroke from './Stroke';\n\nexport default class Character {\n symbol: string;\n strokes: Stroke[];\n\n constructor(symbol: string, strokes: Stroke[]) {\n this.symbol = symbol;\n this.strokes = strokes;\n }\n}\n","import Stroke from './models/Stroke';\nimport Character from './models/Character';\nimport { CharacterJson } from './typings/types';\n\nfunction generateStrokes({ radStrokes, strokes, medians }: CharacterJson) {\n const isInRadical = (strokeNum: number) => (radStrokes?.indexOf(strokeNum) ?? -1) >= 0;\n return strokes.map((path, index) => {\n const points = medians[index].map((pointData) => {\n const [x, y] = pointData;\n return { x, y };\n });\n return new Stroke(path, points, index, isInRadical(index));\n });\n}\n\nexport default function parseCharData(symbol: string, charJson: CharacterJson) {\n const strokes = generateStrokes(charJson);\n return new Character(symbol, strokes);\n}\n","import { Point } from './typings/types';\n\n// All makemeahanzi characters have the same bounding box\nconst CHARACTER_BOUNDS = [\n { x: 0, y: -124 },\n { x: 1024, y: 900 },\n];\nconst [from, to] = CHARACTER_BOUNDS;\nconst preScaledWidth = to.x - from.x;\nconst preScaledHeight = to.y - from.y;\n\nexport type PositionerOptions = {\n /** Default: 0 */\n width: number;\n /** Default: 0 */\n height: number;\n /** Default: 20 */\n padding: number;\n};\n\nexport default class Positioner {\n padding: number;\n width: number;\n height: number;\n xOffset: number;\n yOffset: number;\n scale: number;\n\n constructor(options: PositionerOptions) {\n const { padding, width, height } = options;\n this.padding = padding;\n this.width = width;\n this.height = height;\n\n const effectiveWidth = width - 2 * padding;\n const effectiveHeight = height - 2 * padding;\n const scaleX = effectiveWidth / preScaledWidth;\n const scaleY = effectiveHeight / preScaledHeight;\n\n this.scale = Math.min(scaleX, scaleY);\n\n const xCenteringBuffer = padding + (effectiveWidth - this.scale * preScaledWidth) / 2;\n const yCenteringBuffer =\n padding + (effectiveHeight - this.scale * preScaledHeight) / 2;\n\n this.xOffset = -1 * from.x * this.scale + xCenteringBuffer;\n this.yOffset = -1 * from.y * this.scale + yCenteringBuffer;\n }\n\n convertExternalPoint(point: Point) {\n const x = (point.x - this.xOffset) / this.scale;\n const y = (this.height - this.yOffset - point.y) / this.scale;\n return { x, y };\n }\n}\n","import { average } from './utils';\nimport {\n cosineSimilarity,\n equals,\n frechetDist,\n distance,\n subtract,\n normalizeCurve,\n rotate,\n length,\n} from './geometry';\nimport { Point } from './typings/types';\nimport UserStroke from './models/UserStroke';\nimport Stroke from './models/Stroke';\nimport Character from './models/Character';\n\nconst COSINE_SIMILARITY_THRESHOLD = 0; // -1 to 1, smaller = more lenient\nconst START_AND_END_DIST_THRESHOLD = 250; // bigger = more lenient\nconst FRECHET_THRESHOLD = 0.4; // bigger = more lenient\nconst MIN_LEN_THRESHOLD = 0.35; // smaller = more lenient\n\nexport interface StrokeMatchResultMeta {\n isStrokeBackwards: boolean;\n}\n\nexport interface StrokeMatchResult {\n isMatch: boolean;\n meta: StrokeMatchResultMeta;\n}\n\nexport default function strokeMatches(\n userStroke: UserStroke,\n character: Character,\n strokeNum: number,\n options: {\n leniency?: number;\n isOutlineVisible?: boolean;\n averageDistanceThreshold?: number;\n } = {},\n): StrokeMatchResult {\n const strokes = character.strokes;\n const points = stripDuplicates(userStroke.points);\n\n if (points.length < 2) {\n return { isMatch: false, meta: { isStrokeBackwards: false } };\n }\n\n const { isMatch, meta, avgDist } = getMatchData(points, strokes[strokeNum], options);\n\n if (!isMatch) {\n return { isMatch, meta };\n }\n\n // if there is a better match among strokes the user hasn't drawn yet, the user probably drew the wrong stroke\n const laterStrokes = strokes.slice(strokeNum + 1);\n let closestMatchDist = avgDist;\n\n for (let i = 0; i < laterStrokes.length; i++) {\n const { isMatch, avgDist } = getMatchData(points, laterStrokes[i], {\n ...options,\n checkBackwards: false,\n });\n if (isMatch && avgDist < closestMatchDist) {\n closestMatchDist = avgDist;\n }\n }\n // if there's a better match, rather that returning false automatically, try reducing leniency instead\n // if leniency is already really high we can allow some similar strokes to pass\n if (closestMatchDist < avgDist) {\n // adjust leniency between 0.3 and 0.6 depending on how much of a better match the new match is\n const leniencyAdjustment = (0.6 * (closestMatchDist + avgDist)) / (2 * avgDist);\n const { isMatch, meta } = getMatchData(points, strokes[strokeNum], {\n ...options,\n leniency: (options.leniency || 1) * leniencyAdjustment,\n });\n return { isMatch, meta };\n }\n\n return { isMatch, meta };\n}\n\nconst startAndEndMatches = (points: Point[], closestStroke: Stroke, leniency: number) => {\n const startingDist = distance(closestStroke.getStartingPoint(), points[0]);\n const endingDist = distance(closestStroke.getEndingPoint(), points[points.length - 1]);\n return (\n startingDist <= START_AND_END_DIST_THRESHOLD * leniency &&\n endingDist <= START_AND_END_DIST_THRESHOLD * leniency\n );\n};\n\n// returns a list of the direction of all segments in the line connecting the points\nconst getEdgeVectors = (points: Point[]) => {\n const vectors: Point[] = [];\n let lastPoint = points[0];\n points.slice(1).forEach((point) => {\n vectors.push(subtract(point, lastPoint));\n lastPoint = point;\n });\n return vectors;\n};\n\nconst directionMatches = (points: Point[], stroke: Stroke) => {\n const edgeVectors = getEdgeVectors(points);\n const strokeVectors = stroke.getVectors();\n const similarities = edgeVectors.map((edgeVector) => {\n const strokeSimilarities = strokeVectors.map((strokeVector) =>\n cosineSimilarity(strokeVector, edgeVector),\n );\n return Math.max(...strokeSimilarities);\n });\n const avgSimilarity = average(similarities);\n return avgSimilarity > COSINE_SIMILARITY_THRESHOLD;\n};\n\nconst lengthMatches = (points: Point[], stroke: Stroke, leniency: number) => {\n return (\n (leniency * (length(points) + 25)) / (stroke.getLength() + 25) >= MIN_LEN_THRESHOLD\n );\n};\n\nconst stripDuplicates = (points: Point[]) => {\n if (points.length < 2) return points;\n const [firstPoint, ...rest] = points;\n const dedupedPoints = [firstPoint];\n\n for (const point of rest) {\n if (!equals(point, dedupedPoints[dedupedPoints.length - 1])) {\n dedupedPoints.push(point);\n }\n }\n\n return dedupedPoints;\n};\n\nconst SHAPE_FIT_ROTATIONS = [\n Math.PI / 16,\n Math.PI / 32,\n 0,\n (-1 * Math.PI) / 32,\n (-1 * Math.PI) / 16,\n];\n\nconst shapeFit = (curve1: Point[], curve2: Point[], leniency: number) => {\n const normCurve1 = normalizeCurve(curve1);\n const normCurve2 = normalizeCurve(curve2);\n let minDist = Infinity;\n SHAPE_FIT_ROTATIONS.forEach((theta) => {\n const dist = frechetDist(normCurve1, rotate(normCurve2, theta));\n if (dist < minDist) {\n minDist = dist;\n }\n });\n return minDist <= FRECHET_THRESHOLD * leniency;\n};\n\nconst getMatchData = (\n points: Point[],\n stroke: Stroke,\n options: {\n leniency?: number;\n isOutlineVisible?: boolean;\n checkBackwards?: boolean;\n averageDistanceThreshold?: number;\n },\n): StrokeMatchResult & { avgDist: number } => {\n const {\n leniency = 1,\n isOutlineVisible = false,\n checkBackwards = true,\n averageDistanceThreshold = 350,\n } = options;\n const avgDist = stroke.getAverageDistance(points);\n const distMod = isOutlineVisible || stroke.strokeNum > 0 ? 0.5 : 1;\n const withinDistThresh = avgDist <= averageDistanceThreshold * distMod * leniency;\n // short circuit for faster matching\n if (!withinDistThresh) {\n return { isMatch: false, avgDist, meta: { isStrokeBackwards: false } };\n }\n const startAndEndMatch = startAndEndMatches(points, stroke, leniency);\n const directionMatch = directionMatches(points, stroke);\n const shapeMatch = shapeFit(points, stroke.points, leniency);\n const lengthMatch = lengthMatches(points, stroke, leniency);\n\n const isMatch =\n withinDistThresh && startAndEndMatch && directionMatch && shapeMatch && lengthMatch;\n\n if (checkBackwards && !isMatch) {\n const backwardsMatchData = getMatchData([...points].reverse(), stroke, {\n ...options,\n checkBackwards: false,\n });\n\n if (backwardsMatchData.isMatch) {\n return {\n isMatch,\n avgDist,\n meta: { isStrokeBackwards: true },\n };\n }\n }\n\n return { isMatch, avgDist, meta: { isStrokeBackwards: false } };\n};\n","import { Point } from '../typings/types';\n\nexport default class UserStroke {\n id: number;\n points: Point[];\n externalPoints: Point[];\n\n constructor(id: number, startingPoint: Point, startingExternalPoint: Point) {\n this.id = id;\n this.points = [startingPoint];\n this.externalPoints = [startingExternalPoint];\n }\n\n appendPoint(point: Point, externalPoint: Point) {\n this.points.push(point);\n this.externalPoints.push(externalPoint);\n }\n}\n","import {\n cancelAnimationFrame,\n requestAnimationFrame,\n inflate,\n performanceNow,\n} from './utils';\nimport RenderState from './RenderState';\nimport { RecursivePartial } from './typings/types';\n\n/** Used by `Mutation` & `Delay` */\nexport interface GenericMutation<\n TRenderStateClass extends GenericRenderStateClass = RenderState\n> {\n /** Allows mutations starting with the provided string to be cancelled */\n scope: string;\n /** Can be useful for checking whether the mutation is running */\n _runningPromise: Promise | undefined;\n run(renderState: TRenderStateClass): Promise;\n pause(): void;\n resume(): void;\n cancel(renderState: TRenderStateClass): void;\n}\n\nclass Delay implements GenericMutation {\n scope: string;\n _runningPromise: Promise | undefined;\n _duration: number;\n _startTime: number | null;\n _paused: boolean;\n _timeout!: NodeJS.Timeout;\n _resolve: (() => void) | undefined;\n\n constructor(duration: number) {\n this._duration = duration;\n this._startTime = null;\n this._paused = false;\n this.scope = `delay.${duration}`;\n }\n\n run() {\n this._startTime = performanceNow();\n this._runningPromise = new Promise((resolve) => {\n this._resolve = resolve;\n // @ts-ignore return type of \"setTimeout\" in builds is parsed as `number` instead of `Timeout`\n this._timeout = setTimeout(() => this.cancel(), this._duration);\n }) as Promise;\n return this._runningPromise;\n }\n\n pause() {\n if (this._paused) return;\n // to pause, clear the timeout and rewrite this._duration with whatever time is remaining\n const elapsedDelay = performance.now() - (this._startTime || 0);\n this._duration = Math.max(0, this._duration - elapsedDelay);\n clearTimeout(this._timeout);\n this._paused = true;\n }\n\n resume() {\n if (!this._paused) return;\n this._startTime = performance.now();\n // @ts-ignore return type of \"setTimeout\" in builds is parsed as `number` instead of `Timeout`\n this._timeout = setTimeout(() => this.cancel(), this._duration);\n this._paused = false;\n }\n\n cancel() {\n clearTimeout(this._timeout!);\n if (this._resolve) {\n this._resolve();\n }\n this._resolve = undefined;\n }\n}\n\ntype GenericRenderStateClass = {\n state: T;\n updateState(changes: RecursivePartial): void;\n};\n\nexport default class Mutation<\n TRenderStateClass extends GenericRenderStateClass,\n TRenderStateObj = TRenderStateClass['state']\n> implements GenericMutation {\n static Delay = Delay;\n\n scope: string;\n _runningPromise: Promise | undefined;\n _valuesOrCallable: any;\n _duration: number;\n _force: boolean | undefined;\n _pausedDuration: number;\n _startPauseTime: number | null;\n\n // Only set on .run()\n _startTime: number | undefined;\n _startState: RecursivePartial | undefined;\n _renderState: TRenderStateClass | undefined;\n _frameHandle: number | undefined;\n _values: RecursivePartial | undefined;\n _resolve: ((_val?: any) => void) | undefined;\n\n /**\n *\n * @param scope a string representation of what fields this mutation affects from the state. This is used to cancel conflicting mutations\n * @param valuesOrCallable a thunk containing the value to set, or a callback which will return those values\n */\n constructor(\n scope: string,\n valuesOrCallable: any,\n options: {\n duration?: number;\n /** Updates render state regardless if cancelled */\n force?: boolean;\n } = {},\n ) {\n this.scope = scope;\n this._valuesOrCallable = valuesOrCallable;\n this._duration = options.duration || 0;\n this._force = options.force;\n this._pausedDuration = 0;\n this._startPauseTime = null;\n }\n\n run(renderState: TRenderStateClass): Promise {\n if (!this._values) this._inflateValues(renderState);\n if (this._duration === 0) renderState.updateState(this._values!);\n if (this._duration === 0 || isAlreadyAtEnd(renderState.state, this._values)) {\n return Promise.resolve();\n }\n this._renderState = renderState;\n this._startState = renderState.state;\n this._startTime = performance.now();\n this._frameHandle = requestAnimationFrame(this._tick);\n return new Promise((resolve) => {\n this._resolve = resolve;\n });\n }\n\n private _inflateValues(renderState: TRenderStateClass) {\n let values = this._valuesOrCallable;\n if (typeof this._valuesOrCallable === 'function') {\n values = this._valuesOrCallable(renderState.state);\n }\n this._values = inflate(this.scope, values);\n }\n\n pause() {\n if (this._startPauseTime !== null) {\n return;\n }\n if (this._frameHandle) {\n cancelAnimationFrame(this._frameHandle);\n }\n this._startPauseTime = performance.now();\n }\n\n resume() {\n if (this._startPauseTime === null) {\n return;\n }\n this._frameHandle = requestAnimationFrame(this._tick);\n this._pausedDuration += performance.now() - this._startPauseTime;\n this._startPauseTime = null;\n }\n\n private _tick = (timing: number) => {\n if (this._startPauseTime !== null) {\n return;\n }\n\n const progress = Math.min(\n 1,\n (timing - this._startTime! - this._pausedDuration) / this._duration,\n );\n\n if (progress === 1) {\n this._renderState!.updateState(this._values!);\n this._frameHandle = undefined;\n this.cancel(this._renderState!);\n } else {\n const easedProgress = ease(progress);\n const stateChanges = getPartialValues(\n this._startState as TRenderStateObj,\n this._values!,\n easedProgress,\n );\n\n this._renderState!.updateState(stateChanges);\n this._frameHandle = requestAnimationFrame(this._tick);\n }\n };\n\n cancel(renderState: TRenderStateClass) {\n this._resolve?.();\n this._resolve = undefined;\n\n cancelAnimationFrame(this._frameHandle || -1);\n this._frameHandle = undefined;\n\n if (this._force) {\n if (!this._values) this._inflateValues(renderState);\n renderState.updateState(this._values!);\n }\n }\n}\n\nfunction getPartialValues(\n startValues: T | undefined,\n endValues: RecursivePartial | undefined,\n progress: number,\n) {\n const target: RecursivePartial = {};\n\n for (const key in endValues) {\n const endValue = endValues[key];\n const startValue = startValues?.[key];\n if (typeof startValue === 'number' && typeof endValue === 'number' && endValue >= 0) {\n target[key] = progress * (endValue - startValue) + startValue;\n } else {\n target[key] = getPartialValues(startValue, endValue, progress);\n }\n }\n return target;\n}\n\nfunction isAlreadyAtEnd(\n startValues: T | undefined,\n endValues: RecursivePartial | undefined,\n) {\n for (const key in endValues) {\n const endValue = endValues[key];\n const startValue = startValues?.[key];\n if (endValue >= 0) {\n if (endValue !== startValue) {\n return false;\n }\n } else if (!isAlreadyAtEnd(startValue, endValue)) {\n return false;\n }\n }\n return true;\n}\n\n// from https://github.com/maxwellito/vivus\nconst ease = (x: number) => -Math.cos(x * Math.PI) / 2 + 0.5;\n","import Stroke from './models/Stroke';\nimport { ColorObject, RecursivePartial } from './typings/types';\nimport Character from './models/Character';\nimport Mutation, { GenericMutation } from './Mutation';\nimport { objRepeat } from './utils';\nimport { CharacterName, CharacterRenderState, RenderStateObject } from './RenderState';\n\nexport const showStrokes = (\n charName: CharacterName,\n character: Character,\n duration: number,\n): GenericMutation[] => {\n return [\n new Mutation(\n `character.${charName}.strokes`,\n objRepeat(\n { opacity: 1, displayPortion: 1 },\n character.strokes.length,\n ) as CharacterRenderState['strokes'],\n { duration, force: true },\n ),\n ];\n};\n\nexport const showCharacter = (\n charName: CharacterName,\n character: Character,\n duration: number,\n): GenericMutation[] => {\n return [\n new Mutation(\n `character.${charName}`,\n {\n opacity: 1,\n strokes: objRepeat({ opacity: 1, displayPortion: 1 }, character.strokes.length),\n },\n { duration, force: true },\n ),\n ];\n};\n\nexport const hideCharacter = (\n charName: CharacterName,\n character: Character,\n duration?: number,\n): GenericMutation[] => {\n return [\n new Mutation(`character.${charName}.opacity`, 0, { duration, force: true }),\n ...showStrokes(charName, character, 0),\n ];\n};\n\nexport const updateColor = (\n colorName: string,\n colorVal: ColorObject | null,\n duration: number,\n) => {\n return [new Mutation(`options.${colorName}`, colorVal, { duration })];\n};\n\nexport const highlightStroke = (\n stroke: Stroke,\n color: ColorObject | null,\n speed: number,\n): GenericMutation[] => {\n const strokeNum = stroke.strokeNum;\n const duration = (stroke.getLength() + 600) / (3 * speed);\n return [\n new Mutation('options.highlightColor', color),\n new Mutation('character.highlight', {\n opacity: 1,\n strokes: {\n [strokeNum]: {\n displayPortion: 0,\n opacity: 0,\n },\n },\n }),\n new Mutation(\n `character.highlight.strokes.${strokeNum}`,\n {\n displayPortion: 1,\n opacity: 1,\n },\n { duration },\n ),\n new Mutation(`character.highlight.strokes.${strokeNum}.opacity`, 0, {\n duration,\n force: true,\n }),\n ];\n};\n\nexport const animateStroke = (\n charName: CharacterName,\n stroke: Stroke,\n speed: number,\n): GenericMutation[] => {\n const strokeNum = stroke.strokeNum;\n const duration = (stroke.getLength() + 600) / (3 * speed);\n return [\n new Mutation(`character.${charName}`, {\n opacity: 1,\n strokes: {\n [strokeNum]: {\n displayPortion: 0,\n opacity: 1,\n },\n },\n }),\n new Mutation(`character.${charName}.strokes.${strokeNum}.displayPortion`, 1, {\n duration,\n }),\n ];\n};\n\nexport const animateSingleStroke = (\n charName: CharacterName,\n character: Character,\n strokeNum: number,\n speed: number,\n): GenericMutation[] => {\n const mutationStateFunc = (state: RenderStateObject) => {\n const curCharState = state.character[charName];\n const mutationState: RecursivePartial = {\n opacity: 1,\n strokes: {},\n };\n for (let i = 0; i < character.strokes.length; i++) {\n mutationState.strokes![i] = {\n opacity: curCharState.opacity * curCharState.strokes[i].opacity,\n };\n }\n return mutationState;\n };\n const stroke = character.strokes[strokeNum];\n return [\n new Mutation(`character.${charName}`, mutationStateFunc),\n ...animateStroke(charName, stroke, speed),\n ];\n};\n\nexport const showStroke = (\n charName: CharacterName,\n strokeNum: number,\n duration: number,\n): GenericMutation[] => {\n return [\n new Mutation(\n `character.${charName}.strokes.${strokeNum}`,\n {\n displayPortion: 1,\n opacity: 1,\n },\n { duration, force: true },\n ),\n ];\n};\n\nexport const animateCharacter = (\n charName: CharacterName,\n character: Character,\n fadeDuration: number,\n speed: number,\n delayBetweenStrokes: number,\n): GenericMutation[] => {\n let mutations: GenericMutation[] = hideCharacter(charName, character, fadeDuration);\n mutations = mutations.concat(showStrokes(charName, character, 0));\n mutations.push(\n new Mutation(\n `character.${charName}`,\n {\n opacity: 1,\n strokes: objRepeat({ opacity: 0 }, character.strokes.length),\n },\n { force: true },\n ),\n );\n character.strokes.forEach((stroke, i) => {\n if (i > 0) mutations.push(new Mutation.Delay(delayBetweenStrokes));\n mutations = mutations.concat(animateStroke(charName, stroke, speed));\n });\n return mutations;\n};\n\nexport const animateCharacterLoop = (\n charName: CharacterName,\n character: Character,\n fadeDuration: number,\n speed: number,\n delayBetweenStrokes: number,\n delayBetweenLoops: number,\n): GenericMutation[] => {\n const mutations = animateCharacter(\n charName,\n character,\n fadeDuration,\n speed,\n delayBetweenStrokes,\n );\n mutations.push(new Mutation.Delay(delayBetweenLoops));\n return mutations;\n};\n","import Mutation, { GenericMutation } from './Mutation';\nimport * as characterActions from './characterActions';\nimport { objRepeat, objRepeatCb } from './utils';\nimport Character from './models/Character';\nimport { ColorObject, Point } from './typings/types';\n\nexport const startQuiz = (\n character: Character,\n fadeDuration: number,\n startStrokeNum: number,\n): GenericMutation[] => {\n return [\n ...characterActions.hideCharacter('main', character, fadeDuration),\n new Mutation(\n 'character.highlight',\n {\n opacity: 1,\n strokes: objRepeat({ opacity: 0 }, character.strokes.length),\n },\n { force: true },\n ),\n new Mutation(\n 'character.main',\n {\n opacity: 1,\n strokes: objRepeatCb(character.strokes.length, (i) => ({\n opacity: i < startStrokeNum ? 1 : 0,\n })),\n },\n { force: true },\n ),\n ];\n};\n\nexport const startUserStroke = (id: string | number, point: Point): GenericMutation[] => {\n return [\n new Mutation('quiz.activeUserStrokeId', id, { force: true }),\n new Mutation(\n `userStrokes.${id}`,\n {\n points: [point],\n opacity: 1,\n },\n { force: true },\n ),\n ];\n};\n\nexport const updateUserStroke = (\n userStrokeId: string | number,\n points: Point[],\n): GenericMutation[] => {\n return [new Mutation(`userStrokes.${userStrokeId}.points`, points, { force: true })];\n};\n\nexport const removeUserStroke = (\n userStrokeId: string | number,\n duration: number,\n): GenericMutation[] => {\n return [\n new Mutation(`userStrokes.${userStrokeId}.opacity`, 0, { duration }),\n new Mutation(`userStrokes.${userStrokeId}`, null, { force: true }),\n ];\n};\n\nexport const highlightCompleteChar = (\n character: Character,\n color: ColorObject | null,\n duration: number,\n): GenericMutation[] => {\n return [\n new Mutation('options.highlightColor', color),\n ...characterActions.hideCharacter('highlight', character),\n ...characterActions.showCharacter('highlight', character, duration / 2),\n ...characterActions.hideCharacter('highlight', character, duration / 2),\n ];\n};\n\nexport const highlightStroke = characterActions.highlightStroke;\n","import strokeMatches, { StrokeMatchResultMeta } from './strokeMatches';\nimport UserStroke from './models/UserStroke';\nimport Positioner from './Positioner';\nimport { counter, colorStringToVals, fixIndex } from './utils';\nimport * as quizActions from './quizActions';\nimport * as geometry from './geometry';\nimport * as characterActions from './characterActions';\nimport Character from './models/Character';\nimport { ParsedHanziWriterOptions, Point, StrokeData } from './typings/types';\nimport RenderState from './RenderState';\nimport { GenericMutation } from './Mutation';\n\nconst getDrawnPath = (userStroke: UserStroke) => ({\n pathString: geometry.getPathString(userStroke.externalPoints),\n points: userStroke.points.map((point) => geometry.round(point)),\n});\n\nexport default class Quiz {\n _character: Character;\n _renderState: RenderState;\n _isActive: boolean;\n _positioner: Positioner;\n\n /** Set on startQuiz */\n _options: ParsedHanziWriterOptions | undefined;\n _currentStrokeIndex = 0;\n _mistakesOnStroke = 0;\n _totalMistakes = 0;\n _userStroke: UserStroke | undefined;\n\n constructor(character: Character, renderState: RenderState, positioner: Positioner) {\n this._character = character;\n this._renderState = renderState;\n this._isActive = false;\n this._positioner = positioner;\n }\n\n startQuiz(options: ParsedHanziWriterOptions) {\n this._isActive = true;\n this._options = options;\n const startIndex = fixIndex(\n options.quizStartStrokeNum,\n this._character.strokes.length,\n );\n this._currentStrokeIndex = Math.min(startIndex, this._character.strokes.length - 1);\n this._mistakesOnStroke = 0;\n this._totalMistakes = 0;\n\n return this._renderState.run(\n quizActions.startQuiz(\n this._character,\n options.strokeFadeDuration,\n this._currentStrokeIndex,\n ),\n );\n }\n\n startUserStroke(externalPoint: Point) {\n if (!this._isActive) {\n return null;\n }\n if (this._userStroke) {\n return this.endUserStroke();\n }\n const point = this._positioner.convertExternalPoint(externalPoint);\n const strokeId = counter();\n this._userStroke = new UserStroke(strokeId, point, externalPoint);\n return this._renderState.run(quizActions.startUserStroke(strokeId, point));\n }\n\n continueUserStroke(externalPoint: Point) {\n if (!this._userStroke) {\n return Promise.resolve();\n }\n const point = this._positioner.convertExternalPoint(externalPoint);\n this._userStroke.appendPoint(point, externalPoint);\n const nextPoints = this._userStroke.points.slice(0);\n return this._renderState.run(\n quizActions.updateUserStroke(this._userStroke.id, nextPoints),\n );\n }\n\n setPositioner(positioner: Positioner) {\n this._positioner = positioner;\n }\n\n endUserStroke() {\n if (!this._userStroke) return;\n\n this._renderState.run(\n quizActions.removeUserStroke(\n this._userStroke.id,\n this._options!.drawingFadeDuration ?? 300,\n ),\n );\n\n // skip single-point strokes\n if (this._userStroke.points.length === 1) {\n this._userStroke = undefined;\n return;\n }\n\n const { acceptBackwardsStrokes, markStrokeCorrectAfterMisses } = this._options!;\n\n const currentStroke = this._getCurrentStroke();\n const { isMatch, meta } = strokeMatches(\n this._userStroke,\n this._character,\n this._currentStrokeIndex,\n {\n isOutlineVisible: this._renderState.state.character.outline.opacity > 0,\n leniency: this._options!.leniency,\n averageDistanceThreshold: this._options!.averageDistanceThreshold,\n },\n );\n\n // if markStrokeCorrectAfterMisses is passed, just force the stroke to count as correct after n tries\n const isForceAccepted =\n markStrokeCorrectAfterMisses &&\n this._mistakesOnStroke + 1 >= markStrokeCorrectAfterMisses;\n\n const isAccepted =\n isMatch || isForceAccepted || (meta.isStrokeBackwards && acceptBackwardsStrokes);\n\n if (isAccepted) {\n this._handleSuccess(meta);\n } else {\n this._handleFailure(meta);\n\n const { showHintAfterMisses, highlightColor, strokeHighlightSpeed } =\n this._options!;\n\n if (\n showHintAfterMisses !== false &&\n this._mistakesOnStroke >= showHintAfterMisses\n ) {\n this._renderState.run(\n characterActions.highlightStroke(\n currentStroke,\n colorStringToVals(highlightColor),\n strokeHighlightSpeed,\n ),\n );\n }\n }\n\n this._userStroke = undefined;\n }\n\n cancel() {\n this._isActive = false;\n if (this._userStroke) {\n this._renderState.run(\n quizActions.removeUserStroke(\n this._userStroke.id,\n this._options!.drawingFadeDuration,\n ),\n );\n }\n }\n\n _getStrokeData({\n isCorrect,\n meta,\n }: {\n isCorrect: boolean;\n meta: StrokeMatchResultMeta;\n }): StrokeData {\n return {\n character: this._character.symbol,\n strokeNum: this._currentStrokeIndex,\n mistakesOnStroke: this._mistakesOnStroke,\n totalMistakes: this._totalMistakes,\n strokesRemaining:\n this._character.strokes.length - this._currentStrokeIndex - (isCorrect ? 1 : 0),\n drawnPath: getDrawnPath(this._userStroke!),\n isBackwards: meta.isStrokeBackwards,\n };\n }\n\n _handleSuccess(meta: StrokeMatchResultMeta) {\n if (!this._options) return;\n\n const { strokes, symbol } = this._character;\n\n const {\n onCorrectStroke,\n onComplete,\n highlightOnComplete,\n strokeFadeDuration,\n highlightCompleteColor,\n highlightColor,\n strokeHighlightDuration,\n } = this._options;\n\n onCorrectStroke?.({\n ...this._getStrokeData({ isCorrect: true, meta }),\n });\n\n let animation: GenericMutation[] = characterActions.showStroke(\n 'main',\n this._currentStrokeIndex,\n strokeFadeDuration,\n );\n\n this._mistakesOnStroke = 0;\n this._currentStrokeIndex += 1;\n\n const isComplete = this._currentStrokeIndex === strokes.length;\n\n if (isComplete) {\n this._isActive = false;\n onComplete?.({\n character: symbol,\n totalMistakes: this._totalMistakes,\n });\n if (highlightOnComplete) {\n animation = animation.concat(\n quizActions.highlightCompleteChar(\n this._character,\n colorStringToVals(highlightCompleteColor || highlightColor),\n (strokeHighlightDuration || 0) * 2,\n ),\n );\n }\n }\n\n this._renderState.run(animation);\n }\n\n _handleFailure(meta: StrokeMatchResultMeta) {\n this._mistakesOnStroke += 1;\n this._totalMistakes += 1;\n this._options!.onMistake?.(this._getStrokeData({ isCorrect: false, meta }));\n }\n\n _getCurrentStroke() {\n return this._character.strokes[this._currentStrokeIndex];\n }\n}\n","export function createElm(elmType: string) {\n return document.createElementNS('http://www.w3.org/2000/svg', elmType);\n}\n\nexport function attr(elm: Element, name: string, value: string) {\n elm.setAttributeNS(null, name, value);\n}\n\nexport function attrs(elm: Element, attrsMap: Record) {\n Object.keys(attrsMap).forEach((attrName) => attr(elm, attrName, attrsMap[attrName]));\n}\n\n// inspired by https://talk.observablehq.com/t/hanzi-writer-renders-incorrectly-inside-an-observable-notebook-on-a-mobile-browser/1898\nexport function urlIdRef(id: string) {\n let prefix = '';\n if (window.location && window.location.href) {\n prefix = window.location.href.replace(/#[^#]*$/, '').replace(/\"/gi, '%22');\n }\n return `url(\"${prefix}#${id}\")`;\n}\n\nexport function removeElm(elm: Element | undefined) {\n elm?.parentNode?.removeChild(elm);\n}\n","import Stroke from '../models/Stroke';\nimport { ColorObject } from '../typings/types';\n\nexport default class StrokeRendererBase {\n _pathLength: number;\n stroke: Stroke;\n static STROKE_WIDTH = 200;\n\n constructor(stroke: Stroke) {\n this.stroke = stroke;\n this._pathLength = stroke.getLength() + StrokeRendererBase.STROKE_WIDTH / 2;\n }\n\n _getStrokeDashoffset(displayPortion: number) {\n return this._pathLength * 0.999 * (1 - displayPortion);\n }\n\n _getColor({\n strokeColor,\n radicalColor,\n }: {\n strokeColor: ColorObject;\n radicalColor?: ColorObject | null;\n }) {\n return radicalColor && this.stroke.isInRadical ? radicalColor : strokeColor;\n }\n}\n","import { counter } from '../../utils';\nimport * as svg from './svgUtils';\nimport { extendStart, getPathString } from '../../geometry';\nimport StrokeRendererBase from '../StrokeRendererBase';\nimport Stroke from '../../models/Stroke';\nimport SVGRenderTarget from './RenderTarget';\nimport { ColorObject } from '../../typings/types';\n\nconst STROKE_WIDTH = 200;\n\ntype StrokeRenderProps = {\n strokeColor: ColorObject;\n radicalColor: ColorObject | null;\n displayPortion: number;\n opacity: number;\n};\n\n/** This is a stroke composed of several stroke parts **/\nexport default class StrokeRenderer extends StrokeRendererBase {\n _oldProps: StrokeRenderProps | undefined = undefined;\n\n _animationPath: SVGPathElement | undefined;\n _clip: SVGClipPathElement | undefined;\n _strokePath: SVGPathElement | undefined;\n\n constructor(stroke: Stroke) {\n super(stroke);\n }\n\n mount(target: SVGRenderTarget) {\n this._animationPath = svg.createElm('path') as SVGPathElement;\n this._clip = svg.createElm('clipPath') as SVGClipPathElement;\n this._strokePath = svg.createElm('path') as SVGPathElement;\n const maskId = `mask-${counter()}`;\n svg.attr(this._clip, 'id', maskId);\n\n svg.attr(this._strokePath, 'd', this.stroke.path);\n this._animationPath.style.opacity = '0';\n svg.attr(this._animationPath, 'clip-path', svg.urlIdRef(maskId));\n\n const extendedMaskPoints = extendStart(this.stroke.points, STROKE_WIDTH / 2);\n svg.attr(this._animationPath, 'd', getPathString(extendedMaskPoints));\n svg.attrs(this._animationPath, {\n stroke: '#FFFFFF',\n 'stroke-width': STROKE_WIDTH.toString(),\n fill: 'none',\n 'stroke-linecap': 'round',\n 'stroke-linejoin': 'miter',\n 'stroke-dasharray': `${this._pathLength},${this._pathLength}`,\n });\n\n this._clip.appendChild(this._strokePath);\n target.defs.appendChild(this._clip);\n target.svg.appendChild(this._animationPath);\n return this;\n }\n\n render(props: StrokeRenderProps) {\n if (props === this._oldProps || !this._animationPath) {\n return;\n }\n\n if (props.displayPortion !== this._oldProps?.displayPortion) {\n this._animationPath.style.strokeDashoffset = this._getStrokeDashoffset(\n props.displayPortion,\n ).toString();\n }\n\n const color = this._getColor(props);\n\n if (!this._oldProps || color !== this._getColor(this._oldProps)) {\n const { r, g, b, a } = color;\n svg.attrs(this._animationPath, { stroke: `rgba(${r},${g},${b},${a})` });\n }\n\n if (props.opacity !== this._oldProps?.opacity) {\n this._animationPath.style.opacity = props.opacity.toString();\n }\n this._oldProps = props;\n }\n}\n","import { isMsBrowser } from '../../utils';\nimport StrokeRenderer from './StrokeRenderer';\nimport SVGRenderTarget from './RenderTarget';\nimport Character from '../../models/Character';\nimport { ColorObject } from '../../typings/types';\nimport { StrokeRenderState } from '../../RenderState';\n\ntype SvgCharacterRenderProps = {\n opacity: number;\n strokes: Record;\n strokeColor: ColorObject;\n radicalColor?: ColorObject | null;\n};\n\nexport default class CharacterRenderer {\n _oldProps: SvgCharacterRenderProps | undefined = undefined;\n _strokeRenderers: StrokeRenderer[];\n\n // set on mount()\n _group: SVGElement | SVGSVGElement | undefined;\n\n constructor(character: Character) {\n this._strokeRenderers = character.strokes.map((stroke) => new StrokeRenderer(stroke));\n }\n\n mount(target: SVGRenderTarget) {\n const subTarget = target.createSubRenderTarget();\n this._group = subTarget.svg;\n this._strokeRenderers.forEach((strokeRenderer) => {\n strokeRenderer.mount(subTarget);\n });\n }\n\n render(props: SvgCharacterRenderProps) {\n if (props === this._oldProps || !this._group) {\n return;\n }\n const { opacity, strokes, strokeColor, radicalColor = null } = props;\n if (opacity !== this._oldProps?.opacity) {\n this._group.style.opacity = opacity.toString();\n // MS browsers seem to have a bug where if SVG is set to display:none, it sometimes breaks.\n // More info: https://github.com/chanind/hanzi-writer/issues/164\n // this is just a perf improvement, so disable for MS browsers\n if (!isMsBrowser) {\n if (opacity === 0) {\n this._group.style.display = 'none';\n } else if (this._oldProps?.opacity === 0) {\n this._group.style.removeProperty('display');\n }\n }\n }\n const colorsChanged =\n !this._oldProps ||\n strokeColor !== this._oldProps.strokeColor ||\n radicalColor !== this._oldProps.radicalColor;\n\n if (colorsChanged || strokes !== this._oldProps?.strokes) {\n for (let i = 0; i < this._strokeRenderers.length; i++) {\n if (\n !colorsChanged &&\n this._oldProps?.strokes &&\n strokes[i] === this._oldProps.strokes[i]\n ) {\n continue;\n }\n this._strokeRenderers[i].render({\n strokeColor,\n radicalColor,\n opacity: strokes[i].opacity,\n displayPortion: strokes[i].displayPortion,\n });\n }\n }\n this._oldProps = props;\n }\n}\n","import * as svg from './svgUtils';\nimport { getPathString } from '../../geometry';\nimport { ColorObject, Point } from '../../typings/types';\nimport SVGRenderTarget from './RenderTarget';\n\nexport type UserStrokeProps = {\n strokeWidth: number;\n strokeColor: ColorObject;\n opacity: number;\n points: Point[];\n};\n\nexport default class UserStrokeRenderer {\n _oldProps: UserStrokeProps | undefined = undefined;\n _path: SVGElement | undefined;\n\n mount(target: SVGRenderTarget) {\n this._path = svg.createElm('path');\n target.svg.appendChild(this._path);\n }\n\n render(props: UserStrokeProps) {\n if (!this._path || props === this._oldProps) {\n return;\n }\n if (\n props.strokeColor !== this._oldProps?.strokeColor ||\n props.strokeWidth !== this._oldProps?.strokeWidth\n ) {\n const { r, g, b, a } = props.strokeColor;\n svg.attrs(this._path, {\n fill: 'none',\n stroke: `rgba(${r},${g},${b},${a})`,\n 'stroke-width': props.strokeWidth.toString(),\n 'stroke-linecap': 'round',\n 'stroke-linejoin': 'round',\n });\n }\n if (props.opacity !== this._oldProps?.opacity) {\n svg.attr(this._path, 'opacity', props.opacity.toString());\n }\n if (props.points !== this._oldProps?.points) {\n svg.attr(this._path, 'd', getPathString(props.points));\n }\n this._oldProps = props;\n }\n\n destroy() {\n svg.removeElm(this._path);\n }\n}\n","import { Point } from '../typings/types';\n\ntype BoundEvent = {\n getPoint(): Point;\n preventDefault(): void;\n};\n\n/** Generic render target */\nexport default class RenderTargetBase<\n TElement extends\n | HTMLElement\n | SVGElement\n | SVGSVGElement\n | HTMLCanvasElement = HTMLElement\n> {\n node: TElement;\n\n constructor(node: TElement) {\n this.node = node;\n }\n\n addPointerStartListener(callback: (arg: BoundEvent) => void) {\n this.node.addEventListener('mousedown', (evt) => {\n callback(this._eventify(evt as MouseEvent, this._getMousePoint));\n });\n this.node.addEventListener('touchstart', (evt) => {\n callback(this._eventify(evt as TouchEvent, this._getTouchPoint));\n });\n }\n\n addPointerMoveListener(callback: (arg: BoundEvent) => void) {\n this.node.addEventListener('mousemove', (evt) => {\n callback(this._eventify(evt as MouseEvent, this._getMousePoint));\n });\n this.node.addEventListener('touchmove', (evt) => {\n callback(this._eventify(evt as TouchEvent, this._getTouchPoint));\n });\n }\n\n addPointerEndListener(callback: () => void) {\n // TODO: find a way to not need global listeners\n document.addEventListener('mouseup', callback);\n document.addEventListener('touchend', callback);\n }\n\n getBoundingClientRect() {\n return this.node.getBoundingClientRect();\n }\n\n updateDimensions(width: string | number, height: string | number) {\n this.node.setAttribute('width', `${width}`);\n this.node.setAttribute('height', `${height}`);\n }\n\n _eventify(evt: TEvent, pointFunc: (event: TEvent) => Point) {\n return {\n getPoint: () => pointFunc.call(this, evt),\n preventDefault: () => evt.preventDefault(),\n };\n }\n\n _getMousePoint(evt: MouseEvent): Point {\n const { left, top } = this.getBoundingClientRect();\n const x = evt.clientX - left;\n const y = evt.clientY - top;\n return { x, y };\n }\n\n _getTouchPoint(evt: TouchEvent): Point {\n const { left, top } = this.getBoundingClientRect();\n const x = evt.touches[0].clientX - left;\n const y = evt.touches[0].clientY - top;\n return { x, y };\n }\n}\n","import { createElm, attrs } from './svgUtils';\nimport RenderTargetBase from '../RenderTargetBase';\n\nexport default class RenderTarget extends RenderTargetBase {\n static init(elmOrId: Element | string, width = '100%', height = '100%') {\n const element = (() => {\n if (typeof elmOrId === 'string') {\n return document.getElementById(elmOrId);\n }\n return elmOrId;\n })();\n\n if (!element) {\n throw new Error(`HanziWriter target element not found: ${elmOrId}`);\n }\n const nodeType = element.nodeName.toUpperCase();\n\n const svg = (() => {\n if (nodeType === 'SVG' || nodeType === 'G') {\n return element;\n } else {\n const svg = createElm('svg');\n element.appendChild(svg);\n return svg;\n }\n })() as SVGSVGElement;\n\n attrs(svg, { width, height });\n const defs = createElm('defs');\n svg.appendChild(defs);\n\n return new RenderTarget(svg, defs);\n }\n\n svg: SVGSVGElement | SVGElement;\n defs: SVGElement;\n _pt: DOMPoint | undefined;\n\n constructor(svg: SVGElement | SVGSVGElement, defs: SVGElement) {\n super(svg);\n\n this.svg = svg;\n this.defs = defs;\n\n if ('createSVGPoint' in svg) {\n this._pt = svg.createSVGPoint();\n }\n }\n\n createSubRenderTarget() {\n const group = createElm('g');\n this.svg.appendChild(group);\n return new RenderTarget(group, this.defs);\n }\n\n _getMousePoint(evt: MouseEvent) {\n if (this._pt) {\n this._pt.x = evt.clientX;\n this._pt.y = evt.clientY;\n if ('getScreenCTM' in this.node) {\n const localPt = this._pt.matrixTransform(this.node.getScreenCTM()?.inverse());\n return { x: localPt.x, y: localPt.y };\n }\n }\n return super._getMousePoint.call(this, evt);\n }\n\n _getTouchPoint(evt: TouchEvent) {\n if (this._pt) {\n this._pt.x = evt.touches[0].clientX;\n this._pt.y = evt.touches[0].clientY;\n if ('getScreenCTM' in this.node) {\n const localPt = this._pt.matrixTransform(\n (this.node as SVGSVGElement).getScreenCTM()?.inverse(),\n );\n return { x: localPt.x, y: localPt.y };\n }\n }\n return super._getTouchPoint(evt);\n }\n}\n","import { RenderTargetInitFunction } from '../../typings/types';\nimport HanziWriterRenderer from './HanziWriterRenderer';\nimport RenderTarget from './RenderTarget';\n\nexport default {\n HanziWriterRenderer,\n createRenderTarget: RenderTarget.init as RenderTargetInitFunction<\n SVGSVGElement | SVGElement\n >,\n};\n","import CharacterRenderer from './CharacterRenderer';\nimport UserStrokeRenderer, { UserStrokeProps } from './UserStrokeRenderer';\nimport * as svg from './svgUtils';\nimport Character from '../../models/Character';\nimport Positioner from '../../Positioner';\nimport SVGRenderTarget from './RenderTarget';\nimport HanziWriterRendererBase from '../HanziWriterRendererBase';\nimport { RenderStateObject } from '../../RenderState';\n\nexport default class HanziWriterRenderer\n implements HanziWriterRendererBase {\n _character: Character;\n _positioner: Positioner;\n _mainCharRenderer: CharacterRenderer;\n _outlineCharRenderer: CharacterRenderer;\n _highlightCharRenderer: CharacterRenderer;\n _userStrokeRenderers: Record;\n _positionedTarget: SVGRenderTarget | undefined;\n\n constructor(character: Character, positioner: Positioner) {\n this._character = character;\n this._positioner = positioner;\n this._mainCharRenderer = new CharacterRenderer(character);\n this._outlineCharRenderer = new CharacterRenderer(character);\n this._highlightCharRenderer = new CharacterRenderer(character);\n this._userStrokeRenderers = {};\n }\n\n mount(target: SVGRenderTarget) {\n const positionedTarget = target.createSubRenderTarget();\n const group = positionedTarget.svg;\n const { xOffset, yOffset, height, scale } = this._positioner;\n\n svg.attr(\n group,\n 'transform',\n `translate(${xOffset}, ${height - yOffset}) scale(${scale}, ${-1 * scale})`,\n );\n this._outlineCharRenderer.mount(positionedTarget);\n this._mainCharRenderer.mount(positionedTarget);\n this._highlightCharRenderer.mount(positionedTarget);\n this._positionedTarget = positionedTarget;\n }\n\n render(props: RenderStateObject) {\n const { main, outline, highlight } = props.character;\n const {\n outlineColor,\n radicalColor,\n highlightColor,\n strokeColor,\n drawingWidth,\n drawingColor,\n } = props.options;\n\n this._outlineCharRenderer.render({\n opacity: outline.opacity,\n strokes: outline.strokes,\n strokeColor: outlineColor,\n });\n\n this._mainCharRenderer.render({\n opacity: main.opacity,\n strokes: main.strokes,\n strokeColor,\n radicalColor: radicalColor,\n });\n\n this._highlightCharRenderer.render({\n opacity: highlight.opacity,\n strokes: highlight.strokes,\n strokeColor: highlightColor,\n });\n\n const userStrokes = props.userStrokes || {};\n\n for (const userStrokeId in this._userStrokeRenderers) {\n if (!userStrokes[userStrokeId]) {\n this._userStrokeRenderers[userStrokeId]?.destroy();\n delete this._userStrokeRenderers[userStrokeId];\n }\n }\n\n for (const userStrokeId in userStrokes) {\n const stroke = userStrokes[userStrokeId];\n if (!stroke) {\n continue;\n }\n const userStrokeProps: UserStrokeProps = {\n strokeWidth: drawingWidth,\n strokeColor: drawingColor,\n ...stroke,\n };\n\n const strokeRenderer = (() => {\n if (this._userStrokeRenderers[userStrokeId]) {\n return this._userStrokeRenderers[userStrokeId]!;\n }\n const newStrokeRenderer = new UserStrokeRenderer();\n newStrokeRenderer.mount(this._positionedTarget!);\n this._userStrokeRenderers[userStrokeId] = newStrokeRenderer;\n return newStrokeRenderer;\n })();\n\n strokeRenderer.render(userStrokeProps);\n }\n }\n\n destroy() {\n svg.removeElm(this._positionedTarget!.svg);\n this._positionedTarget!.defs.innerHTML = '';\n }\n}\n","import { Point } from '../../typings/types';\n\nexport const drawPath = (ctx: CanvasRenderingContext2D, points: Point[]) => {\n ctx.beginPath();\n const start = points[0];\n const remainingPoints = points.slice(1);\n ctx.moveTo(start.x, start.y);\n for (const point of remainingPoints) {\n ctx.lineTo(point.x, point.y);\n }\n ctx.stroke();\n};\n\n/**\n * Break a path string into a series of canvas path commands\n *\n * Note: only works with the subset of SVG paths used by MakeMeAHanzi data\n * @param pathString\n */\nexport const pathStringToCanvas = (pathString: string) => {\n const pathParts = pathString.split(/(^|\\s+)(?=[A-Z])/).filter((part) => part !== ' ');\n const commands = [(ctx: CanvasRenderingContext2D) => ctx.beginPath()];\n for (const part of pathParts) {\n const [cmd, ...rawParams] = part.split(/\\s+/);\n const params = rawParams.map((param) => parseFloat(param)) as any[];\n if (cmd === 'M') {\n commands.push((ctx) => ctx.moveTo(...(params as [number, number])));\n } else if (cmd === 'L') {\n commands.push((ctx) => ctx.lineTo(...(params as [number, number])));\n } else if (cmd === 'C') {\n commands.push((ctx) =>\n ctx.bezierCurveTo(...(params as Parameters)),\n );\n } else if (cmd === 'Q') {\n commands.push((ctx) =>\n ctx.quadraticCurveTo(...(params as Parameters)),\n );\n } else if (cmd === 'Z') {\n // commands.push((ctx) => ctx.closePath());\n }\n }\n return (ctx: CanvasRenderingContext2D) => commands.forEach((cmd) => cmd(ctx));\n};\n","import { extendStart } from '../../geometry';\nimport { drawPath, pathStringToCanvas } from './canvasUtils';\nimport StrokeRendererBase from '../StrokeRendererBase';\nimport Stroke from '../../models/Stroke';\nimport { ColorObject, Point } from '../../typings/types';\n\n/** this is a stroke composed of several stroke parts */\nexport default class StrokeRenderer extends StrokeRendererBase {\n _extendedMaskPoints: Point[];\n\n // Conditionally set on constructor\n _path2D: Path2D | undefined;\n _pathCmd: ((ctx: CanvasRenderingContext2D) => void) | undefined;\n\n constructor(stroke: Stroke, usePath2D = true) {\n super(stroke);\n\n if (usePath2D && Path2D) {\n this._path2D = new Path2D(this.stroke.path);\n } else {\n this._pathCmd = pathStringToCanvas(this.stroke.path);\n }\n this._extendedMaskPoints = extendStart(\n this.stroke.points,\n StrokeRendererBase.STROKE_WIDTH / 2,\n );\n }\n\n render(\n ctx: CanvasRenderingContext2D,\n props: {\n opacity: number;\n strokeColor: ColorObject;\n radicalColor?: ColorObject | null;\n displayPortion: number;\n },\n ) {\n if (props.opacity < 0.05) {\n return;\n }\n ctx.save();\n\n if (this._path2D) {\n ctx.clip(this._path2D);\n } else {\n this._pathCmd?.(ctx);\n // wechat bugs out if the clip path isn't stroked or filled\n ctx.globalAlpha = 0;\n ctx.stroke();\n ctx.clip();\n }\n\n const { r, g, b, a } = this._getColor(props);\n const color = a === 1 ? `rgb(${r},${g},${b})` : `rgb(${r},${g},${b},${a})`;\n const dashOffset = this._getStrokeDashoffset(props.displayPortion);\n ctx.globalAlpha = props.opacity;\n ctx.strokeStyle = color;\n ctx.fillStyle = color;\n ctx.lineWidth = StrokeRendererBase.STROKE_WIDTH;\n ctx.lineCap = 'round';\n ctx.lineJoin = 'round';\n // wechat sets dashOffset as a second param here. Should be harmless for browsers to add here too\n // @ts-ignore\n ctx.setLineDash([this._pathLength, this._pathLength], dashOffset);\n ctx.lineDashOffset = dashOffset;\n drawPath(ctx, this._extendedMaskPoints);\n\n ctx.restore();\n }\n}\n","import Character from '../../models/Character';\nimport { StrokeRenderState } from '../../RenderState';\nimport { ColorObject } from '../../typings/types';\nimport StrokeRenderer from './StrokeRenderer';\n\nexport default class CharacterRenderer {\n _strokeRenderers: StrokeRenderer[];\n\n constructor(character: Character) {\n this._strokeRenderers = character.strokes.map((stroke) => new StrokeRenderer(stroke));\n }\n\n render(\n ctx: CanvasRenderingContext2D,\n props: {\n opacity: number;\n strokes: Record;\n strokeColor: ColorObject;\n radicalColor?: ColorObject | null;\n },\n ) {\n if (props.opacity < 0.05) return;\n\n const { opacity, strokeColor, radicalColor, strokes } = props;\n\n for (let i = 0; i < this._strokeRenderers.length; i++) {\n this._strokeRenderers[i].render(ctx, {\n strokeColor,\n radicalColor,\n opacity: strokes[i].opacity * opacity,\n displayPortion: strokes[i].displayPortion || 0,\n });\n }\n }\n}\n","import { ColorObject, Point } from '../../typings/types';\nimport { drawPath } from './canvasUtils';\n\nexport default function renderUserStroke(\n ctx: CanvasRenderingContext2D,\n props: {\n opacity: number;\n strokeWidth: number;\n strokeColor: ColorObject;\n points: Point[];\n },\n) {\n if (props.opacity < 0.05) {\n return;\n }\n const { opacity, strokeWidth, strokeColor, points } = props;\n const { r, g, b, a } = strokeColor;\n\n ctx.save();\n ctx.globalAlpha = opacity;\n ctx.lineWidth = strokeWidth;\n ctx.strokeStyle = `rgba(${r},${g},${b},${a})`;\n ctx.lineCap = 'round';\n ctx.lineJoin = 'round';\n drawPath(ctx, points);\n ctx.restore();\n}\n","import RenderTargetBase from '../RenderTargetBase';\n\nexport default class RenderTarget extends RenderTargetBase {\n constructor(canvas: HTMLCanvasElement) {\n super(canvas);\n }\n\n static init(elmOrId: string | HTMLCanvasElement, width = '100%', height = '100%') {\n const element = (() => {\n if (typeof elmOrId === 'string') {\n return document.getElementById(elmOrId);\n }\n return elmOrId;\n })();\n\n if (!element) {\n throw new Error(`HanziWriter target element not found: ${elmOrId}`);\n }\n\n const nodeType = element.nodeName.toUpperCase();\n\n const canvas = (() => {\n if (nodeType === 'CANVAS') {\n return element as HTMLCanvasElement;\n }\n const canvas = document.createElement('canvas');\n element.appendChild(canvas);\n return canvas;\n })();\n\n canvas.setAttribute('width', width);\n canvas.setAttribute('height', height);\n\n return new RenderTarget(canvas);\n }\n\n getContext() {\n return this.node.getContext('2d');\n }\n}\n","import { RenderTargetInitFunction } from '../../typings/types';\nimport HanziWriterRenderer from './HanziWriterRenderer';\nimport RenderTarget from './RenderTarget';\n\nexport default {\n HanziWriterRenderer,\n createRenderTarget: RenderTarget.init as RenderTargetInitFunction,\n};\n","import Character from '../../models/Character';\nimport Positioner from '../../Positioner';\nimport HanziWriterRendererBase from '../HanziWriterRendererBase';\nimport CanvasRenderTarget from '../canvas/RenderTarget';\nimport CharacterRenderer from './CharacterRenderer';\nimport renderUserStroke from './renderUserStroke';\nimport { RenderStateObject } from '../../RenderState';\nimport { noop } from '../../utils';\n\nexport default class HanziWriterRenderer\n implements HanziWriterRendererBase {\n _character: Character;\n _positioner: Positioner;\n _mainCharRenderer: CharacterRenderer;\n _outlineCharRenderer: CharacterRenderer;\n _highlightCharRenderer: CharacterRenderer;\n _target: CanvasRenderTarget | undefined;\n\n constructor(character: Character, positioner: Positioner) {\n this._character = character;\n this._positioner = positioner;\n this._mainCharRenderer = new CharacterRenderer(character);\n this._outlineCharRenderer = new CharacterRenderer(character);\n this._highlightCharRenderer = new CharacterRenderer(character);\n }\n\n mount(target: CanvasRenderTarget) {\n this._target = target;\n }\n\n destroy = noop;\n\n _animationFrame(cb: (ctx: CanvasRenderingContext2D) => void) {\n const { width, height, scale, xOffset, yOffset } = this._positioner;\n const ctx = this._target!.getContext()!;\n ctx.clearRect(0, 0, width, height);\n ctx.save();\n ctx.translate(xOffset, height - yOffset);\n ctx.transform(1, 0, 0, -1, 0, 0);\n ctx.scale(scale, scale);\n cb(ctx);\n ctx.restore();\n // @ts-expect-error Verify if this is still needed for the \"wechat miniprogram\".\n if (ctx.draw) {\n // @ts-expect-error\n ctx.draw();\n }\n }\n\n render(props: RenderStateObject) {\n const { outline, main, highlight } = props.character;\n const {\n outlineColor,\n strokeColor,\n radicalColor,\n highlightColor,\n drawingColor,\n drawingWidth,\n } = props.options;\n\n this._animationFrame((ctx) => {\n this._outlineCharRenderer.render(ctx, {\n opacity: outline.opacity,\n strokes: outline.strokes,\n strokeColor: outlineColor,\n });\n this._mainCharRenderer.render(ctx, {\n opacity: main.opacity,\n strokes: main.strokes,\n strokeColor: strokeColor,\n radicalColor: radicalColor,\n });\n this._highlightCharRenderer.render(ctx, {\n opacity: highlight.opacity,\n strokes: highlight.strokes,\n strokeColor: highlightColor,\n });\n\n const userStrokes = props.userStrokes || {};\n\n for (const userStrokeId in userStrokes) {\n const userStroke = userStrokes[userStrokeId];\n if (userStroke) {\n const userStrokeProps = {\n strokeWidth: drawingWidth,\n strokeColor: drawingColor,\n ...userStroke,\n };\n renderUserStroke(ctx, userStrokeProps);\n }\n }\n });\n }\n}\n","import { CharacterJson } from './typings/types';\n\nconst VERSION = '2.0';\nconst getCharDataUrl = (char: string) =>\n `https://cdn.jsdelivr.net/npm/hanzi-writer-data@${VERSION}/${char}.json`;\n\nconst defaultCharDataLoader = (\n char: string,\n onLoad: (parsedJson: CharacterJson) => void,\n onError: (error?: any, context?: any) => void,\n) => {\n // load char data from hanziwriter cdn (currently hosted on jsdelivr)\n const xhr = new XMLHttpRequest();\n if (xhr.overrideMimeType) {\n // IE 9 and 10 don't seem to support this...\n xhr.overrideMimeType('application/json');\n }\n xhr.open('GET', getCharDataUrl(char), true);\n xhr.onerror = (event) => {\n onError(xhr, event);\n };\n xhr.onreadystatechange = () => {\n // TODO: error handling\n if (xhr.readyState !== 4) return;\n\n if (xhr.status === 200) {\n onLoad(JSON.parse(xhr.responseText));\n } else if (xhr.status !== 0 && onError) {\n onError(xhr);\n }\n };\n xhr.send(null);\n};\n\nexport default defaultCharDataLoader;\n","import { HanziWriterOptions } from './typings/types';\nimport defaultCharDataLoader from './defaultCharDataLoader';\n\nconst defaultOptions: HanziWriterOptions = {\n charDataLoader: defaultCharDataLoader,\n onLoadCharDataError: null,\n onLoadCharDataSuccess: null,\n showOutline: true,\n showCharacter: true,\n renderer: 'svg',\n\n // positioning options\n\n width: 0,\n height: 0,\n padding: 20,\n\n // animation options\n\n strokeAnimationSpeed: 1,\n strokeFadeDuration: 400,\n strokeHighlightDuration: 200,\n strokeHighlightSpeed: 2,\n delayBetweenStrokes: 1000,\n delayBetweenLoops: 2000,\n\n // colors\n\n strokeColor: '#555',\n radicalColor: null,\n highlightColor: '#AAF',\n outlineColor: '#DDD',\n drawingColor: '#333',\n\n // quiz options\n\n leniency: 1,\n showHintAfterMisses: 3,\n highlightOnComplete: true,\n highlightCompleteColor: null,\n markStrokeCorrectAfterMisses: false,\n acceptBackwardsStrokes: false,\n quizStartStrokeNum: 0,\n averageDistanceThreshold: 350,\n\n // undocumented obscure options\n\n drawingFadeDuration: 300,\n drawingWidth: 4,\n strokeWidth: 2,\n outlineWidth: 2,\n rendererOverride: {},\n};\n\nexport default defaultOptions;\n","import { CharacterJson, LoadingManagerOptions } from './typings/types';\n\ntype CustomError = Error & { reason: string };\n\nexport default class LoadingManager {\n _loadCounter = 0;\n _isLoading = false;\n _resolve: ((data: CharacterJson) => void) | undefined;\n _reject: ((error?: Error | CustomError | string) => void) | undefined;\n _options: LoadingManagerOptions;\n\n /** Set when calling LoadingManager.loadCharData */\n _loadingChar: string | undefined;\n /** use this to attribute to determine if there was a problem with loading */\n loadingFailed = false;\n\n constructor(options: LoadingManagerOptions) {\n this._options = options;\n }\n\n _debouncedLoad(char: string, count: number) {\n // these wrappers ignore all responses except the most recent.\n const wrappedResolve = (data: CharacterJson) => {\n if (count === this._loadCounter) {\n this._resolve?.(data);\n }\n };\n const wrappedReject = (reason?: Error | string) => {\n if (count === this._loadCounter) {\n this._reject?.(reason);\n }\n };\n\n const returnedData = this._options.charDataLoader(\n char,\n wrappedResolve,\n wrappedReject,\n );\n\n if (returnedData) {\n if ('then' in returnedData) {\n returnedData.then(wrappedResolve).catch(wrappedReject);\n } else {\n wrappedResolve(returnedData);\n }\n }\n }\n\n _setupLoadingPromise() {\n return new Promise(\n (\n resolve: (data: CharacterJson) => void,\n reject: (err?: Error | CustomError | string) => void,\n ) => {\n this._resolve = resolve;\n this._reject = reject;\n },\n )\n .then((data: CharacterJson) => {\n this._isLoading = false;\n this._options.onLoadCharDataSuccess?.(data);\n return data;\n })\n .catch((reason) => {\n this._isLoading = false;\n this.loadingFailed = true;\n\n // If the user has provided an \"onLoadCharDataError\", call this function\n // Otherwise, throw the promise\n if (this._options.onLoadCharDataError) {\n this._options.onLoadCharDataError(reason);\n return;\n }\n\n // If error callback wasn't provided, throw an error so the developer will be aware something went wrong\n if (reason instanceof Error) {\n throw reason;\n }\n\n const err = new Error(\n `Failed to load char data for ${this._loadingChar}`,\n ) as CustomError;\n\n err.reason = reason;\n\n throw err;\n });\n }\n\n loadCharData(char: string) {\n this._loadingChar = char;\n const promise = this._setupLoadingPromise();\n this.loadingFailed = false;\n this._isLoading = true;\n this._loadCounter++;\n this._debouncedLoad(char, this._loadCounter);\n return promise;\n }\n}\n","import RenderState from './RenderState';\nimport parseCharData from './parseCharData';\nimport Positioner from './Positioner';\nimport Quiz from './Quiz';\nimport svgRenderer from './renderers/svg';\nimport canvasRenderer from './renderers/canvas';\nimport defaultOptions from './defaultOptions';\nimport LoadingManager from './LoadingManager';\nimport * as characterActions from './characterActions';\nimport { trim, colorStringToVals, selectIndex, fixIndex } from './utils';\nimport Character from './models/Character';\nimport HanziWriterRendererBase, {\n HanziWriterRendererConstructor,\n} from './renderers/HanziWriterRendererBase';\nimport RenderTargetBase from './renderers/RenderTargetBase';\nimport { GenericMutation } from './Mutation';\n\n// Typings\nimport {\n ColorOptions,\n DimensionOptions,\n HanziWriterOptions,\n LoadingManagerOptions,\n OnCompleteFunction,\n ParsedHanziWriterOptions,\n QuizOptions,\n RenderTargetInitFunction,\n} from './typings/types';\n\n// Export type interfaces\nexport * from './typings/types';\n\nexport default class HanziWriter {\n _options: ParsedHanziWriterOptions;\n _loadingManager: LoadingManager;\n /** Only set when calling .setCharacter() */\n _char: string | undefined;\n /** Only set when calling .setCharacter() */\n _renderState: RenderState | undefined;\n /** Only set when calling .setCharacter() */\n _character: Character | undefined;\n /** Only set when calling .setCharacter() */\n _positioner: Positioner | undefined;\n /** Only set when calling .setCharacter() */\n _hanziWriterRenderer: HanziWriterRendererBase | null | undefined;\n /** Only set when calling .setCharacter() */\n _withDataPromise: Promise | undefined;\n\n _quiz: Quiz | undefined;\n _renderer: {\n HanziWriterRenderer: HanziWriterRendererConstructor;\n createRenderTarget: RenderTargetInitFunction;\n };\n\n target: RenderTargetBase;\n\n /** Main entry point */\n static create(\n element: string | HTMLElement,\n character: string,\n options?: Partial,\n ) {\n const writer = new HanziWriter(element, options);\n writer.setCharacter(character);\n\n return writer;\n }\n\n /** Singleton instance of LoadingManager. Only set in `loadCharacterData` */\n static _loadingManager: LoadingManager | null = null;\n /** Singleton loading options. Only set in `loadCharacterData` */\n static _loadingOptions: Partial | null = null;\n\n static loadCharacterData(\n character: string,\n options: Partial = {},\n ) {\n const loadingManager = (() => {\n const { _loadingManager, _loadingOptions } = HanziWriter;\n if (_loadingManager?._loadingChar === character && _loadingOptions === options) {\n return _loadingManager;\n }\n return new LoadingManager({ ...defaultOptions, ...options });\n })();\n\n HanziWriter._loadingManager = loadingManager;\n HanziWriter._loadingOptions = options;\n return loadingManager.loadCharData(character);\n }\n\n static getScalingTransform(width: number, height: number, padding = 0) {\n const positioner = new Positioner({ width, height, padding });\n return {\n x: positioner.xOffset,\n y: positioner.yOffset,\n scale: positioner.scale,\n transform: trim(`\n translate(${positioner.xOffset}, ${positioner.height - positioner.yOffset})\n scale(${positioner.scale}, ${-1 * positioner.scale})\n `).replace(/\\s+/g, ' '),\n };\n }\n\n constructor(element: string | HTMLElement, options: Partial = {}) {\n const { HanziWriterRenderer, createRenderTarget } =\n options.renderer === 'canvas' ? canvasRenderer : svgRenderer;\n const rendererOverride = options.rendererOverride || {};\n\n this._renderer = {\n HanziWriterRenderer: rendererOverride.HanziWriterRenderer || HanziWriterRenderer,\n createRenderTarget: rendererOverride.createRenderTarget || createRenderTarget,\n };\n // wechat miniprogram component needs direct access to the render target, so this is public\n this.target = this._renderer.createRenderTarget(\n element,\n options.width,\n options.height,\n );\n this._options = this._assignOptions(options);\n this._loadingManager = new LoadingManager(this._options);\n this._setupListeners();\n }\n\n showCharacter(\n options: {\n onComplete?: OnCompleteFunction;\n duration?: number;\n } = {},\n ) {\n this._options.showCharacter = true;\n return this._withData(() =>\n this._renderState\n ?.run(\n characterActions.showCharacter(\n 'main',\n this._character!,\n typeof options.duration === 'number'\n ? options.duration\n : this._options.strokeFadeDuration,\n ),\n )\n .then((res) => {\n options.onComplete?.(res);\n return res;\n }),\n );\n }\n\n hideCharacter(\n options: {\n onComplete?: OnCompleteFunction;\n duration?: number;\n } = {},\n ) {\n this._options.showCharacter = false;\n return this._withData(() =>\n this._renderState\n ?.run(\n characterActions.hideCharacter(\n 'main',\n this._character!,\n typeof options.duration === 'number'\n ? options.duration\n : this._options.strokeFadeDuration,\n ),\n )\n .then((res) => {\n options.onComplete?.(res);\n return res;\n }),\n );\n }\n\n animateCharacter(\n options: {\n onComplete?: OnCompleteFunction;\n } = {},\n ) {\n this.cancelQuiz();\n\n return this._withData(() =>\n this._renderState\n ?.run(\n characterActions.animateCharacter(\n 'main',\n this._character!,\n this._options.strokeFadeDuration,\n this._options.strokeAnimationSpeed,\n this._options.delayBetweenStrokes,\n ),\n )\n .then((res) => {\n options.onComplete?.(res);\n return res;\n }),\n );\n }\n\n animateStroke(\n strokeNum: number,\n options: {\n onComplete?: OnCompleteFunction;\n } = {},\n ) {\n this.cancelQuiz();\n return this._withData(() =>\n this._renderState\n ?.run(\n characterActions.animateSingleStroke(\n 'main',\n this._character!,\n fixIndex(strokeNum, this._character!.strokes.length),\n this._options.strokeAnimationSpeed,\n ),\n )\n .then((res) => {\n options.onComplete?.(res);\n return res;\n }),\n );\n }\n\n highlightStroke(\n strokeNum: number,\n options: {\n onComplete?: OnCompleteFunction;\n } = {},\n ) {\n const promise = () => {\n if (!this._character || !this._renderState) {\n return;\n }\n\n return this._renderState\n .run(\n characterActions.highlightStroke(\n selectIndex(this._character.strokes, strokeNum),\n colorStringToVals(this._options.highlightColor),\n this._options.strokeHighlightSpeed,\n ),\n )\n .then((res) => {\n options.onComplete?.(res);\n return res;\n });\n };\n\n return this._withData(promise);\n }\n\n async loopCharacterAnimation() {\n this.cancelQuiz();\n return this._withData(() =>\n this._renderState!.run(\n characterActions.animateCharacterLoop(\n 'main',\n this._character!,\n this._options.strokeFadeDuration,\n this._options.strokeAnimationSpeed,\n this._options.delayBetweenStrokes,\n this._options.delayBetweenLoops,\n ),\n { loop: true },\n ),\n );\n }\n\n pauseAnimation() {\n return this._withData(() => this._renderState?.pauseAll());\n }\n\n resumeAnimation() {\n return this._withData(() => this._renderState?.resumeAll());\n }\n\n showOutline(\n options: {\n duration?: number;\n onComplete?: OnCompleteFunction;\n } = {},\n ) {\n this._options.showOutline = true;\n return this._withData(() =>\n this._renderState\n ?.run(\n characterActions.showCharacter(\n 'outline',\n this._character!,\n typeof options.duration === 'number'\n ? options.duration\n : this._options.strokeFadeDuration,\n ),\n )\n .then((res) => {\n options.onComplete?.(res);\n return res;\n }),\n );\n }\n\n hideOutline(\n options: {\n duration?: number;\n onComplete?: OnCompleteFunction;\n } = {},\n ) {\n this._options.showOutline = false;\n return this._withData(() =>\n this._renderState\n ?.run(\n characterActions.hideCharacter(\n 'outline',\n this._character!,\n typeof options.duration === 'number'\n ? options.duration\n : this._options.strokeFadeDuration,\n ),\n )\n .then((res) => {\n options.onComplete?.(res);\n return res;\n }),\n );\n }\n\n /** Updates the size of the writer instance without resetting render state */\n updateDimensions({ width, height, padding }: Partial) {\n if (width !== undefined) this._options.width = width;\n if (height !== undefined) this._options.height = height;\n if (padding !== undefined) this._options.padding = padding;\n this.target.updateDimensions(this._options.width, this._options.height);\n // if there's already a character drawn, destroy and recreate the renderer in the same state\n if (\n this._character &&\n this._renderState &&\n this._hanziWriterRenderer &&\n this._positioner\n ) {\n this._hanziWriterRenderer.destroy();\n const hanziWriterRenderer = this._initAndMountHanziWriterRenderer(this._character);\n // TODO: this should probably implement EventEmitter instead of manually tracking updates like this\n this._renderState.overwriteOnStateChange((nextState) =>\n hanziWriterRenderer.render(nextState),\n );\n hanziWriterRenderer.render(this._renderState.state);\n // update the current quiz as well, if one is active\n if (this._quiz) {\n this._quiz.setPositioner(this._positioner);\n }\n }\n }\n\n updateColor(\n colorName: keyof ColorOptions,\n colorVal: string | null,\n options: {\n duration?: number;\n onComplete?: OnCompleteFunction;\n } = {},\n ) {\n let mutations: GenericMutation[] = [];\n\n const fixedColorVal = (() => {\n // If we're removing radical color, tween it to the stroke color\n if (colorName === 'radicalColor' && !colorVal) {\n return this._options.strokeColor;\n }\n return colorVal;\n })();\n\n const mappedColor = colorStringToVals(fixedColorVal as string);\n\n this._options[colorName] = colorVal as any;\n\n const duration = options.duration ?? this._options.strokeFadeDuration;\n\n mutations = mutations.concat(\n characterActions.updateColor(colorName, mappedColor, duration),\n );\n\n // make sure to set radicalColor back to null after the transition finishes if val == null\n if (colorName === 'radicalColor' && !colorVal) {\n mutations = mutations.concat(characterActions.updateColor(colorName, null, 0));\n }\n\n return this._withData(() =>\n this._renderState?.run(mutations).then((res) => {\n options.onComplete?.(res);\n return res;\n }),\n );\n }\n\n quiz(quizOptions: Partial = {}) {\n return this._withData(async () => {\n if (this._character && this._renderState && this._positioner) {\n this.cancelQuiz();\n this._quiz = new Quiz(this._character, this._renderState, this._positioner);\n this._options = {\n ...this._options,\n ...quizOptions,\n };\n this._quiz.startQuiz(this._options);\n }\n });\n }\n\n cancelQuiz() {\n if (this._quiz) {\n this._quiz.cancel();\n this._quiz = undefined;\n }\n }\n\n setCharacter(char: string) {\n this.cancelQuiz();\n this._char = char;\n if (this._hanziWriterRenderer) {\n this._hanziWriterRenderer.destroy();\n }\n if (this._renderState) {\n this._renderState.cancelAll();\n }\n this._hanziWriterRenderer = null;\n this._withDataPromise = this._loadingManager\n .loadCharData(char)\n .then((pathStrings) => {\n // if \"pathStrings\" isn't set, \".catch()\"\" was probably called and loading likely failed\n if (!pathStrings || this._loadingManager.loadingFailed) {\n return;\n }\n\n this._character = parseCharData(char, pathStrings);\n this._renderState = new RenderState(this._character, this._options, (nextState) =>\n hanziWriterRenderer.render(nextState),\n );\n\n const hanziWriterRenderer = this._initAndMountHanziWriterRenderer(\n this._character,\n );\n hanziWriterRenderer.render(this._renderState.state);\n });\n return this._withDataPromise;\n }\n\n _initAndMountHanziWriterRenderer(character: Character) {\n const { width, height, padding } = this._options;\n this._positioner = new Positioner({ width, height, padding });\n const hanziWriterRenderer = new this._renderer.HanziWriterRenderer(\n character,\n this._positioner,\n );\n hanziWriterRenderer.mount(this.target);\n this._hanziWriterRenderer = hanziWriterRenderer;\n return hanziWriterRenderer;\n }\n\n async getCharacterData(): Promise {\n if (!this._char) {\n throw new Error('setCharacter() must be called before calling getCharacterData()');\n }\n const character = await this._withData(() => this._character);\n return character!;\n }\n\n _assignOptions(options: Partial): ParsedHanziWriterOptions {\n const mergedOptions = {\n ...defaultOptions,\n ...options,\n };\n\n // backfill strokeAnimationSpeed if deprecated strokeAnimationDuration is provided instead\n if (options.strokeAnimationDuration && !options.strokeAnimationSpeed) {\n mergedOptions.strokeAnimationSpeed = 500 / options.strokeAnimationDuration;\n }\n if (options.strokeHighlightDuration && !options.strokeHighlightSpeed) {\n mergedOptions.strokeHighlightSpeed = 500 / mergedOptions.strokeHighlightDuration;\n }\n\n if (!options.highlightCompleteColor) {\n mergedOptions.highlightCompleteColor = mergedOptions.highlightColor;\n }\n\n return this._fillWidthAndHeight(mergedOptions);\n }\n\n /** returns a new options object with width and height filled in if missing */\n _fillWidthAndHeight(options: HanziWriterOptions): ParsedHanziWriterOptions {\n const filledOpts = { ...options };\n if (filledOpts.width && !filledOpts.height) {\n filledOpts.height = filledOpts.width;\n } else if (filledOpts.height && !filledOpts.width) {\n filledOpts.width = filledOpts.height;\n } else if (!filledOpts.width && !filledOpts.height) {\n const { width, height } = this.target.getBoundingClientRect();\n const minDim = Math.min(width, height);\n filledOpts.width = minDim;\n filledOpts.height = minDim;\n }\n return filledOpts as ParsedHanziWriterOptions;\n }\n\n _withData(func: () => T) {\n // if this._loadingManager.loadingFailed, then loading failed before this method was called\n if (this._loadingManager.loadingFailed) {\n throw Error('Failed to load character data. Call setCharacter and try again.');\n }\n\n if (this._withDataPromise) {\n return this._withDataPromise.then(() => {\n if (!this._loadingManager.loadingFailed) {\n return func();\n }\n });\n }\n return Promise.resolve().then(func);\n }\n\n _setupListeners() {\n this.target.addPointerStartListener((evt) => {\n if (this._quiz) {\n evt.preventDefault();\n this._quiz.startUserStroke(evt.getPoint());\n }\n });\n this.target.addPointerMoveListener((evt) => {\n if (this._quiz) {\n evt.preventDefault();\n this._quiz.continueUserStroke(evt.getPoint());\n }\n });\n this.target.addPointerEndListener(() => {\n this._quiz?.endUserStroke();\n });\n }\n}\n"],"names":["globalObj","window","global","performanceNow","performance","now","Date","requestAnimationFrame","callback","setTimeout","cancelAnimationFrame","clearTimeout","arrLast","arr","length","fixIndex","index","copyAndMergeDeep","base","override","output","key","baseVal","overrideVal","Array","isArray","count","counter","average","reduce","acc","val","colorStringToVals","colorString","normalizedColor","toUpperCase","trim","test","hexParts","substring","split","hexStr","join","r","parseInt","slice","g","b","a","rgbMatch","match","parseFloat","Error","objRepeat","item","times","obj","i","objRepeatCb","cb","ua","navigator","userAgent","isMsBrowser","indexOf","noop","RenderState","constructor","character","options","onStateChange","_mutationChains","_onStateChange","state","drawingFadeDuration","drawingWidth","drawingColor","strokeColor","outlineColor","radicalColor","highlightColor","main","opacity","showCharacter","strokes","outline","showOutline","highlight","userStrokes","displayPortion","overwriteOnStateChange","updateState","stateChanges","nextState","this","run","mutations","scopes","map","mut","scope","cancelMutations","Promise","resolve","mutationChain","_isActive","_index","_resolve","_mutations","_loop","loop","_scopes","push","_run","filter","chain","canceled","then","_getActiveMutations","pauseAll","forEach","mutation","pause","resumeAll","resume","scopesToCancel","chainId","scopeToCancel","startsWith","_cancelMutationChain","cancelAll","cancel","subtract","p1","p2","x","y","magnitude","point","Math","sqrt","pow","distance","point1","point2","round","precision","multiplier","points","lastPoint","dist","_extendPointOnLine","vect","norm","normalizeCurve","curve","outlinedCurve","numPoints","segmentLen","outlinePoints","endPoint","remainingCurvePoints","remainingDist","outlinePointFound","nextPointDist","shift","nextPoint","outlineCurve","mean","translatedCurve","scale","maxLen","newCurve","prevPoint","segLen","numNewPoints","ceil","newSegLen","subdivideCurve","getPathString","close","start","remainingPoints","pathString","roundedPoint","extendStart","filteredPoints","numFilteredPoints","curVect","prevVect","pop","_filterParallelPoints","newStart","extendedPoints","unshift","Stroke","path","strokeNum","isInRadical","getStartingPoint","getEndingPoint","getLength","getVectors","vector","getDistance","distances","strokePoint","min","getAverageDistance","Character","symbol","generateStrokes","radStrokes","medians","pointData","from","to","preScaledWidth","preScaledHeight","Positioner","padding","width","height","effectiveWidth","effectiveHeight","scaleX","scaleY","xCenteringBuffer","yCenteringBuffer","xOffset","yOffset","convertExternalPoint","directionMatches","stroke","edgeVectors","vectors","getEdgeVectors","strokeVectors","edgeVector","strokeSimilarities","strokeVector","cosineSimilarity","max","stripDuplicates","firstPoint","rest","dedupedPoints","SHAPE_FIT_ROTATIONS","PI","shapeFit","curve1","curve2","leniency","normCurve1","normCurve2","minDist","Infinity","theta","longCurve","shortCurve","calcVal","j","prevResultsCol","curResultsCol","lastResult","frechetDist","cos","sin","rotate","getMatchData","isOutlineVisible","checkBackwards","averageDistanceThreshold","avgDist","withinDistThresh","isMatch","meta","isStrokeBackwards","startAndEndMatch","closestStroke","startingDist","endingDist","startAndEndMatches","directionMatch","shapeMatch","lengthMatch","lengthMatches","reverse","UserStroke","id","startingPoint","startingExternalPoint","externalPoints","appendPoint","externalPoint","Mutation","valuesOrCallable","_tick","timing","_startPauseTime","progress","_startTime","_pausedDuration","_duration","_renderState","_values","_frameHandle","undefined","easedProgress","ease","getPartialValues","_startState","_valuesOrCallable","duration","_force","force","renderState","_inflateValues","isAlreadyAtEnd","values","parts","final","current","cap","inflate","startValues","endValues","target","endValue","startValue","Delay","_paused","_runningPromise","_timeout","elapsedDelay","showStrokes","charName","hideCharacter","updateColor","colorName","colorVal","highlightStroke","color","speed","animateStroke","animateCharacter","fadeDuration","delayBetweenStrokes","concat","removeUserStroke","userStrokeId","Quiz","positioner","_currentStrokeIndex","_mistakesOnStroke","_totalMistakes","_character","_positioner","startQuiz","_options","startIndex","quizStartStrokeNum","strokeFadeDuration","startStrokeNum","characterActions","startUserStroke","_userStroke","endUserStroke","strokeId","quizActions","continueUserStroke","nextPoints","setPositioner","acceptBackwardsStrokes","markStrokeCorrectAfterMisses","currentStroke","_getCurrentStroke","userStroke","laterStrokes","closestMatchDist","leniencyAdjustment","strokeMatches","isForceAccepted","_handleSuccess","_handleFailure","showHintAfterMisses","strokeHighlightSpeed","_getStrokeData","isCorrect","mistakesOnStroke","totalMistakes","strokesRemaining","drawnPath","geometry","isBackwards","onCorrectStroke","onComplete","highlightOnComplete","highlightCompleteColor","strokeHighlightDuration","animation","onMistake","createElm","elmType","document","createElementNS","attr","elm","name","value","setAttributeNS","attrs","attrsMap","Object","keys","attrName","removeElm","parentNode","removeChild","StrokeRendererBase","_pathLength","STROKE_WIDTH","_getStrokeDashoffset","_getColor","StrokeRenderer","_oldProps","mount","_animationPath","svg","_clip","_strokePath","maskId","style","prefix","location","href","replace","extendedMaskPoints","toString","fill","appendChild","defs","render","props","_this$_oldProps","strokeDashoffset","_this$_oldProps2","CharacterRenderer","_strokeRenderers","subTarget","createSubRenderTarget","_group","strokeRenderer","display","removeProperty","colorsChanged","_this$_oldProps3","_this$_oldProps4","UserStrokeRenderer","_path","strokeWidth","destroy","RenderTargetBase","node","addPointerStartListener","addEventListener","evt","_eventify","_getMousePoint","_getTouchPoint","addPointerMoveListener","addPointerEndListener","getBoundingClientRect","updateDimensions","setAttribute","pointFunc","getPoint","call","preventDefault","left","top","clientX","clientY","touches","RenderTarget","_pt","createSVGPoint","elmOrId","element","getElementById","nodeType","nodeName","group","localPt","matrixTransform","getScreenCTM","_this$node$getScreenC","inverse","super","_this$node$getScreenC2","HanziWriterRenderer","_mainCharRenderer","_outlineCharRenderer","_highlightCharRenderer","_userStrokeRenderers","positionedTarget","_positionedTarget","userStrokeProps","newStrokeRenderer","innerHTML","createRenderTarget","init","drawPath","ctx","beginPath","moveTo","lineTo","usePath2D","Path2D","_path2D","_pathCmd","pathParts","part","commands","cmd","rawParams","params","param","bezierCurveTo","quadraticCurveTo","pathStringToCanvas","_extendedMaskPoints","save","clip","globalAlpha","dashOffset","strokeStyle","fillStyle","lineWidth","lineCap","lineJoin","setLineDash","lineDashOffset","restore","renderUserStroke","canvas","createElement","getContext","_target","_animationFrame","clearRect","translate","transform","draw","defaultOptions","charDataLoader","char","onLoad","onError","xhr","XMLHttpRequest","overrideMimeType","open","getCharDataUrl","onerror","event","onreadystatechange","readyState","status","JSON","parse","responseText","send","onLoadCharDataError","onLoadCharDataSuccess","renderer","strokeAnimationSpeed","delayBetweenLoops","outlineWidth","rendererOverride","LoadingManager","_loadCounter","_isLoading","loadingFailed","_debouncedLoad","wrappedResolve","data","wrappedReject","reason","_reject","returnedData","catch","_setupLoadingPromise","reject","err","_loadingChar","loadCharData","promise","HanziWriter","canvasRenderer","svgRenderer","_renderer","_assignOptions","_loadingManager","_setupListeners","writer","setCharacter","loadingManager","_loadingOptions","string","_withData","_this$_renderState","res","_this$_renderState2","cancelQuiz","_this$_renderState3","_this$_renderState4","curCharState","mutationState","pauseAnimation","_this$_renderState5","resumeAnimation","_this$_renderState6","_this$_renderState7","hideOutline","_this$_renderState8","_hanziWriterRenderer","hanziWriterRenderer","_initAndMountHanziWriterRenderer","_quiz","mappedColor","_this$_renderState9","quiz","quizOptions","async","_char","_withDataPromise","pathStrings","charJson","parseCharData","mergedOptions","strokeAnimationDuration","_fillWidthAndHeight","filledOpts","minDim","func"],"mappings":"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"}