modern-text 1.11.1 → 2.0.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/README.md +283 -22
- package/dist/deformations/index.cjs +566 -0
- package/dist/deformations/index.d.cts +11 -0
- package/dist/deformations/index.d.mts +11 -0
- package/dist/deformations/index.d.ts +11 -0
- package/dist/deformations/index.mjs +563 -0
- package/dist/index.cjs +15 -3
- package/dist/index.d.cts +186 -6
- package/dist/index.d.mts +186 -6
- package/dist/index.d.ts +186 -6
- package/dist/index.js +6 -5
- package/dist/index.mjs +4 -2
- package/dist/shared/modern-text.B2xfrqDc.cjs +556 -0
- package/dist/shared/modern-text.BD7PBYt7.d.cts +100 -0
- package/dist/shared/modern-text.BxijkspX.d.ts +100 -0
- package/dist/shared/{modern-text.BKZQdmgG.cjs → modern-text.CBgc-cQ1.cjs} +288 -18
- package/dist/shared/modern-text.CYa4lfoG.d.mts +100 -0
- package/dist/shared/{modern-text.Dqw5Z6MV.mjs → modern-text.ChzjFjsk.mjs} +283 -13
- package/dist/shared/{modern-text.Db7Uoht6.d.cts → modern-text.D4WopQCu.d.cts} +56 -22
- package/dist/shared/{modern-text.Db7Uoht6.d.mts → modern-text.D4WopQCu.d.mts} +56 -22
- package/dist/shared/{modern-text.Db7Uoht6.d.ts → modern-text.D4WopQCu.d.ts} +56 -22
- package/dist/shared/modern-text.JF1ny7A-.mjs +550 -0
- package/dist/shared/modern-text.MC5bIC9E.cjs +316 -0
- package/dist/shared/modern-text.fT17R5HY.mjs +310 -0
- package/dist/web-components/index.cjs +2 -1
- package/dist/web-components/index.d.cts +1 -1
- package/dist/web-components/index.d.mts +1 -1
- package/dist/web-components/index.d.ts +1 -1
- package/dist/web-components/index.mjs +2 -1
- package/package.json +12 -7
|
@@ -57,6 +57,16 @@ declare class Character {
|
|
|
57
57
|
constructor(content: string, index: number, parent: Fragment);
|
|
58
58
|
protected _getFontSFNT(fonts?: Fonts): SFNT | undefined;
|
|
59
59
|
updateGlyph(sfnt?: SFNT | undefined): this;
|
|
60
|
+
/**
|
|
61
|
+
* Populate glyph metrics only (advance width/height, ascender/descender,
|
|
62
|
+
* baseline, …) without building the glyph `path` or touching boxes.
|
|
63
|
+
*
|
|
64
|
+
* The DOM {@link DomMeasurer} never needs this — it reads positions back from the
|
|
65
|
+
* browser. A pure-JS measurer (e.g. `FontMeasurer`) must know advances *before*
|
|
66
|
+
* it can place characters, so it calls this ahead of layout. `update()` later
|
|
67
|
+
* recomputes the same metrics while building the path, so this is idempotent.
|
|
68
|
+
*/
|
|
69
|
+
measureGlyph(fonts?: Fonts): this;
|
|
60
70
|
update(fonts?: Fonts): this;
|
|
61
71
|
protected _italic(path: Path2D, startPoint?: Vector2Like): void;
|
|
62
72
|
getGlyphMinMax(min?: Vector2, max?: Vector2, withStyle?: boolean): {
|
|
@@ -66,24 +76,6 @@ declare class Character {
|
|
|
66
76
|
getGlyphBoundingBox(withStyle?: boolean): BoundingBox | undefined;
|
|
67
77
|
}
|
|
68
78
|
|
|
69
|
-
interface Plugin {
|
|
70
|
-
name: string;
|
|
71
|
-
pathSet?: Path2DSet;
|
|
72
|
-
getBoundingBox?: (text: Text$1) => BoundingBox | undefined;
|
|
73
|
-
update?: (text: Text$1) => void;
|
|
74
|
-
updateOrder?: number;
|
|
75
|
-
render?: (renderer: Canvas2DRenderer) => void;
|
|
76
|
-
renderOrder?: number;
|
|
77
|
-
load?: (text: Text$1) => Promise<void>;
|
|
78
|
-
context?: Record<string, any>;
|
|
79
|
-
}
|
|
80
|
-
interface Options extends TextObject {
|
|
81
|
-
debug?: boolean;
|
|
82
|
-
measureDom?: HTMLElement;
|
|
83
|
-
fonts?: Fonts;
|
|
84
|
-
plugins?: Plugin[];
|
|
85
|
-
}
|
|
86
|
-
|
|
87
79
|
interface MeasuredParagraph {
|
|
88
80
|
paragraphIndex: number;
|
|
89
81
|
left: number;
|
|
@@ -127,7 +119,7 @@ interface RootDomStyles {
|
|
|
127
119
|
section: Record<string, any>;
|
|
128
120
|
ul: Record<string, any>;
|
|
129
121
|
}
|
|
130
|
-
declare class
|
|
122
|
+
declare class DomMeasurer {
|
|
131
123
|
static notZeroStyles: Set<string>;
|
|
132
124
|
static pxStyles: Set<string>;
|
|
133
125
|
protected _styleCache: WeakMap<object, Record<string, any>>;
|
|
@@ -166,6 +158,47 @@ declare class Measurer {
|
|
|
166
158
|
dispose(): void;
|
|
167
159
|
}
|
|
168
160
|
|
|
161
|
+
interface Plugin {
|
|
162
|
+
name: string;
|
|
163
|
+
pathSet?: Path2DSet;
|
|
164
|
+
getBoundingBox?: (text: Text$1) => BoundingBox | undefined;
|
|
165
|
+
update?: (text: Text$1) => void;
|
|
166
|
+
updateOrder?: number;
|
|
167
|
+
render?: (renderer: Canvas2DRenderer) => void;
|
|
168
|
+
renderOrder?: number;
|
|
169
|
+
load?: (text: Text$1) => Promise<void>;
|
|
170
|
+
context?: Record<string, any>;
|
|
171
|
+
}
|
|
172
|
+
/**
|
|
173
|
+
* Pluggable layout backend. Implementations fill the four-level boxes
|
|
174
|
+
* (`character.inlineBox`/`lineBox`, `fragment.inlineBox`, `paragraph.lineBox`)
|
|
175
|
+
* in place and return the overall bounding box.
|
|
176
|
+
*
|
|
177
|
+
* - `DomMeasurer` — DOM-based, uses the browser as ground truth (default).
|
|
178
|
+
* - `FontMeasurer` — pure-JS, DOM-free; needs `fonts` to resolve glyph advances.
|
|
179
|
+
*
|
|
180
|
+
* `fonts` is passed positionally by `Text.measure()`; DOM-based measurers ignore
|
|
181
|
+
* it (a method may safely declare fewer parameters than the interface).
|
|
182
|
+
*/
|
|
183
|
+
interface TextMeasurer {
|
|
184
|
+
measure: (paragraphs: Paragraph[], rootStyle: FullStyle, dom?: HTMLElement, fonts?: Fonts) => MeasureDomResult;
|
|
185
|
+
createDom?: (paragraphs: Paragraph[], rootStyle: FullStyle) => HTMLElement;
|
|
186
|
+
dispose?: () => void;
|
|
187
|
+
}
|
|
188
|
+
/** Built-in layout backends. `'font'` → `FontMeasurer`, `'dom'` → `DomMeasurer`. */
|
|
189
|
+
type MeasurerKind = 'dom' | 'font';
|
|
190
|
+
interface Options extends TextObject {
|
|
191
|
+
debug?: boolean;
|
|
192
|
+
measureDom?: HTMLElement;
|
|
193
|
+
fonts?: Fonts;
|
|
194
|
+
plugins?: Plugin[];
|
|
195
|
+
/**
|
|
196
|
+
* Layout backend: `'font'` (pure-JS) or `'dom'` (browser), or a custom
|
|
197
|
+
* `TextMeasurer`. Defaults to `'font'` when `fonts` are provided, else `'dom'`.
|
|
198
|
+
*/
|
|
199
|
+
measurer?: MeasurerKind | TextMeasurer;
|
|
200
|
+
}
|
|
201
|
+
|
|
169
202
|
interface RenderOptions {
|
|
170
203
|
view: HTMLCanvasElement;
|
|
171
204
|
pixelRatio?: number;
|
|
@@ -207,6 +240,7 @@ declare class Text$1 extends Reactivable {
|
|
|
207
240
|
effects?: NormalizedText['effects'];
|
|
208
241
|
fill?: NormalizedText['fill'];
|
|
209
242
|
outline?: NormalizedText['outline'];
|
|
243
|
+
deformation?: NormalizedText['deformation'];
|
|
210
244
|
measureDom?: HTMLElement;
|
|
211
245
|
fonts?: Fonts;
|
|
212
246
|
needsUpdate: boolean;
|
|
@@ -220,7 +254,7 @@ declare class Text$1 extends Reactivable {
|
|
|
220
254
|
glyphBox: BoundingBox;
|
|
221
255
|
pathBox: BoundingBox;
|
|
222
256
|
boundingBox: BoundingBox;
|
|
223
|
-
measurer:
|
|
257
|
+
measurer: TextMeasurer;
|
|
224
258
|
plugins: Map<string, Plugin>;
|
|
225
259
|
pathSets: Path2DSet[];
|
|
226
260
|
protected _paragraphs: Paragraph[];
|
|
@@ -303,5 +337,5 @@ declare class Canvas2DRenderer {
|
|
|
303
337
|
drawCharacter: (character: Character, effect?: NormalizedEffect) => void;
|
|
304
338
|
}
|
|
305
339
|
|
|
306
|
-
export { Canvas2DRenderer as C, Fragment as F, Paragraph as P, Text$1 as T, Character as a,
|
|
307
|
-
export type {
|
|
340
|
+
export { Canvas2DRenderer as C, DomMeasurer as D, Fragment as F, Paragraph as P, Text$1 as T, Character as a, textDefaultStyle as t };
|
|
341
|
+
export type { MeasureDomResult as M, Options as O, RenderOptions as R, DrawShapePathsOptions as b, MeasureResult as c, MeasuredCharacter as d, MeasuredCharacterRect as e, MeasuredFragment as f, MeasuredParagraph as g, MeasurerKind as h, Plugin as i, TextEvents as j, TextMeasurer as k };
|
|
@@ -0,0 +1,550 @@
|
|
|
1
|
+
import { isNone } from 'modern-idoc';
|
|
2
|
+
import { QuadraticBezierCurve, Vector2, CubicBezierCurve, LineCurve } from 'modern-path2d';
|
|
3
|
+
|
|
4
|
+
function definePlugin(options) {
|
|
5
|
+
return options;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
function splitCurve(curve, point) {
|
|
9
|
+
let { x, y } = point;
|
|
10
|
+
if (curve instanceof QuadraticBezierCurve) {
|
|
11
|
+
const { p1: v0, cp: v1, p2: v2 } = curve;
|
|
12
|
+
const s = x !== void 0 && Math.abs(Math.sign(v0.x - x) + Math.sign(v1.x - x) + Math.sign(v2.x - x)) === 1;
|
|
13
|
+
const n = y !== void 0 && Math.abs(Math.sign(v0.y - y) + Math.sign(v1.y - y) + Math.sign(v2.y - y)) === 1;
|
|
14
|
+
let o, e, dist, c;
|
|
15
|
+
if (s) {
|
|
16
|
+
o = v0.x - 2 * v1.x + v2.x;
|
|
17
|
+
e = 2 * (-v0.x + v1.x);
|
|
18
|
+
dist = v0.x - x;
|
|
19
|
+
}
|
|
20
|
+
if (n) {
|
|
21
|
+
o = v0.y - 2 * v1.y + v2.y;
|
|
22
|
+
e = 2 * (-v0.y + v1.y);
|
|
23
|
+
dist = v0.y - y;
|
|
24
|
+
}
|
|
25
|
+
if (s || n) {
|
|
26
|
+
c = e * e - 4 * o * dist;
|
|
27
|
+
if (c > 0) {
|
|
28
|
+
const h = Math.sqrt(c);
|
|
29
|
+
const a = (-e + h) / 2 / o;
|
|
30
|
+
const b = (-e - h) / 2 / o;
|
|
31
|
+
const u = [a, b].find((v) => v > 0 && v < 1);
|
|
32
|
+
if (u) {
|
|
33
|
+
const r = Vector2.lerp(v0, v1, u);
|
|
34
|
+
const d = Vector2.lerp(v1, v2, u);
|
|
35
|
+
const B = Vector2.lerp(r, d, u);
|
|
36
|
+
return [new QuadraticBezierCurve(v0, r, B), new QuadraticBezierCurve(B.clone(), d, v2)];
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
} else if (curve instanceof CubicBezierCurve) {
|
|
41
|
+
const { p1: v0, cp1: v1, cp2: v2, p2: v3 } = curve;
|
|
42
|
+
const { min, max } = curve.getMinMax();
|
|
43
|
+
const e = x !== void 0 && (min.x - x) * (max.x - x) < 0;
|
|
44
|
+
const l = y !== void 0 && (min.y - y) * (max.y - y) < 0;
|
|
45
|
+
if (e || l) {
|
|
46
|
+
x = 0.5 * v0.x + 0.5 * v1.x;
|
|
47
|
+
y = 0.5 * v0.y + 0.5 * v1.y;
|
|
48
|
+
const c = new Vector2(x, y);
|
|
49
|
+
x = 0.25 * v0.x + 0.5 * v1.x + 0.25 * v2.x;
|
|
50
|
+
y = 0.25 * v0.y + 0.5 * v1.y + 0.25 * v2.y;
|
|
51
|
+
const h = new Vector2(x, y);
|
|
52
|
+
x = 0.125 * v0.x + 0.375 * v1.x + 0.375 * v2.x + 0.125 * v3.x;
|
|
53
|
+
y = 0.125 * v0.y + 0.375 * v1.y + 0.375 * v2.y + 0.125 * v3.y;
|
|
54
|
+
const a = new Vector2(x, y);
|
|
55
|
+
x = 0.25 * v1.x + 0.5 * v2.x + 0.25 * v3.x;
|
|
56
|
+
y = 0.25 * v1.y + 0.5 * v2.y + 0.25 * v3.y;
|
|
57
|
+
const b = new Vector2(x, y);
|
|
58
|
+
x = 0.5 * v2.x + 0.5 * v3.x;
|
|
59
|
+
y = 0.5 * v2.y + 0.5 * v3.y;
|
|
60
|
+
const u = new Vector2(x, y);
|
|
61
|
+
return [new CubicBezierCurve(v0, c, h, a), new CubicBezierCurve(a.clone(), b, u, v3)];
|
|
62
|
+
}
|
|
63
|
+
} else if (curve instanceof LineCurve) {
|
|
64
|
+
const { p1: v1, p2: v2 } = curve;
|
|
65
|
+
const changedX = x !== void 0 && (v1.x - x) * (v2.x - x) < 0;
|
|
66
|
+
const changedY = y !== void 0 && (v1.y - y) * (v2.y - y) < 0;
|
|
67
|
+
if (changedX) {
|
|
68
|
+
y = v1.y + (v2.y - v1.y) / (v2.x - v1.x) * (x - v1.x);
|
|
69
|
+
}
|
|
70
|
+
if (changedY) {
|
|
71
|
+
x = v1.x + (v2.x - v1.x) / (v2.y - v1.y) * (y - v1.y);
|
|
72
|
+
}
|
|
73
|
+
if (changedX || changedY) {
|
|
74
|
+
return [new LineCurve(v1, new Vector2(x, y)), new LineCurve(new Vector2(x, y), v2)];
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
return [curve];
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
class Deformer {
|
|
81
|
+
get boundingBox() {
|
|
82
|
+
return this.text.lineBox;
|
|
83
|
+
}
|
|
84
|
+
get paragraphs() {
|
|
85
|
+
return this.text.paragraphs;
|
|
86
|
+
}
|
|
87
|
+
get isHorizontal() {
|
|
88
|
+
return this.text.computedStyle.writingMode.startsWith("horizontal");
|
|
89
|
+
}
|
|
90
|
+
get baseWidth() {
|
|
91
|
+
return this.isHorizontal ? this.boundingBox.width : this.boundingBox.height;
|
|
92
|
+
}
|
|
93
|
+
get baseHeight() {
|
|
94
|
+
return this.isHorizontal ? this.boundingBox.height : this.boundingBox.width;
|
|
95
|
+
}
|
|
96
|
+
get characters() {
|
|
97
|
+
return this.paragraphs.flatMap((p) => p.fragments.flatMap((f) => f.characters));
|
|
98
|
+
}
|
|
99
|
+
constructor({ text, intensities = [], maxFontSize = 100 }) {
|
|
100
|
+
this.text = text;
|
|
101
|
+
this.intensities = intensities.map((val) => val / 100);
|
|
102
|
+
this.lineHeight = maxFontSize;
|
|
103
|
+
}
|
|
104
|
+
_breakLine() {
|
|
105
|
+
const isVertical = !this.isHorizontal;
|
|
106
|
+
const { left, top, bottom, right } = this.boundingBox;
|
|
107
|
+
const x = 0.5 * (left + right);
|
|
108
|
+
const y = 0.5 * (top + bottom);
|
|
109
|
+
this.characters.forEach((character) => {
|
|
110
|
+
if (!character.glyphBox) {
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
character.path.curves.forEach((subPath) => {
|
|
114
|
+
subPath.curves = subPath.curves.flatMap((curve) => {
|
|
115
|
+
return splitCurve(curve, isVertical ? { y } : { x });
|
|
116
|
+
});
|
|
117
|
+
});
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
_makeTheJointSmooth() {
|
|
121
|
+
this.characters.forEach((character) => {
|
|
122
|
+
if (!character.glyphBox) {
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
character.path.getFlatCurves().forEach((curve) => {
|
|
126
|
+
if (curve instanceof QuadraticBezierCurve && curve.isFromLine) {
|
|
127
|
+
const { p1, cp, p2 } = curve;
|
|
128
|
+
cp.x = 2 * cp.x - 0.5 * (p1.x + p2.x);
|
|
129
|
+
cp.y = 2 * cp.y - 0.5 * (p1.y + p2.y);
|
|
130
|
+
}
|
|
131
|
+
});
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
_lineToQuadraticBezier() {
|
|
135
|
+
this.characters.forEach((character) => {
|
|
136
|
+
if (!character.glyphBox) {
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
character.path.curves.forEach((subPath) => {
|
|
140
|
+
subPath.curves = subPath.curves.map((curve) => {
|
|
141
|
+
if (curve instanceof LineCurve && curve.getLength() > 5) {
|
|
142
|
+
const { p1, p2 } = curve;
|
|
143
|
+
const res = new QuadraticBezierCurve(
|
|
144
|
+
p1.clone(),
|
|
145
|
+
new Vector2(0.5 * (p1.x + p2.x), 0.5 * (p1.y + p2.y)),
|
|
146
|
+
p2.clone()
|
|
147
|
+
);
|
|
148
|
+
res.isFromLine = true;
|
|
149
|
+
return res;
|
|
150
|
+
}
|
|
151
|
+
return curve;
|
|
152
|
+
});
|
|
153
|
+
});
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
_transform(transform, getArg1, getArg) {
|
|
157
|
+
const highlight = this.text.plugins.get("highlight");
|
|
158
|
+
const arg = getArg?.();
|
|
159
|
+
let i = 0;
|
|
160
|
+
const charactersLength = this.characters.filter((c) => c.glyphBox).length;
|
|
161
|
+
this.paragraphs.forEach((paragraph, paragraphIndex) => {
|
|
162
|
+
paragraph.fragments.forEach((fragment, fragmentIndex) => {
|
|
163
|
+
fragment.characters.forEach((character, characterIndex) => {
|
|
164
|
+
if (!character.glyphBox) {
|
|
165
|
+
return;
|
|
166
|
+
}
|
|
167
|
+
const arg1 = getArg1?.(
|
|
168
|
+
{
|
|
169
|
+
paragraphIndex,
|
|
170
|
+
fragmentIndex,
|
|
171
|
+
characterIndex,
|
|
172
|
+
character
|
|
173
|
+
},
|
|
174
|
+
arg
|
|
175
|
+
);
|
|
176
|
+
character.path.getControlPointRefs().forEach((point) => {
|
|
177
|
+
const [x, y] = transform(point, arg1);
|
|
178
|
+
point.set(x, y);
|
|
179
|
+
});
|
|
180
|
+
if (getArg1 && highlight?.pathSet?.paths) {
|
|
181
|
+
const step = highlight.pathSet.paths.length / charactersLength;
|
|
182
|
+
const start = i * step;
|
|
183
|
+
for (let _i = 0; _i < step; _i++) {
|
|
184
|
+
highlight?.pathSet?.paths[start + _i]?.getControlPointRefs().forEach((point) => {
|
|
185
|
+
const [x, y] = transform(point, arg1);
|
|
186
|
+
point.set(x, y);
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
i++;
|
|
190
|
+
}
|
|
191
|
+
});
|
|
192
|
+
});
|
|
193
|
+
});
|
|
194
|
+
this.paragraphs.forEach((paragraph) => {
|
|
195
|
+
paragraph.fragments.forEach((fragment) => {
|
|
196
|
+
fragment.characters.forEach((character) => {
|
|
197
|
+
character.glyphBox = character.getGlyphBoundingBox();
|
|
198
|
+
if (character.glyphBox) {
|
|
199
|
+
character.inlineBox = character.glyphBox;
|
|
200
|
+
}
|
|
201
|
+
});
|
|
202
|
+
});
|
|
203
|
+
});
|
|
204
|
+
if (!getArg1) {
|
|
205
|
+
highlight?.pathSet?.paths?.forEach((v) => {
|
|
206
|
+
v.getControlPointRefs().forEach((point) => {
|
|
207
|
+
const [x, y] = transform(point);
|
|
208
|
+
point.set(x, y);
|
|
209
|
+
});
|
|
210
|
+
});
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
class BendDeformer extends Deformer {
|
|
216
|
+
constructor(options, preset) {
|
|
217
|
+
super(options);
|
|
218
|
+
this.preset = preset;
|
|
219
|
+
}
|
|
220
|
+
deform() {
|
|
221
|
+
if (!Math.hypot(...this.intensities)) {
|
|
222
|
+
return;
|
|
223
|
+
}
|
|
224
|
+
this._lineToQuadraticBezier();
|
|
225
|
+
this._bend();
|
|
226
|
+
this._makeTheJointSmooth();
|
|
227
|
+
}
|
|
228
|
+
_bend() {
|
|
229
|
+
const { boundingBox, baseWidth, baseHeight, preset } = this;
|
|
230
|
+
const lineHeight = this.lineHeight * 2 / Math.sin(this.intensities[0] * Math.PI * 0.5);
|
|
231
|
+
const isVertical = preset.vertical === void 0 || preset.vertical === "auto" ? !this.isHorizontal : preset.vertical;
|
|
232
|
+
const { left, top, width, height } = boundingBox;
|
|
233
|
+
const size = (isVertical ? height : width) / 2 / lineHeight;
|
|
234
|
+
const center = {
|
|
235
|
+
x: left + width / 2,
|
|
236
|
+
y: top + height / 2
|
|
237
|
+
};
|
|
238
|
+
let centerDistAngle;
|
|
239
|
+
const centerDist = { x: center.x, y: center.y };
|
|
240
|
+
if (isVertical) {
|
|
241
|
+
centerDistAngle = 0;
|
|
242
|
+
centerDist.x -= baseHeight / 2 / Math.tan(size) + Math.sign(size) * width / 2;
|
|
243
|
+
} else {
|
|
244
|
+
centerDistAngle = 1.5 * Math.PI;
|
|
245
|
+
centerDist.y += baseWidth / 2 / Math.tan(size) + Math.sign(size) * height / 2;
|
|
246
|
+
}
|
|
247
|
+
const method = preset.transform({
|
|
248
|
+
lineHeight,
|
|
249
|
+
size,
|
|
250
|
+
center,
|
|
251
|
+
centerDist,
|
|
252
|
+
centerDistAngle,
|
|
253
|
+
width,
|
|
254
|
+
height,
|
|
255
|
+
isHorizontal: this.isHorizontal
|
|
256
|
+
});
|
|
257
|
+
this._transform(method);
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
class FfdDeformer extends Deformer {
|
|
262
|
+
constructor(options, preset) {
|
|
263
|
+
super(options);
|
|
264
|
+
this.preset = preset;
|
|
265
|
+
}
|
|
266
|
+
deform() {
|
|
267
|
+
if (!Math.hypot(...this.intensities)) {
|
|
268
|
+
return;
|
|
269
|
+
}
|
|
270
|
+
const { preset } = this;
|
|
271
|
+
if (preset.breakLine) {
|
|
272
|
+
this._breakLine();
|
|
273
|
+
}
|
|
274
|
+
if (preset.lineToQuad) {
|
|
275
|
+
this._lineToQuadraticBezier();
|
|
276
|
+
}
|
|
277
|
+
const [a, b] = this.intensities;
|
|
278
|
+
const points = this._createFFDControlPoints(preset.hBlocks, preset.vBlocks);
|
|
279
|
+
preset.build(points, {
|
|
280
|
+
a,
|
|
281
|
+
b,
|
|
282
|
+
baseWidth: this.baseWidth,
|
|
283
|
+
baseHeight: this.baseHeight,
|
|
284
|
+
lineHeight: this.lineHeight,
|
|
285
|
+
adjust: (point, dx, dy) => this._adjustControlPoints(point, { x: dx, y: dy })
|
|
286
|
+
});
|
|
287
|
+
if (preset.bezier) {
|
|
288
|
+
this._calculateForBezierFFD(points, preset.hBlocks, preset.vBlocks);
|
|
289
|
+
this._makeTheJointSmooth();
|
|
290
|
+
} else {
|
|
291
|
+
this._calculateForLinearFFD(points, preset.hBlocks, preset.vBlocks);
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
_calculateForFFD(cb, points, hBlocks, vBlocks) {
|
|
295
|
+
const { left, top, right, width, height } = this.boundingBox;
|
|
296
|
+
this._transform(
|
|
297
|
+
this.isHorizontal ? (point) => {
|
|
298
|
+
const xProgress = (point.x - left) / width;
|
|
299
|
+
const yProgress = (point.y - top) / height;
|
|
300
|
+
let [x, y] = [0, 0];
|
|
301
|
+
for (let h = 0; h < hBlocks + 2; h++) {
|
|
302
|
+
const _h = cb(hBlocks, h, xProgress);
|
|
303
|
+
for (let v = 0; v < vBlocks + 2; v++) {
|
|
304
|
+
const _v = cb(vBlocks, v, yProgress);
|
|
305
|
+
const p = points[h * (vBlocks + 2) + v];
|
|
306
|
+
x += _h * _v * p.x;
|
|
307
|
+
y += _h * _v * p.y;
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
return [x, y];
|
|
311
|
+
} : (point) => {
|
|
312
|
+
const xProgress = (right - point.x) / width;
|
|
313
|
+
const yProgress = (point.y - top) / height;
|
|
314
|
+
let [x, y] = [0, 0];
|
|
315
|
+
for (let h = 0; h < hBlocks + 2; h++) {
|
|
316
|
+
const _h = cb(hBlocks, h, yProgress);
|
|
317
|
+
for (let v = 0; v < vBlocks + 2; v++) {
|
|
318
|
+
const _v = cb(vBlocks, v, xProgress);
|
|
319
|
+
const p = points[h * (vBlocks + 2) + v];
|
|
320
|
+
x += _h * _v * p.x;
|
|
321
|
+
y += _h * _v * p.y;
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
return [x, y];
|
|
325
|
+
}
|
|
326
|
+
);
|
|
327
|
+
}
|
|
328
|
+
_createFFDControlPoints(hBlocks, vBlocks) {
|
|
329
|
+
const { left, top, right, width, height } = this.boundingBox;
|
|
330
|
+
const points = [];
|
|
331
|
+
if (this.isHorizontal) {
|
|
332
|
+
const avgWidth = width / (hBlocks + 1);
|
|
333
|
+
const avgHeight = height / (vBlocks + 1);
|
|
334
|
+
for (let h = 0; h < hBlocks + 2; h++) {
|
|
335
|
+
for (let v = 0; v < vBlocks + 2; v++) {
|
|
336
|
+
points.push(new Vector2(left + h * avgWidth, top + v * avgHeight));
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
} else {
|
|
340
|
+
const avgWidth = width / (vBlocks + 1);
|
|
341
|
+
const avgHeight = height / (hBlocks + 1);
|
|
342
|
+
for (let h = 0; h < hBlocks + 2; h++) {
|
|
343
|
+
for (let v = 0; v < vBlocks + 2; v++) {
|
|
344
|
+
points.push(new Vector2(right - v * avgWidth, top + h * avgHeight));
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
return points;
|
|
349
|
+
}
|
|
350
|
+
_adjustControlPoints(point1, point2) {
|
|
351
|
+
if (this.isHorizontal) {
|
|
352
|
+
point1.x += point2.x;
|
|
353
|
+
point1.y += point2.y;
|
|
354
|
+
} else {
|
|
355
|
+
point1.x -= point2.y;
|
|
356
|
+
point1.y += point2.x;
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
_factorialForFFD(val) {
|
|
360
|
+
let result = 1;
|
|
361
|
+
for (let i = 2; i <= val; i++) {
|
|
362
|
+
result *= i;
|
|
363
|
+
}
|
|
364
|
+
return result;
|
|
365
|
+
}
|
|
366
|
+
_combineForFFD(total, current) {
|
|
367
|
+
return this._factorialForFFD(total) / this._factorialForFFD(total - current) / this._factorialForFFD(current);
|
|
368
|
+
}
|
|
369
|
+
_calculateForBezierFFD(points, hBlocks, vBlocks) {
|
|
370
|
+
this._calculateForFFD(
|
|
371
|
+
(count, current, progress) => {
|
|
372
|
+
return this._combineForFFD(count + 1, current) * (1 - progress) ** (count + 1 - current) * progress ** current;
|
|
373
|
+
},
|
|
374
|
+
points,
|
|
375
|
+
hBlocks,
|
|
376
|
+
vBlocks
|
|
377
|
+
);
|
|
378
|
+
}
|
|
379
|
+
_linearBasis(count, current, progress) {
|
|
380
|
+
const t = current < progress * count ? current + 1 - progress * count : progress * count - current + 1;
|
|
381
|
+
return Math.max(0, t);
|
|
382
|
+
}
|
|
383
|
+
_calculateForLinearFFD(points, hBlocks, vBlocks) {
|
|
384
|
+
this._calculateForFFD(
|
|
385
|
+
(count, current, progress) => {
|
|
386
|
+
return this._linearBasis(count + 1, current, progress);
|
|
387
|
+
},
|
|
388
|
+
points,
|
|
389
|
+
hBlocks,
|
|
390
|
+
vBlocks
|
|
391
|
+
);
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
class VerbatimDeformer extends Deformer {
|
|
396
|
+
constructor(options, preset) {
|
|
397
|
+
super(options);
|
|
398
|
+
this.preset = preset;
|
|
399
|
+
}
|
|
400
|
+
_context() {
|
|
401
|
+
return {
|
|
402
|
+
intensities: this.intensities,
|
|
403
|
+
baseWidth: this.baseWidth,
|
|
404
|
+
lineHeight: this.lineHeight,
|
|
405
|
+
isHorizontal: this.isHorizontal,
|
|
406
|
+
boundingBox: this.boundingBox
|
|
407
|
+
};
|
|
408
|
+
}
|
|
409
|
+
deform() {
|
|
410
|
+
if (!Math.hypot(...this.intensities)) {
|
|
411
|
+
return;
|
|
412
|
+
}
|
|
413
|
+
const { preset } = this;
|
|
414
|
+
const ctx = this._context();
|
|
415
|
+
if (preset.engine === "offset") {
|
|
416
|
+
this._transform(
|
|
417
|
+
preset.point,
|
|
418
|
+
preset.perChar ? (info) => preset.perChar(ctx, info) : void 0
|
|
419
|
+
);
|
|
420
|
+
} else {
|
|
421
|
+
const followTangent = preset.followTangent ?? true;
|
|
422
|
+
const { funcPerChar, funcPerPoint } = this._getPerCharAndPointFunc(followTangent);
|
|
423
|
+
this._transform(funcPerPoint, funcPerChar, () => {
|
|
424
|
+
const { width, height, left, top } = this.boundingBox;
|
|
425
|
+
return {
|
|
426
|
+
width,
|
|
427
|
+
height,
|
|
428
|
+
left,
|
|
429
|
+
top,
|
|
430
|
+
curve: preset.makeCurve(ctx),
|
|
431
|
+
isHorizontal: this.isHorizontal,
|
|
432
|
+
needExpandAlongNormal: preset.expandAlongNormal ?? false
|
|
433
|
+
};
|
|
434
|
+
});
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
_getPerCharAndPointFunc(rotate = false) {
|
|
438
|
+
let funcPerChar;
|
|
439
|
+
let funcPerPoint;
|
|
440
|
+
if (rotate) {
|
|
441
|
+
funcPerPoint = this._resetPointPos;
|
|
442
|
+
funcPerChar = this._calculateNewCenter;
|
|
443
|
+
} else {
|
|
444
|
+
funcPerPoint = this._resetPointPosWithoutRotate;
|
|
445
|
+
funcPerChar = this._calculateNewCenterWithoutRotate;
|
|
446
|
+
}
|
|
447
|
+
return {
|
|
448
|
+
funcPerPoint,
|
|
449
|
+
funcPerChar
|
|
450
|
+
};
|
|
451
|
+
}
|
|
452
|
+
_calculateNewCenter({ character }, arg) {
|
|
453
|
+
const { width, height, left, top, curve, isHorizontal, needExpandAlongNormal = false } = arg;
|
|
454
|
+
const { center, centerDiviation } = character;
|
|
455
|
+
const pos = isHorizontal ? (center.x - left) / width : (center.y - top) / height;
|
|
456
|
+
const a = isHorizontal ? new Vector2(0, center.y - top) : new Vector2(center.x - left, 0);
|
|
457
|
+
if (needExpandAlongNormal) {
|
|
458
|
+
const p = curve.getNormal(pos);
|
|
459
|
+
a.x += p.x * -centerDiviation;
|
|
460
|
+
a.y += p.y * -centerDiviation;
|
|
461
|
+
}
|
|
462
|
+
const newCenter = curve.getPointAt(pos).add(a);
|
|
463
|
+
const tangent = curve.getTangent(pos);
|
|
464
|
+
const cos = tangent.x;
|
|
465
|
+
const sin = tangent.y;
|
|
466
|
+
return {
|
|
467
|
+
cos,
|
|
468
|
+
sin,
|
|
469
|
+
newCenter,
|
|
470
|
+
center
|
|
471
|
+
};
|
|
472
|
+
}
|
|
473
|
+
_calculateNewCenterWithoutRotate({ character }, arg) {
|
|
474
|
+
const { width, height, left, top, curve, isHorizontal } = arg;
|
|
475
|
+
const { center } = character;
|
|
476
|
+
const pos = isHorizontal ? (center.x - left) / width : (center.y - top) / height;
|
|
477
|
+
const offsetPoint = isHorizontal ? new Vector2(0, center.y - top) : new Vector2(center.x - left, 0);
|
|
478
|
+
return {
|
|
479
|
+
newCenter: curve.getPointAt(pos).add(offsetPoint),
|
|
480
|
+
center
|
|
481
|
+
};
|
|
482
|
+
}
|
|
483
|
+
_resetPointPos(point, arg) {
|
|
484
|
+
const { cos = 1, sin = 0, center, newCenter } = arg;
|
|
485
|
+
const dx = point.x - center.x;
|
|
486
|
+
const dy = point.y - center.y;
|
|
487
|
+
const x = newCenter.x + dx * cos - dy * sin;
|
|
488
|
+
const y = newCenter.y + dx * sin + dy * cos;
|
|
489
|
+
return [x, y];
|
|
490
|
+
}
|
|
491
|
+
_resetPointPosWithoutRotate(point, arg) {
|
|
492
|
+
const { center, newCenter } = arg;
|
|
493
|
+
const x = point.x - center.x + newCenter.x;
|
|
494
|
+
const y = point.y - center.y + newCenter.y;
|
|
495
|
+
return [x, y];
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
const deformationPresets = /* @__PURE__ */ new Map();
|
|
500
|
+
function defineDeformation(name, preset) {
|
|
501
|
+
deformationPresets.set(name, preset);
|
|
502
|
+
}
|
|
503
|
+
function removeDeformation(name) {
|
|
504
|
+
deformationPresets.delete(name);
|
|
505
|
+
}
|
|
506
|
+
function getDeformationNames() {
|
|
507
|
+
return [...deformationPresets.keys()];
|
|
508
|
+
}
|
|
509
|
+
function deformationPlugin() {
|
|
510
|
+
return definePlugin({
|
|
511
|
+
name: "deformation",
|
|
512
|
+
updateOrder: 2,
|
|
513
|
+
update: (text) => {
|
|
514
|
+
const config = text.deformation;
|
|
515
|
+
const type = config?.type;
|
|
516
|
+
if (isNone(type) || !type) {
|
|
517
|
+
return;
|
|
518
|
+
}
|
|
519
|
+
const preset = deformationPresets.get(type);
|
|
520
|
+
if (!preset) {
|
|
521
|
+
return;
|
|
522
|
+
}
|
|
523
|
+
const options = {
|
|
524
|
+
text,
|
|
525
|
+
intensities: config.intensities ?? preset.defaultIntensities,
|
|
526
|
+
maxFontSize: config.maxFontSize
|
|
527
|
+
};
|
|
528
|
+
let deformer;
|
|
529
|
+
switch (preset.engine) {
|
|
530
|
+
case "ffd":
|
|
531
|
+
deformer = new FfdDeformer(options, preset);
|
|
532
|
+
break;
|
|
533
|
+
case "bend":
|
|
534
|
+
deformer = new BendDeformer(options, preset);
|
|
535
|
+
break;
|
|
536
|
+
case "curve":
|
|
537
|
+
case "offset":
|
|
538
|
+
deformer = new VerbatimDeformer(options, preset);
|
|
539
|
+
break;
|
|
540
|
+
}
|
|
541
|
+
deformer.deform();
|
|
542
|
+
const box = text.getGlyphBox();
|
|
543
|
+
text.rawGlyphBox = box;
|
|
544
|
+
text.glyphBox = box;
|
|
545
|
+
text.lineBox = box;
|
|
546
|
+
}
|
|
547
|
+
});
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
export { definePlugin as a, deformationPlugin as b, defineDeformation as d, getDeformationNames as g, removeDeformation as r };
|