@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/Model/TagRegistry.ts
CHANGED
|
@@ -20,7 +20,10 @@ import type { RedNode } from '../Syntax/RedNode'
|
|
|
20
20
|
import { GreenNode } from '../Syntax/GreenNode'
|
|
21
21
|
import type { RenderNode } from '../RenderPipeline/RenderTree'
|
|
22
22
|
import type { Validator } from '../Semantic/SemanticAnalyzer'
|
|
23
|
-
import {
|
|
23
|
+
import {
|
|
24
|
+
evaluateEffect,
|
|
25
|
+
type EffectKind, type EffectParams, type EffectSpan, type StyledSegment,
|
|
26
|
+
} from '../Utils/EffectMath'
|
|
24
27
|
|
|
25
28
|
// ─── Tag Handler ───────────────────────────────────────────────
|
|
26
29
|
|
|
@@ -101,25 +104,21 @@ export interface PropertyDefinition {
|
|
|
101
104
|
|
|
102
105
|
// ─── Internal tag helpers (gradient/grow export) ──────────────
|
|
103
106
|
|
|
104
|
-
/**
|
|
105
|
-
|
|
107
|
+
/**
|
|
108
|
+
* Recursively extract plain text from a RedNode tree.
|
|
109
|
+
*
|
|
110
|
+
* A line break is a childless `spacing` leaf with no text of its own, so
|
|
111
|
+
* it has to be spelled out here or an effect spanning two lines is handed
|
|
112
|
+
* one line and exports without its break — silent data loss, and the
|
|
113
|
+
* reason every two-dimensional axis (`line`, `column`, `radial`) measured
|
|
114
|
+
* a block one line tall.
|
|
115
|
+
*/
|
|
116
|
+
export function extractTextContent(node: RedNode): string {
|
|
106
117
|
if (node.kind === 'text') return node.text
|
|
118
|
+
if (node.kind === 'spacing' || node.kind === 'empty_line') return '\n'
|
|
107
119
|
return node.children.map(extractTextContent).join('')
|
|
108
120
|
}
|
|
109
121
|
|
|
110
|
-
/** Split text by unit: 'character', 'word', or 'line' */
|
|
111
|
-
function splitByUnit(text: string, unit: string): string[] {
|
|
112
|
-
if (!text) return []
|
|
113
|
-
switch (unit) {
|
|
114
|
-
case 'word':
|
|
115
|
-
return text.split(/(\s+)/).filter(s => s.length > 0)
|
|
116
|
-
case 'line':
|
|
117
|
-
return text.split('\n')
|
|
118
|
-
default: // 'character'
|
|
119
|
-
return [...text]
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
|
|
123
122
|
/** Escape [ and ] inside BBCode content so they're not parsed as tags */
|
|
124
123
|
function escapeBracket(ch: string): string {
|
|
125
124
|
// BBCode has no standard escape. For internal tags produced by TextStudio,
|
|
@@ -128,109 +127,37 @@ function escapeBracket(ch: string): string {
|
|
|
128
127
|
return ch.replace(/\[/g, '\\[').replace(/\]/g, '\\]')
|
|
129
128
|
}
|
|
130
129
|
|
|
131
|
-
// ─── Effect
|
|
132
|
-
//
|
|
133
|
-
// Each effect (gradient, sinewave, grow, rainbow) serializes two ways: to
|
|
134
|
-
// osu!-compatible BBCode (`toBBCode`) and to a RenderNode tree
|
|
135
|
-
// (`toRenderNode`). Both used to carry their own copy of the per-segment
|
|
136
|
-
// math — t-progression, easing, color mixing, wave sizing — differing only in
|
|
137
|
-
// presentation, which is exactly how the two drift apart. The math now lives
|
|
138
|
-
// in ONE `*Segments` function per effect; the handlers are thin presenters.
|
|
130
|
+
// ─── Effect segments ──────────────────────────────────────────
|
|
139
131
|
//
|
|
140
|
-
//
|
|
141
|
-
//
|
|
142
|
-
//
|
|
143
|
-
//
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
*/
|
|
162
|
-
function progressAt(i: number, count: number, node: RedNode, single: number): number {
|
|
163
|
-
const globalOffset = (node.metadata?.globalOffset as number) || 0
|
|
164
|
-
const documentLength = (node.metadata?.documentLength as number) || count
|
|
165
|
-
if (documentLength > 1) return (globalOffset + i) / (documentLength - 1)
|
|
166
|
-
return count > 1 ? i / (count - 1) : single
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
function gradientSegments(node: RedNode): StyledSegment[] {
|
|
170
|
-
const colors = (node.metadata?.colors as string[]) || ['#FF0000', '#00FF00']
|
|
171
|
-
const unit = (node.metadata?.unit as string) || 'character'
|
|
172
|
-
const easing = (node.metadata?.easing as Easing) || 'linear'
|
|
173
|
-
const text = extractTextContent(node)
|
|
174
|
-
if (!text) return []
|
|
175
|
-
const parts = splitByUnit(text, unit)
|
|
176
|
-
return parts.map((seg, i) => ({
|
|
177
|
-
text: seg,
|
|
178
|
-
color: mixMultiple(colors, ease(clamp(progressAt(i, parts.length, node, 0)), easing)),
|
|
179
|
-
}))
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
function sinewaveSegments(node: RedNode): StyledSegment[] {
|
|
183
|
-
const min = (node.metadata?.min as number) ?? 20
|
|
184
|
-
const max = (node.metadata?.max as number) ?? 80
|
|
185
|
-
const freq = (node.metadata?.freq as number) ?? 0.4
|
|
186
|
-
const step = (node.metadata?.step as string) ?? 'char'
|
|
187
|
-
const text = extractTextContent(node)
|
|
188
|
-
if (!text) return []
|
|
189
|
-
const amplitude = (max - min) / 2
|
|
190
|
-
const center = min + amplitude
|
|
191
|
-
|
|
192
|
-
if (step === 'word') {
|
|
193
|
-
let wordIndex = 0
|
|
194
|
-
return text.split(/(\s+)/).map(chunk => {
|
|
195
|
-
if (chunk.trim() === '') return { text: chunk }
|
|
196
|
-
const size = Math.round(center + Math.sin(wordIndex * freq) * amplitude)
|
|
197
|
-
wordIndex++
|
|
198
|
-
return { text: chunk, size }
|
|
199
|
-
})
|
|
132
|
+
// The maths lives in `Utils/EffectMath`, not here. It used to live in
|
|
133
|
+
// this file, in four `*Segments` functions, while Text Studio kept its
|
|
134
|
+
// own copy and the HTML renderer kept a third — so the same document
|
|
135
|
+
// exported one way, previewed another, and lost every parameter the
|
|
136
|
+
// studio grew. These handlers now only read the node and present the
|
|
137
|
+
// result.
|
|
138
|
+
|
|
139
|
+
/** Effect parameters carried on a node, plus its span in a longer effect. */
|
|
140
|
+
function effectParamsOf(node: RedNode): { params: EffectParams; span: EffectSpan } {
|
|
141
|
+
const meta = (node.metadata ?? {}) as Record<string, unknown>
|
|
142
|
+
const params: EffectParams = {}
|
|
143
|
+
for (const [key, value] of Object.entries(meta)) {
|
|
144
|
+
if (key === 'globalOffset' || key === 'documentLength') continue
|
|
145
|
+
;(params as Record<string, unknown>)[key] = value
|
|
146
|
+
}
|
|
147
|
+
return {
|
|
148
|
+
params,
|
|
149
|
+
span: {
|
|
150
|
+
globalOffset: meta.globalOffset as number | undefined,
|
|
151
|
+
documentLength: meta.documentLength as number | undefined,
|
|
152
|
+
},
|
|
200
153
|
}
|
|
201
|
-
|
|
202
|
-
return [...text].map((ch, i) => ({
|
|
203
|
-
text: ch,
|
|
204
|
-
size: Math.round(center + Math.sin(i * freq) * amplitude),
|
|
205
|
-
}))
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
function growSegments(node: RedNode): StyledSegment[] {
|
|
209
|
-
const min = (node.metadata?.min as number) ?? 50
|
|
210
|
-
const max = (node.metadata?.max as number) ?? 200
|
|
211
|
-
const cycles = (node.metadata?.cycles as number) ?? 1
|
|
212
|
-
const text = extractTextContent(node)
|
|
213
|
-
if (!text) return []
|
|
214
|
-
const chars = [...text]
|
|
215
|
-
return chars.map((ch, i) => {
|
|
216
|
-
const sine = Math.sin(progressAt(i, chars.length, node, 0.5) * cycles * Math.PI * 2)
|
|
217
|
-
const normalized = (sine + 1) / 2
|
|
218
|
-
return { text: ch, size: Math.round(min + (max - min) * normalized) }
|
|
219
|
-
})
|
|
220
154
|
}
|
|
221
155
|
|
|
222
|
-
function
|
|
223
|
-
const saturation = ((node.metadata?.saturation as number) ?? 80) / 100
|
|
224
|
-
const lightness = ((node.metadata?.lightness as number) ?? 60) / 100
|
|
225
|
-
const spread = (node.metadata?.spread as number) ?? 300
|
|
226
|
-
const offset = (node.metadata?.offset as number) ?? 0
|
|
156
|
+
function effectSegments(node: RedNode, kind: EffectKind): StyledSegment[] {
|
|
227
157
|
const text = extractTextContent(node)
|
|
228
158
|
if (!text) return []
|
|
229
|
-
const
|
|
230
|
-
return
|
|
231
|
-
text: ch,
|
|
232
|
-
color: hslToHex(offset + progressAt(i, chars.length, node, 0) * spread, saturation, lightness),
|
|
233
|
-
}))
|
|
159
|
+
const { params, span } = effectParamsOf(node)
|
|
160
|
+
return evaluateEffect(text, kind, params, span)
|
|
234
161
|
}
|
|
235
162
|
|
|
236
163
|
/** Present styled segments as osu!-compatible BBCode. */
|
|
@@ -264,11 +191,44 @@ export type TagHandler = {
|
|
|
264
191
|
[K in keyof TagDefinition]: TagDefinition[K]
|
|
265
192
|
}
|
|
266
193
|
|
|
194
|
+
/**
|
|
195
|
+
* The language's own tags, built once for the process.
|
|
196
|
+
*
|
|
197
|
+
* `registerBuiltins` allocates ~130 definition objects, several of them
|
|
198
|
+
* carrying closures. Constructing a registry cost 14.7 µs because of it, and
|
|
199
|
+
* a registry is constructed by every `DocumentModel`, every
|
|
200
|
+
* `SemanticAnalyzer` and every exporter that is handed no registry — so a
|
|
201
|
+
* throwaway parse paid for the whole table three times over. The table never
|
|
202
|
+
* changes, so it is shared, and a registry only takes a private copy when a
|
|
203
|
+
* plugin actually mutates it.
|
|
204
|
+
*/
|
|
205
|
+
let BUILTIN_TAGS: ReadonlyMap<string, TagDefinition> | null = null
|
|
206
|
+
let BUILTIN_KINDS: ReadonlyMap<NodeKind, TagDefinition> | null = null
|
|
207
|
+
let BUILTIN_NAMES: ReadonlySet<string> | null = null
|
|
208
|
+
|
|
209
|
+
function ensureBuiltins(): void {
|
|
210
|
+
if (BUILTIN_TAGS !== null) return
|
|
211
|
+
const tags = new Map<string, TagDefinition>()
|
|
212
|
+
const kinds = new Map<NodeKind, TagDefinition>()
|
|
213
|
+
for (const tag of builtinTagDefinitions()) {
|
|
214
|
+
tags.set(tag.name, tag)
|
|
215
|
+
kinds.set(tag.kind, tag)
|
|
216
|
+
}
|
|
217
|
+
BUILTIN_TAGS = tags
|
|
218
|
+
BUILTIN_KINDS = kinds
|
|
219
|
+
BUILTIN_NAMES = new Set(tags.keys())
|
|
220
|
+
}
|
|
221
|
+
|
|
267
222
|
export class TagRegistry {
|
|
268
|
-
private tags: Map<string, TagDefinition>
|
|
269
|
-
private kinds: Map<NodeKind, TagDefinition>
|
|
223
|
+
private tags: Map<string, TagDefinition>
|
|
224
|
+
private kinds: Map<NodeKind, TagDefinition>
|
|
270
225
|
/** Names registered by the constructor — the language's own tags. */
|
|
271
|
-
private builtins:
|
|
226
|
+
private builtins: ReadonlySet<string>
|
|
227
|
+
/**
|
|
228
|
+
* Whether `tags`/`kinds` are still the process-wide builtin maps. While
|
|
229
|
+
* they are, this registry must not write to them.
|
|
230
|
+
*/
|
|
231
|
+
private shared: boolean = true
|
|
272
232
|
|
|
273
233
|
/**
|
|
274
234
|
* Bumped on every register/unregister, so consumers that derive something
|
|
@@ -278,14 +238,25 @@ export class TagRegistry {
|
|
|
278
238
|
version: number = 0
|
|
279
239
|
|
|
280
240
|
constructor() {
|
|
281
|
-
|
|
282
|
-
|
|
241
|
+
ensureBuiltins()
|
|
242
|
+
this.tags = BUILTIN_TAGS as Map<string, TagDefinition>
|
|
243
|
+
this.kinds = BUILTIN_KINDS as Map<NodeKind, TagDefinition>
|
|
244
|
+
this.builtins = BUILTIN_NAMES!
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
/** Take a private copy of the builtin maps before the first write. */
|
|
248
|
+
private own(): void {
|
|
249
|
+
if (!this.shared) return
|
|
250
|
+
this.tags = new Map(this.tags)
|
|
251
|
+
this.kinds = new Map(this.kinds)
|
|
252
|
+
this.shared = false
|
|
283
253
|
}
|
|
284
254
|
|
|
285
255
|
/**
|
|
286
256
|
* Register a tag definition.
|
|
287
257
|
*/
|
|
288
258
|
register(tag: TagDefinition): void {
|
|
259
|
+
this.own()
|
|
289
260
|
this.tags.set(tag.name, tag)
|
|
290
261
|
this.kinds.set(tag.kind, tag)
|
|
291
262
|
this.version++
|
|
@@ -295,6 +266,8 @@ export class TagRegistry {
|
|
|
295
266
|
* Unregister a tag definition.
|
|
296
267
|
*/
|
|
297
268
|
unregister(name: string): void {
|
|
269
|
+
if (!this.tags.has(name)) return
|
|
270
|
+
this.own()
|
|
298
271
|
const tag = this.tags.get(name)
|
|
299
272
|
if (tag) {
|
|
300
273
|
this.tags.delete(name)
|
|
@@ -320,6 +293,7 @@ export class TagRegistry {
|
|
|
320
293
|
* costs one size comparison and no allocation.
|
|
321
294
|
*/
|
|
322
295
|
customTags(): Map<string, NodeKind> | null {
|
|
296
|
+
if (this.shared) return null
|
|
323
297
|
if (this.tags.size === this.builtins.size) return null
|
|
324
298
|
const out = new Map<string, NodeKind>()
|
|
325
299
|
for (const [name, def] of this.tags) {
|
|
@@ -377,10 +351,16 @@ export class TagRegistry {
|
|
|
377
351
|
return this.getAll().filter(t => !t.isInline && !t.isSelfClosing)
|
|
378
352
|
}
|
|
379
353
|
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
/**
|
|
357
|
+
* The built-in BBCode tags.
|
|
358
|
+
*
|
|
359
|
+
* Called once per process, through `ensureBuiltins`. It used to be a private
|
|
360
|
+
* method that registered each definition into `this`, which meant rebuilding
|
|
361
|
+
* this whole table for every registry.
|
|
362
|
+
*/
|
|
363
|
+
function builtinTagDefinitions(): TagDefinition[] {
|
|
384
364
|
const tags: TagDefinition[] = [
|
|
385
365
|
// ── Formatting ──
|
|
386
366
|
{ name: 'b', kind: 'bold', label: 'Bold', category: 'formatting', isInline: true, isSelfClosing: false, canHaveChildren: true, toolbar: { group: 'formatting', label: 'Bold', icon: 'Bold', shortcut: 'Ctrl+B' } },
|
|
@@ -409,10 +389,10 @@ export class TagRegistry {
|
|
|
409
389
|
isSelfClosing: false,
|
|
410
390
|
canHaveChildren: true,
|
|
411
391
|
toBBCode: (ctx) => {
|
|
412
|
-
const segments =
|
|
392
|
+
const segments = effectSegments(ctx.node, 'gradient')
|
|
413
393
|
return segments.length === 0 ? ctx.visitChildren(ctx.node) : segmentsToBBCode(segments)
|
|
414
394
|
},
|
|
415
|
-
toRenderNode: (ctx) => segmentsToRenderNode('gradient',
|
|
395
|
+
toRenderNode: (ctx) => segmentsToRenderNode('gradient', effectSegments(ctx.node, 'gradient')),
|
|
416
396
|
},
|
|
417
397
|
{
|
|
418
398
|
name: 'sinewave',
|
|
@@ -423,10 +403,10 @@ export class TagRegistry {
|
|
|
423
403
|
isSelfClosing: false,
|
|
424
404
|
canHaveChildren: true,
|
|
425
405
|
toBBCode: (ctx) => {
|
|
426
|
-
const segments =
|
|
406
|
+
const segments = effectSegments(ctx.node, 'sinewave')
|
|
427
407
|
return segments.length === 0 ? ctx.visitChildren(ctx.node) : segmentsToBBCode(segments)
|
|
428
408
|
},
|
|
429
|
-
toRenderNode: (ctx) => segmentsToRenderNode('sinewave',
|
|
409
|
+
toRenderNode: (ctx) => segmentsToRenderNode('sinewave', effectSegments(ctx.node, 'sinewave')),
|
|
430
410
|
},
|
|
431
411
|
{
|
|
432
412
|
name: 'grow',
|
|
@@ -437,10 +417,28 @@ export class TagRegistry {
|
|
|
437
417
|
isSelfClosing: false,
|
|
438
418
|
canHaveChildren: true,
|
|
439
419
|
toBBCode: (ctx) => {
|
|
440
|
-
const segments =
|
|
420
|
+
const segments = effectSegments(ctx.node, 'grow')
|
|
421
|
+
return segments.length === 0 ? ctx.visitChildren(ctx.node) : segmentsToBBCode(segments)
|
|
422
|
+
},
|
|
423
|
+
toRenderNode: (ctx) => segmentsToRenderNode('grow', effectSegments(ctx.node, 'grow')),
|
|
424
|
+
},
|
|
425
|
+
{
|
|
426
|
+
// Colour driven by a picture rather than a ramp: the tag carries a
|
|
427
|
+
// small indexed grid, and each character takes the colour of the
|
|
428
|
+
// cell it lands on. Nothing about the text changes — this is the
|
|
429
|
+
// one effect whose whole point is that the art keeps its shape.
|
|
430
|
+
name: 'paint',
|
|
431
|
+
kind: 'paint',
|
|
432
|
+
label: 'Paint',
|
|
433
|
+
category: 'text',
|
|
434
|
+
isInline: true,
|
|
435
|
+
isSelfClosing: false,
|
|
436
|
+
canHaveChildren: true,
|
|
437
|
+
toBBCode: (ctx) => {
|
|
438
|
+
const segments = effectSegments(ctx.node, 'paint')
|
|
441
439
|
return segments.length === 0 ? ctx.visitChildren(ctx.node) : segmentsToBBCode(segments)
|
|
442
440
|
},
|
|
443
|
-
toRenderNode: (ctx) => segmentsToRenderNode('
|
|
441
|
+
toRenderNode: (ctx) => segmentsToRenderNode('paint', effectSegments(ctx.node, 'paint')),
|
|
444
442
|
},
|
|
445
443
|
{
|
|
446
444
|
name: 'rainbow',
|
|
@@ -451,10 +449,10 @@ export class TagRegistry {
|
|
|
451
449
|
isSelfClosing: false,
|
|
452
450
|
canHaveChildren: true,
|
|
453
451
|
toBBCode: (ctx) => {
|
|
454
|
-
const segments =
|
|
452
|
+
const segments = effectSegments(ctx.node, 'rainbow')
|
|
455
453
|
return segments.length === 0 ? ctx.visitChildren(ctx.node) : segmentsToBBCode(segments)
|
|
456
454
|
},
|
|
457
|
-
toRenderNode: (ctx) => segmentsToRenderNode('rainbow',
|
|
455
|
+
toRenderNode: (ctx) => segmentsToRenderNode('rainbow', effectSegments(ctx.node, 'rainbow')),
|
|
458
456
|
},
|
|
459
457
|
|
|
460
458
|
// ── Layout ──
|
|
@@ -491,8 +489,5 @@ export class TagRegistry {
|
|
|
491
489
|
{ name: 'group', kind: 'group', label: 'Group', category: 'layout', isInline: false, isSelfClosing: false, canHaveChildren: true },
|
|
492
490
|
]
|
|
493
491
|
|
|
494
|
-
|
|
495
|
-
this.register(tag)
|
|
496
|
-
}
|
|
497
|
-
}
|
|
492
|
+
return tags
|
|
498
493
|
}
|