modern-text 1.12.0 → 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.d.cts +2 -2
- package/dist/deformations/index.d.mts +2 -2
- package/dist/deformations/index.d.ts +2 -2
- package/dist/index.cjs +3 -2
- package/dist/index.d.cts +67 -8
- package/dist/index.d.mts +67 -8
- package/dist/index.d.ts +67 -8
- package/dist/index.js +4 -3
- package/dist/index.mjs +2 -2
- package/dist/shared/{modern-text.d-RdVkZg.d.cts → modern-text.BD7PBYt7.d.cts} +1 -1
- package/dist/shared/{modern-text.Bo2MzSls.d.ts → modern-text.BxijkspX.d.ts} +1 -1
- package/dist/shared/{modern-text.DR1hbjgA.cjs → modern-text.CBgc-cQ1.cjs} +276 -7
- package/dist/shared/{modern-text.w5MRHZDB.d.mts → modern-text.CYa4lfoG.d.mts} +1 -1
- package/dist/shared/{modern-text.CrFHIJtB.mjs → modern-text.ChzjFjsk.mjs} +275 -7
- package/dist/shared/{modern-text.C8g4NZgY.d.cts → modern-text.D4WopQCu.d.cts} +55 -22
- package/dist/shared/{modern-text.C8g4NZgY.d.mts → modern-text.D4WopQCu.d.mts} +55 -22
- package/dist/shared/{modern-text.C8g4NZgY.d.ts → modern-text.D4WopQCu.d.ts} +55 -22
- package/dist/web-components/index.cjs +2 -2
- 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 -2
- package/package.json +6 -6
package/README.md
CHANGED
|
@@ -18,35 +18,296 @@
|
|
|
18
18
|
</a>
|
|
19
19
|
</p>
|
|
20
20
|
|
|
21
|
-
|
|
21
|
+
`modern-text` measures and renders rich text on Canvas with a layout model that
|
|
22
|
+
mirrors the browser's. It has no React/Vue dependency, ships ESM + CJS, and can
|
|
23
|
+
run **either in the browser (using the DOM as ground truth) or fully DOM-free in
|
|
24
|
+
Node / SSR / Web Workers**.
|
|
25
|
+
|
|
26
|
+
## Features
|
|
27
|
+
|
|
28
|
+
- 📐 **DOM-accurate layout** — paragraphs, line wrapping, baselines, alignment.
|
|
29
|
+
- 🧩 **Two interchangeable layout backends**
|
|
30
|
+
- `DomMeasurer` — measures via a hidden DOM tree + `getBoundingClientRect()`.
|
|
31
|
+
- `FontMeasurer` — pure-JS, computes layout from font glyph metrics; runs with
|
|
32
|
+
no `document`, so it works in **Node / SSR / Workers** and is deterministic.
|
|
33
|
+
- ↔️ **Horizontal & vertical** writing modes (`horizontal-tb`, `vertical-rl`).
|
|
34
|
+
- 🅰️ **Rich inline styling** — per-fragment font size/family/weight/style, color,
|
|
35
|
+
letter-spacing, line-height, text-indent, text-align, vertical-align,
|
|
36
|
+
text-decoration (underline / line-through / overline), text-transform,
|
|
37
|
+
text-stroke, padding / margin.
|
|
38
|
+
- 🎨 **Fills & strokes** — solid colors and linear gradients per fragment.
|
|
39
|
+
- 🖍️ **Highlights** — draw an image/SVG behind selected fragments.
|
|
40
|
+
- 🔵 **List markers** — `disc` / `none` / custom image bullets.
|
|
41
|
+
- 🌑 **Effects** — stacked translate / skew / color layers (shadows, offsets).
|
|
42
|
+
- 🌀 **Text deformation** — 34 opt-in presets (arch, bend, wave, trapezoid,
|
|
43
|
+
ellipse, heart, …).
|
|
44
|
+
- ✏️ **`<text-editor>` web component** — cursor, selection and keyboard editing.
|
|
45
|
+
|
|
46
|
+
## Install
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
npm i modern-text modern-font
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
`modern-font` provides the font parsing/loading used for measuring and drawing
|
|
53
|
+
glyphs.
|
|
54
|
+
|
|
55
|
+
## Quick start
|
|
22
56
|
|
|
23
57
|
```ts
|
|
24
58
|
import { fonts } from 'modern-font'
|
|
25
59
|
import { renderText } from 'modern-text'
|
|
26
60
|
|
|
27
|
-
fonts.loadFallbackFont('/fallback.woff')
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
61
|
+
await fonts.loadFallbackFont('/fallback.woff')
|
|
62
|
+
|
|
63
|
+
const view = document.createElement('canvas')
|
|
64
|
+
document.body.append(view)
|
|
65
|
+
|
|
66
|
+
renderText({
|
|
67
|
+
view,
|
|
68
|
+
fonts,
|
|
69
|
+
style: { width: 300, fontSize: 22, textDecoration: 'underline' },
|
|
70
|
+
content: [
|
|
71
|
+
{
|
|
72
|
+
letterSpacing: 3,
|
|
73
|
+
fragments: [
|
|
74
|
+
{ content: 'He', color: 'red', fontSize: 12 },
|
|
75
|
+
{ content: 'llo', color: 'black' },
|
|
76
|
+
],
|
|
38
77
|
},
|
|
39
|
-
content:
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
78
|
+
{ content: ', ', color: 'grey' },
|
|
79
|
+
{ content: 'World!', color: 'black' },
|
|
80
|
+
],
|
|
81
|
+
})
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
## Layout backends
|
|
85
|
+
|
|
86
|
+
By default `modern-text` picks **`'font'`** (the pure-JS `FontMeasurer`) when
|
|
87
|
+
`fonts` are provided, otherwise **`'dom'`** (the browser-based `DomMeasurer`).
|
|
88
|
+
You can force either, or pass a custom `TextMeasurer`:
|
|
89
|
+
|
|
90
|
+
```ts
|
|
91
|
+
new Text({ fonts, measurer: 'font' }) // pure-JS, DOM-free (default with fonts)
|
|
92
|
+
new Text({ fonts, measurer: 'dom' }) // browser ground truth
|
|
93
|
+
new Text({ measurer: myCustomMeasurer }) // any object implementing TextMeasurer
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
### Node / SSR / Workers
|
|
97
|
+
|
|
98
|
+
`FontMeasurer` needs no `document`, so the whole measure → render pipeline runs
|
|
99
|
+
outside the browser. Register fonts from a buffer with `modern-font`:
|
|
100
|
+
|
|
101
|
+
```ts
|
|
102
|
+
import { readFileSync } from 'node:fs'
|
|
103
|
+
import { Fonts, parseFont } from 'modern-font'
|
|
104
|
+
import { Text } from 'modern-text'
|
|
105
|
+
|
|
106
|
+
const buffer = readFileSync('./fonts/NotoSansSC.woff').buffer
|
|
107
|
+
const font = parseFont(buffer)
|
|
108
|
+
const sfnt = font.createSFNT() // .woff → SFNT
|
|
109
|
+
const fonts = new Fonts()
|
|
110
|
+
const entry = { src: '', familySet: new Set(['Noto']), buffer, getFont: () => font, getSFNT: () => sfnt } as any
|
|
111
|
+
fonts.set('Noto', entry)
|
|
112
|
+
fonts.setFallbackFont(entry)
|
|
113
|
+
|
|
114
|
+
const text = new Text({ fonts, content: '你好世界', style: { fontFamily: 'Noto', fontSize: 32 } })
|
|
115
|
+
const result = text.measure() // → boxes for every paragraph / fragment / character
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
## Content model
|
|
119
|
+
|
|
120
|
+
Content is a hierarchy: **Text → Paragraph → Fragment → Character**. Each level
|
|
121
|
+
inherits and merges style downward. `content` accepts several shapes that are
|
|
122
|
+
normalized by [`modern-idoc`](https://github.com/qq15725/modern-idoc):
|
|
123
|
+
|
|
124
|
+
```ts
|
|
125
|
+
// a plain string (single paragraph)
|
|
126
|
+
content: 'Hello World'
|
|
127
|
+
|
|
128
|
+
// an array of paragraphs, each a string or { content, ...paragraphStyle }
|
|
129
|
+
content: [
|
|
130
|
+
{ content: 'Title', fontSize: 40, textAlign: 'center' },
|
|
131
|
+
{ content: 'Body text', color: '#333' },
|
|
132
|
+
]
|
|
133
|
+
|
|
134
|
+
// per-fragment styling inside a paragraph
|
|
135
|
+
content: [
|
|
136
|
+
{
|
|
137
|
+
textAlign: 'center',
|
|
138
|
+
fragments: [
|
|
139
|
+
{ content: 'red ', color: 'red' },
|
|
140
|
+
{ content: 'bold', fontWeight: 'bold' },
|
|
141
|
+
],
|
|
142
|
+
},
|
|
143
|
+
]
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
A newline (`\n`) splits into a new paragraph.
|
|
147
|
+
|
|
148
|
+
## Styling
|
|
149
|
+
|
|
150
|
+
Style can be set at the text (root), paragraph, or fragment level.
|
|
151
|
+
|
|
152
|
+
```ts
|
|
153
|
+
style: {
|
|
154
|
+
// box
|
|
155
|
+
width: 400, height: 200, padding: 16,
|
|
156
|
+
// font
|
|
157
|
+
fontSize: 24, fontFamily: 'Arial', fontWeight: 700, fontStyle: 'italic',
|
|
158
|
+
// text
|
|
159
|
+
color: '#222', lineHeight: 1.4, letterSpacing: 1, textIndent: 24,
|
|
160
|
+
textAlign: 'center', // start | left | center | end | right
|
|
161
|
+
verticalAlign: 'middle', // top | middle | bottom
|
|
162
|
+
writingMode: 'vertical-rl', // horizontal-tb | vertical-rl
|
|
163
|
+
textDecoration: 'underline', // underline | line-through | overline | none
|
|
164
|
+
textTransform: 'uppercase', // uppercase | lowercase
|
|
165
|
+
textStrokeWidth: 2, textStrokeColor: '#000', // outline stroke
|
|
166
|
+
}
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
### Gradient fills
|
|
170
|
+
|
|
171
|
+
```ts
|
|
172
|
+
content: [{
|
|
173
|
+
fragments: [{
|
|
174
|
+
content: 'Gradient',
|
|
175
|
+
fill: {
|
|
176
|
+
linearGradient: {
|
|
177
|
+
angle: 180,
|
|
178
|
+
stops: [
|
|
179
|
+
{ color: '#c7f1ff', offset: 0 },
|
|
180
|
+
{ color: '#ffffff', offset: 1 },
|
|
45
181
|
],
|
|
46
182
|
},
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
183
|
+
},
|
|
184
|
+
}],
|
|
185
|
+
}]
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
### Highlights & list markers
|
|
189
|
+
|
|
190
|
+
```ts
|
|
191
|
+
content: [
|
|
192
|
+
// image drawn behind the fragment
|
|
193
|
+
{ fragments: [{ content: 'highlighted', highlightImage: '/brush.svg' }] },
|
|
194
|
+
// list bullet
|
|
195
|
+
{ content: 'a bullet item', listStyleType: 'disc' },
|
|
196
|
+
{ content: 'a custom bullet', listStyleImage: '/dot.svg' },
|
|
197
|
+
]
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
## Effects
|
|
201
|
+
|
|
202
|
+
`effects` is an ordered stack of transform/color layers drawn behind the main
|
|
203
|
+
glyphs — useful for shadows, 3D offsets and outlines. `translateX/Y` are
|
|
204
|
+
fractions of the font size; `skewX/Y` are degrees.
|
|
205
|
+
|
|
206
|
+
```ts
|
|
207
|
+
renderText({
|
|
208
|
+
view,
|
|
209
|
+
fonts,
|
|
210
|
+
content: 'Effect',
|
|
211
|
+
style: { fontSize: 80, color: '#FEE90C' },
|
|
212
|
+
effects: [
|
|
213
|
+
{ translateX: 0.05, translateY: 0.05, skewY: -5, color: '#000' }, // shadow
|
|
214
|
+
{ skewY: -5, color: '#FEE90C' }, // face
|
|
215
|
+
],
|
|
51
216
|
})
|
|
52
217
|
```
|
|
218
|
+
|
|
219
|
+
## Text deformation
|
|
220
|
+
|
|
221
|
+
Deformation presets are an opt-in subpath. Register them once, then set
|
|
222
|
+
`deformation.type`:
|
|
223
|
+
|
|
224
|
+
```ts
|
|
225
|
+
import { registerDeformations } from 'modern-text/deformations'
|
|
226
|
+
import { renderText } from 'modern-text'
|
|
227
|
+
|
|
228
|
+
registerDeformations()
|
|
229
|
+
|
|
230
|
+
renderText({
|
|
231
|
+
view,
|
|
232
|
+
fonts,
|
|
233
|
+
content: 'Deformation',
|
|
234
|
+
style: { fontSize: 100 },
|
|
235
|
+
deformation: { type: 'arch-curve' },
|
|
236
|
+
})
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
<details>
|
|
240
|
+
<summary>Available presets (34)</summary>
|
|
241
|
+
|
|
242
|
+
`bend` · `bend-vertical` · `arch-curve` · `concave-curve` · `upper-arch-curve` ·
|
|
243
|
+
`lower-arch-curve` · `bulb-curve` · `skew` · `flag-curve` · `trapezoid` ·
|
|
244
|
+
`lower-trapezoid` · `top-trapezoid` · `horizontal-trapezoid` · `bevel` ·
|
|
245
|
+
`upper-roof` · `lower-roof` · `angled-projection` · `folded-corner` ·
|
|
246
|
+
`lateral-stretching` · `vertical-stretching` · `patchwork-by-word` ·
|
|
247
|
+
`step-by-word` · `arch2-by-word` · `wave-by-word` · `step-far-and-near-by-word` ·
|
|
248
|
+
`arch-far-and-near-by-word` · `horizontal-rotate-by-word` ·
|
|
249
|
+
`arbitrary-offset-rotate-by-word` · `horizontal-curved-rotate-by-word` ·
|
|
250
|
+
`ellipse-by-word` · `triangle-by-word` · `pentagon-by-word` ·
|
|
251
|
+
`rectangular-by-word` · `heart-by-word`
|
|
252
|
+
|
|
253
|
+
Register your own with `defineDeformation(name, preset)`.
|
|
254
|
+
</details>
|
|
255
|
+
|
|
256
|
+
## `Text` API
|
|
257
|
+
|
|
258
|
+
For finer control, drive a `Text` instance directly:
|
|
259
|
+
|
|
260
|
+
```ts
|
|
261
|
+
import { Text } from 'modern-text'
|
|
262
|
+
|
|
263
|
+
const text = new Text({ fonts, content: 'Hello', style: { fontSize: 24 } })
|
|
264
|
+
|
|
265
|
+
text.on('update', () => text.render({ view })) // re-render on any change
|
|
266
|
+
await text.load() // load async resources (fonts, plugin assets)
|
|
267
|
+
text.update() // measure + commit + emit 'update'
|
|
268
|
+
text.render({ view, pixelRatio: 2 })
|
|
269
|
+
|
|
270
|
+
text.boundingBox // overall box after measuring
|
|
271
|
+
text.characters // flat list of measured Character (inlineBox / lineBox / path)
|
|
272
|
+
|
|
273
|
+
text.dispose() // release the cached measurer / renderer
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
- `measure()` returns a non-destructive snapshot of all boxes.
|
|
277
|
+
- `update()` measures and commits the result onto the instance.
|
|
278
|
+
- `render({ view })` updates if needed, then draws.
|
|
279
|
+
- Events: `update`, `measure`, `render`.
|
|
280
|
+
|
|
281
|
+
### One-shot helpers
|
|
282
|
+
|
|
283
|
+
```ts
|
|
284
|
+
import { measureText, renderText } from 'modern-text'
|
|
285
|
+
|
|
286
|
+
const result = measureText(options) // sync
|
|
287
|
+
const result = await measureText(options, true) // load fonts first
|
|
288
|
+
|
|
289
|
+
renderText({ view, ...options }) // sync
|
|
290
|
+
await renderText({ view, ...options }, true) // load fonts first
|
|
291
|
+
```
|
|
292
|
+
|
|
293
|
+
## `<text-editor>` web component
|
|
294
|
+
|
|
295
|
+
```ts
|
|
296
|
+
import { TextEditor } from 'modern-text/web-components'
|
|
297
|
+
|
|
298
|
+
TextEditor.register()
|
|
299
|
+
```
|
|
300
|
+
|
|
301
|
+
```html
|
|
302
|
+
<text-editor></text-editor>
|
|
303
|
+
```
|
|
304
|
+
|
|
305
|
+
```ts
|
|
306
|
+
const editor = document.querySelector('text-editor')
|
|
307
|
+
editor.moveToDom(canvas) // overlay the editor on a rendered canvas
|
|
308
|
+
editor.set(text) // bind a Text instance — provides cursor, selection, typing
|
|
309
|
+
```
|
|
310
|
+
|
|
311
|
+
## License
|
|
312
|
+
|
|
313
|
+
[MIT](./LICENSE)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { c as DeformationPreset } from '../shared/modern-text.
|
|
1
|
+
import { c as DeformationPreset } from '../shared/modern-text.BD7PBYt7.cjs';
|
|
2
2
|
import 'modern-path2d';
|
|
3
|
-
import '../shared/modern-text.
|
|
3
|
+
import '../shared/modern-text.D4WopQCu.cjs';
|
|
4
4
|
import 'modern-idoc';
|
|
5
5
|
import 'modern-font';
|
|
6
6
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { c as DeformationPreset } from '../shared/modern-text.
|
|
1
|
+
import { c as DeformationPreset } from '../shared/modern-text.CYa4lfoG.mjs';
|
|
2
2
|
import 'modern-path2d';
|
|
3
|
-
import '../shared/modern-text.
|
|
3
|
+
import '../shared/modern-text.D4WopQCu.mjs';
|
|
4
4
|
import 'modern-idoc';
|
|
5
5
|
import 'modern-font';
|
|
6
6
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { c as DeformationPreset } from '../shared/modern-text.
|
|
1
|
+
import { c as DeformationPreset } from '../shared/modern-text.BxijkspX.js';
|
|
2
2
|
import 'modern-path2d';
|
|
3
|
-
import '../shared/modern-text.
|
|
3
|
+
import '../shared/modern-text.D4WopQCu.js';
|
|
4
4
|
import 'modern-idoc';
|
|
5
5
|
import 'modern-font';
|
|
6
6
|
|
package/dist/index.cjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
const Text = require('./shared/modern-text.
|
|
3
|
+
const Text = require('./shared/modern-text.CBgc-cQ1.cjs');
|
|
4
4
|
const deformation = require('./shared/modern-text.B2xfrqDc.cjs');
|
|
5
5
|
const curves = require('./shared/modern-text.MC5bIC9E.cjs');
|
|
6
6
|
require('modern-idoc');
|
|
@@ -29,8 +29,9 @@ function renderText(options, load) {
|
|
|
29
29
|
|
|
30
30
|
exports.Canvas2DRenderer = Text.Canvas2DRenderer;
|
|
31
31
|
exports.Character = Text.Character;
|
|
32
|
+
exports.DomMeasurer = Text.DomMeasurer;
|
|
33
|
+
exports.FontMeasurer = Text.FontMeasurer;
|
|
32
34
|
exports.Fragment = Text.Fragment;
|
|
33
|
-
exports.Measurer = Text.Measurer;
|
|
34
35
|
exports.Paragraph = Text.Paragraph;
|
|
35
36
|
exports.Text = Text.Text;
|
|
36
37
|
exports.backgroundPlugin = Text.backgroundPlugin;
|
package/dist/index.d.cts
CHANGED
|
@@ -1,13 +1,72 @@
|
|
|
1
|
-
import {
|
|
2
|
-
export { C as Canvas2DRenderer,
|
|
3
|
-
import {
|
|
4
|
-
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
|
|
1
|
+
import { i as Plugin, k as TextMeasurer, P as Paragraph, M as MeasureDomResult, a as Character, O as Options, c as MeasureResult, R as RenderOptions, T as Text } from './shared/modern-text.D4WopQCu.cjs';
|
|
2
|
+
export { C as Canvas2DRenderer, D as DomMeasurer, b as DrawShapePathsOptions, F as Fragment, d as MeasuredCharacter, e as MeasuredCharacterRect, f as MeasuredFragment, g as MeasuredParagraph, h as MeasurerKind, j as TextEvents, t as textDefaultStyle } from './shared/modern-text.D4WopQCu.cjs';
|
|
3
|
+
import { Fonts } from 'modern-font';
|
|
4
|
+
import { FullStyle, NormalizedStyle, NormalizedHighlight, NormalizedEffect } from 'modern-idoc';
|
|
5
|
+
import { BoundingBox, Vector2, Path2DSet, Transform2D } from 'modern-path2d';
|
|
6
|
+
import { c as DeformationPreset } from './shared/modern-text.BD7PBYt7.cjs';
|
|
7
|
+
export { B as BendContext, a as BendPreset, C as CurvePreset, D as DeformationCharInfo, b as DeformationCurve, F as FfdContext, d as FfdPreset, O as OffsetPreset, V as VerbatimContext } from './shared/modern-text.BD7PBYt7.cjs';
|
|
8
8
|
|
|
9
9
|
declare function definePlugin(options: Plugin): Plugin;
|
|
10
10
|
|
|
11
|
+
/**
|
|
12
|
+
* Pure-JS, DOM-free text measurer — a drop-in alternative to {@link DomMeasurer}.
|
|
13
|
+
*
|
|
14
|
+
* Instead of mounting a `<section>/<ul>/<li>/<span>` tree and reading
|
|
15
|
+
* `getBoundingClientRect()`, it computes the same four-level boxes
|
|
16
|
+
* (`character.inlineBox`/`lineBox`, `fragment.inlineBox`, `paragraph.lineBox`)
|
|
17
|
+
* from `modern-font` glyph advances, so the downstream
|
|
18
|
+
* measure → glyph → plugin pipeline is unchanged and it runs in Node/SSR/Worker.
|
|
19
|
+
*
|
|
20
|
+
* ## Scope
|
|
21
|
+
* - `horizontal-tb` (LTR) and `vertical-rl` (columns right-to-left, glyphs top↓)
|
|
22
|
+
* - line breaking: `word-break: break-all` (greedy, break anywhere) + explicit `\n`
|
|
23
|
+
* - per-line `text-align` (start/left/center/end/right), `text-indent` (first line)
|
|
24
|
+
* - box model: root + paragraph `padding`/`margin` (horizontal), `letter-spacing`,
|
|
25
|
+
* `line-height`; block `vertical-align` (top/middle/bottom) at fixed height
|
|
26
|
+
*
|
|
27
|
+
* ## Not yet implemented (TODO)
|
|
28
|
+
* - UAX#14 `word-break: normal` + 避头尾/kinsoku (`line-break`)
|
|
29
|
+
* - BiDi, per-fragment inline `vertical-align`, borders
|
|
30
|
+
* - vertical: per-paragraph margin/padding and block alignment
|
|
31
|
+
* - kerning/ligatures (GSUB/GPOS) — fine for CJK, diverges for proportional Latin
|
|
32
|
+
*
|
|
33
|
+
* Coordinates are relative to the root border-box top-left (matching the DOM
|
|
34
|
+
* measurer, whose rects are taken relative to `section.getBoundingClientRect()`).
|
|
35
|
+
*/
|
|
36
|
+
declare class FontMeasurer implements TextMeasurer {
|
|
37
|
+
measure(paragraphs: Paragraph[], rootStyle: FullStyle, _dom?: HTMLElement, fonts?: Fonts): MeasureDomResult;
|
|
38
|
+
protected _rootPadding(rootStyle: FullStyle): {
|
|
39
|
+
top: number;
|
|
40
|
+
right: number;
|
|
41
|
+
bottom: number;
|
|
42
|
+
left: number;
|
|
43
|
+
};
|
|
44
|
+
protected _measureHorizontal(paragraphs: Paragraph[], rootStyle: FullStyle): MeasureDomResult;
|
|
45
|
+
/**
|
|
46
|
+
* Vertical writing-mode (`vertical-rl`): columns stack right-to-left, glyphs
|
|
47
|
+
* flow top→bottom. It is the horizontal layout with the inline and block axes
|
|
48
|
+
* swapped — the inline (down-column) advance is `advanceWidth` (CJK upright = em;
|
|
49
|
+
* Latin is rotated, so its advance ≈ its width), the cross-axis content box is
|
|
50
|
+
* `advanceHeight`, and the column thickness is `fontHeight`. The lineBox is
|
|
51
|
+
* derived exactly as DomMeasurer.measureParagraphDom's vertical branch, so it
|
|
52
|
+
* stays accurate even though inlineBox carries the content-box rounding residual.
|
|
53
|
+
*
|
|
54
|
+
* v1: `vertical-rl` only; no per-paragraph margin/padding or block alignment.
|
|
55
|
+
*/
|
|
56
|
+
protected _measureVertical(paragraphs: Paragraph[], rootStyle: FullStyle): MeasureDomResult;
|
|
57
|
+
/** Advance step including CSS letter-spacing (px). */
|
|
58
|
+
protected _advance(character: Character): number;
|
|
59
|
+
/**
|
|
60
|
+
* Break a paragraph's characters into visual lines.
|
|
61
|
+
* v1: `word-break: break-all` (break before any character that would overflow)
|
|
62
|
+
* plus explicit `\n`/`\r` hard breaks. The newline itself occupies no line box.
|
|
63
|
+
*/
|
|
64
|
+
protected _breakLines(paragraph: Paragraph, avail: number): Character[][];
|
|
65
|
+
protected _unionInto(target: BoundingBox, boxes: BoundingBox[]): void;
|
|
66
|
+
protected _shiftAll(paragraphs: Paragraph[], dy: number): void;
|
|
67
|
+
dispose(): void;
|
|
68
|
+
}
|
|
69
|
+
|
|
11
70
|
declare function measureText(options: Options, load: true): Promise<MeasureResult>;
|
|
12
71
|
declare function measureText(options: Options): MeasureResult;
|
|
13
72
|
|
|
@@ -181,5 +240,5 @@ declare function parseColormap(colormap: 'none' | Record<string, string>): Recor
|
|
|
181
240
|
declare function isEqualObject(obj1: Record<string, any>, obj2: Record<string, any>): boolean;
|
|
182
241
|
declare function isEqualValue(val1: any, val2: any): boolean;
|
|
183
242
|
|
|
184
|
-
export { CircleCurve, DeformationPreset, EllipseCurve, HeartCurve, MeasureResult, Options, Plugin, PolygonCurve, RectangularCurve, RenderOptions, Text, backgroundPlugin, createSvgLoader, createSvgParser, defineDeformation, definePlugin, deformationPlugin, getDeformationNames, getEffectTransform2D, getHighlightStyle, highlightPlugin, isEqualObject, isEqualValue, listStylePlugin, measureText, outlinePlugin, parseColormap, parseTransformOrigin, parseValueNumber, removeDeformation, renderPlugin, renderText, textDecorationPlugin };
|
|
243
|
+
export { Character, CircleCurve, DeformationPreset, EllipseCurve, FontMeasurer, HeartCurve, MeasureDomResult, MeasureResult, Options, Paragraph, Plugin, PolygonCurve, RectangularCurve, RenderOptions, Text, TextMeasurer, backgroundPlugin, createSvgLoader, createSvgParser, defineDeformation, definePlugin, deformationPlugin, getDeformationNames, getEffectTransform2D, getHighlightStyle, highlightPlugin, isEqualObject, isEqualValue, listStylePlugin, measureText, outlinePlugin, parseColormap, parseTransformOrigin, parseValueNumber, removeDeformation, renderPlugin, renderText, textDecorationPlugin };
|
|
185
244
|
export type { RenderTextOptions, SvgLoader, SvgParser };
|
package/dist/index.d.mts
CHANGED
|
@@ -1,13 +1,72 @@
|
|
|
1
|
-
import {
|
|
2
|
-
export { C as Canvas2DRenderer,
|
|
3
|
-
import {
|
|
4
|
-
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
|
|
1
|
+
import { i as Plugin, k as TextMeasurer, P as Paragraph, M as MeasureDomResult, a as Character, O as Options, c as MeasureResult, R as RenderOptions, T as Text } from './shared/modern-text.D4WopQCu.mjs';
|
|
2
|
+
export { C as Canvas2DRenderer, D as DomMeasurer, b as DrawShapePathsOptions, F as Fragment, d as MeasuredCharacter, e as MeasuredCharacterRect, f as MeasuredFragment, g as MeasuredParagraph, h as MeasurerKind, j as TextEvents, t as textDefaultStyle } from './shared/modern-text.D4WopQCu.mjs';
|
|
3
|
+
import { Fonts } from 'modern-font';
|
|
4
|
+
import { FullStyle, NormalizedStyle, NormalizedHighlight, NormalizedEffect } from 'modern-idoc';
|
|
5
|
+
import { BoundingBox, Vector2, Path2DSet, Transform2D } from 'modern-path2d';
|
|
6
|
+
import { c as DeformationPreset } from './shared/modern-text.CYa4lfoG.mjs';
|
|
7
|
+
export { B as BendContext, a as BendPreset, C as CurvePreset, D as DeformationCharInfo, b as DeformationCurve, F as FfdContext, d as FfdPreset, O as OffsetPreset, V as VerbatimContext } from './shared/modern-text.CYa4lfoG.mjs';
|
|
8
8
|
|
|
9
9
|
declare function definePlugin(options: Plugin): Plugin;
|
|
10
10
|
|
|
11
|
+
/**
|
|
12
|
+
* Pure-JS, DOM-free text measurer — a drop-in alternative to {@link DomMeasurer}.
|
|
13
|
+
*
|
|
14
|
+
* Instead of mounting a `<section>/<ul>/<li>/<span>` tree and reading
|
|
15
|
+
* `getBoundingClientRect()`, it computes the same four-level boxes
|
|
16
|
+
* (`character.inlineBox`/`lineBox`, `fragment.inlineBox`, `paragraph.lineBox`)
|
|
17
|
+
* from `modern-font` glyph advances, so the downstream
|
|
18
|
+
* measure → glyph → plugin pipeline is unchanged and it runs in Node/SSR/Worker.
|
|
19
|
+
*
|
|
20
|
+
* ## Scope
|
|
21
|
+
* - `horizontal-tb` (LTR) and `vertical-rl` (columns right-to-left, glyphs top↓)
|
|
22
|
+
* - line breaking: `word-break: break-all` (greedy, break anywhere) + explicit `\n`
|
|
23
|
+
* - per-line `text-align` (start/left/center/end/right), `text-indent` (first line)
|
|
24
|
+
* - box model: root + paragraph `padding`/`margin` (horizontal), `letter-spacing`,
|
|
25
|
+
* `line-height`; block `vertical-align` (top/middle/bottom) at fixed height
|
|
26
|
+
*
|
|
27
|
+
* ## Not yet implemented (TODO)
|
|
28
|
+
* - UAX#14 `word-break: normal` + 避头尾/kinsoku (`line-break`)
|
|
29
|
+
* - BiDi, per-fragment inline `vertical-align`, borders
|
|
30
|
+
* - vertical: per-paragraph margin/padding and block alignment
|
|
31
|
+
* - kerning/ligatures (GSUB/GPOS) — fine for CJK, diverges for proportional Latin
|
|
32
|
+
*
|
|
33
|
+
* Coordinates are relative to the root border-box top-left (matching the DOM
|
|
34
|
+
* measurer, whose rects are taken relative to `section.getBoundingClientRect()`).
|
|
35
|
+
*/
|
|
36
|
+
declare class FontMeasurer implements TextMeasurer {
|
|
37
|
+
measure(paragraphs: Paragraph[], rootStyle: FullStyle, _dom?: HTMLElement, fonts?: Fonts): MeasureDomResult;
|
|
38
|
+
protected _rootPadding(rootStyle: FullStyle): {
|
|
39
|
+
top: number;
|
|
40
|
+
right: number;
|
|
41
|
+
bottom: number;
|
|
42
|
+
left: number;
|
|
43
|
+
};
|
|
44
|
+
protected _measureHorizontal(paragraphs: Paragraph[], rootStyle: FullStyle): MeasureDomResult;
|
|
45
|
+
/**
|
|
46
|
+
* Vertical writing-mode (`vertical-rl`): columns stack right-to-left, glyphs
|
|
47
|
+
* flow top→bottom. It is the horizontal layout with the inline and block axes
|
|
48
|
+
* swapped — the inline (down-column) advance is `advanceWidth` (CJK upright = em;
|
|
49
|
+
* Latin is rotated, so its advance ≈ its width), the cross-axis content box is
|
|
50
|
+
* `advanceHeight`, and the column thickness is `fontHeight`. The lineBox is
|
|
51
|
+
* derived exactly as DomMeasurer.measureParagraphDom's vertical branch, so it
|
|
52
|
+
* stays accurate even though inlineBox carries the content-box rounding residual.
|
|
53
|
+
*
|
|
54
|
+
* v1: `vertical-rl` only; no per-paragraph margin/padding or block alignment.
|
|
55
|
+
*/
|
|
56
|
+
protected _measureVertical(paragraphs: Paragraph[], rootStyle: FullStyle): MeasureDomResult;
|
|
57
|
+
/** Advance step including CSS letter-spacing (px). */
|
|
58
|
+
protected _advance(character: Character): number;
|
|
59
|
+
/**
|
|
60
|
+
* Break a paragraph's characters into visual lines.
|
|
61
|
+
* v1: `word-break: break-all` (break before any character that would overflow)
|
|
62
|
+
* plus explicit `\n`/`\r` hard breaks. The newline itself occupies no line box.
|
|
63
|
+
*/
|
|
64
|
+
protected _breakLines(paragraph: Paragraph, avail: number): Character[][];
|
|
65
|
+
protected _unionInto(target: BoundingBox, boxes: BoundingBox[]): void;
|
|
66
|
+
protected _shiftAll(paragraphs: Paragraph[], dy: number): void;
|
|
67
|
+
dispose(): void;
|
|
68
|
+
}
|
|
69
|
+
|
|
11
70
|
declare function measureText(options: Options, load: true): Promise<MeasureResult>;
|
|
12
71
|
declare function measureText(options: Options): MeasureResult;
|
|
13
72
|
|
|
@@ -181,5 +240,5 @@ declare function parseColormap(colormap: 'none' | Record<string, string>): Recor
|
|
|
181
240
|
declare function isEqualObject(obj1: Record<string, any>, obj2: Record<string, any>): boolean;
|
|
182
241
|
declare function isEqualValue(val1: any, val2: any): boolean;
|
|
183
242
|
|
|
184
|
-
export { CircleCurve, DeformationPreset, EllipseCurve, HeartCurve, MeasureResult, Options, Plugin, PolygonCurve, RectangularCurve, RenderOptions, Text, backgroundPlugin, createSvgLoader, createSvgParser, defineDeformation, definePlugin, deformationPlugin, getDeformationNames, getEffectTransform2D, getHighlightStyle, highlightPlugin, isEqualObject, isEqualValue, listStylePlugin, measureText, outlinePlugin, parseColormap, parseTransformOrigin, parseValueNumber, removeDeformation, renderPlugin, renderText, textDecorationPlugin };
|
|
243
|
+
export { Character, CircleCurve, DeformationPreset, EllipseCurve, FontMeasurer, HeartCurve, MeasureDomResult, MeasureResult, Options, Paragraph, Plugin, PolygonCurve, RectangularCurve, RenderOptions, Text, TextMeasurer, backgroundPlugin, createSvgLoader, createSvgParser, defineDeformation, definePlugin, deformationPlugin, getDeformationNames, getEffectTransform2D, getHighlightStyle, highlightPlugin, isEqualObject, isEqualValue, listStylePlugin, measureText, outlinePlugin, parseColormap, parseTransformOrigin, parseValueNumber, removeDeformation, renderPlugin, renderText, textDecorationPlugin };
|
|
185
244
|
export type { RenderTextOptions, SvgLoader, SvgParser };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,13 +1,72 @@
|
|
|
1
|
-
import {
|
|
2
|
-
export { C as Canvas2DRenderer,
|
|
3
|
-
import {
|
|
4
|
-
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
|
|
1
|
+
import { i as Plugin, k as TextMeasurer, P as Paragraph, M as MeasureDomResult, a as Character, O as Options, c as MeasureResult, R as RenderOptions, T as Text } from './shared/modern-text.D4WopQCu.js';
|
|
2
|
+
export { C as Canvas2DRenderer, D as DomMeasurer, b as DrawShapePathsOptions, F as Fragment, d as MeasuredCharacter, e as MeasuredCharacterRect, f as MeasuredFragment, g as MeasuredParagraph, h as MeasurerKind, j as TextEvents, t as textDefaultStyle } from './shared/modern-text.D4WopQCu.js';
|
|
3
|
+
import { Fonts } from 'modern-font';
|
|
4
|
+
import { FullStyle, NormalizedStyle, NormalizedHighlight, NormalizedEffect } from 'modern-idoc';
|
|
5
|
+
import { BoundingBox, Vector2, Path2DSet, Transform2D } from 'modern-path2d';
|
|
6
|
+
import { c as DeformationPreset } from './shared/modern-text.BxijkspX.js';
|
|
7
|
+
export { B as BendContext, a as BendPreset, C as CurvePreset, D as DeformationCharInfo, b as DeformationCurve, F as FfdContext, d as FfdPreset, O as OffsetPreset, V as VerbatimContext } from './shared/modern-text.BxijkspX.js';
|
|
8
8
|
|
|
9
9
|
declare function definePlugin(options: Plugin): Plugin;
|
|
10
10
|
|
|
11
|
+
/**
|
|
12
|
+
* Pure-JS, DOM-free text measurer — a drop-in alternative to {@link DomMeasurer}.
|
|
13
|
+
*
|
|
14
|
+
* Instead of mounting a `<section>/<ul>/<li>/<span>` tree and reading
|
|
15
|
+
* `getBoundingClientRect()`, it computes the same four-level boxes
|
|
16
|
+
* (`character.inlineBox`/`lineBox`, `fragment.inlineBox`, `paragraph.lineBox`)
|
|
17
|
+
* from `modern-font` glyph advances, so the downstream
|
|
18
|
+
* measure → glyph → plugin pipeline is unchanged and it runs in Node/SSR/Worker.
|
|
19
|
+
*
|
|
20
|
+
* ## Scope
|
|
21
|
+
* - `horizontal-tb` (LTR) and `vertical-rl` (columns right-to-left, glyphs top↓)
|
|
22
|
+
* - line breaking: `word-break: break-all` (greedy, break anywhere) + explicit `\n`
|
|
23
|
+
* - per-line `text-align` (start/left/center/end/right), `text-indent` (first line)
|
|
24
|
+
* - box model: root + paragraph `padding`/`margin` (horizontal), `letter-spacing`,
|
|
25
|
+
* `line-height`; block `vertical-align` (top/middle/bottom) at fixed height
|
|
26
|
+
*
|
|
27
|
+
* ## Not yet implemented (TODO)
|
|
28
|
+
* - UAX#14 `word-break: normal` + 避头尾/kinsoku (`line-break`)
|
|
29
|
+
* - BiDi, per-fragment inline `vertical-align`, borders
|
|
30
|
+
* - vertical: per-paragraph margin/padding and block alignment
|
|
31
|
+
* - kerning/ligatures (GSUB/GPOS) — fine for CJK, diverges for proportional Latin
|
|
32
|
+
*
|
|
33
|
+
* Coordinates are relative to the root border-box top-left (matching the DOM
|
|
34
|
+
* measurer, whose rects are taken relative to `section.getBoundingClientRect()`).
|
|
35
|
+
*/
|
|
36
|
+
declare class FontMeasurer implements TextMeasurer {
|
|
37
|
+
measure(paragraphs: Paragraph[], rootStyle: FullStyle, _dom?: HTMLElement, fonts?: Fonts): MeasureDomResult;
|
|
38
|
+
protected _rootPadding(rootStyle: FullStyle): {
|
|
39
|
+
top: number;
|
|
40
|
+
right: number;
|
|
41
|
+
bottom: number;
|
|
42
|
+
left: number;
|
|
43
|
+
};
|
|
44
|
+
protected _measureHorizontal(paragraphs: Paragraph[], rootStyle: FullStyle): MeasureDomResult;
|
|
45
|
+
/**
|
|
46
|
+
* Vertical writing-mode (`vertical-rl`): columns stack right-to-left, glyphs
|
|
47
|
+
* flow top→bottom. It is the horizontal layout with the inline and block axes
|
|
48
|
+
* swapped — the inline (down-column) advance is `advanceWidth` (CJK upright = em;
|
|
49
|
+
* Latin is rotated, so its advance ≈ its width), the cross-axis content box is
|
|
50
|
+
* `advanceHeight`, and the column thickness is `fontHeight`. The lineBox is
|
|
51
|
+
* derived exactly as DomMeasurer.measureParagraphDom's vertical branch, so it
|
|
52
|
+
* stays accurate even though inlineBox carries the content-box rounding residual.
|
|
53
|
+
*
|
|
54
|
+
* v1: `vertical-rl` only; no per-paragraph margin/padding or block alignment.
|
|
55
|
+
*/
|
|
56
|
+
protected _measureVertical(paragraphs: Paragraph[], rootStyle: FullStyle): MeasureDomResult;
|
|
57
|
+
/** Advance step including CSS letter-spacing (px). */
|
|
58
|
+
protected _advance(character: Character): number;
|
|
59
|
+
/**
|
|
60
|
+
* Break a paragraph's characters into visual lines.
|
|
61
|
+
* v1: `word-break: break-all` (break before any character that would overflow)
|
|
62
|
+
* plus explicit `\n`/`\r` hard breaks. The newline itself occupies no line box.
|
|
63
|
+
*/
|
|
64
|
+
protected _breakLines(paragraph: Paragraph, avail: number): Character[][];
|
|
65
|
+
protected _unionInto(target: BoundingBox, boxes: BoundingBox[]): void;
|
|
66
|
+
protected _shiftAll(paragraphs: Paragraph[], dy: number): void;
|
|
67
|
+
dispose(): void;
|
|
68
|
+
}
|
|
69
|
+
|
|
11
70
|
declare function measureText(options: Options, load: true): Promise<MeasureResult>;
|
|
12
71
|
declare function measureText(options: Options): MeasureResult;
|
|
13
72
|
|
|
@@ -181,5 +240,5 @@ declare function parseColormap(colormap: 'none' | Record<string, string>): Recor
|
|
|
181
240
|
declare function isEqualObject(obj1: Record<string, any>, obj2: Record<string, any>): boolean;
|
|
182
241
|
declare function isEqualValue(val1: any, val2: any): boolean;
|
|
183
242
|
|
|
184
|
-
export { CircleCurve, DeformationPreset, EllipseCurve, HeartCurve, MeasureResult, Options, Plugin, PolygonCurve, RectangularCurve, RenderOptions, Text, backgroundPlugin, createSvgLoader, createSvgParser, defineDeformation, definePlugin, deformationPlugin, getDeformationNames, getEffectTransform2D, getHighlightStyle, highlightPlugin, isEqualObject, isEqualValue, listStylePlugin, measureText, outlinePlugin, parseColormap, parseTransformOrigin, parseValueNumber, removeDeformation, renderPlugin, renderText, textDecorationPlugin };
|
|
243
|
+
export { Character, CircleCurve, DeformationPreset, EllipseCurve, FontMeasurer, HeartCurve, MeasureDomResult, MeasureResult, Options, Paragraph, Plugin, PolygonCurve, RectangularCurve, RenderOptions, Text, TextMeasurer, backgroundPlugin, createSvgLoader, createSvgParser, defineDeformation, definePlugin, deformationPlugin, getDeformationNames, getEffectTransform2D, getHighlightStyle, highlightPlugin, isEqualObject, isEqualValue, listStylePlugin, measureText, outlinePlugin, parseColormap, parseTransformOrigin, parseValueNumber, removeDeformation, renderPlugin, renderText, textDecorationPlugin };
|
|
185
244
|
export type { RenderTextOptions, SvgLoader, SvgParser };
|