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
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import { Vector2Like, BoundingBox, Vector2 } from 'modern-path2d';
|
|
2
|
+
import { a as Character } from './modern-text.D4WopQCu.js';
|
|
3
|
+
|
|
4
|
+
/** 逐字沿形状排布所需的曲线最小接口(实现了这三个方法即可当作变形曲线) */
|
|
5
|
+
interface DeformationCurve {
|
|
6
|
+
getPointAt: (u: number, output?: Vector2) => Vector2;
|
|
7
|
+
getTangent: (t: number, output?: Vector2) => Vector2;
|
|
8
|
+
getNormal: (t: number, output?: Vector2) => Vector2;
|
|
9
|
+
}
|
|
10
|
+
/** FFD 预设 `build` 的上下文 */
|
|
11
|
+
interface FfdContext {
|
|
12
|
+
/** 强度 1(intensities[0],已 /100) */
|
|
13
|
+
a: number;
|
|
14
|
+
/** 强度 2(intensities[1],已 /100) */
|
|
15
|
+
b: number;
|
|
16
|
+
baseWidth: number;
|
|
17
|
+
baseHeight: number;
|
|
18
|
+
lineHeight: number;
|
|
19
|
+
/** 沿当前书写方向调整控制点(内部处理横/竖轴向互换) */
|
|
20
|
+
adjust: (point: Vector2Like, dx: number, dy: number) => void;
|
|
21
|
+
}
|
|
22
|
+
/** Bend 预设 `transform` 的上下文(引擎已算好弯曲几何) */
|
|
23
|
+
interface BendContext {
|
|
24
|
+
/** 弯曲半径 */
|
|
25
|
+
lineHeight: number;
|
|
26
|
+
size: number;
|
|
27
|
+
center: {
|
|
28
|
+
x: number;
|
|
29
|
+
y: number;
|
|
30
|
+
};
|
|
31
|
+
centerDist: {
|
|
32
|
+
x: number;
|
|
33
|
+
y: number;
|
|
34
|
+
};
|
|
35
|
+
/** 起始角 */
|
|
36
|
+
centerDistAngle: number;
|
|
37
|
+
width: number;
|
|
38
|
+
height: number;
|
|
39
|
+
isHorizontal: boolean;
|
|
40
|
+
}
|
|
41
|
+
/** 逐字预设(offset / curve)的上下文 */
|
|
42
|
+
interface VerbatimContext {
|
|
43
|
+
/** 强度数组(已 /100) */
|
|
44
|
+
intensities: number[];
|
|
45
|
+
baseWidth: number;
|
|
46
|
+
lineHeight: number;
|
|
47
|
+
isHorizontal: boolean;
|
|
48
|
+
boundingBox: BoundingBox;
|
|
49
|
+
}
|
|
50
|
+
/** 逐字 `perChar` 回调拿到的字符信息 */
|
|
51
|
+
interface DeformationCharInfo {
|
|
52
|
+
paragraphIndex: number;
|
|
53
|
+
fragmentIndex: number;
|
|
54
|
+
characterIndex: number;
|
|
55
|
+
character: Character;
|
|
56
|
+
}
|
|
57
|
+
interface PresetBase {
|
|
58
|
+
/** 未显式提供 intensities 时使用的默认值 */
|
|
59
|
+
defaultIntensities?: number[];
|
|
60
|
+
}
|
|
61
|
+
/** FFD(自由变形)预设:网格规格 + 预处理开关 + 控制点构造 */
|
|
62
|
+
interface FfdPreset extends PresetBase {
|
|
63
|
+
engine: 'ffd';
|
|
64
|
+
/** true=贝塞尔 FFD(需平滑),false=线性 FFD */
|
|
65
|
+
bezier: boolean;
|
|
66
|
+
hBlocks: number;
|
|
67
|
+
vBlocks: number;
|
|
68
|
+
/** 变形前先把字形直线拆段 */
|
|
69
|
+
breakLine?: boolean;
|
|
70
|
+
/** 变形前把直线转二次贝塞尔 */
|
|
71
|
+
lineToQuad?: boolean;
|
|
72
|
+
build: (points: Vector2[], ctx: FfdContext) => void;
|
|
73
|
+
}
|
|
74
|
+
/** Bend(整体弯曲)预设 */
|
|
75
|
+
interface BendPreset extends PresetBase {
|
|
76
|
+
engine: 'bend';
|
|
77
|
+
/** true=竖向弯曲,false=横向,'auto'/省略=跟随书写方向 */
|
|
78
|
+
vertical?: boolean | 'auto';
|
|
79
|
+
transform: (ctx: BendContext) => (point: Vector2Like) => readonly [number, number];
|
|
80
|
+
}
|
|
81
|
+
/** 逐字沿曲线排布预设 */
|
|
82
|
+
interface CurvePreset extends PresetBase {
|
|
83
|
+
engine: 'curve';
|
|
84
|
+
/** 字符是否沿切线旋转,默认 true */
|
|
85
|
+
followTangent?: boolean;
|
|
86
|
+
/** 是否沿法线方向按 centerDiviation 外扩(多边形需要) */
|
|
87
|
+
expandAlongNormal?: boolean;
|
|
88
|
+
makeCurve: (ctx: VerbatimContext) => DeformationCurve;
|
|
89
|
+
}
|
|
90
|
+
/** 逐字位移/旋转预设 */
|
|
91
|
+
interface OffsetPreset extends PresetBase {
|
|
92
|
+
engine: 'offset';
|
|
93
|
+
/** 逐控制点变换;arg 为 perChar 的返回值 */
|
|
94
|
+
point: (point: Vector2Like, arg: any) => readonly [number, number];
|
|
95
|
+
/** 逐字计算参数(透传给 point 的 arg) */
|
|
96
|
+
perChar?: (ctx: VerbatimContext, info: DeformationCharInfo) => any;
|
|
97
|
+
}
|
|
98
|
+
type DeformationPreset = FfdPreset | BendPreset | CurvePreset | OffsetPreset;
|
|
99
|
+
|
|
100
|
+
export type { BendContext as B, CurvePreset as C, DeformationCharInfo as D, FfdContext as F, OffsetPreset as O, VerbatimContext as V, BendPreset as a, DeformationCurve as b, DeformationPreset as c, FfdPreset as d };
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
const modernIdoc = require('modern-idoc');
|
|
4
4
|
const modernPath2d = require('modern-path2d');
|
|
5
5
|
const modernFont = require('modern-font');
|
|
6
|
+
const deformation = require('./modern-text.B2xfrqDc.cjs');
|
|
6
7
|
|
|
7
8
|
function createSvgLoader() {
|
|
8
9
|
const loaded = /* @__PURE__ */ new Map();
|
|
@@ -530,6 +531,18 @@ class Character {
|
|
|
530
531
|
this.fontStyle = fsSelectionMap[os2.fsSelection] ?? macStyleMap[head.macStyle];
|
|
531
532
|
return this;
|
|
532
533
|
}
|
|
534
|
+
/**
|
|
535
|
+
* Populate glyph metrics only (advance width/height, ascender/descender,
|
|
536
|
+
* baseline, …) without building the glyph `path` or touching boxes.
|
|
537
|
+
*
|
|
538
|
+
* The DOM {@link DomMeasurer} never needs this — it reads positions back from the
|
|
539
|
+
* browser. A pure-JS measurer (e.g. `FontMeasurer`) must know advances *before*
|
|
540
|
+
* it can place characters, so it calls this ahead of layout. `update()` later
|
|
541
|
+
* recomputes the same metrics while building the path, so this is idempotent.
|
|
542
|
+
*/
|
|
543
|
+
measureGlyph(fonts) {
|
|
544
|
+
return this.updateGlyph(this._getFontSFNT(fonts));
|
|
545
|
+
}
|
|
533
546
|
update(fonts) {
|
|
534
547
|
const sfnt = this._getFontSFNT(fonts);
|
|
535
548
|
if (!sfnt) {
|
|
@@ -704,10 +717,6 @@ class Paragraph {
|
|
|
704
717
|
}
|
|
705
718
|
}
|
|
706
719
|
|
|
707
|
-
function definePlugin(options) {
|
|
708
|
-
return options;
|
|
709
|
-
}
|
|
710
|
-
|
|
711
720
|
let sharedContainer;
|
|
712
721
|
function getSharedContainer() {
|
|
713
722
|
if (sharedContainer?.isConnected) {
|
|
@@ -726,7 +735,7 @@ function getSharedContainer() {
|
|
|
726
735
|
sharedContainer = container;
|
|
727
736
|
return container;
|
|
728
737
|
}
|
|
729
|
-
class
|
|
738
|
+
class DomMeasurer {
|
|
730
739
|
static notZeroStyles = /* @__PURE__ */ new Set([
|
|
731
740
|
"width",
|
|
732
741
|
"height"
|
|
@@ -761,7 +770,7 @@ class Measurer {
|
|
|
761
770
|
return cached;
|
|
762
771
|
}
|
|
763
772
|
const domStyle = {};
|
|
764
|
-
const { notZeroStyles, pxStyles } =
|
|
773
|
+
const { notZeroStyles, pxStyles } = DomMeasurer;
|
|
765
774
|
for (const key in style) {
|
|
766
775
|
const value = style[key];
|
|
767
776
|
if (notZeroStyles.has(key) && value === 0) {
|
|
@@ -1070,6 +1079,253 @@ class Measurer {
|
|
|
1070
1079
|
}
|
|
1071
1080
|
}
|
|
1072
1081
|
|
|
1082
|
+
function side(style, name, edge) {
|
|
1083
|
+
return style[`${name}${edge}`] ?? style[name] ?? 0;
|
|
1084
|
+
}
|
|
1085
|
+
class FontMeasurer {
|
|
1086
|
+
measure(paragraphs, rootStyle, _dom, fonts) {
|
|
1087
|
+
const _fonts = fonts ?? modernFont.fonts;
|
|
1088
|
+
for (const paragraph of paragraphs) {
|
|
1089
|
+
for (const fragment of paragraph.fragments) {
|
|
1090
|
+
for (const character of fragment.characters) {
|
|
1091
|
+
character.measureGlyph(_fonts);
|
|
1092
|
+
}
|
|
1093
|
+
}
|
|
1094
|
+
}
|
|
1095
|
+
return rootStyle.writingMode.includes("vertical") ? this._measureVertical(paragraphs, rootStyle) : this._measureHorizontal(paragraphs, rootStyle);
|
|
1096
|
+
}
|
|
1097
|
+
_rootPadding(rootStyle) {
|
|
1098
|
+
return {
|
|
1099
|
+
top: side(rootStyle, "padding", "Top"),
|
|
1100
|
+
right: side(rootStyle, "padding", "Right"),
|
|
1101
|
+
bottom: side(rootStyle, "padding", "Bottom"),
|
|
1102
|
+
left: side(rootStyle, "padding", "Left")
|
|
1103
|
+
};
|
|
1104
|
+
}
|
|
1105
|
+
_measureHorizontal(paragraphs, rootStyle) {
|
|
1106
|
+
const rootPad = this._rootPadding(rootStyle);
|
|
1107
|
+
const hasWidth = typeof rootStyle.width === "number";
|
|
1108
|
+
const availWidth = hasWidth ? rootStyle.width - rootPad.left - rootPad.right : Infinity;
|
|
1109
|
+
let y = rootPad.top;
|
|
1110
|
+
let maxRight = rootPad.left;
|
|
1111
|
+
for (const paragraph of paragraphs) {
|
|
1112
|
+
const pStyle = paragraph.computedStyle;
|
|
1113
|
+
const pBox = paragraph.style;
|
|
1114
|
+
const mTop = side(pBox, "margin", "Top");
|
|
1115
|
+
const mBottom = side(pBox, "margin", "Bottom");
|
|
1116
|
+
const mLeft = side(pBox, "margin", "Left");
|
|
1117
|
+
const mRight = side(pBox, "margin", "Right");
|
|
1118
|
+
const pTop = side(pBox, "padding", "Top");
|
|
1119
|
+
const pBottom = side(pBox, "padding", "Bottom");
|
|
1120
|
+
const pLeft = side(pBox, "padding", "Left");
|
|
1121
|
+
const pRight = side(pBox, "padding", "Right");
|
|
1122
|
+
const liLeft = rootPad.left + mLeft + pLeft;
|
|
1123
|
+
const liAvail = availWidth === Infinity ? Infinity : availWidth - mLeft - mRight - pLeft - pRight;
|
|
1124
|
+
y += mTop + pTop;
|
|
1125
|
+
const paraTop = y;
|
|
1126
|
+
let paraRight = liLeft;
|
|
1127
|
+
const lines = this._breakLines(paragraph, liAvail);
|
|
1128
|
+
const align = pStyle.textAlign;
|
|
1129
|
+
const indent = pStyle.textIndent ?? 0;
|
|
1130
|
+
for (let i = 0; i < lines.length; i++) {
|
|
1131
|
+
const line = lines[i];
|
|
1132
|
+
const lineIndent = i === 0 ? indent : 0;
|
|
1133
|
+
let lineHeight = pStyle.fontSize * pStyle.lineHeight;
|
|
1134
|
+
let contentWidth = 0;
|
|
1135
|
+
for (const c of line) {
|
|
1136
|
+
if (c.fontHeight > lineHeight) {
|
|
1137
|
+
lineHeight = c.fontHeight;
|
|
1138
|
+
}
|
|
1139
|
+
contentWidth += this._advance(c);
|
|
1140
|
+
}
|
|
1141
|
+
let x = liLeft + lineIndent;
|
|
1142
|
+
if (liAvail !== Infinity) {
|
|
1143
|
+
const slack = liAvail - lineIndent - contentWidth;
|
|
1144
|
+
if (align === "center") {
|
|
1145
|
+
x += slack / 2;
|
|
1146
|
+
} else if (align === "end" || align === "right") {
|
|
1147
|
+
x += slack;
|
|
1148
|
+
}
|
|
1149
|
+
}
|
|
1150
|
+
for (const c of line) {
|
|
1151
|
+
const adv = c.advanceWidth;
|
|
1152
|
+
const contentHeight = c.advanceHeight;
|
|
1153
|
+
const fontHeight = c.fontHeight;
|
|
1154
|
+
c.inlineBox.left = x;
|
|
1155
|
+
c.inlineBox.top = y + (lineHeight - contentHeight) / 2;
|
|
1156
|
+
c.inlineBox.width = adv;
|
|
1157
|
+
c.inlineBox.height = contentHeight;
|
|
1158
|
+
c.lineBox.left = x;
|
|
1159
|
+
c.lineBox.top = c.inlineBox.top + (contentHeight - fontHeight) / 2;
|
|
1160
|
+
c.lineBox.width = adv;
|
|
1161
|
+
c.lineBox.height = fontHeight;
|
|
1162
|
+
x += this._advance(c);
|
|
1163
|
+
}
|
|
1164
|
+
if (x > paraRight) {
|
|
1165
|
+
paraRight = x;
|
|
1166
|
+
}
|
|
1167
|
+
y += lineHeight;
|
|
1168
|
+
}
|
|
1169
|
+
if (paraRight > maxRight) {
|
|
1170
|
+
maxRight = paraRight;
|
|
1171
|
+
}
|
|
1172
|
+
for (const fragment of paragraph.fragments) {
|
|
1173
|
+
this._unionInto(fragment.inlineBox, fragment.characters.map((c) => c.inlineBox));
|
|
1174
|
+
}
|
|
1175
|
+
paragraph.lineBox.left = liLeft;
|
|
1176
|
+
paragraph.lineBox.top = paraTop;
|
|
1177
|
+
paragraph.lineBox.width = liAvail === Infinity ? paraRight - liLeft : liAvail;
|
|
1178
|
+
paragraph.lineBox.height = y - paraTop;
|
|
1179
|
+
y += pBottom + mBottom;
|
|
1180
|
+
}
|
|
1181
|
+
const contentBottom = y + rootPad.bottom;
|
|
1182
|
+
const totalWidth = hasWidth ? rootStyle.width : maxRight + rootPad.right;
|
|
1183
|
+
const totalHeight = typeof rootStyle.height === "number" ? rootStyle.height : contentBottom;
|
|
1184
|
+
if (typeof rootStyle.height === "number") {
|
|
1185
|
+
const slack = totalHeight - contentBottom;
|
|
1186
|
+
const va = rootStyle.verticalAlign;
|
|
1187
|
+
const dy = va === "middle" ? slack / 2 : va === "bottom" ? slack : 0;
|
|
1188
|
+
if (dy) {
|
|
1189
|
+
this._shiftAll(paragraphs, dy);
|
|
1190
|
+
}
|
|
1191
|
+
}
|
|
1192
|
+
return {
|
|
1193
|
+
paragraphs,
|
|
1194
|
+
boundingBox: new modernPath2d.BoundingBox(0, 0, totalWidth, totalHeight)
|
|
1195
|
+
};
|
|
1196
|
+
}
|
|
1197
|
+
/**
|
|
1198
|
+
* Vertical writing-mode (`vertical-rl`): columns stack right-to-left, glyphs
|
|
1199
|
+
* flow top→bottom. It is the horizontal layout with the inline and block axes
|
|
1200
|
+
* swapped — the inline (down-column) advance is `advanceWidth` (CJK upright = em;
|
|
1201
|
+
* Latin is rotated, so its advance ≈ its width), the cross-axis content box is
|
|
1202
|
+
* `advanceHeight`, and the column thickness is `fontHeight`. The lineBox is
|
|
1203
|
+
* derived exactly as DomMeasurer.measureParagraphDom's vertical branch, so it
|
|
1204
|
+
* stays accurate even though inlineBox carries the content-box rounding residual.
|
|
1205
|
+
*
|
|
1206
|
+
* v1: `vertical-rl` only; no per-paragraph margin/padding or block alignment.
|
|
1207
|
+
*/
|
|
1208
|
+
_measureVertical(paragraphs, rootStyle) {
|
|
1209
|
+
const rootPad = this._rootPadding(rootStyle);
|
|
1210
|
+
const hasHeight = typeof rootStyle.height === "number";
|
|
1211
|
+
const availHeight = hasHeight ? rootStyle.height - rootPad.top - rootPad.bottom : Infinity;
|
|
1212
|
+
const columns = [];
|
|
1213
|
+
for (const paragraph of paragraphs) {
|
|
1214
|
+
const strut = paragraph.computedStyle.fontSize * paragraph.computedStyle.lineHeight;
|
|
1215
|
+
for (const line of this._breakLines(paragraph, availHeight)) {
|
|
1216
|
+
let thickness = strut;
|
|
1217
|
+
for (const c of line) {
|
|
1218
|
+
if (c.fontHeight > thickness) {
|
|
1219
|
+
thickness = c.fontHeight;
|
|
1220
|
+
}
|
|
1221
|
+
}
|
|
1222
|
+
columns.push({ chars: line, thickness });
|
|
1223
|
+
}
|
|
1224
|
+
}
|
|
1225
|
+
const totalThickness = columns.reduce((sum, c) => sum + c.thickness, 0);
|
|
1226
|
+
const hasWidth = typeof rootStyle.width === "number";
|
|
1227
|
+
const blockWidth = hasWidth ? Math.max(rootStyle.width - rootPad.left - rootPad.right, totalThickness) : totalThickness;
|
|
1228
|
+
let xRight = rootPad.left + blockWidth;
|
|
1229
|
+
let maxBottom = rootPad.top;
|
|
1230
|
+
for (const column of columns) {
|
|
1231
|
+
xRight -= column.thickness;
|
|
1232
|
+
const colLeft = xRight;
|
|
1233
|
+
let y = rootPad.top;
|
|
1234
|
+
for (const c of column.chars) {
|
|
1235
|
+
const advance = c.advanceWidth;
|
|
1236
|
+
const contentWidth = c.advanceHeight;
|
|
1237
|
+
const fontHeight = c.fontHeight;
|
|
1238
|
+
c.inlineBox.top = y;
|
|
1239
|
+
c.inlineBox.height = advance;
|
|
1240
|
+
c.inlineBox.width = contentWidth;
|
|
1241
|
+
c.inlineBox.left = colLeft + (column.thickness - contentWidth) / 2;
|
|
1242
|
+
c.lineBox.left = c.inlineBox.left + (contentWidth - fontHeight) / 2;
|
|
1243
|
+
c.lineBox.top = y;
|
|
1244
|
+
c.lineBox.width = fontHeight;
|
|
1245
|
+
c.lineBox.height = advance;
|
|
1246
|
+
y += this._advance(c);
|
|
1247
|
+
}
|
|
1248
|
+
if (y > maxBottom) {
|
|
1249
|
+
maxBottom = y;
|
|
1250
|
+
}
|
|
1251
|
+
}
|
|
1252
|
+
for (const paragraph of paragraphs) {
|
|
1253
|
+
for (const fragment of paragraph.fragments) {
|
|
1254
|
+
this._unionInto(fragment.inlineBox, fragment.characters.map((c) => c.inlineBox));
|
|
1255
|
+
}
|
|
1256
|
+
this._unionInto(
|
|
1257
|
+
paragraph.lineBox,
|
|
1258
|
+
paragraph.fragments.flatMap((f) => f.characters.map((c) => c.inlineBox))
|
|
1259
|
+
);
|
|
1260
|
+
}
|
|
1261
|
+
const totalWidth = hasWidth ? rootStyle.width : blockWidth + rootPad.left + rootPad.right;
|
|
1262
|
+
const totalHeight = hasHeight ? rootStyle.height : maxBottom + rootPad.bottom;
|
|
1263
|
+
return {
|
|
1264
|
+
paragraphs,
|
|
1265
|
+
boundingBox: new modernPath2d.BoundingBox(0, 0, totalWidth, totalHeight)
|
|
1266
|
+
};
|
|
1267
|
+
}
|
|
1268
|
+
/** Advance step including CSS letter-spacing (px). */
|
|
1269
|
+
_advance(character) {
|
|
1270
|
+
return character.advanceWidth + (character.computedStyle.letterSpacing ?? 0);
|
|
1271
|
+
}
|
|
1272
|
+
/**
|
|
1273
|
+
* Break a paragraph's characters into visual lines.
|
|
1274
|
+
* v1: `word-break: break-all` (break before any character that would overflow)
|
|
1275
|
+
* plus explicit `\n`/`\r` hard breaks. The newline itself occupies no line box.
|
|
1276
|
+
*/
|
|
1277
|
+
_breakLines(paragraph, avail) {
|
|
1278
|
+
const lines = [];
|
|
1279
|
+
let current = [];
|
|
1280
|
+
let width = 0;
|
|
1281
|
+
const flush = () => {
|
|
1282
|
+
lines.push(current);
|
|
1283
|
+
current = [];
|
|
1284
|
+
width = 0;
|
|
1285
|
+
};
|
|
1286
|
+
for (const fragment of paragraph.fragments) {
|
|
1287
|
+
for (const c of fragment.characters) {
|
|
1288
|
+
if (c.content === "\n" || c.content === "\r") {
|
|
1289
|
+
flush();
|
|
1290
|
+
continue;
|
|
1291
|
+
}
|
|
1292
|
+
if (avail !== Infinity && current.length > 0 && width + c.advanceWidth > avail) {
|
|
1293
|
+
flush();
|
|
1294
|
+
}
|
|
1295
|
+
current.push(c);
|
|
1296
|
+
width += this._advance(c);
|
|
1297
|
+
}
|
|
1298
|
+
}
|
|
1299
|
+
flush();
|
|
1300
|
+
return lines;
|
|
1301
|
+
}
|
|
1302
|
+
_unionInto(target, boxes) {
|
|
1303
|
+
const used = boxes.filter((b) => b.width !== 0 || b.height !== 0);
|
|
1304
|
+
if (!used.length) {
|
|
1305
|
+
return;
|
|
1306
|
+
}
|
|
1307
|
+
const u = modernPath2d.BoundingBox.from(...used);
|
|
1308
|
+
target.left = u.left;
|
|
1309
|
+
target.top = u.top;
|
|
1310
|
+
target.width = u.width;
|
|
1311
|
+
target.height = u.height;
|
|
1312
|
+
}
|
|
1313
|
+
_shiftAll(paragraphs, dy) {
|
|
1314
|
+
for (const paragraph of paragraphs) {
|
|
1315
|
+
paragraph.lineBox.top += dy;
|
|
1316
|
+
for (const fragment of paragraph.fragments) {
|
|
1317
|
+
fragment.inlineBox.top += dy;
|
|
1318
|
+
for (const character of fragment.characters) {
|
|
1319
|
+
character.inlineBox.top += dy;
|
|
1320
|
+
character.lineBox.top += dy;
|
|
1321
|
+
}
|
|
1322
|
+
}
|
|
1323
|
+
}
|
|
1324
|
+
}
|
|
1325
|
+
dispose() {
|
|
1326
|
+
}
|
|
1327
|
+
}
|
|
1328
|
+
|
|
1073
1329
|
function backgroundPlugin() {
|
|
1074
1330
|
const pathSet = new modernPath2d.Path2DSet();
|
|
1075
1331
|
const loader = createSvgLoader();
|
|
@@ -1213,7 +1469,7 @@ function highlightPlugin() {
|
|
|
1213
1469
|
const clipRects = [];
|
|
1214
1470
|
const loader = createSvgLoader();
|
|
1215
1471
|
const parser = createSvgParser(loader);
|
|
1216
|
-
return definePlugin({
|
|
1472
|
+
return deformation.definePlugin({
|
|
1217
1473
|
name: "highlight",
|
|
1218
1474
|
pathSet,
|
|
1219
1475
|
context: {
|
|
@@ -1458,7 +1714,7 @@ function highlightPlugin() {
|
|
|
1458
1714
|
}
|
|
1459
1715
|
boundingBoxs.push(box);
|
|
1460
1716
|
});
|
|
1461
|
-
return modernPath2d.BoundingBox.from(...boundingBoxs);
|
|
1717
|
+
return boundingBoxs.length ? modernPath2d.BoundingBox.from(...boundingBoxs) : void 0;
|
|
1462
1718
|
},
|
|
1463
1719
|
render: (renderer) => {
|
|
1464
1720
|
const { text, context } = renderer;
|
|
@@ -1482,7 +1738,7 @@ function genDisc(r, color) {
|
|
|
1482
1738
|
}
|
|
1483
1739
|
function listStylePlugin() {
|
|
1484
1740
|
const pathSet = new modernPath2d.Path2DSet();
|
|
1485
|
-
return definePlugin({
|
|
1741
|
+
return deformation.definePlugin({
|
|
1486
1742
|
name: "listStyle",
|
|
1487
1743
|
pathSet,
|
|
1488
1744
|
update: (text) => {
|
|
@@ -1564,7 +1820,7 @@ function outlinePlugin() {
|
|
|
1564
1820
|
|
|
1565
1821
|
function renderPlugin() {
|
|
1566
1822
|
const pathSet = new modernPath2d.Path2DSet();
|
|
1567
|
-
return definePlugin({
|
|
1823
|
+
return deformation.definePlugin({
|
|
1568
1824
|
name: "render",
|
|
1569
1825
|
pathSet,
|
|
1570
1826
|
update: (text) => {
|
|
@@ -1679,7 +1935,7 @@ function renderPlugin() {
|
|
|
1679
1935
|
|
|
1680
1936
|
function textDecorationPlugin() {
|
|
1681
1937
|
const pathSet = new modernPath2d.Path2DSet();
|
|
1682
|
-
return definePlugin({
|
|
1938
|
+
return deformation.definePlugin({
|
|
1683
1939
|
name: "textDecoration",
|
|
1684
1940
|
pathSet,
|
|
1685
1941
|
update: (text) => {
|
|
@@ -1828,7 +2084,7 @@ class Text extends modernIdoc.Reactivable {
|
|
|
1828
2084
|
glyphBox = new modernPath2d.BoundingBox();
|
|
1829
2085
|
pathBox = new modernPath2d.BoundingBox();
|
|
1830
2086
|
boundingBox = new modernPath2d.BoundingBox();
|
|
1831
|
-
measurer = new
|
|
2087
|
+
measurer = new DomMeasurer();
|
|
1832
2088
|
plugins = /* @__PURE__ */ new Map();
|
|
1833
2089
|
pathSets = [];
|
|
1834
2090
|
_paragraphs = [];
|
|
@@ -1883,8 +2139,15 @@ class Text extends modernIdoc.Reactivable {
|
|
|
1883
2139
|
measureDom,
|
|
1884
2140
|
fonts,
|
|
1885
2141
|
fill,
|
|
1886
|
-
outline
|
|
2142
|
+
outline,
|
|
2143
|
+
deformation: deformation$1
|
|
1887
2144
|
} = modernIdoc.normalizeText(options);
|
|
2145
|
+
if (options.measurer && typeof options.measurer !== "string") {
|
|
2146
|
+
this.measurer = options.measurer;
|
|
2147
|
+
} else {
|
|
2148
|
+
const kind = options.measurer ?? (fonts ? "font" : "dom");
|
|
2149
|
+
this.measurer = kind === "font" ? new FontMeasurer() : new DomMeasurer();
|
|
2150
|
+
}
|
|
1888
2151
|
this.debug = options.debug ?? false;
|
|
1889
2152
|
this.content = content;
|
|
1890
2153
|
this.effects = effects;
|
|
@@ -1893,7 +2156,8 @@ class Text extends modernIdoc.Reactivable {
|
|
|
1893
2156
|
this.fonts = fonts;
|
|
1894
2157
|
this.fill = fill;
|
|
1895
2158
|
this.outline = outline;
|
|
1896
|
-
this.
|
|
2159
|
+
this.deformation = deformation$1;
|
|
2160
|
+
this.use(backgroundPlugin()).use(outlinePlugin()).use(listStylePlugin()).use(textDecorationPlugin()).use(highlightPlugin()).use(renderPlugin()).use(deformation.deformationPlugin());
|
|
1897
2161
|
(options.plugins ?? []).forEach((plugin) => {
|
|
1898
2162
|
this.use(plugin);
|
|
1899
2163
|
});
|
|
@@ -1958,6 +2222,9 @@ class Text extends modernIdoc.Reactivable {
|
|
|
1958
2222
|
}
|
|
1959
2223
|
createDom() {
|
|
1960
2224
|
this._update();
|
|
2225
|
+
if (!this.measurer.createDom) {
|
|
2226
|
+
throw new Error("current measurer does not support createDom()");
|
|
2227
|
+
}
|
|
1961
2228
|
return this.measurer.createDom(this.paragraphs, this.computedStyle);
|
|
1962
2229
|
}
|
|
1963
2230
|
measure(dom = this.measureDom) {
|
|
@@ -1971,7 +2238,7 @@ class Text extends modernIdoc.Reactivable {
|
|
|
1971
2238
|
boundingBox: this.boundingBox
|
|
1972
2239
|
};
|
|
1973
2240
|
this._update();
|
|
1974
|
-
const result = this.measurer.measure(this.paragraphs, this.computedStyle, dom);
|
|
2241
|
+
const result = this.measurer.measure(this.paragraphs, this.computedStyle, dom, this.fonts);
|
|
1975
2242
|
this.paragraphs = result.paragraphs;
|
|
1976
2243
|
this.lineBox = result.boundingBox;
|
|
1977
2244
|
const characters = this.characters;
|
|
@@ -2103,7 +2370,7 @@ class Text extends modernIdoc.Reactivable {
|
|
|
2103
2370
|
options.onContext?.(ctx);
|
|
2104
2371
|
}
|
|
2105
2372
|
dispose() {
|
|
2106
|
-
this.measurer.dispose();
|
|
2373
|
+
this.measurer.dispose?.();
|
|
2107
2374
|
this._renderer = void 0;
|
|
2108
2375
|
this._rendererCtx = void 0;
|
|
2109
2376
|
}
|
|
@@ -2129,6 +2396,9 @@ __decorateClass([
|
|
|
2129
2396
|
__decorateClass([
|
|
2130
2397
|
modernIdoc.property()
|
|
2131
2398
|
], Text.prototype, "outline");
|
|
2399
|
+
__decorateClass([
|
|
2400
|
+
modernIdoc.property()
|
|
2401
|
+
], Text.prototype, "deformation");
|
|
2132
2402
|
__decorateClass([
|
|
2133
2403
|
modernIdoc.property({ internal: true })
|
|
2134
2404
|
], Text.prototype, "measureDom");
|
|
@@ -2138,14 +2408,14 @@ __decorateClass([
|
|
|
2138
2408
|
|
|
2139
2409
|
exports.Canvas2DRenderer = Canvas2DRenderer;
|
|
2140
2410
|
exports.Character = Character;
|
|
2411
|
+
exports.DomMeasurer = DomMeasurer;
|
|
2412
|
+
exports.FontMeasurer = FontMeasurer;
|
|
2141
2413
|
exports.Fragment = Fragment;
|
|
2142
|
-
exports.Measurer = Measurer;
|
|
2143
2414
|
exports.Paragraph = Paragraph;
|
|
2144
2415
|
exports.Text = Text;
|
|
2145
2416
|
exports.backgroundPlugin = backgroundPlugin;
|
|
2146
2417
|
exports.createSvgLoader = createSvgLoader;
|
|
2147
2418
|
exports.createSvgParser = createSvgParser;
|
|
2148
|
-
exports.definePlugin = definePlugin;
|
|
2149
2419
|
exports.getEffectTransform2D = getEffectTransform2D;
|
|
2150
2420
|
exports.getHighlightStyle = getHighlightStyle;
|
|
2151
2421
|
exports.highlightPlugin = highlightPlugin;
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import { Vector2Like, BoundingBox, Vector2 } from 'modern-path2d';
|
|
2
|
+
import { a as Character } from './modern-text.D4WopQCu.mjs';
|
|
3
|
+
|
|
4
|
+
/** 逐字沿形状排布所需的曲线最小接口(实现了这三个方法即可当作变形曲线) */
|
|
5
|
+
interface DeformationCurve {
|
|
6
|
+
getPointAt: (u: number, output?: Vector2) => Vector2;
|
|
7
|
+
getTangent: (t: number, output?: Vector2) => Vector2;
|
|
8
|
+
getNormal: (t: number, output?: Vector2) => Vector2;
|
|
9
|
+
}
|
|
10
|
+
/** FFD 预设 `build` 的上下文 */
|
|
11
|
+
interface FfdContext {
|
|
12
|
+
/** 强度 1(intensities[0],已 /100) */
|
|
13
|
+
a: number;
|
|
14
|
+
/** 强度 2(intensities[1],已 /100) */
|
|
15
|
+
b: number;
|
|
16
|
+
baseWidth: number;
|
|
17
|
+
baseHeight: number;
|
|
18
|
+
lineHeight: number;
|
|
19
|
+
/** 沿当前书写方向调整控制点(内部处理横/竖轴向互换) */
|
|
20
|
+
adjust: (point: Vector2Like, dx: number, dy: number) => void;
|
|
21
|
+
}
|
|
22
|
+
/** Bend 预设 `transform` 的上下文(引擎已算好弯曲几何) */
|
|
23
|
+
interface BendContext {
|
|
24
|
+
/** 弯曲半径 */
|
|
25
|
+
lineHeight: number;
|
|
26
|
+
size: number;
|
|
27
|
+
center: {
|
|
28
|
+
x: number;
|
|
29
|
+
y: number;
|
|
30
|
+
};
|
|
31
|
+
centerDist: {
|
|
32
|
+
x: number;
|
|
33
|
+
y: number;
|
|
34
|
+
};
|
|
35
|
+
/** 起始角 */
|
|
36
|
+
centerDistAngle: number;
|
|
37
|
+
width: number;
|
|
38
|
+
height: number;
|
|
39
|
+
isHorizontal: boolean;
|
|
40
|
+
}
|
|
41
|
+
/** 逐字预设(offset / curve)的上下文 */
|
|
42
|
+
interface VerbatimContext {
|
|
43
|
+
/** 强度数组(已 /100) */
|
|
44
|
+
intensities: number[];
|
|
45
|
+
baseWidth: number;
|
|
46
|
+
lineHeight: number;
|
|
47
|
+
isHorizontal: boolean;
|
|
48
|
+
boundingBox: BoundingBox;
|
|
49
|
+
}
|
|
50
|
+
/** 逐字 `perChar` 回调拿到的字符信息 */
|
|
51
|
+
interface DeformationCharInfo {
|
|
52
|
+
paragraphIndex: number;
|
|
53
|
+
fragmentIndex: number;
|
|
54
|
+
characterIndex: number;
|
|
55
|
+
character: Character;
|
|
56
|
+
}
|
|
57
|
+
interface PresetBase {
|
|
58
|
+
/** 未显式提供 intensities 时使用的默认值 */
|
|
59
|
+
defaultIntensities?: number[];
|
|
60
|
+
}
|
|
61
|
+
/** FFD(自由变形)预设:网格规格 + 预处理开关 + 控制点构造 */
|
|
62
|
+
interface FfdPreset extends PresetBase {
|
|
63
|
+
engine: 'ffd';
|
|
64
|
+
/** true=贝塞尔 FFD(需平滑),false=线性 FFD */
|
|
65
|
+
bezier: boolean;
|
|
66
|
+
hBlocks: number;
|
|
67
|
+
vBlocks: number;
|
|
68
|
+
/** 变形前先把字形直线拆段 */
|
|
69
|
+
breakLine?: boolean;
|
|
70
|
+
/** 变形前把直线转二次贝塞尔 */
|
|
71
|
+
lineToQuad?: boolean;
|
|
72
|
+
build: (points: Vector2[], ctx: FfdContext) => void;
|
|
73
|
+
}
|
|
74
|
+
/** Bend(整体弯曲)预设 */
|
|
75
|
+
interface BendPreset extends PresetBase {
|
|
76
|
+
engine: 'bend';
|
|
77
|
+
/** true=竖向弯曲,false=横向,'auto'/省略=跟随书写方向 */
|
|
78
|
+
vertical?: boolean | 'auto';
|
|
79
|
+
transform: (ctx: BendContext) => (point: Vector2Like) => readonly [number, number];
|
|
80
|
+
}
|
|
81
|
+
/** 逐字沿曲线排布预设 */
|
|
82
|
+
interface CurvePreset extends PresetBase {
|
|
83
|
+
engine: 'curve';
|
|
84
|
+
/** 字符是否沿切线旋转,默认 true */
|
|
85
|
+
followTangent?: boolean;
|
|
86
|
+
/** 是否沿法线方向按 centerDiviation 外扩(多边形需要) */
|
|
87
|
+
expandAlongNormal?: boolean;
|
|
88
|
+
makeCurve: (ctx: VerbatimContext) => DeformationCurve;
|
|
89
|
+
}
|
|
90
|
+
/** 逐字位移/旋转预设 */
|
|
91
|
+
interface OffsetPreset extends PresetBase {
|
|
92
|
+
engine: 'offset';
|
|
93
|
+
/** 逐控制点变换;arg 为 perChar 的返回值 */
|
|
94
|
+
point: (point: Vector2Like, arg: any) => readonly [number, number];
|
|
95
|
+
/** 逐字计算参数(透传给 point 的 arg) */
|
|
96
|
+
perChar?: (ctx: VerbatimContext, info: DeformationCharInfo) => any;
|
|
97
|
+
}
|
|
98
|
+
type DeformationPreset = FfdPreset | BendPreset | CurvePreset | OffsetPreset;
|
|
99
|
+
|
|
100
|
+
export type { BendContext as B, CurvePreset as C, DeformationCharInfo as D, FfdContext as F, OffsetPreset as O, VerbatimContext as V, BendPreset as a, DeformationCurve as b, DeformationPreset as c, FfdPreset as d };
|