{
  "version": 3,
  "sources": ["../../src/index.ts", "../../src/multi-line.ts"],
  "sourcesContent": ["export { splitMultilineText as split, clearMultilineCache as clearCache } from \"./multi-line\";\n", "const resultCache: Map<string, readonly string[]> = new Map();\n\n// font -> avg pixels per char\nconst metrics: Map<string, { count: number; size: number }> = new Map();\n\nconst hyperMaps: Map<string, Map<string, number>> = new Map();\n\ntype BreakCallback = (str: string) => readonly number[];\n\nexport function clearMultilineCache() {\n    resultCache.clear();\n    hyperMaps.clear();\n    metrics.clear();\n}\n\nexport function backProp(\n    text: string,\n    realWidth: number,\n    keyMap: Map<string, number>,\n    temperature: number,\n    avgSize: number\n) {\n    let guessWidth = 0;\n    const contribMap: Record<string, number> = {};\n    for (const char of text) {\n        const v = keyMap.get(char) ?? avgSize;\n        guessWidth += v;\n        contribMap[char] = (contribMap[char] ?? 0) + 1;\n        ``;\n    }\n\n    const diff = realWidth - guessWidth;\n\n    for (const key of Object.keys(contribMap)) {\n        const numContribution = contribMap[key];\n        const contribWidth = keyMap.get(key) ?? avgSize;\n        const contribAmount = (contribWidth * numContribution) / guessWidth;\n        const adjustment = (diff * contribAmount * temperature) / numContribution;\n        const newVal = contribWidth + adjustment;\n        keyMap.set(key, newVal);\n    }\n}\n\nfunction makeHyperMap(ctx: CanvasRenderingContext2D, avgSize: number): Map<string, number> {\n    const result: Map<string, number> = new Map();\n    let total = 0;\n    for (const char of \"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890,.-+=?\") {\n        const w = ctx.measureText(char).width;\n        result.set(char, w);\n        total += w;\n    }\n\n    const avg = total / result.size;\n\n    // Artisnal hand-tuned constants that have no real meaning other than they make it work better for most fonts\n    // These don't really need to be accurate, we are going to be adjusting the weights. It just converges faster\n    // if they start somewhere close.\n    const damper = 3;\n    const scaler = (avgSize / avg + damper) / (damper + 1);\n    const keys = result.keys();\n    for (const key of keys) {\n        result.set(key, (result.get(key) ?? avg) * scaler);\n    }\n    return result;\n}\n\nfunction measureText(ctx: CanvasRenderingContext2D, text: string, fontStyle: string, hyperMode: boolean): number {\n    const current = metrics.get(fontStyle);\n\n    if (hyperMode && current !== undefined && current.count > 20_000) {\n        let hyperMap = hyperMaps.get(fontStyle);\n        if (hyperMap === undefined) {\n            hyperMap = makeHyperMap(ctx, current.size);\n            hyperMaps.set(fontStyle, hyperMap);\n        }\n\n        if (current.count > 500_000) {\n            let final = 0;\n            for (const char of text) {\n                final += hyperMap.get(char) ?? current.size;\n            }\n            return final * 1.01; //safety margin\n        }\n\n        const result = ctx.measureText(text);\n        backProp(text, result.width, hyperMap, Math.max(0.05, 1 - current.count / 200_000), current.size);\n        metrics.set(fontStyle, {\n            count: current.count + text.length,\n            size: current.size,\n        });\n        return result.width;\n    }\n\n    const result = ctx.measureText(text);\n\n    const avg = result.width / text.length;\n\n    // we've collected enough data\n    if ((current?.count ?? 0) > 20_000) {\n        return result.width;\n    }\n\n    if (current === undefined) {\n        metrics.set(fontStyle, {\n            count: text.length,\n            size: avg,\n        });\n    } else {\n        const diff = avg - current.size;\n        const contribution = text.length / (current.count + text.length);\n        const newVal = current.size + diff * contribution;\n        metrics.set(fontStyle, {\n            count: current.count + text.length,\n            size: newVal,\n        });\n    }\n\n    return result.width;\n}\n\nfunction getSplitPoint(\n    ctx: CanvasRenderingContext2D,\n    text: string,\n    width: number,\n    fontStyle: string,\n    totalWidth: number,\n    measuredChars: number,\n    hyperMode: boolean,\n    getBreakOpportunities?: BreakCallback\n): number {\n    if (text.length <= 1) return text.length;\n\n    // this should never happen, but we are protecting anyway\n    if (totalWidth < width) return -1;\n\n    let guess = Math.floor((width / totalWidth) * measuredChars);\n    let guessWidth = measureText(ctx, text.slice(0, Math.max(0, guess)), fontStyle, hyperMode);\n\n    const oppos = getBreakOpportunities?.(text);\n\n    if (guessWidth === width) {\n        // NAILED IT\n    } else if (guessWidth < width) {\n        while (guessWidth < width) {\n            guess++;\n            guessWidth = measureText(ctx, text.slice(0, Math.max(0, guess)), fontStyle, hyperMode);\n        }\n        guess--;\n    } else {\n        // we only need to check for spaces as we go back\n        while (guessWidth > width) {\n            const lastSpace = oppos !== undefined ? 0 : text.lastIndexOf(\" \", guess - 1);\n            if (lastSpace > 0) {\n                guess = lastSpace;\n            } else {\n                guess--;\n            }\n            guessWidth = measureText(ctx, text.slice(0, Math.max(0, guess)), fontStyle, hyperMode);\n        }\n    }\n\n    if (text[guess] !== \" \") {\n        let greedyBreak = 0;\n        if (oppos === undefined) {\n            greedyBreak = text.lastIndexOf(\" \", guess);\n        } else {\n            for (const o of oppos) {\n                if (o > guess) break;\n                greedyBreak = o;\n            }\n        }\n        if (greedyBreak > 0) {\n            guess = greedyBreak;\n        }\n    }\n\n    return guess;\n}\n\n// Algorithm improved from https://github.com/geongeorge/Canvas-Txt/blob/master/src/index.js\nexport function splitMultilineText(\n    ctx: CanvasRenderingContext2D,\n    value: string,\n    fontStyle: string,\n    width: number,\n    hyperWrappingAllowed: boolean,\n    getBreakOpportunities?: BreakCallback\n): readonly string[] {\n    const key = `${value}_${fontStyle}_${width}px`;\n    const cacheResult = resultCache.get(key);\n    if (cacheResult !== undefined) return cacheResult;\n\n    if (width <= 0) {\n        // dont render 0 width stuff\n        return [];\n    }\n\n    let result: string[] = [];\n    const encodedLines: string[] = value.split(\"\\n\");\n\n    const fontMetrics = metrics.get(fontStyle);\n    const safeLineGuess = fontMetrics === undefined ? value.length : (width / fontMetrics.size) * 1.5;\n    const hyperMode = hyperWrappingAllowed && fontMetrics !== undefined && fontMetrics.count > 20_000;\n\n    for (let line of encodedLines) {\n        let textWidth = measureText(ctx, line.slice(0, Math.max(0, safeLineGuess)), fontStyle, hyperMode);\n        let measuredChars = Math.min(line.length, safeLineGuess);\n        if (textWidth <= width) {\n            // line fits, just push it\n            result.push(line);\n        } else {\n            while (textWidth > width) {\n                const splitPoint = getSplitPoint(\n                    ctx,\n                    line,\n                    width,\n                    fontStyle,\n                    textWidth,\n                    measuredChars,\n                    hyperMode,\n                    getBreakOpportunities\n                );\n                const subLine = line.slice(0, Math.max(0, splitPoint));\n\n                line = line.slice(subLine.length);\n                result.push(subLine);\n                textWidth = measureText(ctx, line.slice(0, Math.max(0, safeLineGuess)), fontStyle, hyperMode);\n                measuredChars = Math.min(line.length, safeLineGuess);\n            }\n            if (textWidth > 0) {\n                result.push(line);\n            }\n        }\n    }\n\n    result = result.map((l, i) => (i === 0 ? l.trimEnd() : l.trim()));\n    resultCache.set(key, result);\n    if (resultCache.size > 500) {\n        // this is not technically LRU behavior but it works \"close enough\" and is much cheaper\n        resultCache.delete(resultCache.keys().next().value);\n    }\n    return result;\n}\n"],
  "mappings": "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",
  "names": []
}
