@miliastry/quasar 1.0.6 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/Visuals/lyne.css +117 -6
- package/dist/Visuals/osu.css +36 -5
- package/dist/index.js +9664 -3282
- package/dist/index.mjs +9535 -3283
- package/dist/types/Analysis/Contracts/AnalysisReport.d.ts +22 -0
- package/dist/types/Analysis/Contracts/Contribution.d.ts +68 -0
- package/dist/types/Analysis/Contracts/Pass.d.ts +59 -0
- package/dist/types/Analysis/Contracts/PipelineContext.d.ts +34 -0
- package/dist/types/Analysis/Passes/Analysis/ColorUsageAnalyzer.d.ts +56 -0
- package/dist/types/Analysis/Passes/Analysis/GradientAnalyzer.d.ts +111 -0
- package/dist/types/Analysis/Passes/Analysis/MergeableColorAnalyzer.d.ts +26 -0
- package/dist/types/Analysis/Passes/Analysis/RainbowAnalyzer.d.ts +47 -0
- package/dist/types/Analysis/Passes/Analysis/SymbolAnalyzer.d.ts +89 -0
- package/dist/types/Analysis/Passes/Analysis/WaveAnalyzer.d.ts +44 -0
- package/dist/types/Analysis/Passes/Analysis/__tests__/GradientAnalyzer.test.d.ts +4 -0
- package/dist/types/Analysis/Passes/Analysis/__tests__/MergeableColorAnalyzer.test.d.ts +4 -0
- package/dist/types/Analysis/Passes/Analysis/__tests__/RainbowAnalyzer.test.d.ts +4 -0
- package/dist/types/Analysis/Passes/Analysis/__tests__/WaveAnalyzer.test.d.ts +4 -0
- package/dist/types/Analysis/Passes/Decision/DefaultDecision.d.ts +30 -0
- package/dist/types/Analysis/Passes/Decision/PaletteRemapDecision.d.ts +169 -0
- package/dist/types/Analysis/Passes/Decision/__tests__/DefaultDecision.test.d.ts +4 -0
- package/dist/types/Analysis/Passes/Transform/CollapseGradientTransform.d.ts +37 -0
- package/dist/types/Analysis/Passes/Transform/MergeColorsTransform.d.ts +28 -0
- package/dist/types/Analysis/Passes/Transform/RainbowCollapseTransform.d.ts +19 -0
- package/dist/types/Analysis/Passes/Transform/WaveCollapseTransform.d.ts +20 -0
- package/dist/types/Analysis/Passes/Utility/CharacterCountAnalyzer.d.ts +19 -0
- package/dist/types/Analysis/Pipeline/Pipeline.d.ts +69 -0
- package/dist/types/Analysis/Pipeline/PipelineBuilder.d.ts +31 -0
- package/dist/types/Analysis/Pipeline/PipelineStage.d.ts +17 -0
- package/dist/types/Analysis/Utils/color-utils.d.ts +60 -0
- package/dist/types/Analysis/__tests__/Integration.test.d.ts +8 -0
- package/dist/types/Analysis/__tests__/PaletteRemap.test.d.ts +14 -0
- package/dist/types/Analysis/__tests__/Pipeline.test.d.ts +10 -0
- package/dist/types/Analysis/__tests__/RoundTrip.test.d.ts +30 -0
- package/dist/types/Analysis/__tests__/SymbolAnalyzer.test.d.ts +11 -0
- package/dist/types/Analysis/index.d.ts +40 -0
- package/dist/types/BBCode/BBCodeDocumentModel.d.ts +92 -0
- package/dist/types/BBCode/BBCodeToGreenNode.d.ts +111 -0
- package/dist/types/BBCode/Parser.d.ts +78 -0
- package/dist/types/BBCode/index.d.ts +4 -0
- package/dist/types/Collab/positions.d.ts +55 -0
- package/dist/types/Commands/Command.d.ts +37 -0
- package/dist/types/Commands/CommandRegistry.d.ts +48 -0
- package/dist/types/Commands/DeleteNode.d.ts +7 -0
- package/dist/types/Commands/InsertText.d.ts +8 -0
- package/dist/types/Commands/SplitMerge.d.ts +9 -0
- package/dist/types/Commands/WrapInTag.d.ts +8 -0
- package/dist/types/Commands/index.d.ts +6 -0
- package/dist/types/Diff/TreeDiffer.d.ts +52 -0
- package/dist/types/Diff/__tests__/TreeDiffer.test.d.ts +1 -0
- package/dist/types/Diff/index.d.ts +2 -0
- package/dist/types/Edits/EditPlan.d.ts +144 -0
- package/dist/types/Edits/Optimizer.d.ts +78 -0
- package/dist/types/Edits/Rules/Rule.d.ts +91 -0
- package/dist/types/Edits/Rules/dropEmptyTags.d.ts +40 -0
- package/dist/types/Edits/Rules/dropRedundantNesting.d.ts +59 -0
- package/dist/types/Edits/Rules/index.d.ts +9 -0
- package/dist/types/Edits/Rules/mergeAdjacent.d.ts +93 -0
- package/dist/types/Edits/Rules/reorderWrappers.d.ts +42 -0
- package/dist/types/Edits/Rules/shortenHex.d.ts +30 -0
- package/dist/types/Edits/Rules/tagValue.d.ts +56 -0
- package/dist/types/Edits/Rules/unwrapInvisibleColor.d.ts +47 -0
- package/dist/types/Edits/applyEdits.d.ts +36 -0
- package/dist/types/Edits/index.d.ts +6 -0
- package/dist/types/Events/EventBus.d.ts +103 -0
- package/dist/types/Events/index.d.ts +2 -0
- package/dist/types/Formatter/Formatter.d.ts +32 -0
- package/dist/types/Formatter/index.d.ts +2 -0
- package/dist/types/HTML/HTMLDocumentModel.d.ts +13 -0
- package/dist/types/HTML/HTMLToGreenNode.d.ts +4 -0
- package/dist/types/Incremental/BracketIndex.d.ts +101 -0
- package/dist/types/Incremental/ChangeTracker.d.ts +69 -0
- package/dist/types/Incremental/IncrementalParser.d.ts +246 -0
- package/dist/types/Incremental/__tests__/BracketIndex.test.d.ts +1 -0
- package/dist/types/Incremental/__tests__/IncrementalParser.test.d.ts +1 -0
- package/dist/types/Incremental/index.d.ts +4 -0
- package/dist/types/Lexer/BBCodeLexer.d.ts +74 -0
- package/dist/types/Lexer/Lexer.d.ts +54 -0
- package/dist/types/Lexer/index.d.ts +4 -0
- package/dist/types/Linter/Linter.d.ts +58 -0
- package/dist/types/Linter/index.d.ts +2 -0
- package/dist/types/Markdown/MarkdownAST.d.ts +122 -0
- package/dist/types/Markdown/MarkdownDocumentModel.d.ts +15 -0
- package/dist/types/Markdown/MarkdownLexer.d.ts +155 -0
- package/dist/types/Markdown/MarkdownParser.d.ts +42 -0
- package/dist/types/Markdown/MarkdownToGreenNode.d.ts +6 -0
- package/dist/types/MilHibri/MilHibriDocumentModel.d.ts +21 -0
- package/dist/types/Model/DocumentModel.d.ts +318 -0
- package/dist/types/Model/NodeFactory.d.ts +29 -0
- package/dist/types/Model/TagRegistry.d.ts +165 -0
- package/dist/types/Model/index.d.ts +5 -0
- package/dist/types/Plugins/PluginAPI.d.ts +44 -0
- package/dist/types/Plugins/PluginRegistry.d.ts +81 -0
- package/dist/types/Plugins/index.d.ts +3 -0
- package/dist/types/Queries/QueryEngine.d.ts +32 -0
- package/dist/types/Queries/index.d.ts +1 -0
- package/dist/types/Reconciler/SurgicalReconciler.d.ts +23 -0
- package/dist/types/RenderPipeline/RenderPipeline.d.ts +46 -0
- package/dist/types/RenderPipeline/RenderTree.d.ts +55 -0
- package/dist/types/RenderPipeline/index.d.ts +4 -0
- package/dist/types/Repair/NestingRepair.d.ts +48 -0
- package/dist/types/Repair/index.d.ts +1 -0
- package/dist/types/Semantic/SemanticAnalyzer.d.ts +321 -0
- package/dist/types/Semantic/index.d.ts +2 -0
- package/dist/types/Symbols/SymbolTable.d.ts +47 -0
- package/dist/types/Symbols/index.d.ts +1 -0
- package/dist/types/Syntax/GreenNode.d.ts +154 -0
- package/dist/types/Syntax/GreenNodePool.d.ts +134 -0
- package/dist/types/Syntax/NodeMatcher.d.ts +75 -0
- package/dist/types/Syntax/RedNode.d.ts +280 -0
- package/dist/types/Syntax/RedNodeStore.d.ts +116 -0
- package/dist/types/Syntax/TreeBuilder.d.ts +54 -0
- package/dist/types/Syntax/__tests__/GreenNode.test.d.ts +1 -0
- package/dist/types/Syntax/__tests__/RedNode.test.d.ts +1 -0
- package/dist/types/Syntax/__tests__/RedNodeStore.test.d.ts +1 -0
- package/dist/types/Syntax/greenEdit.d.ts +64 -0
- package/dist/types/Syntax/hash.d.ts +17 -0
- package/dist/types/Syntax/index.d.ts +8 -0
- package/dist/types/Syntax/nodeAttr.d.ts +63 -0
- package/dist/types/Syntax/partition.d.ts +67 -0
- package/dist/types/Syntax/preserveNodeIds.d.ts +47 -0
- package/dist/types/Tokens/TokenResolver.d.ts +25 -0
- package/dist/types/Tokens/index.d.ts +1 -0
- package/dist/types/Transactions/Transaction.d.ts +36 -0
- package/dist/types/Transactions/UndoManager.d.ts +63 -0
- package/dist/types/Transactions/index.d.ts +3 -0
- package/dist/types/Transformers/ASTOptimizer.d.ts +34 -0
- package/dist/types/Transformers/GradientTransformer.d.ts +18 -0
- package/dist/types/Transformers/GrowTransformer.d.ts +16 -0
- package/dist/types/Transformers/RainbowTransformer.d.ts +17 -0
- package/dist/types/Transformers/SineWaveTransformer.d.ts +17 -0
- package/dist/types/Transformers/Transformer.d.ts +17 -0
- package/dist/types/Types/core.d.ts +104 -0
- package/dist/types/Types/diagnostics.d.ts +85 -0
- package/dist/types/Types/index.d.ts +11 -0
- package/dist/types/Types/operations.d.ts +109 -0
- package/dist/types/Types/queries.d.ts +67 -0
- package/dist/types/Types/symbols.d.ts +42 -0
- package/dist/types/Types/tokens.d.ts +75 -0
- package/dist/types/Utils/BBCodeGenerator.d.ts +21 -0
- package/dist/types/Utils/ColorMath.d.ts +92 -0
- package/dist/types/Utils/EffectMath.d.ts +643 -0
- package/dist/types/Utils/TagVocabulary.d.ts +104 -0
- package/dist/types/Utils/TagVocabulary.test.d.ts +1 -0
- package/dist/types/Utils/color.d.ts +29 -0
- package/dist/types/Utils/dom-to-svg.d.ts +28 -0
- package/dist/types/Utils/dom-to-svg.test.d.ts +1 -0
- package/dist/types/Utils/treeTransformers.d.ts +160 -0
- package/dist/types/Visitors/BBBlocksExporter.d.ts +27 -0
- package/dist/types/Visitors/BBCodeExporter.d.ts +90 -0
- package/dist/types/Visitors/BlockPatcher.d.ts +91 -0
- package/dist/types/Visitors/DOMMorpher.d.ts +13 -0
- package/dist/types/Visitors/HTMLRenderer.d.ts +380 -0
- package/dist/types/Visitors/JSONExporter.d.ts +42 -0
- package/dist/types/Visitors/MarkdownExporter.d.ts +28 -0
- package/dist/types/Visitors/SVGRenderer.d.ts +16 -0
- package/dist/types/Visitors/TiptapExporter.d.ts +40 -0
- package/dist/types/Visitors/Visitor.d.ts +35 -0
- package/dist/types/Visitors/index.d.ts +9 -0
- package/dist/types/Visuals/BoxDrawer.d.ts +49 -0
- package/dist/types/Visuals/index.d.ts +38 -0
- package/dist/types/index.d.ts +154 -0
- package/package.json +14 -10
- package/src/Analysis/Passes/Analysis/ColorUsageAnalyzer.ts +103 -0
- package/src/Analysis/Passes/Analysis/GradientAnalyzer.ts +294 -20
- package/src/Analysis/Passes/Analysis/SymbolAnalyzer.ts +257 -0
- package/src/Analysis/Passes/Decision/PaletteRemapDecision.ts +425 -0
- package/src/Analysis/Pipeline/Pipeline.ts +15 -2
- package/src/Analysis/index.ts +8 -2
- package/src/BBCode/BBCodeDocumentModel.ts +3 -0
- package/src/BBCode/BBCodeToGreenNode.ts +18 -12
- package/src/BBCode/Parser.ts +38 -1
- package/src/Edits/EditPlan.ts +290 -0
- package/src/Edits/Optimizer.ts +185 -0
- package/src/Edits/Rules/Rule.ts +145 -0
- package/src/Edits/Rules/dropEmptyTags.ts +110 -0
- package/src/Edits/Rules/dropRedundantNesting.ts +120 -0
- package/src/Edits/Rules/index.ts +28 -0
- package/src/Edits/Rules/mergeAdjacent.ts +224 -0
- package/src/Edits/Rules/reorderWrappers.ts +154 -0
- package/src/Edits/Rules/shortenHex.ts +79 -0
- package/src/Edits/Rules/tagValue.ts +105 -0
- package/src/Edits/Rules/unwrapInvisibleColor.ts +93 -0
- package/src/Edits/applyEdits.ts +54 -0
- package/src/Edits/index.ts +22 -0
- package/src/Events/EventBus.ts +18 -0
- package/src/HTML/HTMLToGreenNode.ts +185 -37
- package/src/Incremental/BracketIndex.ts +249 -0
- package/src/Incremental/IncrementalParser.ts +464 -41
- package/src/Lexer/BBCodeLexer.ts +25 -3
- package/src/Lexer/index.ts +1 -1
- package/src/Markdown/MarkdownAST.ts +65 -2
- package/src/Markdown/MarkdownLexer.ts +69 -14
- package/src/Markdown/MarkdownParser.ts +749 -62
- package/src/Markdown/MarkdownToGreenNode.ts +148 -27
- package/src/MilHibri/MilHibriDocumentModel.ts +56 -0
- package/src/Model/DocumentModel.ts +138 -5
- package/src/Model/TagRegistry.ts +129 -134
- package/src/Reconciler/SurgicalReconciler.ts +460 -0
- package/src/Repair/NestingRepair.ts +116 -0
- package/src/Repair/index.ts +1 -0
- package/src/Semantic/SemanticAnalyzer.ts +1802 -65
- package/src/Syntax/GreenNodePool.ts +12 -12
- package/src/Syntax/RedNode.ts +17 -3
- package/src/Syntax/RedNodeStore.ts +9 -1
- package/src/Syntax/nodeAttr.ts +172 -0
- package/src/Tokens/TokenResolver.ts +55 -0
- package/src/Tokens/index.ts +1 -0
- package/src/Types/core.ts +15 -0
- package/src/Utils/ColorMath.ts +276 -55
- package/src/Utils/EffectMath.ts +2462 -0
- package/src/Utils/TagVocabulary.ts +141 -0
- package/src/Utils/dom-to-svg.ts +28 -12
- package/src/Visitors/BBBlocksExporter.ts +15 -1
- package/src/Visitors/BBCodeExporter.ts +319 -32
- package/src/Visitors/BlockPatcher.ts +87 -11
- package/src/Visitors/DOMMorpher.ts +15 -5
- package/src/Visitors/HTMLRenderer.ts +374 -151
- package/src/Visitors/MarkdownExporter.ts +98 -41
- package/src/Visitors/TiptapExporter.ts +1 -0
- package/src/Visuals/index.ts +7 -4
- package/src/Visuals/lyne.css +117 -6
- package/src/Visuals/osu.css +36 -5
- package/src/index.ts +172 -5
- package/dist/index.d.mts +0 -4612
- package/dist/index.d.ts +0 -4612
- package/src/Analysis/Passes/Analysis/__tests__/GradientAnalyzer.test.ts +0 -135
- package/src/Analysis/Passes/Analysis/__tests__/MergeableColorAnalyzer.test.ts +0 -84
- package/src/Analysis/Passes/Analysis/__tests__/RainbowAnalyzer.test.ts +0 -99
- package/src/Analysis/Passes/Analysis/__tests__/WaveAnalyzer.test.ts +0 -119
- package/src/Analysis/Passes/Decision/__tests__/DefaultDecision.test.ts +0 -179
- package/src/Analysis/__tests__/Integration.test.ts +0 -162
- package/src/Analysis/__tests__/Pipeline.test.ts +0 -133
- package/src/Diff/__tests__/TreeDiffer.test.ts +0 -65
- package/src/Incremental/__tests__/IncrementalParser.test.ts +0 -164
- package/src/Syntax/__tests__/GreenNode.test.ts +0 -33
- package/src/Syntax/__tests__/RedNode.test.ts +0 -81
- package/src/Syntax/__tests__/RedNodeStore.test.ts +0 -104
- package/src/Tests/ASTOptimizerIdempotence.test.ts +0 -77
- package/src/Tests/BlockPatcher.test.ts +0 -437
- package/src/Tests/BlockPatcherWindowed.test.ts +0 -364
- package/src/Tests/BoxDrawer.test.ts +0 -217
- package/src/Tests/BoxRichTitle.test.ts +0 -105
- package/src/Tests/Chars500kBenchmark.test.ts +0 -151
- package/src/Tests/Chars500kEdits.test.ts +0 -321
- package/src/Tests/CollabPositions.test.ts +0 -146
- package/src/Tests/CompilerPathProfiling.test.ts +0 -186
- package/src/Tests/DOMMorpher.test.ts +0 -142
- package/src/Tests/DomPatchPerf.test.ts +0 -60
- package/src/Tests/EffectSegments.snapshot.json +0 -616
- package/src/Tests/EffectSegments.test.ts +0 -68
- package/src/Tests/FindNodeAtOffset.test.ts +0 -65
- package/src/Tests/Fuzzer.test.ts +0 -166
- package/src/Tests/GreenNodePool.test.ts +0 -153
- package/src/Tests/Lexer.test.ts +0 -238
- package/src/Tests/LyneMode.test.ts +0 -291
- package/src/Tests/ModelCoherence.test.ts +0 -180
- package/src/Tests/Partition.test.ts +0 -238
- package/src/Tests/PluginTags.test.ts +0 -150
- package/src/Tests/ProblematicSection.test.ts +0 -46
- package/src/Tests/ProblematicSectionHTML.test.ts +0 -58
- package/src/Tests/RedReuse.test.ts +0 -134
- package/src/Tests/ReproDelete20k.test.ts +0 -62
- package/src/Tests/SemanticValidators.test.ts +0 -136
- package/src/Tests/StableNodeIds.test.ts +0 -210
- package/src/Tests/StudioColorBloat.test.ts +0 -25
- package/src/Tests/StudioDebugText.test.ts +0 -27
- package/src/Tests/StudioTrailingChar.test.ts +0 -25
- package/src/Tests/StudioValidText.test.ts +0 -25
- package/src/Tests/UrlImgBug.test.ts +0 -23
- package/src/Tests/VisualBuilderFidelity.test.ts +0 -105
- package/src/Tests/referenceDocument.ts +0 -119
- package/src/Utils/dom-to-svg.test.ts +0 -86
package/src/Utils/ColorMath.ts
CHANGED
|
@@ -21,19 +21,102 @@ export function solveCubicBezierY(x: number, x1: number, y1: number, x2: number,
|
|
|
21
21
|
return 3 * Math.pow(1 - t, 2) * t * y1 + 3 * (1 - t) * Math.pow(t, 2) * y2 + Math.pow(t, 3);
|
|
22
22
|
}
|
|
23
23
|
|
|
24
|
+
/**
|
|
25
|
+
* Set by EffectMath at module load. ColorMath cannot import it directly —
|
|
26
|
+
* EffectMath depends on ColorMath — and `ease` is the only place that
|
|
27
|
+
* needs it, so the dependency is injected rather than inverted.
|
|
28
|
+
*/
|
|
29
|
+
type ExpressionFn = (vars: Record<string, number>) => number
|
|
30
|
+
let compileExpressionRef: ((src: string) => ExpressionFn | null) | null = null
|
|
31
|
+
|
|
32
|
+
/** @internal Wire the expression compiler into `ease`. */
|
|
33
|
+
export function __setExpressionCompiler(fn: (src: string) => ExpressionFn | null): void {
|
|
34
|
+
compileExpressionRef = fn
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Resuelve una vez todo lo que en `ease` no depende de `t`, y devuelve la
|
|
39
|
+
* función que sí lo hace.
|
|
40
|
+
*
|
|
41
|
+
* `evaluateEffect` llama a `ease` UNA VEZ POR CARÁCTER con la misma cadena de
|
|
42
|
+
* suavizado. Para `bezier(a,b,c,d)` eso significaba volver a lanzar la
|
|
43
|
+
* expresión regular y cuatro `parseFloat` sobre la misma cadena en cada
|
|
44
|
+
* carácter; para `expr(...)`, un `slice` por carácter. Compilar arriba del
|
|
45
|
+
* bucle deja el trabajo por carácter en la aritmética que de verdad varía.
|
|
46
|
+
*
|
|
47
|
+
* Medido sobre 150 líneas de `[gradient …;easing=bezier(...)]` (19 KiB): el
|
|
48
|
+
* render pasó de 16,4 ms a 1,2 ms.
|
|
49
|
+
*/
|
|
50
|
+
export function compileEase(
|
|
51
|
+
easing: Easing,
|
|
52
|
+
center: number = 0.5,
|
|
53
|
+
power: number = 2,
|
|
54
|
+
): (t: number) => number {
|
|
55
|
+
if (typeof easing !== 'string' || easing === 'linear') return identityEase
|
|
56
|
+
|
|
57
|
+
if (easing.startsWith('expr(')) {
|
|
58
|
+
const fn = compileExpressionRef?.(easing.slice(5, -1))
|
|
59
|
+
if (!fn) return identityEase
|
|
60
|
+
return (t) => {
|
|
61
|
+
const u = clamp01(t)
|
|
62
|
+
const out = fn({ u, t: u, x: u, i: 0, n: 1, line: 0, lines: 1, col: 0, cols: 1, word: 0, words: 1, rnd: 0 })
|
|
63
|
+
return Number.isFinite(out) ? out : u
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
if (easing.startsWith('bezier')) {
|
|
68
|
+
const match = easing.match(BEZIER_RE)
|
|
69
|
+
if (match) {
|
|
70
|
+
const x1 = parseFloat(match[1])
|
|
71
|
+
const y1 = parseFloat(match[2])
|
|
72
|
+
const x2 = parseFloat(match[3])
|
|
73
|
+
const y2 = parseFloat(match[4])
|
|
74
|
+
return (t) => solveCubicBezierY(clamp01(t), x1, y1, x2, y2)
|
|
75
|
+
}
|
|
76
|
+
return identityEase
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
switch (easing) {
|
|
80
|
+
case "easeIn":
|
|
81
|
+
case "ease-in":
|
|
82
|
+
case "half-parabola": return (t) => Math.pow(clamp01(t), power)
|
|
83
|
+
case "easeOut":
|
|
84
|
+
case "ease-out": return (t) => { const u = clamp01(t); return u * (2 - u) }
|
|
85
|
+
case "easeInOut":
|
|
86
|
+
case "ease-in-out":
|
|
87
|
+
case "easeIO":
|
|
88
|
+
return (t) => { const u = clamp01(t); return u < 0.5 ? 2 * u * u : -1 + (4 - 2 * u) * u }
|
|
89
|
+
case "parabola":
|
|
90
|
+
if (center <= 0) return (t) => Math.pow(clamp01(t), power)
|
|
91
|
+
if (center >= 1) return (t) => Math.pow(1 - clamp01(t), power)
|
|
92
|
+
return (t) => {
|
|
93
|
+
const u = clamp01(t)
|
|
94
|
+
return u < center
|
|
95
|
+
? Math.pow((center - u) / center, power)
|
|
96
|
+
: Math.pow((u - center) / (1 - center), power)
|
|
97
|
+
}
|
|
98
|
+
default: return identityEase
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const BEZIER_RE = /bezier\(([^,]+),([^,]+),([^,]+),([^)]+)\)/
|
|
103
|
+
function clamp01(t: number): number { return t < 0 ? 0 : t > 1 ? 1 : t }
|
|
104
|
+
function identityEase(t: number): number { return clamp01(t) }
|
|
105
|
+
|
|
24
106
|
// Bezier easing approximation or simple evaluation
|
|
25
107
|
export function ease(t: number, easing: Easing, center: number = 0.5, power: number = 2): number {
|
|
26
108
|
t = Math.max(0, Math.min(1, t))
|
|
27
|
-
|
|
109
|
+
if (typeof easing !== 'string' || easing === 'linear') return t
|
|
110
|
+
|
|
28
111
|
if (easing.startsWith('expr(')) {
|
|
29
|
-
//
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
112
|
+
// `easing` can arrive straight from a BBCode attribute, so the body is
|
|
113
|
+
// compiled through the validating evaluator in EffectMath rather than
|
|
114
|
+
// handed to `new Function` as-is. A rejected expression is the
|
|
115
|
+
// identity, which degrades the effect instead of the document.
|
|
116
|
+
const fn = compileExpressionRef?.(easing.slice(5, -1))
|
|
117
|
+
if (!fn) return t
|
|
118
|
+
const out = fn({ u: t, t, x: t, i: 0, n: 1, line: 0, lines: 1, col: 0, cols: 1, word: 0, words: 1, rnd: 0 })
|
|
119
|
+
return Number.isFinite(out) ? out : t
|
|
37
120
|
}
|
|
38
121
|
|
|
39
122
|
if (easing.startsWith('bezier')) {
|
|
@@ -47,11 +130,14 @@ export function ease(t: number, easing: Easing, center: number = 0.5, power: num
|
|
|
47
130
|
}
|
|
48
131
|
}
|
|
49
132
|
switch (easing) {
|
|
50
|
-
case "easeIn":
|
|
133
|
+
case "easeIn":
|
|
134
|
+
case "ease-in":
|
|
51
135
|
case "half-parabola": return Math.pow(t, power)
|
|
52
|
-
case "easeOut":
|
|
53
|
-
case "
|
|
54
|
-
case "
|
|
136
|
+
case "easeOut":
|
|
137
|
+
case "ease-out": return t * (2 - t)
|
|
138
|
+
case "easeInOut":
|
|
139
|
+
case "ease-in-out":
|
|
140
|
+
case "easeIO":
|
|
55
141
|
return t < 0.5 ? 2 * t * t : -1 + (4 - 2 * t) * t
|
|
56
142
|
case "parabola":
|
|
57
143
|
if (center <= 0) return Math.pow(t, power);
|
|
@@ -69,26 +155,92 @@ export function hslToHex(h: number, s: number, l: number): string {
|
|
|
69
155
|
const a = (s * Math.min(l, 1 - l)) / 100
|
|
70
156
|
const f = (n: number) => {
|
|
71
157
|
const k = (n + h / 30) % 12
|
|
72
|
-
|
|
73
|
-
return Math.round(255 * color).toString(16).padStart(2, "0")
|
|
158
|
+
return byteHex(255 * (l - a * Math.max(Math.min(k - 3, 9 - k, 1), -1)))
|
|
74
159
|
}
|
|
75
160
|
return `#${f(0)}${f(8)}${f(4)}`
|
|
76
161
|
}
|
|
77
162
|
|
|
163
|
+
/** Hex digit value per char code, `-1` for anything else. */
|
|
164
|
+
const HEX_VALUE: Int8Array = (() => {
|
|
165
|
+
const table = new Int8Array(128).fill(-1)
|
|
166
|
+
for (let c = 0; c < 10; c++) table[48 + c] = c // 0-9
|
|
167
|
+
for (let c = 0; c < 6; c++) table[97 + c] = 10 + c // a-f
|
|
168
|
+
for (let c = 0; c < 6; c++) table[65 + c] = 10 + c // A-F
|
|
169
|
+
return table
|
|
170
|
+
})()
|
|
171
|
+
|
|
172
|
+
/** Two-character lowercase hex for every byte, built once. */
|
|
173
|
+
const BYTE_HEX: string[] = Array.from({ length: 256 }, (_, i) =>
|
|
174
|
+
i.toString(16).padStart(2, "0"))
|
|
175
|
+
|
|
176
|
+
function hexAt(s: string, i: number): number {
|
|
177
|
+
const c = s.charCodeAt(i)
|
|
178
|
+
return c < 128 ? HEX_VALUE[c] : -1
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* A hex colour token to RGB.
|
|
183
|
+
*
|
|
184
|
+
* Accepts `#rgb`, `#rgba`, `#rrggbb` and `#rrggbbaa`, hash optional.
|
|
185
|
+
* Anything else returns black instead of `NaN`: these strings come from
|
|
186
|
+
* BBCode attributes and from a text field the user is mid-way through
|
|
187
|
+
* typing, and a `NaN` channel propagates into `#NaNNaNNaN`, corrupting
|
|
188
|
+
* the whole gradient rather than the one bad stop.
|
|
189
|
+
*
|
|
190
|
+
* Parsed by char code rather than by regex + `parseInt`. This runs once
|
|
191
|
+
* per character per colour layer per keystroke — two calls for every
|
|
192
|
+
* `mixHex` — and the regex version was the single largest cost in
|
|
193
|
+
* compiling a long document.
|
|
194
|
+
*/
|
|
78
195
|
export function hexToRgb(hex: string): [number, number, number] {
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
196
|
+
if (typeof hex !== "string") return [0, 0, 0]
|
|
197
|
+
let start = 0
|
|
198
|
+
let end = hex.length
|
|
199
|
+
while (start < end && hex.charCodeAt(start) <= 32) start++
|
|
200
|
+
while (end > start && hex.charCodeAt(end - 1) <= 32) end--
|
|
201
|
+
if (start < end && hex.charCodeAt(start) === 35 /* # */) start++
|
|
202
|
+
|
|
203
|
+
const len = end - start
|
|
204
|
+
const short = len === 3 || len === 4
|
|
205
|
+
if (!short && len !== 6 && len !== 8) return [0, 0, 0]
|
|
206
|
+
|
|
207
|
+
const digits = short ? 3 : 6
|
|
208
|
+
const out: [number, number, number] = [0, 0, 0]
|
|
209
|
+
for (let i = 0; i < digits; i += short ? 1 : 2) {
|
|
210
|
+
const hi = hexAt(hex, start + i)
|
|
211
|
+
if (hi < 0) return [0, 0, 0]
|
|
212
|
+
if (short) {
|
|
213
|
+
out[i] = hi * 17
|
|
214
|
+
} else {
|
|
215
|
+
const lo = hexAt(hex, start + i + 1)
|
|
216
|
+
if (lo < 0) return [0, 0, 0]
|
|
217
|
+
out[i >> 1] = hi * 16 + lo
|
|
218
|
+
}
|
|
86
219
|
}
|
|
87
|
-
return
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
220
|
+
return out
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/** True when a token is a hex colour `hexToRgb` can read exactly. */
|
|
224
|
+
export function isHexColor(value: string): boolean {
|
|
225
|
+
if (typeof value !== "string") return false
|
|
226
|
+
let start = 0
|
|
227
|
+
let end = value.length
|
|
228
|
+
while (start < end && value.charCodeAt(start) <= 32) start++
|
|
229
|
+
while (end > start && value.charCodeAt(end - 1) <= 32) end--
|
|
230
|
+
if (start < end && value.charCodeAt(start) === 35) start++
|
|
231
|
+
|
|
232
|
+
const len = end - start
|
|
233
|
+
if (len !== 3 && len !== 4 && len !== 6 && len !== 8) return false
|
|
234
|
+
for (let i = start; i < end; i++) {
|
|
235
|
+
if (hexAt(value, i) < 0) return false
|
|
236
|
+
}
|
|
237
|
+
return true
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
/** Clamp to a byte and format as two lowercase hex characters. */
|
|
241
|
+
function byteHex(v: number): string {
|
|
242
|
+
const n = v < 0 ? 0 : v > 255 ? 255 : Math.round(v)
|
|
243
|
+
return BYTE_HEX[n]
|
|
92
244
|
}
|
|
93
245
|
|
|
94
246
|
export function hexToHsl(hex: string): [number, number, number] {
|
|
@@ -113,13 +265,13 @@ export function hexToHsl(hex: string): [number, number, number] {
|
|
|
113
265
|
}
|
|
114
266
|
|
|
115
267
|
export function mixHex(color1: string, color2: string, weight: number): string {
|
|
268
|
+
// Identical endpoints are the common case in a flat run; skipping the
|
|
269
|
+
// interpolation also skips two parses and a string build.
|
|
270
|
+
if (color1 === color2) return color1
|
|
116
271
|
const [r1, g1, b1] = hexToRgb(color1)
|
|
117
272
|
const [r2, g2, b2] = hexToRgb(color2)
|
|
118
|
-
const w =
|
|
119
|
-
|
|
120
|
-
const g = Math.round(g1 + (g2 - g1) * w)
|
|
121
|
-
const b = Math.round(b1 + (b2 - b1) * w)
|
|
122
|
-
return `#${r.toString(16).padStart(2, "0")}${g.toString(16).padStart(2, "0")}${b.toString(16).padStart(2, "0")}`
|
|
273
|
+
const w = weight < 0 ? 0 : weight > 1 ? 1 : weight
|
|
274
|
+
return `#${byteHex(r1 + (r2 - r1) * w)}${byteHex(g1 + (g2 - g1) * w)}${byteHex(b1 + (b2 - b1) * w)}`
|
|
123
275
|
}
|
|
124
276
|
|
|
125
277
|
export function mixMultiple(colors: string[], weight: number): string {
|
|
@@ -138,22 +290,34 @@ export interface ColorStop {
|
|
|
138
290
|
position: number; // 0 to 1
|
|
139
291
|
}
|
|
140
292
|
|
|
141
|
-
|
|
293
|
+
/**
|
|
294
|
+
* Sample a stop list at `weight`.
|
|
295
|
+
*
|
|
296
|
+
* `perceptual` interpolates in OKLab instead of sRGB. The difference
|
|
297
|
+
* shows wherever two stops differ in hue: an sRGB ramp from blue to
|
|
298
|
+
* yellow dips through a desaturated grey at the midpoint, while OKLab
|
|
299
|
+
* holds chroma. Two conversions per character, so it stays opt-in.
|
|
300
|
+
*/
|
|
301
|
+
export function mixMultipleStops(stops: ColorStop[], weight: number, perceptual = false): string {
|
|
142
302
|
if (stops.length === 0) return "#FFFFFF";
|
|
143
303
|
if (stops.length === 1) return stops[0].color;
|
|
144
304
|
weight = Math.max(0, Math.min(1, weight || 0));
|
|
145
|
-
|
|
305
|
+
|
|
146
306
|
let i = 0;
|
|
147
307
|
while (i < stops.length - 1 && stops[i + 1].position <= weight) {
|
|
148
308
|
i++;
|
|
149
309
|
}
|
|
150
|
-
|
|
310
|
+
|
|
151
311
|
if (i >= stops.length - 1) return stops[stops.length - 1].color;
|
|
152
312
|
if (weight <= stops[i].position) return stops[i].color;
|
|
153
|
-
|
|
313
|
+
|
|
154
314
|
const range = stops[i + 1].position - stops[i].position;
|
|
155
|
-
|
|
156
|
-
|
|
315
|
+
// Coincident stops are a hard colour break, not a zero-width ramp:
|
|
316
|
+
// dividing by the gap would yield Infinity.
|
|
317
|
+
const frac = range <= 0 ? 1 : (weight - stops[i].position) / range;
|
|
318
|
+
return perceptual
|
|
319
|
+
? mixHexOklab(stops[i].color, stops[i + 1].color, frac)
|
|
320
|
+
: mixHex(stops[i].color, stops[i + 1].color, frac);
|
|
157
321
|
}
|
|
158
322
|
|
|
159
323
|
// ═══════════════════════════════════════════════════════════════
|
|
@@ -189,6 +353,37 @@ function srgbToLinear(c: number): number {
|
|
|
189
353
|
return c <= 0.04045 ? c / 12.92 : Math.pow((c + 0.055) / 1.055, 2.4)
|
|
190
354
|
}
|
|
191
355
|
|
|
356
|
+
/**
|
|
357
|
+
* `srgbToLinear` for the 256 byte values, precomputed.
|
|
358
|
+
*
|
|
359
|
+
* The only caller is `hexToOklab`, whose input is always an integer channel
|
|
360
|
+
* over 255, so the continuous function has exactly 256 reachable results.
|
|
361
|
+
* It was the single largest cost in a document analysis — 15% of the whole
|
|
362
|
+
* semantic pass — because `Math.pow` ran three times per colour per
|
|
363
|
+
* comparison, and gradient detection compares every stop against every
|
|
364
|
+
* other. The table is 2 KB and the values are bit-identical to the call it
|
|
365
|
+
* replaces.
|
|
366
|
+
*/
|
|
367
|
+
const SRGB_TO_LINEAR_BYTE: Float64Array = (() => {
|
|
368
|
+
const table = new Float64Array(256)
|
|
369
|
+
for (let i = 0; i < 256; i++) table[i] = srgbToLinear(i / 255)
|
|
370
|
+
return table
|
|
371
|
+
})()
|
|
372
|
+
|
|
373
|
+
/**
|
|
374
|
+
* `hexToOklab` results keyed by the hex string that produced them.
|
|
375
|
+
*
|
|
376
|
+
* A document draws from a small palette — tens of distinct colours — but a
|
|
377
|
+
* gradient scan converts each of them hundreds of times: once per chord
|
|
378
|
+
* check, once per plateau test, once per perceptual distance. The cache is
|
|
379
|
+
* bounded because the key space is user input (a half-typed colour in the
|
|
380
|
+
* editor produces a new string on every keystroke); on overflow it is
|
|
381
|
+
* cleared outright rather than evicted one by one, which keeps the hot path
|
|
382
|
+
* a single `Map.get` with no bookkeeping.
|
|
383
|
+
*/
|
|
384
|
+
const OKLAB_CACHE = new Map<string, readonly [number, number, number]>()
|
|
385
|
+
const OKLAB_CACHE_LIMIT = 4096
|
|
386
|
+
|
|
192
387
|
/**
|
|
193
388
|
* Convert linear RGB to sRGB (gamma compression for display).
|
|
194
389
|
*/
|
|
@@ -202,9 +397,9 @@ function linearToSrgb(c: number): number {
|
|
|
202
397
|
*/
|
|
203
398
|
export function hexToOklab(hex: string): [number, number, number] {
|
|
204
399
|
const [r255, g255, b255] = hexToRgb(hex)
|
|
205
|
-
const r =
|
|
206
|
-
const g =
|
|
207
|
-
const b =
|
|
400
|
+
const r = SRGB_TO_LINEAR_BYTE[r255]
|
|
401
|
+
const g = SRGB_TO_LINEAR_BYTE[g255]
|
|
402
|
+
const b = SRGB_TO_LINEAR_BYTE[b255]
|
|
208
403
|
|
|
209
404
|
// Linear sRGB → LMS
|
|
210
405
|
const l_ = r * SRGB_TO_LMS[0] + g * SRGB_TO_LMS[1] + b * SRGB_TO_LMS[2]
|
|
@@ -217,11 +412,13 @@ export function hexToOklab(hex: string): [number, number, number] {
|
|
|
217
412
|
const s = Math.cbrt(s_)
|
|
218
413
|
|
|
219
414
|
// LMS → OKLab
|
|
220
|
-
|
|
415
|
+
const out: [number, number, number] = [
|
|
221
416
|
l * LMS_TO_OKLAB[0] + m * LMS_TO_OKLAB[1] + s * LMS_TO_OKLAB[2],
|
|
222
417
|
l * LMS_TO_OKLAB[3] + m * LMS_TO_OKLAB[4] + s * LMS_TO_OKLAB[5],
|
|
223
418
|
l * LMS_TO_OKLAB[6] + m * LMS_TO_OKLAB[7] + s * LMS_TO_OKLAB[8],
|
|
224
419
|
]
|
|
420
|
+
|
|
421
|
+
return out
|
|
225
422
|
}
|
|
226
423
|
|
|
227
424
|
/**
|
|
@@ -232,11 +429,24 @@ export function hexToOklab(hex: string): [number, number, number] {
|
|
|
232
429
|
* > 0.1 ≈ very noticeable
|
|
233
430
|
*/
|
|
234
431
|
export function perceptualDistance(hex1: string, hex2: string): number {
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
432
|
+
return perceptualDistanceOklab(hexToOklab(hex1), hexToOklab(hex2))
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
/**
|
|
436
|
+
* `perceptualDistance` for callers that already hold both OKLab triples.
|
|
437
|
+
*
|
|
438
|
+
* Gradient detection converts a colour to OKLab once per stop and then asks
|
|
439
|
+
* for distances between neighbours, between chord endpoints and against an
|
|
440
|
+
* ideal ramp — each of which re-derived both triples from their hex strings.
|
|
441
|
+
* The results are identical; this just stops paying for the conversion again.
|
|
442
|
+
*/
|
|
443
|
+
export function perceptualDistanceOklab(
|
|
444
|
+
c1: readonly [number, number, number],
|
|
445
|
+
c2: readonly [number, number, number],
|
|
446
|
+
): number {
|
|
447
|
+
const dL = c2[0] - c1[0]
|
|
448
|
+
const da = c2[1] - c1[1]
|
|
449
|
+
const db = c2[2] - c1[2]
|
|
240
450
|
return Math.sqrt(dL * dL + da * da + db * db)
|
|
241
451
|
}
|
|
242
452
|
|
|
@@ -244,8 +454,21 @@ export function perceptualDistance(hex1: string, hex2: string): number {
|
|
|
244
454
|
* Mix two hex colours in OKLab space for perceptually uniform interpolation.
|
|
245
455
|
*/
|
|
246
456
|
export function mixHexOklab(color1: string, color2: string, weight: number): string {
|
|
247
|
-
|
|
248
|
-
|
|
457
|
+
return mixOklabToHex(hexToOklab(color1), hexToOklab(color2), weight)
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
/**
|
|
461
|
+
* `mixHexOklab` for callers that already hold both OKLab triples — the same
|
|
462
|
+
* saving as {@link perceptualDistanceOklab}, for the ideal-ramp comparison
|
|
463
|
+
* that mixes the *same* two endpoints once per colour in the sequence.
|
|
464
|
+
*/
|
|
465
|
+
export function mixOklabToHex(
|
|
466
|
+
c1: readonly [number, number, number],
|
|
467
|
+
c2: readonly [number, number, number],
|
|
468
|
+
weight: number,
|
|
469
|
+
): string {
|
|
470
|
+
const [L1, a1, b1] = c1
|
|
471
|
+
const [L2, a2, b2] = c2
|
|
249
472
|
const w = Math.max(0, Math.min(1, weight))
|
|
250
473
|
|
|
251
474
|
const L = L1 + (L2 - L1) * w
|
|
@@ -267,10 +490,8 @@ export function mixHexOklab(color1: string, color2: string, weight: number): str
|
|
|
267
490
|
const gLin = l * -1.2684380046 + m * 2.6097574011 + s * -0.3413193965
|
|
268
491
|
const bLin = l * -0.0041960863 + m * -0.7034186147 + s * 1.7076147010
|
|
269
492
|
|
|
270
|
-
// Linear sRGB → gamma-corrected sRGB → hex
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
return `#${r255.toString(16).padStart(2, '0')}${g255.toString(16).padStart(2, '0')}${b255.toString(16).padStart(2, '0')}`
|
|
493
|
+
// Linear sRGB → gamma-corrected sRGB → hex. `byteHex` clamps, which is
|
|
494
|
+
// what keeps an out-of-gamut OKLab mix from producing a negative or
|
|
495
|
+
// five-digit channel.
|
|
496
|
+
return `#${byteHex(linearToSrgb(rLin) * 255)}${byteHex(linearToSrgb(gLin) * 255)}${byteHex(linearToSrgb(bLin) * 255)}`
|
|
276
497
|
}
|