vgpu-render 0.1.0 → 0.2.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 +76 -26
- package/dist/curve.d.ts +1 -1
- package/dist/debug-draw-3d.d.ts +3 -3
- package/dist/debug-draw-3d.js +10 -10
- package/dist/{frame-recorder-2d.d.ts → frame-draw-list-2d.d.ts} +11 -11
- package/dist/{frame-recorder-2d.js → frame-draw-list-2d.js} +6 -6
- package/dist/{frame-recorder.d.ts → frame-draw-list.d.ts} +60 -8
- package/dist/{frame-recorder.js → frame-draw-list.js} +51 -10
- package/dist/geometry-generators.d.ts +2 -2
- package/dist/geometry-generators.js +1 -1
- package/dist/gpu-debug-draw-2d.d.ts +1 -1
- package/dist/gpu-debug-draw-3d.d.ts +1 -1
- package/dist/gpu-debug-draw-3d.js +4 -2
- package/dist/gpu-text-3d.d.ts +1 -1
- package/dist/gpu-text-3d.js +49 -2
- package/dist/index.d.ts +3 -3
- package/dist/index.js +2 -3
- package/dist/mesh-batch-3d.d.ts +1 -1
- package/dist/shape2d-shader.js +3 -2
- package/dist/text-atlas.d.ts +6 -4
- package/dist/text-atlas.js +9 -6
- package/dist/text-font.d.ts +48 -7
- package/dist/text-font.js +63 -11
- package/dist/text-layout.d.ts +63 -4
- package/dist/text-layout.js +58 -8
- package/dist/text-msdf-baked.d.ts +9 -4
- package/dist/text-msdf-baked.js +9 -4
- package/dist/text-msdf-font.d.ts +10 -4
- package/dist/text-msdf-font.js +9 -3
- package/dist/vector-math.d.ts +19 -0
- package/dist/vector-math.js +37 -0
- package/package.json +1 -1
- package/dist/layout-3d.d.ts +0 -227
- package/dist/layout-3d.js +0 -226
- package/dist/scene-examples.d.ts +0 -39
- package/dist/scene-examples.js +0 -200
- package/dist/scenes-2d.d.ts +0 -18
- package/dist/scenes-2d.js +0 -160
- package/dist/scenes-3d.d.ts +0 -46
- package/dist/scenes-3d.js +0 -1994
- package/dist/text-scenes.d.ts +0 -85
- package/dist/text-scenes.js +0 -338
package/dist/text-scenes.d.ts
DELETED
|
@@ -1,85 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Screen-space text pages.
|
|
3
|
-
*
|
|
4
|
-
* These are vgpu's own capability rather than a port — the reference has no text API — so they
|
|
5
|
-
* are organised as **one page per question**:
|
|
6
|
-
*
|
|
7
|
-
* - `text-encodings` — the same text from every glyph source, at two sizes, plus the outline pair.
|
|
8
|
-
* - `text-scale` — the same sentence at six sizes, over three raster strategies at one size.
|
|
9
|
-
* - `text-world-zoom` — world-space rows meant to be magnified with the camera, which is the only way
|
|
10
|
-
* to see what a source does once a block outgrows the raster its glyphs were built at.
|
|
11
|
-
* - `text-layout` — wrapping, justification, bounds, line height, letter spacing and spans.
|
|
12
|
-
* - `text-anchor` — the nine anchors against the marker they land on and their own bounds.
|
|
13
|
-
* - `text-cjk` — CJK wrapping, kinsoku and mixed-script runs.
|
|
14
|
-
*
|
|
15
|
-
* Three conventions run through all of them, because a comparison without them is unreadable:
|
|
16
|
-
*
|
|
17
|
-
* 1. **One sentence, repeated per condition**, with the condition labelled above the column. A page
|
|
18
|
-
* whose rows each say something different cannot be compared at all.
|
|
19
|
-
* 2. **Every caption stays inside the width of its column**, which is the mistake this layout makes
|
|
20
|
-
* first: overlapping captions are worse than a shorter label.
|
|
21
|
-
* 3. **A 1 px rule** in the bottom-left corner of every screen-space page, so an edge has something of
|
|
22
|
-
* a known width next to it. It is drawn with one-pixel **points**, because `recorder.line` has no
|
|
23
|
-
* screen-space mode: a line is a world-space segment projected through the camera, which lands as
|
|
24
|
-
* an arbitrary diagonal on a page whose content is in pixels.
|
|
25
|
-
*
|
|
26
|
-
* Text blocks deliberately name no glyph source: they take the default one, which rasterises each
|
|
27
|
-
* glyph at the size it covers on screen. That is what a caller gets out of the box, and the thing
|
|
28
|
-
* these pages exist to show; a block that wants a fixed raster says so explicitly.
|
|
29
|
-
*
|
|
30
|
-
* A page that mixes screen-space captions with world-space content cannot work: the camera is framed
|
|
31
|
-
* around every command's position, so a caption at `[24, 520]` drags the viewport to those world
|
|
32
|
-
* coordinates and the real content ends up off-screen. `text-world-zoom` is therefore world-space end
|
|
33
|
-
* to end, captions included.
|
|
34
|
-
*/
|
|
35
|
-
import type { FrameRecorder } from "./frame-recorder.js";
|
|
36
|
-
/**
|
|
37
|
-
* `text-encodings` — the same text from four sources.
|
|
38
|
-
*
|
|
39
|
-
* Two bands per column: the word at a normal size, and a shorter run of the same ink at more than
|
|
40
|
-
* twice that, which is where a coverage source and a distance field stop looking alike. The outline
|
|
41
|
-
* pair belongs on this page rather than its own: how a ring is obtained is a property of the encoding.
|
|
42
|
-
*/
|
|
43
|
-
export declare function textEncodingsScene(recorder: FrameRecorder): void;
|
|
44
|
-
/**
|
|
45
|
-
* `text-scale` — three raster strategies at one size, then one size per row.
|
|
46
|
-
*
|
|
47
|
-
* The top row is the comparison the screen-driven ladder exists for: the same 26 px text under the
|
|
48
|
-
* ladder, a fixed 1× raster and a fixed 4× raster. Below it the sizes run from 8 px to 96 px, so the
|
|
49
|
-
* rung changes — and the absence of a soft step between them — are visible in one frame.
|
|
50
|
-
*/
|
|
51
|
-
export declare function textScaleScene(recorder: FrameRecorder): void;
|
|
52
|
-
/**
|
|
53
|
-
* `text-world-zoom` — rows meant to be magnified.
|
|
54
|
-
*
|
|
55
|
-
* Screen-space text cannot be magnified: its pixel size is fixed, so a bigger block is simply a
|
|
56
|
-
* request for a new rasterisation. World-space text is where the camera does the magnifying, which is
|
|
57
|
-
* the only honest test of what a raster does past its own size — a coverage source runs into the
|
|
58
|
-
* texels it stored, a distance field keeps rebuilding the edge from them. The whole page is world
|
|
59
|
-
* space, captions included, so the framing sees one coordinate system.
|
|
60
|
-
*/
|
|
61
|
-
export declare function textWorldZoomScene(recorder: FrameRecorder): void;
|
|
62
|
-
/**
|
|
63
|
-
* `text-layout` — what the layout can do, one row per capability.
|
|
64
|
-
*
|
|
65
|
-
* Every row is labelled on the left, and every bounded row draws the box it wrapped into: a wrapping
|
|
66
|
-
* rule is unreadable without the box it wrapped against.
|
|
67
|
-
*/
|
|
68
|
-
export declare function textLayoutScene(recorder: FrameRecorder): void;
|
|
69
|
-
/**
|
|
70
|
-
* `text-anchor` — the nine anchors against the marker they land on.
|
|
71
|
-
*
|
|
72
|
-
* The `+` marks the position the block is given and the box is the block's own bounds, so both of the
|
|
73
|
-
* things an anchor moves between are visible at once. Captions sit above the cell rather than beside
|
|
74
|
-
* the box, which is what keeps them from being drawn over.
|
|
75
|
-
*/
|
|
76
|
-
export declare function textAnchorScene(recorder: FrameRecorder): void;
|
|
77
|
-
/**
|
|
78
|
-
* `text-cjk` — wrapping and kinsoku, which no latin page can show.
|
|
79
|
-
*
|
|
80
|
-
* CJK has no spaces to break at, so the wrap is per character, and kinsoku is what keeps a closing
|
|
81
|
-
* mark off the start of a line and an opening bracket off its end.
|
|
82
|
-
*/
|
|
83
|
-
export declare function textCjkScene(recorder: FrameRecorder): void;
|
|
84
|
-
/** Dispatches one of the text pages by scene id. */
|
|
85
|
-
export declare function textScene(id: string, recorder: FrameRecorder): void;
|
package/dist/text-scenes.js
DELETED
|
@@ -1,338 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Screen-space text pages.
|
|
3
|
-
*
|
|
4
|
-
* These are vgpu's own capability rather than a port — the reference has no text API — so they
|
|
5
|
-
* are organised as **one page per question**:
|
|
6
|
-
*
|
|
7
|
-
* - `text-encodings` — the same text from every glyph source, at two sizes, plus the outline pair.
|
|
8
|
-
* - `text-scale` — the same sentence at six sizes, over three raster strategies at one size.
|
|
9
|
-
* - `text-world-zoom` — world-space rows meant to be magnified with the camera, which is the only way
|
|
10
|
-
* to see what a source does once a block outgrows the raster its glyphs were built at.
|
|
11
|
-
* - `text-layout` — wrapping, justification, bounds, line height, letter spacing and spans.
|
|
12
|
-
* - `text-anchor` — the nine anchors against the marker they land on and their own bounds.
|
|
13
|
-
* - `text-cjk` — CJK wrapping, kinsoku and mixed-script runs.
|
|
14
|
-
*
|
|
15
|
-
* Three conventions run through all of them, because a comparison without them is unreadable:
|
|
16
|
-
*
|
|
17
|
-
* 1. **One sentence, repeated per condition**, with the condition labelled above the column. A page
|
|
18
|
-
* whose rows each say something different cannot be compared at all.
|
|
19
|
-
* 2. **Every caption stays inside the width of its column**, which is the mistake this layout makes
|
|
20
|
-
* first: overlapping captions are worse than a shorter label.
|
|
21
|
-
* 3. **A 1 px rule** in the bottom-left corner of every screen-space page, so an edge has something of
|
|
22
|
-
* a known width next to it. It is drawn with one-pixel **points**, because `recorder.line` has no
|
|
23
|
-
* screen-space mode: a line is a world-space segment projected through the camera, which lands as
|
|
24
|
-
* an arbitrary diagonal on a page whose content is in pixels.
|
|
25
|
-
*
|
|
26
|
-
* Text blocks deliberately name no glyph source: they take the default one, which rasterises each
|
|
27
|
-
* glyph at the size it covers on screen. That is what a caller gets out of the box, and the thing
|
|
28
|
-
* these pages exist to show; a block that wants a fixed raster says so explicitly.
|
|
29
|
-
*
|
|
30
|
-
* A page that mixes screen-space captions with world-space content cannot work: the camera is framed
|
|
31
|
-
* around every command's position, so a caption at `[24, 520]` drags the viewport to those world
|
|
32
|
-
* coordinates and the real content ends up off-screen. `text-world-zoom` is therefore world-space end
|
|
33
|
-
* to end, captions included.
|
|
34
|
-
*/
|
|
35
|
-
const LABEL = [0.62, 0.68, 0.78, 1];
|
|
36
|
-
const TITLE = [0.92, 0.95, 1, 1];
|
|
37
|
-
const INK = [0.93, 0.95, 0.98, 1];
|
|
38
|
-
const ACCENT = [1, 0.85, 0.35, 1];
|
|
39
|
-
const FRAME = [0.52, 0.6, 0.72, 1];
|
|
40
|
-
const HINT = [0.55, 0.62, 0.72, 1];
|
|
41
|
-
/** The sentence every page repeats, chosen for its straights, round bowls and diagonal. */
|
|
42
|
-
const SENTENCE = "Handgloves 0123";
|
|
43
|
-
/** The first word alone, for columns that cannot afford the whole sentence. */
|
|
44
|
-
const WORD = "Handgloves";
|
|
45
|
-
const MARGIN = 24;
|
|
46
|
-
/** A page heading plus its one-line explanation. */
|
|
47
|
-
function heading(recorder, title, note) {
|
|
48
|
-
recorder.text([MARGIN, 16, 0], title, { fontSize: 17, space: "screen", anchor: "top-left", color: TITLE });
|
|
49
|
-
recorder.text([MARGIN, 38, 0], note, { fontSize: 12, space: "screen", anchor: "top-left", color: HINT });
|
|
50
|
-
}
|
|
51
|
-
/** A column caption: what condition this column shows, with an optional second line. */
|
|
52
|
-
function caption(recorder, x, y, text, color = LABEL, size = 12) {
|
|
53
|
-
recorder.text([x, y, 0], text, { fontSize: size, space: "screen", anchor: "top-left", color });
|
|
54
|
-
}
|
|
55
|
-
/**
|
|
56
|
-
* A 1 px ruler, drawn with screen-space points.
|
|
57
|
-
*
|
|
58
|
-
* `recorder.line` has no screen-space mode: a line is always a segment in the world, projected through
|
|
59
|
-
* whatever camera is looking, so a "pixel" line drawn with it lands as an arbitrary diagonal. Until
|
|
60
|
-
* there is a screen-space line, one-pixel points are the only primitive that lands on the pixel grid.
|
|
61
|
-
*/
|
|
62
|
-
function pixelLine(recorder, from, to, color) {
|
|
63
|
-
const dx = to[0] - from[0];
|
|
64
|
-
const dy = to[1] - from[1];
|
|
65
|
-
const steps = Math.max(1, Math.round(Math.max(Math.abs(dx), Math.abs(dy))));
|
|
66
|
-
for (let step = 0; step <= steps; step++) {
|
|
67
|
-
const t = step / steps;
|
|
68
|
-
recorder.point([from[0] + dx * t, from[1] + dy * t, 0], { color, size: 1, sizeMode: "screen", round: false, layer: 0 });
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
/** The 1 px rule that gives a page a width reference. */
|
|
72
|
-
function pixelRule(recorder, x, y) {
|
|
73
|
-
caption(recorder, x, y - 15, "1 px");
|
|
74
|
-
pixelLine(recorder, [x, y], [x + 48, y], [0.75, 0.8, 0.9, 1]);
|
|
75
|
-
}
|
|
76
|
-
/** A one-pixel rectangle outline, in the top-left pixel convention the layout speaks. */
|
|
77
|
-
function boundsBox(recorder, x, y, width, height, color = FRAME) {
|
|
78
|
-
const corners = [
|
|
79
|
-
[x, y],
|
|
80
|
-
[x + width, y],
|
|
81
|
-
[x + width, y + height],
|
|
82
|
-
[x, y + height],
|
|
83
|
-
];
|
|
84
|
-
for (let index = 0; index < corners.length; index++) {
|
|
85
|
-
pixelLine(recorder, corners[index], corners[(index + 1) % corners.length], color);
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
/** Screen-space text that names no source, so it moves with the gallery's source selection. */
|
|
89
|
-
function block(recorder, position, text, options) {
|
|
90
|
-
recorder.text([position[0], position[1], 0], text, { space: "screen", anchor: "top-left", color: INK, ...options });
|
|
91
|
-
}
|
|
92
|
-
/**
|
|
93
|
-
* `text-encodings` — the same text from four sources.
|
|
94
|
-
*
|
|
95
|
-
* Two bands per column: the word at a normal size, and a shorter run of the same ink at more than
|
|
96
|
-
* twice that, which is where a coverage source and a distance field stop looking alike. The outline
|
|
97
|
-
* pair belongs on this page rather than its own: how a ring is obtained is a property of the encoding.
|
|
98
|
-
*/
|
|
99
|
-
export function textEncodingsScene(recorder) {
|
|
100
|
-
heading(recorder, "encodings", "one text, four sources · a column pins a source only when it says so; everything else takes the default, which rasterises each glyph at the size it covers on screen");
|
|
101
|
-
const columns = [
|
|
102
|
-
["bitmap", "bitmap 1×", "coverage, one step per size"],
|
|
103
|
-
["sdf4x", "sdf 4×", "distance, 4 texels per px"],
|
|
104
|
-
["msdf2x", "msdf 2×", "per-edge distance, on demand"],
|
|
105
|
-
["baked", "msdf baked", "rebuilt from a 24 px page"],
|
|
106
|
-
];
|
|
107
|
-
const columnWidth = 238;
|
|
108
|
-
for (let index = 0; index < columns.length; index++) {
|
|
109
|
-
const [source, name, note] = columns[index];
|
|
110
|
-
const x = MARGIN + index * columnWidth;
|
|
111
|
-
caption(recorder, x, 68, name, source === "baked" ? ACCENT : TITLE, 13);
|
|
112
|
-
caption(recorder, x, 86, note, HINT, 11);
|
|
113
|
-
block(recorder, [x, 104], WORD, { fontSize: 26, source });
|
|
114
|
-
block(recorder, [x, 142], "Hand", { fontSize: 62, source });
|
|
115
|
-
}
|
|
116
|
-
caption(recorder, MARGIN, 240, "outline · width 3 font px, ring colour from the block");
|
|
117
|
-
block(recorder, [MARGIN, 260], "outlined distance", { fontSize: 28, source: "sdf4x", outlineWidth: 3, outlineColor: [0.02, 0.03, 0.05, 1] });
|
|
118
|
-
block(recorder, [MARGIN + 360, 260], "outlined mask", { fontSize: 28, source: "bitmap", outlineWidth: 3, outlineColor: [0.02, 0.03, 0.05, 1] });
|
|
119
|
-
caption(recorder, MARGIN, 306, "a distance field reads the true distance alpha carries; a coverage mask is sampled in eight directions instead", HINT, 11);
|
|
120
|
-
caption(recorder, MARGIN, 344, "one block per rung · 12 / 18 / 28 / 48 / 96 px");
|
|
121
|
-
let x = MARGIN;
|
|
122
|
-
for (const size of [12, 18, 28, 48, 96]) {
|
|
123
|
-
block(recorder, [x, 362], "Hg", { fontSize: size });
|
|
124
|
-
x += size * 1.55 + 20;
|
|
125
|
-
}
|
|
126
|
-
pixelRule(recorder, MARGIN, 500);
|
|
127
|
-
}
|
|
128
|
-
/**
|
|
129
|
-
* `text-scale` — three raster strategies at one size, then one size per row.
|
|
130
|
-
*
|
|
131
|
-
* The top row is the comparison the screen-driven ladder exists for: the same 26 px text under the
|
|
132
|
-
* ladder, a fixed 1× raster and a fixed 4× raster. Below it the sizes run from 8 px to 96 px, so the
|
|
133
|
-
* rung changes — and the absence of a soft step between them — are visible in one frame.
|
|
134
|
-
*/
|
|
135
|
-
export function textScaleScene(recorder) {
|
|
136
|
-
heading(recorder, "scale", "one text, six sizes · the top row pins three raster strategies at 26 px · the first column is what a caller gets by default");
|
|
137
|
-
const pinned = [
|
|
138
|
-
["ladder · screen decides", undefined],
|
|
139
|
-
["fixed 1× raster", "bitmap"],
|
|
140
|
-
["fixed 4× raster", "bitmap4x"],
|
|
141
|
-
];
|
|
142
|
-
for (let index = 0; index < pinned.length; index++) {
|
|
143
|
-
const [label, source] = pinned[index];
|
|
144
|
-
const x = MARGIN + index * 326;
|
|
145
|
-
caption(recorder, x, 66, label, index === 0 ? ACCENT : LABEL, 13);
|
|
146
|
-
block(recorder, [x, 86], WORD, { fontSize: 26, ...(source === undefined ? {} : { source }) });
|
|
147
|
-
}
|
|
148
|
-
caption(recorder, MARGIN, 140, "one size per row · the label is the requested size in screen pixels");
|
|
149
|
-
let y = 162;
|
|
150
|
-
for (const size of [8, 12, 18, 28, 48, 96]) {
|
|
151
|
-
const text = size <= 28 ? "Hamburgefonstiv 0123" : size === 48 ? "Hamburg" : "Abc";
|
|
152
|
-
caption(recorder, MARGIN, y + size * 0.42, `${size} px`);
|
|
153
|
-
block(recorder, [MARGIN + 74, y], text, { fontSize: size });
|
|
154
|
-
y += Math.max(size * 1.45, 28);
|
|
155
|
-
}
|
|
156
|
-
pixelRule(recorder, MARGIN, y + 14);
|
|
157
|
-
}
|
|
158
|
-
/**
|
|
159
|
-
* `text-world-zoom` — rows meant to be magnified.
|
|
160
|
-
*
|
|
161
|
-
* Screen-space text cannot be magnified: its pixel size is fixed, so a bigger block is simply a
|
|
162
|
-
* request for a new rasterisation. World-space text is where the camera does the magnifying, which is
|
|
163
|
-
* the only honest test of what a raster does past its own size — a coverage source runs into the
|
|
164
|
-
* texels it stored, a distance field keeps rebuilding the edge from them. The whole page is world
|
|
165
|
-
* space, captions included, so the framing sees one coordinate system.
|
|
166
|
-
*/
|
|
167
|
-
export function textWorldZoomScene(recorder) {
|
|
168
|
-
const rows = [
|
|
169
|
-
["bitmap", "bitmap 1×"],
|
|
170
|
-
["bitmap4x", "bitmap 4×"],
|
|
171
|
-
["sdf4x", "sdf 4×"],
|
|
172
|
-
["baked", "msdf baked"],
|
|
173
|
-
];
|
|
174
|
-
const pixelSize = 0.02;
|
|
175
|
-
for (let index = 0; index < rows.length; index++) {
|
|
176
|
-
const [source, label] = rows[index];
|
|
177
|
-
// Above `y = 0` with room to spare: the grid writes depth, so a row that dips below the plane
|
|
178
|
-
// gets clipped along its lines. The lowest row's ink box still starts well above it.
|
|
179
|
-
const y = 3.6 - index * 0.9;
|
|
180
|
-
recorder.text([-2.05, y, 0], label, {
|
|
181
|
-
fontSize: 12,
|
|
182
|
-
pixelSize: 0.012,
|
|
183
|
-
space: "world-billboard",
|
|
184
|
-
anchor: "center-right",
|
|
185
|
-
color: index === 2 ? ACCENT : LABEL,
|
|
186
|
-
});
|
|
187
|
-
recorder.text([-1.95, y, 0], WORD, {
|
|
188
|
-
fontSize: 30,
|
|
189
|
-
pixelSize,
|
|
190
|
-
space: "world-billboard",
|
|
191
|
-
anchor: "center-left",
|
|
192
|
-
color: INK,
|
|
193
|
-
source,
|
|
194
|
-
});
|
|
195
|
-
}
|
|
196
|
-
recorder.text([-1.95, 0.15, 0], "scroll to magnify", {
|
|
197
|
-
fontSize: 13,
|
|
198
|
-
pixelSize: 0.012,
|
|
199
|
-
space: "world-billboard",
|
|
200
|
-
anchor: "top-left",
|
|
201
|
-
color: HINT,
|
|
202
|
-
});
|
|
203
|
-
}
|
|
204
|
-
/**
|
|
205
|
-
* `text-layout` — what the layout can do, one row per capability.
|
|
206
|
-
*
|
|
207
|
-
* Every row is labelled on the left, and every bounded row draws the box it wrapped into: a wrapping
|
|
208
|
-
* rule is unreadable without the box it wrapped against.
|
|
209
|
-
*/
|
|
210
|
-
export function textLayoutScene(recorder) {
|
|
211
|
-
heading(recorder, "layout", "one row per capability · every bounded row draws the box it wrapped into");
|
|
212
|
-
const textX = 196;
|
|
213
|
-
const rowWidth = 250;
|
|
214
|
-
let y = 72;
|
|
215
|
-
caption(recorder, MARGIN, y + 8, "wrap · word");
|
|
216
|
-
block(recorder, [textX, y], "alpha beta gamma delta epsilon", { fontSize: 14, bounds: [rowWidth, Number.POSITIVE_INFINITY], lineBreak: "word" });
|
|
217
|
-
boundsBox(recorder, textX - 5, y - 5, rowWidth + 10, 40);
|
|
218
|
-
y += 52;
|
|
219
|
-
caption(recorder, MARGIN, y + 8, "wrap · character");
|
|
220
|
-
block(recorder, [textX, y], "alpha beta gamma delta epsilon", { fontSize: 14, bounds: [rowWidth, Number.POSITIVE_INFINITY], lineBreak: "character" });
|
|
221
|
-
boundsBox(recorder, textX - 5, y - 5, rowWidth + 10, 40);
|
|
222
|
-
y += 58;
|
|
223
|
-
caption(recorder, MARGIN, y + 8, "justify");
|
|
224
|
-
for (const [index, justify] of ["left", "center", "right", "justified"].entries()) {
|
|
225
|
-
const x = textX + index * 152;
|
|
226
|
-
caption(recorder, x, y, justify, LABEL, 11);
|
|
227
|
-
block(recorder, [x, y + 16], "wrap me now", { fontSize: 14, justify, bounds: [124, Number.POSITIVE_INFINITY] });
|
|
228
|
-
}
|
|
229
|
-
y += 76;
|
|
230
|
-
caption(recorder, MARGIN, y + 8, "line height");
|
|
231
|
-
for (const [index, height] of [1.0, 2.0].entries()) {
|
|
232
|
-
const x = textX + index * 220;
|
|
233
|
-
caption(recorder, x, y, `${height} × font size`, LABEL, 11);
|
|
234
|
-
block(recorder, [x, y + 16], "first\nsecond", { fontSize: 14, lineHeight: 14 * height });
|
|
235
|
-
}
|
|
236
|
-
y += 86;
|
|
237
|
-
caption(recorder, MARGIN, y + 8, "letter spacing");
|
|
238
|
-
for (const [index, spacing] of [0, 3].entries()) {
|
|
239
|
-
const x = textX + index * 220;
|
|
240
|
-
caption(recorder, x, y, `${spacing} px`, LABEL, 11);
|
|
241
|
-
block(recorder, [x, y + 16], "spacing", { fontSize: 16, letterSpacing: spacing });
|
|
242
|
-
}
|
|
243
|
-
y += 60;
|
|
244
|
-
caption(recorder, MARGIN, y + 8, "spans");
|
|
245
|
-
block(recorder, [textX, y], [
|
|
246
|
-
{ text: "spans: " },
|
|
247
|
-
{ text: "red ", color: [1, 0.4, 0.4, 1] },
|
|
248
|
-
{ text: "green ", color: [0.45, 1, 0.55, 1] },
|
|
249
|
-
{ text: "BIG", color: [0.55, 0.8, 1, 1], fontSize: 30 },
|
|
250
|
-
], { fontSize: 16 });
|
|
251
|
-
y += 56;
|
|
252
|
-
caption(recorder, MARGIN, y + 8, "anchor + bounds");
|
|
253
|
-
block(recorder, [textX, y], "anchored box", { fontSize: 15, bounds: [130, Number.POSITIVE_INFINITY] });
|
|
254
|
-
boundsBox(recorder, textX - 5, y - 5, 140, 28);
|
|
255
|
-
pixelRule(recorder, MARGIN, y + 56);
|
|
256
|
-
}
|
|
257
|
-
/**
|
|
258
|
-
* `text-anchor` — the nine anchors against the marker they land on.
|
|
259
|
-
*
|
|
260
|
-
* The `+` marks the position the block is given and the box is the block's own bounds, so both of the
|
|
261
|
-
* things an anchor moves between are visible at once. Captions sit above the cell rather than beside
|
|
262
|
-
* the box, which is what keeps them from being drawn over.
|
|
263
|
-
*/
|
|
264
|
-
export function textAnchorScene(recorder) {
|
|
265
|
-
heading(recorder, "anchors", "the plus is the position the block is given · the box is the block's bounds · the caption names the anchor");
|
|
266
|
-
const anchors = [
|
|
267
|
-
["top-left", "top-center", "top-right"],
|
|
268
|
-
["center-left", "center", "center-right"],
|
|
269
|
-
["bottom-left", "bottom-center", "bottom-right"],
|
|
270
|
-
];
|
|
271
|
-
const columnStep = 240;
|
|
272
|
-
const rowStep = 128;
|
|
273
|
-
const width = 108;
|
|
274
|
-
const height = 28;
|
|
275
|
-
for (let row = 0; row < anchors.length; row++) {
|
|
276
|
-
for (let column = 0; column < anchors[row].length; column++) {
|
|
277
|
-
const anchor = anchors[row][column];
|
|
278
|
-
const x = 122 + column * columnStep;
|
|
279
|
-
const y = 158 + row * rowStep;
|
|
280
|
-
caption(recorder, x - 54, y - 102, anchor, LABEL, 11);
|
|
281
|
-
recorder.point([x, y, 0], { color: ACCENT, size: 7, sizeMode: "screen", layer: 0 });
|
|
282
|
-
recorder.text([x, y, 0], "+", { fontSize: 18, space: "screen", anchor: "center", color: ACCENT });
|
|
283
|
-
block(recorder, [x, y], "anchor box", { fontSize: 15, anchor, bounds: [width, Number.POSITIVE_INFINITY] });
|
|
284
|
-
const originX = anchor.endsWith("left") ? x : anchor.endsWith("right") ? x - width : x - width / 2;
|
|
285
|
-
const originY = anchor.startsWith("top") ? y : anchor.startsWith("bottom") ? y - height : y - height / 2;
|
|
286
|
-
boundsBox(recorder, originX - 4, originY - 4, width + 8, height + 8);
|
|
287
|
-
}
|
|
288
|
-
}
|
|
289
|
-
pixelRule(recorder, MARGIN, 520);
|
|
290
|
-
}
|
|
291
|
-
/**
|
|
292
|
-
* `text-cjk` — wrapping and kinsoku, which no latin page can show.
|
|
293
|
-
*
|
|
294
|
-
* CJK has no spaces to break at, so the wrap is per character, and kinsoku is what keeps a closing
|
|
295
|
-
* mark off the start of a line and an opening bracket off its end.
|
|
296
|
-
*/
|
|
297
|
-
export function textCjkScene(recorder) {
|
|
298
|
-
heading(recorder, "cjk", "no spaces to break at, so the wrap is per character · kinsoku keeps a closing mark off a line start and an opening bracket off a line end");
|
|
299
|
-
const paragraph = "中文排版按字符换行,行首不留标点,行尾不留开括号(像这样)。中英混排 mixed with latin words 也要能正常断行。";
|
|
300
|
-
caption(recorder, MARGIN, 74, "wrapped to 300 px");
|
|
301
|
-
block(recorder, [MARGIN, 92], paragraph, { fontSize: 18, bounds: [300, Number.POSITIVE_INFINITY] });
|
|
302
|
-
boundsBox(recorder, MARGIN - 5, 87, 310, 108);
|
|
303
|
-
caption(recorder, 404, 74, "wrapped to 150 px · narrow enough that kinsoku has to act");
|
|
304
|
-
block(recorder, [404, 92], paragraph, { fontSize: 18, bounds: [150, Number.POSITIVE_INFINITY] });
|
|
305
|
-
boundsBox(recorder, 399, 87, 160, 236);
|
|
306
|
-
caption(recorder, MARGIN, 344, "sizes · the ladder follows the screen, so no size has to be baked ahead");
|
|
307
|
-
let y = 366;
|
|
308
|
-
for (const size of [12, 18, 28, 48]) {
|
|
309
|
-
caption(recorder, MARGIN, y + size * 0.42, `${size} px`);
|
|
310
|
-
block(recorder, [MARGIN + 74, y], "中文排版与字号", { fontSize: size });
|
|
311
|
-
y += Math.max(size * 1.45, 26);
|
|
312
|
-
}
|
|
313
|
-
pixelRule(recorder, MARGIN, y + 14);
|
|
314
|
-
}
|
|
315
|
-
/** Dispatches one of the text pages by scene id. */
|
|
316
|
-
export function textScene(id, recorder) {
|
|
317
|
-
switch (id) {
|
|
318
|
-
case "text-encodings":
|
|
319
|
-
textEncodingsScene(recorder);
|
|
320
|
-
break;
|
|
321
|
-
case "text-scale":
|
|
322
|
-
textScaleScene(recorder);
|
|
323
|
-
break;
|
|
324
|
-
case "text-world-zoom":
|
|
325
|
-
textWorldZoomScene(recorder);
|
|
326
|
-
break;
|
|
327
|
-
case "text-layout":
|
|
328
|
-
textLayoutScene(recorder);
|
|
329
|
-
break;
|
|
330
|
-
case "text-anchor":
|
|
331
|
-
textAnchorScene(recorder);
|
|
332
|
-
break;
|
|
333
|
-
case "text-cjk":
|
|
334
|
-
textCjkScene(recorder);
|
|
335
|
-
break;
|
|
336
|
-
default: break;
|
|
337
|
-
}
|
|
338
|
-
}
|