@pixerate/editor 0.192.0 → 0.193.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.
Files changed (52) hide show
  1. package/dist/animation-editor/index.cjs +56 -56
  2. package/dist/animation-editor/index.js +2 -2
  3. package/dist/{chunk-ZQGZWFPU.cjs → chunk-3T5VCDIV.cjs} +97 -5
  4. package/dist/{chunk-RQ2EM5TY.js → chunk-5OC22IEO.js} +97 -5
  5. package/dist/{chunk-EGZM52EO.cjs → chunk-BRKVNXDZ.cjs} +354 -125
  6. package/dist/{chunk-XCDGM7TD.cjs → chunk-FR2Q2LVF.cjs} +35 -35
  7. package/dist/{chunk-LPIKXPBM.js → chunk-GHCXLQ47.js} +262 -33
  8. package/dist/{chunk-VRXXAXUT.js → chunk-MLC7HUHO.js} +1 -1
  9. package/dist/{chunk-2UR4OX3B.cjs → chunk-UYXL6ZWQ.cjs} +114 -97
  10. package/dist/{chunk-TXYXF32N.js → chunk-ZIDUPSP4.js} +35 -18
  11. package/dist/image-editor/controller.d.cts +16 -6
  12. package/dist/image-editor/controller.d.ts +16 -6
  13. package/dist/image-editor/index.cjs +867 -831
  14. package/dist/image-editor/index.js +103 -67
  15. package/dist/image-editor/layers/model.d.cts +7 -2
  16. package/dist/image-editor/layers/model.d.ts +7 -2
  17. package/dist/image-editor/layers/serialize.d.cts +2 -0
  18. package/dist/image-editor/layers/serialize.d.ts +2 -0
  19. package/dist/image-editor/layers/shape-type-ui.d.cts +8 -2
  20. package/dist/image-editor/layers/shape-type-ui.d.ts +8 -2
  21. package/dist/image-editor/layers/text.d.cts +6 -2
  22. package/dist/image-editor/layers/text.d.ts +6 -2
  23. package/dist/image-editor/psd/type.d.cts +2 -1
  24. package/dist/image-editor/psd/type.d.ts +2 -1
  25. package/dist/image-editor/tools.d.cts +1 -1
  26. package/dist/image-editor/tools.d.ts +1 -1
  27. package/dist/image-editor/type-tools.d.cts +2 -2
  28. package/dist/image-editor/type-tools.d.ts +2 -2
  29. package/dist/paint-editor/index.cjs +7 -7
  30. package/dist/paint-editor/index.js +2 -2
  31. package/dist/text/layout.d.cts +93 -12
  32. package/dist/text/layout.d.ts +93 -12
  33. package/dist/text/opentype.d.cts +13 -3
  34. package/dist/text/opentype.d.ts +13 -3
  35. package/dist/text/shapers.d.cts +12 -0
  36. package/dist/text/shapers.d.ts +12 -0
  37. package/dist/text/types.d.cts +36 -2
  38. package/dist/text/types.d.ts +36 -2
  39. package/dist/vector/controller.d.cts +8 -0
  40. package/dist/vector/controller.d.ts +8 -0
  41. package/dist/vector/index.cjs +1039 -967
  42. package/dist/vector/index.js +90 -18
  43. package/dist/vector/text.d.cts +5 -3
  44. package/dist/vector/text.d.ts +5 -3
  45. package/dist/vector/textPanel.d.cts +6 -0
  46. package/dist/vector/textPanel.d.ts +6 -0
  47. package/dist/vector/types.d.cts +9 -0
  48. package/dist/vector/types.d.ts +9 -0
  49. package/package.json +1 -1
  50. package/skills/pixerate-image-editor/SKILL.md +4 -3
  51. package/skills/pixerate-image-editor/references/shapes-and-type.md +17 -5
  52. package/skills/pixerate-vector/SKILL.md +9 -3
@@ -48,7 +48,7 @@ export interface ImageEditorToolProviderOptions {
48
48
  * `add_shape_component`, `remove_shape_component`, `merge_shape_components` (see
49
49
  * `createImageShapeTools`); `rasterize_layer` turns one into pixels.
50
50
  * - Type layers: `list_fonts`, `get_text_layer`, `add_text_layer`, `set_text`, `set_text_style`,
51
- * `set_paragraph_style`, `set_text_frame`, `convert_text_to_shape` (see `createImageTypeTools`).
51
+ * `set_paragraph_style`, `set_text_frame`, `set_text_orientation`, `convert_text_to_shape` (see `createImageTypeTools`).
52
52
  * - Colour management: `get_color_profile`, `assign_profile`, `convert_to_profile`,
53
53
  * `set_proof_setup`, `convert_colors` (see `createImageColorTools`).
54
54
  * - Retouching: `clone_stamp`, `heal_brush`, `spot_heal`, `smudge`, `dodge`, `burn`, `sponge`,
@@ -48,7 +48,7 @@ export interface ImageEditorToolProviderOptions {
48
48
  * `add_shape_component`, `remove_shape_component`, `merge_shape_components` (see
49
49
  * `createImageShapeTools`); `rasterize_layer` turns one into pixels.
50
50
  * - Type layers: `list_fonts`, `get_text_layer`, `add_text_layer`, `set_text`, `set_text_style`,
51
- * `set_paragraph_style`, `set_text_frame`, `convert_text_to_shape` (see `createImageTypeTools`).
51
+ * `set_paragraph_style`, `set_text_frame`, `set_text_orientation`, `convert_text_to_shape` (see `createImageTypeTools`).
52
52
  * - Colour management: `get_color_profile`, `assign_profile`, `convert_to_profile`,
53
53
  * `set_proof_setup`, `convert_colors` (see `createImageColorTools`).
54
54
  * - Retouching: `clone_stamp`, `heal_brush`, `spot_heal`, `smudge`, `dodge`, `burn`, `sponge`,
@@ -2,8 +2,8 @@ import type { AgentTool } from '../tools/registry.cjs';
2
2
  import type { ImageEditorController } from './controller.cjs';
3
3
  /**
4
4
  * Agent tools for type layers in the image editor: `list_fonts`, `get_text_layer`,
5
- * `add_text_layer`, `set_text`, `set_text_style`, `set_paragraph_style`, `set_text_frame` and
6
- * `convert_text_to_shape`. Every write is one undo step, validates first and returns the layer as
5
+ * `add_text_layer`, `set_text`, `set_text_style`, `set_paragraph_style`, `set_text_frame`,
6
+ * `set_text_orientation` and `convert_text_to_shape`. Every write is one undo step, validates first and returns the layer as
7
7
  * it is afterwards (with any missing fonts).
8
8
  */
9
9
  export declare function createImageTypeTools(controller: ImageEditorController): AgentTool[];
@@ -2,8 +2,8 @@ import type { AgentTool } from '../tools/registry.js';
2
2
  import type { ImageEditorController } from './controller.js';
3
3
  /**
4
4
  * Agent tools for type layers in the image editor: `list_fonts`, `get_text_layer`,
5
- * `add_text_layer`, `set_text`, `set_text_style`, `set_paragraph_style`, `set_text_frame` and
6
- * `convert_text_to_shape`. Every write is one undo step, validates first and returns the layer as
5
+ * `add_text_layer`, `set_text`, `set_text_style`, `set_paragraph_style`, `set_text_frame`,
6
+ * `set_text_orientation` and `convert_text_to_shape`. Every write is one undo step, validates first and returns the layer as
7
7
  * it is afterwards (with any missing fonts).
8
8
  */
9
9
  export declare function createImageTypeTools(controller: ImageEditorController): AgentTool[];
@@ -7,7 +7,7 @@ var _chunkZ64NRDEHcjs = require('../chunk-Z64NRDEH.cjs');
7
7
 
8
8
 
9
9
 
10
- var _chunk2UR4OX3Bcjs = require('../chunk-2UR4OX3B.cjs');
10
+ var _chunkUYXL6ZWQcjs = require('../chunk-UYXL6ZWQ.cjs');
11
11
  require('../chunk-A2IS7EHE.cjs');
12
12
 
13
13
 
@@ -54,7 +54,7 @@ var _chunkJLWLX52Lcjs = require('../chunk-JLWLX52L.cjs');
54
54
 
55
55
 
56
56
  var _chunkK7ENM55Pcjs = require('../chunk-K7ENM55P.cjs');
57
- require('../chunk-EGZM52EO.cjs');
57
+ require('../chunk-BRKVNXDZ.cjs');
58
58
 
59
59
 
60
60
 
@@ -1653,11 +1653,11 @@ function header(state, options) {
1653
1653
  return out;
1654
1654
  }
1655
1655
  function serializePaintDocument(state, options = {}) {
1656
- const { layers, assets } = _chunk2UR4OX3Bcjs.serializeLayers.call(void 0, state.layers);
1656
+ const { layers, assets } = _chunkUYXL6ZWQcjs.serializeLayers.call(void 0, state.layers);
1657
1657
  return { ...header(state, options), layers, assets };
1658
1658
  }
1659
1659
  async function serializePaintDocumentExternal(state, store, options = {}) {
1660
- const { layers, assets } = await _chunk2UR4OX3Bcjs.serializeLayersExternal.call(void 0, state.layers, store);
1660
+ const { layers, assets } = await _chunkUYXL6ZWQcjs.serializeLayersExternal.call(void 0, state.layers, store);
1661
1661
  return { ...header(state, options), layers, assets };
1662
1662
  }
1663
1663
  function paintLayersOnly(layers, problems) {
@@ -1694,7 +1694,7 @@ function deserializePaintDocument(raw) {
1694
1694
  const canvas = normalizeCanvas({ width: r.width, height: r.height, dpi: _nullishCoalesce(r.dpi, () => ( void 0)) });
1695
1695
  const paper = _chunkYQ5C5BVKcjs.normalizePaper.call(void 0, _nullishCoalesce(r.paper, () => ( {})));
1696
1696
  const assets = r.assets && typeof r.assets === "object" ? r.assets : {};
1697
- const layers = paintLayersOnly(_chunk2UR4OX3Bcjs.deserializeLayers.call(void 0, r.layers, { assets, problems }), problems);
1697
+ const layers = paintLayersOnly(_chunkUYXL6ZWQcjs.deserializeLayers.call(void 0, r.layers, { assets, problems }), problems);
1698
1698
  const wanted = typeof r.activeLayerId === "string" ? r.activeLayerId : null;
1699
1699
  const activeLayerId = wanted && _chunkHGTDMKNEcjs.findLayer.call(void 0, layers, wanted) ? wanted : _nullishCoalesce(_optionalChain([_chunkHGTDMKNEcjs.flattenLayers.call(void 0, layers), 'access', _70 => _70.filter, 'call', _71 => _71((l) => l.type === "pixel"), 'access', _72 => _72.pop, 'call', _73 => _73(), 'optionalAccess', _74 => _74.id]), () => ( null));
1700
1700
  const wetEvents = Array.isArray(r.wetEvents) ? r.wetEvents.filter((e) => e && typeof e === "object" && Number.isInteger(e.clock) && typeof e.kind === "string") : [];
@@ -1710,7 +1710,7 @@ async function deserializePaintDocumentAsync(raw, store) {
1710
1710
  if (!raw || typeof raw !== "object") throw new Error("Not a paint document: expected a JSON object.");
1711
1711
  const r = raw;
1712
1712
  const problems = [];
1713
- const assets = await _chunk2UR4OX3Bcjs.resolveExternalAssets.call(void 0, _nullishCoalesce(r.assets, () => ( {})), store, problems);
1713
+ const assets = await _chunkUYXL6ZWQcjs.resolveExternalAssets.call(void 0, _nullishCoalesce(r.assets, () => ( {})), store, problems);
1714
1714
  const loaded = deserializePaintDocument({ ...r, assets });
1715
1715
  return { ...loaded, problems: [...problems, ...loaded.problems] };
1716
1716
  }
@@ -5409,7 +5409,7 @@ var PaintEditorController = (_class10 = class _PaintEditorController {
5409
5409
  return next.type === "group" ? { ...next, children: toStandard(next.children) } : next;
5410
5410
  });
5411
5411
  const layers = toStandard(dried);
5412
- const result = _chunk2UR4OX3Bcjs.exportPsd.call(void 0, { width, height, layers: [paperLayer, ...layers], merged, resolution: dpi });
5412
+ const result = _chunkUYXL6ZWQcjs.exportPsd.call(void 0, { width, height, layers: [paperLayer, ...layers], merged, resolution: dpi });
5413
5413
  for (const [mode, names] of changed) {
5414
5414
  const label = _nullishCoalesce(_optionalChain([_chunkCWM3CUUXcjs.BLEND_MODES, 'access', _271 => _271.find, 'call', _272 => _272((m) => m.id === mode), 'optionalAccess', _273 => _273.label]), () => ( mode));
5415
5415
  const std = _nullishCoalesce(_optionalChain([_chunkCWM3CUUXcjs.BLEND_MODES, 'access', _274 => _274.find, 'call', _275 => _275((m) => m.id === _chunkCWM3CUUXcjs.standardBlendMode.call(void 0, mode)), 'optionalAccess', _276 => _276.label]), () => ( _chunkCWM3CUUXcjs.standardBlendMode.call(void 0, mode)));
@@ -7,7 +7,7 @@ import {
7
7
  resolveExternalAssets,
8
8
  serializeLayers,
9
9
  serializeLayersExternal
10
- } from "../chunk-TXYXF32N.js";
10
+ } from "../chunk-ZIDUPSP4.js";
11
11
  import "../chunk-3CI7FRMK.js";
12
12
  import {
13
13
  EffectRendererPool
@@ -54,7 +54,7 @@ import {
54
54
  realPresenceClock,
55
55
  tweenPresenceCursor
56
56
  } from "../chunk-IVSWRVB4.js";
57
- import "../chunk-LPIKXPBM.js";
57
+ import "../chunk-GHCXLQ47.js";
58
58
  import {
59
59
  BRUSH_ID,
60
60
  BRUSH_ROTATIONS,
@@ -2,11 +2,11 @@
2
2
  * Text layout shared by the image editor's type layers and the vector editor's text nodes: styled
3
3
  * runs, paragraphs, line breaking (at spaces and after break characters, and between wide CJK
4
4
  * characters), alignment and justification, tracking, leading, indents, spacing, baseline shift,
5
- * caret positions and hit testing. Everything that needs a font goes through a `TextShaper`, so
5
+ * horizontal and vertical writing, caret positions and hit testing. Everything that needs a font goes through a `TextShaper`, so
6
6
  * the same measurements give the same lines in every host.
7
7
  */
8
8
  import type { BezierPath } from '../canvas/pathops/bezier.cjs';
9
- import type { ParagraphRun, TextFont, TextRect, TextShaper, TextStyle } from './types.cjs';
9
+ import type { ParagraphRun, TextFont, TextOrientation, TextRect, TextShaper, TextStyle } from './types.cjs';
10
10
  /** What layout needs: the text, its character and paragraph runs and its frame. */
11
11
  export interface TextBlock {
12
12
  text: string;
@@ -28,6 +28,12 @@ export interface TextBlock {
28
28
  width: number;
29
29
  height: number;
30
30
  };
31
+ /**
32
+ * Default `horizontal`. `vertical`: lines are columns running down, stacked right to left. Point
33
+ * text then starts at (0, 0) at the top of the first column's centre line (`origin: 'top'`: its
34
+ * right edge); a box's columns start at its right edge and wrap at its height.
35
+ */
36
+ orientation?: TextOrientation;
31
37
  }
32
38
  export declare const DEFAULT_TEXT_STYLE: TextStyle;
33
39
  export declare const DEFAULT_AUTO_LEADING = 1.2;
@@ -65,14 +71,39 @@ export declare function paragraphRanges(text: string): Array<{
65
71
  }>;
66
72
  /** Paragraph runs snapped to whole paragraphs: each paragraph takes the style at its start. */
67
73
  export declare function fitParagraphRuns(runs: readonly ParagraphRun[], text: string): ParagraphRun[];
74
+ /** A point in text space (or line space, where said). */
75
+ export interface TextPoint {
76
+ x: number;
77
+ y: number;
78
+ }
79
+ /** An affine matrix `[a, b, c, d, e, f]`: `x' = a·x + c·y + e`, `y' = b·x + d·y + f`. */
80
+ export type TextMatrix = readonly [number, number, number, number, number, number];
68
81
  /** One laid-out character. */
69
82
  export interface LaidChar {
70
83
  /** Index in the text (UTF-16 code unit). */
71
84
  index: number;
72
- /** Text-space x of its start and its advance (tracking and justification included). */
85
+ /** Line-space x of its start and its advance (tracking and justification included). */
73
86
  x: number;
74
87
  advance: number;
75
88
  }
89
+ /**
90
+ * A character of vertical text as drawn, text space: its origin on its own baseline, its turn
91
+ * (`90`: turned sideways, clockwise; `0`: upright), its advance along its baseline and its box
92
+ * above and below the baseline.
93
+ */
94
+ export interface LaidGlyph {
95
+ index: number;
96
+ /** Code units (2 for a surrogate pair). */
97
+ length: number;
98
+ x: number;
99
+ y: number;
100
+ angle: 0 | 90;
101
+ advance: number;
102
+ ascent: number;
103
+ descent: number;
104
+ /** Drawn with the font's vertical alternate (`vert`). */
105
+ vert: boolean;
106
+ }
76
107
  /** A run of characters on one line in one style. */
77
108
  export interface LaidFragment {
78
109
  start: number;
@@ -81,18 +112,24 @@ export interface LaidFragment {
81
112
  font: TextFont;
82
113
  /** The family the shaper draws it with. */
83
114
  family: string;
115
+ /** Line space. */
84
116
  x: number;
85
- /** Baseline in text space (baseline shift applied). */
117
+ /** Baseline in line space (baseline shift applied); for upright vertical text, the column's centre line. */
86
118
  y: number;
87
119
  /** Per-character x positions relative to `x` (`end − start + 1` entries). */
88
120
  carets: number[];
89
121
  /** Whether characters are placed one by one (tracking or justification). */
90
122
  spaced: boolean;
123
+ /** Vertical text only: whether the fragment's characters stand upright (else they are turned sideways). */
124
+ upright?: boolean;
125
+ /** Vertical text only: each character as drawn, text space (low surrogates join their pair). */
126
+ glyphs?: LaidGlyph[];
91
127
  }
92
128
  export interface LaidLine {
93
129
  start: number;
94
130
  /** End of the line's characters (line break and trailing spaces excluded from `width`). */
95
131
  end: number;
132
+ /** Line space; for vertical text the column's centre line. */
96
133
  baseline: number;
97
134
  ascent: number;
98
135
  descent: number;
@@ -100,34 +137,78 @@ export interface LaidLine {
100
137
  width: number;
101
138
  chars: LaidChar[];
102
139
  fragments: LaidFragment[];
140
+ /** Vertical text only: line space → text space (the layout's `matrix`). */
141
+ matrix?: TextMatrix;
103
142
  }
104
- /** A laid-out type layer in text space. */
143
+ /** A laid-out type layer. */
105
144
  export interface TextLayout {
106
145
  lines: LaidLine[];
107
146
  /** Bounds of the lines (ascent to descent), text space; the box for paragraph text. */
108
147
  bounds: TextRect;
109
148
  /** Families replaced by a fallback. */
110
149
  missingFonts: string[];
150
+ orientation: TextOrientation;
151
+ /** Vertical text only: line space → text space. */
152
+ matrix?: TextMatrix;
111
153
  }
112
154
  export declare function fontOf(style: TextStyle): TextFont;
113
155
  /** Whether a character is a space or tab (spaces hang at line ends and stretch when justifying). */
114
156
  export declare const isTextSpace: (c: string) => c is "\t" | " ";
157
+ /**
158
+ * How a code point is set in vertical text, after Unicode's Vertical_Orientation (UAX #50),
159
+ * simplified to CJK: `U` upright (ideographs, kana, Hangul, full-width forms, emoji), `R` turned
160
+ * sideways (Latin and other horizontal scripts, spaces), `Tu` upright in a vertical form when the
161
+ * font has one (、。 and small kana), `Tr` a vertical form when the font has one, else turned
162
+ * sideways (brackets, dashes, the long vowel mark, wave dash).
163
+ */
164
+ export declare function verticalOrientationOf(cp: number): 'U' | 'R' | 'Tu' | 'Tr';
165
+ /** A rectangle mapped through `m` (a quarter turn keeps it a rectangle); `r` itself without `m`. */
166
+ export declare function transformTextRect(m: TextMatrix | undefined, r: TextRect): TextRect;
167
+ /** A line-space point of `layout` in text space (the same point for horizontal text). */
168
+ export declare function lineToTextSpace(layout: Pick<TextLayout, 'matrix'>, p: TextPoint): TextPoint;
169
+ /** A text-space point of `layout` in line space (the same point for horizontal text). */
170
+ export declare function textToLineSpace(layout: Pick<TextLayout, 'matrix'>, p: TextPoint): TextPoint;
171
+ /** A line's box (its advance by ascent to descent) in text space. */
172
+ export declare function textLineRect(l: LaidLine): TextRect;
115
173
  /** Lays out a type layer with `shaper` (memoised per layer object and shaper). */
116
174
  export declare function layoutText(layer: TextBlock, shaper: TextShaper): TextLayout;
117
175
  /** The text index nearest a text-space point (for clicks): the line under it, then the closest caret. */
118
- export declare function textIndexAt(layout: TextLayout, p: {
119
- x: number;
120
- y: number;
121
- }): number;
122
- /** The caret at text index `index`: its x and the line's top and bottom, text space. */
176
+ export declare function textIndexAt(layout: TextLayout, point: TextPoint): number;
177
+ /**
178
+ * The caret at text index `index`: in line space its x and the line's top and bottom, and in text
179
+ * space the segment it draws (`from` the line's top or, vertically, its right edge, `to` the other
180
+ * side).
181
+ */
123
182
  export declare function caretAt(layout: TextLayout, index: number): {
124
183
  x: number;
125
184
  top: number;
126
185
  bottom: number;
127
186
  line: number;
187
+ from: TextPoint;
188
+ to: TextPoint;
128
189
  };
129
- /** Underline and strike-through bars of a fragment, text space, as rectangles. */
190
+ /** Quads (text space, four corners each) covering the characters of [start, end), one per line. */
191
+ export declare function textSelectionQuads(layout: TextLayout, start: number, end: number): TextPoint[][];
192
+ /** What an arrow key does to a text caret: along the line (`previous` / `next` character) or across lines. */
193
+ export type TextCaretStep = 'previous' | 'next' | 'line-before' | 'line-after';
194
+ /**
195
+ * The caret step of an arrow key. Horizontal text: ← → move along the line, ↑ ↓ between lines.
196
+ * Vertical text: ↑ ↓ move along the column, → to the column before (on the right), ← to the next.
197
+ */
198
+ export declare function textArrowStep(orientation: TextOrientation | undefined, key: 'ArrowLeft' | 'ArrowRight' | 'ArrowUp' | 'ArrowDown'): TextCaretStep;
199
+ /**
200
+ * The index on the line before (`dir` −1) or after (+1) the caret at `index`, at the same place
201
+ * along the line; the start or end of the text (`length`) past the first or last line.
202
+ */
203
+ export declare function textLineMove(layout: TextLayout, index: number, dir: -1 | 1, length: number): number;
204
+ /** Underline and strike-through bars of a fragment, text space, as rectangles. Vertical text underlines on the right. */
130
205
  export declare function textDecorations(f: LaidFragment, l: LaidLine): TextRect[];
131
206
  export declare const textRectPath: (r: TextRect) => BezierPath;
132
- /** The text of a fragment as outlines (one placement per character when spaced), text space. */
207
+ /** The text of a fragment as outlines (one placement per character when spaced or vertical), text space. */
133
208
  export declare function textFragmentOutline(shaper: TextShaper, text: string, f: LaidFragment): BezierPath;
209
+ /**
210
+ * Draws a fragment's text with a drawing shaper in the context's current transform (text space),
211
+ * fill style and alpha: each glyph of vertical text turned or upright, spaced text a character at
212
+ * a time. Decorations are not drawn (see `textDecorations`).
213
+ */
214
+ export declare function drawTextFragment(ctx: CanvasRenderingContext2D, shaper: TextShaper, text: string, f: LaidFragment): void;
@@ -2,11 +2,11 @@
2
2
  * Text layout shared by the image editor's type layers and the vector editor's text nodes: styled
3
3
  * runs, paragraphs, line breaking (at spaces and after break characters, and between wide CJK
4
4
  * characters), alignment and justification, tracking, leading, indents, spacing, baseline shift,
5
- * caret positions and hit testing. Everything that needs a font goes through a `TextShaper`, so
5
+ * horizontal and vertical writing, caret positions and hit testing. Everything that needs a font goes through a `TextShaper`, so
6
6
  * the same measurements give the same lines in every host.
7
7
  */
8
8
  import type { BezierPath } from '../canvas/pathops/bezier.js';
9
- import type { ParagraphRun, TextFont, TextRect, TextShaper, TextStyle } from './types.js';
9
+ import type { ParagraphRun, TextFont, TextOrientation, TextRect, TextShaper, TextStyle } from './types.js';
10
10
  /** What layout needs: the text, its character and paragraph runs and its frame. */
11
11
  export interface TextBlock {
12
12
  text: string;
@@ -28,6 +28,12 @@ export interface TextBlock {
28
28
  width: number;
29
29
  height: number;
30
30
  };
31
+ /**
32
+ * Default `horizontal`. `vertical`: lines are columns running down, stacked right to left. Point
33
+ * text then starts at (0, 0) at the top of the first column's centre line (`origin: 'top'`: its
34
+ * right edge); a box's columns start at its right edge and wrap at its height.
35
+ */
36
+ orientation?: TextOrientation;
31
37
  }
32
38
  export declare const DEFAULT_TEXT_STYLE: TextStyle;
33
39
  export declare const DEFAULT_AUTO_LEADING = 1.2;
@@ -65,14 +71,39 @@ export declare function paragraphRanges(text: string): Array<{
65
71
  }>;
66
72
  /** Paragraph runs snapped to whole paragraphs: each paragraph takes the style at its start. */
67
73
  export declare function fitParagraphRuns(runs: readonly ParagraphRun[], text: string): ParagraphRun[];
74
+ /** A point in text space (or line space, where said). */
75
+ export interface TextPoint {
76
+ x: number;
77
+ y: number;
78
+ }
79
+ /** An affine matrix `[a, b, c, d, e, f]`: `x' = a·x + c·y + e`, `y' = b·x + d·y + f`. */
80
+ export type TextMatrix = readonly [number, number, number, number, number, number];
68
81
  /** One laid-out character. */
69
82
  export interface LaidChar {
70
83
  /** Index in the text (UTF-16 code unit). */
71
84
  index: number;
72
- /** Text-space x of its start and its advance (tracking and justification included). */
85
+ /** Line-space x of its start and its advance (tracking and justification included). */
73
86
  x: number;
74
87
  advance: number;
75
88
  }
89
+ /**
90
+ * A character of vertical text as drawn, text space: its origin on its own baseline, its turn
91
+ * (`90`: turned sideways, clockwise; `0`: upright), its advance along its baseline and its box
92
+ * above and below the baseline.
93
+ */
94
+ export interface LaidGlyph {
95
+ index: number;
96
+ /** Code units (2 for a surrogate pair). */
97
+ length: number;
98
+ x: number;
99
+ y: number;
100
+ angle: 0 | 90;
101
+ advance: number;
102
+ ascent: number;
103
+ descent: number;
104
+ /** Drawn with the font's vertical alternate (`vert`). */
105
+ vert: boolean;
106
+ }
76
107
  /** A run of characters on one line in one style. */
77
108
  export interface LaidFragment {
78
109
  start: number;
@@ -81,18 +112,24 @@ export interface LaidFragment {
81
112
  font: TextFont;
82
113
  /** The family the shaper draws it with. */
83
114
  family: string;
115
+ /** Line space. */
84
116
  x: number;
85
- /** Baseline in text space (baseline shift applied). */
117
+ /** Baseline in line space (baseline shift applied); for upright vertical text, the column's centre line. */
86
118
  y: number;
87
119
  /** Per-character x positions relative to `x` (`end − start + 1` entries). */
88
120
  carets: number[];
89
121
  /** Whether characters are placed one by one (tracking or justification). */
90
122
  spaced: boolean;
123
+ /** Vertical text only: whether the fragment's characters stand upright (else they are turned sideways). */
124
+ upright?: boolean;
125
+ /** Vertical text only: each character as drawn, text space (low surrogates join their pair). */
126
+ glyphs?: LaidGlyph[];
91
127
  }
92
128
  export interface LaidLine {
93
129
  start: number;
94
130
  /** End of the line's characters (line break and trailing spaces excluded from `width`). */
95
131
  end: number;
132
+ /** Line space; for vertical text the column's centre line. */
96
133
  baseline: number;
97
134
  ascent: number;
98
135
  descent: number;
@@ -100,34 +137,78 @@ export interface LaidLine {
100
137
  width: number;
101
138
  chars: LaidChar[];
102
139
  fragments: LaidFragment[];
140
+ /** Vertical text only: line space → text space (the layout's `matrix`). */
141
+ matrix?: TextMatrix;
103
142
  }
104
- /** A laid-out type layer in text space. */
143
+ /** A laid-out type layer. */
105
144
  export interface TextLayout {
106
145
  lines: LaidLine[];
107
146
  /** Bounds of the lines (ascent to descent), text space; the box for paragraph text. */
108
147
  bounds: TextRect;
109
148
  /** Families replaced by a fallback. */
110
149
  missingFonts: string[];
150
+ orientation: TextOrientation;
151
+ /** Vertical text only: line space → text space. */
152
+ matrix?: TextMatrix;
111
153
  }
112
154
  export declare function fontOf(style: TextStyle): TextFont;
113
155
  /** Whether a character is a space or tab (spaces hang at line ends and stretch when justifying). */
114
156
  export declare const isTextSpace: (c: string) => c is "\t" | " ";
157
+ /**
158
+ * How a code point is set in vertical text, after Unicode's Vertical_Orientation (UAX #50),
159
+ * simplified to CJK: `U` upright (ideographs, kana, Hangul, full-width forms, emoji), `R` turned
160
+ * sideways (Latin and other horizontal scripts, spaces), `Tu` upright in a vertical form when the
161
+ * font has one (、。 and small kana), `Tr` a vertical form when the font has one, else turned
162
+ * sideways (brackets, dashes, the long vowel mark, wave dash).
163
+ */
164
+ export declare function verticalOrientationOf(cp: number): 'U' | 'R' | 'Tu' | 'Tr';
165
+ /** A rectangle mapped through `m` (a quarter turn keeps it a rectangle); `r` itself without `m`. */
166
+ export declare function transformTextRect(m: TextMatrix | undefined, r: TextRect): TextRect;
167
+ /** A line-space point of `layout` in text space (the same point for horizontal text). */
168
+ export declare function lineToTextSpace(layout: Pick<TextLayout, 'matrix'>, p: TextPoint): TextPoint;
169
+ /** A text-space point of `layout` in line space (the same point for horizontal text). */
170
+ export declare function textToLineSpace(layout: Pick<TextLayout, 'matrix'>, p: TextPoint): TextPoint;
171
+ /** A line's box (its advance by ascent to descent) in text space. */
172
+ export declare function textLineRect(l: LaidLine): TextRect;
115
173
  /** Lays out a type layer with `shaper` (memoised per layer object and shaper). */
116
174
  export declare function layoutText(layer: TextBlock, shaper: TextShaper): TextLayout;
117
175
  /** The text index nearest a text-space point (for clicks): the line under it, then the closest caret. */
118
- export declare function textIndexAt(layout: TextLayout, p: {
119
- x: number;
120
- y: number;
121
- }): number;
122
- /** The caret at text index `index`: its x and the line's top and bottom, text space. */
176
+ export declare function textIndexAt(layout: TextLayout, point: TextPoint): number;
177
+ /**
178
+ * The caret at text index `index`: in line space its x and the line's top and bottom, and in text
179
+ * space the segment it draws (`from` the line's top or, vertically, its right edge, `to` the other
180
+ * side).
181
+ */
123
182
  export declare function caretAt(layout: TextLayout, index: number): {
124
183
  x: number;
125
184
  top: number;
126
185
  bottom: number;
127
186
  line: number;
187
+ from: TextPoint;
188
+ to: TextPoint;
128
189
  };
129
- /** Underline and strike-through bars of a fragment, text space, as rectangles. */
190
+ /** Quads (text space, four corners each) covering the characters of [start, end), one per line. */
191
+ export declare function textSelectionQuads(layout: TextLayout, start: number, end: number): TextPoint[][];
192
+ /** What an arrow key does to a text caret: along the line (`previous` / `next` character) or across lines. */
193
+ export type TextCaretStep = 'previous' | 'next' | 'line-before' | 'line-after';
194
+ /**
195
+ * The caret step of an arrow key. Horizontal text: ← → move along the line, ↑ ↓ between lines.
196
+ * Vertical text: ↑ ↓ move along the column, → to the column before (on the right), ← to the next.
197
+ */
198
+ export declare function textArrowStep(orientation: TextOrientation | undefined, key: 'ArrowLeft' | 'ArrowRight' | 'ArrowUp' | 'ArrowDown'): TextCaretStep;
199
+ /**
200
+ * The index on the line before (`dir` −1) or after (+1) the caret at `index`, at the same place
201
+ * along the line; the start or end of the text (`length`) past the first or last line.
202
+ */
203
+ export declare function textLineMove(layout: TextLayout, index: number, dir: -1 | 1, length: number): number;
204
+ /** Underline and strike-through bars of a fragment, text space, as rectangles. Vertical text underlines on the right. */
130
205
  export declare function textDecorations(f: LaidFragment, l: LaidLine): TextRect[];
131
206
  export declare const textRectPath: (r: TextRect) => BezierPath;
132
- /** The text of a fragment as outlines (one placement per character when spaced), text space. */
207
+ /** The text of a fragment as outlines (one placement per character when spaced or vertical), text space. */
133
208
  export declare function textFragmentOutline(shaper: TextShaper, text: string, f: LaidFragment): BezierPath;
209
+ /**
210
+ * Draws a fragment's text with a drawing shaper in the context's current transform (text space),
211
+ * fill style and alpha: each glyph of vertical text turned or upright, spaced text a character at
212
+ * a time. Decorations are not drawn (see `textDecorations`).
213
+ */
214
+ export declare function drawTextFragment(ctx: CanvasRenderingContext2D, shaper: TextShaper, text: string, f: LaidFragment): void;
@@ -2,9 +2,10 @@
2
2
  * A small OpenType reader: enough of a font file to set Latin, Cyrillic, Greek and CJK text as
3
3
  * outlines without a third-party library. It reads TrueType (`glyf`) and CFF (`CFF `, CID-keyed
4
4
  * included) outlines, `cmap` (formats 4 and 12), horizontal metrics, `kern` (format 0) and `GPOS`
5
- * pair kerning (lookup type 2, formats 1 and 2, the `kern` feature), and the family, PostScript
6
- * name, weight and style. It does not apply `GSUB` (ligatures, contextual forms) or complex-script
7
- * shaping, so Arabic, Indic and similar scripts set as isolated glyphs.
5
+ * pair kerning (lookup type 2, formats 1 and 2, the `kern` feature), vertical metrics (`vhea`,
6
+ * `vmtx`, `VORG`) and vertical alternates (`GSUB` single substitutions of the `vert` feature), and
7
+ * the family, PostScript name, weight and style. It applies no other `GSUB` (ligatures, contextual
8
+ * forms) or complex-script shaping, so Arabic, Indic and similar scripts set as isolated glyphs.
8
9
  *
9
10
  * The result has the shape of opentype.js's `Font` that `createOutlineTextShaper` takes, so hosts
10
11
  * can pass either. Written from the OpenType specification (TrueType glyph outlines, the CFF
@@ -31,6 +32,15 @@ export interface OpenTypeFont extends OutlineFontFace {
31
32
  kerning(left: number, right: number): number;
32
33
  /** A glyph's outline in font units, y up, origin on the baseline. */
33
34
  glyphPath(glyph: number): OutlinePathCommand[];
35
+ /** Whether the font has vertical metrics (`vhea` and `vmtx`). */
36
+ readonly hasVerticalMetrics: boolean;
37
+ /** A glyph's vertical alternate (the `vert` feature), or the glyph itself when it has none. */
38
+ verticalGlyph(glyph: number): number;
39
+ getVerticalMetrics(char: string): {
40
+ advance: number;
41
+ originY: number;
42
+ alternate: boolean;
43
+ };
34
44
  }
35
45
  /**
36
46
  * Reads a TrueType or OpenType font (`.ttf`, `.otf`, or face `index` of a `.ttc` collection).
@@ -2,9 +2,10 @@
2
2
  * A small OpenType reader: enough of a font file to set Latin, Cyrillic, Greek and CJK text as
3
3
  * outlines without a third-party library. It reads TrueType (`glyf`) and CFF (`CFF `, CID-keyed
4
4
  * included) outlines, `cmap` (formats 4 and 12), horizontal metrics, `kern` (format 0) and `GPOS`
5
- * pair kerning (lookup type 2, formats 1 and 2, the `kern` feature), and the family, PostScript
6
- * name, weight and style. It does not apply `GSUB` (ligatures, contextual forms) or complex-script
7
- * shaping, so Arabic, Indic and similar scripts set as isolated glyphs.
5
+ * pair kerning (lookup type 2, formats 1 and 2, the `kern` feature), vertical metrics (`vhea`,
6
+ * `vmtx`, `VORG`) and vertical alternates (`GSUB` single substitutions of the `vert` feature), and
7
+ * the family, PostScript name, weight and style. It applies no other `GSUB` (ligatures, contextual
8
+ * forms) or complex-script shaping, so Arabic, Indic and similar scripts set as isolated glyphs.
8
9
  *
9
10
  * The result has the shape of opentype.js's `Font` that `createOutlineTextShaper` takes, so hosts
10
11
  * can pass either. Written from the OpenType specification (TrueType glyph outlines, the CFF
@@ -31,6 +32,15 @@ export interface OpenTypeFont extends OutlineFontFace {
31
32
  kerning(left: number, right: number): number;
32
33
  /** A glyph's outline in font units, y up, origin on the baseline. */
33
34
  glyphPath(glyph: number): OutlinePathCommand[];
35
+ /** Whether the font has vertical metrics (`vhea` and `vmtx`). */
36
+ readonly hasVerticalMetrics: boolean;
37
+ /** A glyph's vertical alternate (the `vert` feature), or the glyph itself when it has none. */
38
+ verticalGlyph(glyph: number): number;
39
+ getVerticalMetrics(char: string): {
40
+ advance: number;
41
+ originY: number;
42
+ alternate: boolean;
43
+ };
34
44
  }
35
45
  /**
36
46
  * Reads a TrueType or OpenType font (`.ttf`, `.otf`, or face `index` of a `.ttc` collection).
@@ -42,11 +42,23 @@ export interface OutlineFontFace {
42
42
  getAdvanceWidth(text: string, fontSize: number, options?: {
43
43
  kerning?: boolean;
44
44
  }): number;
45
+ /** `vertical`: the font's vertical alternates (`vert`), where it has them (faces without `getVerticalMetrics` ignore it). */
45
46
  getPath(text: string, x: number, y: number, fontSize: number, options?: {
46
47
  kerning?: boolean;
48
+ vertical?: boolean;
47
49
  }): {
48
50
  commands: OutlinePathCommand[];
49
51
  };
52
+ /**
53
+ * Vertical metrics of one character set upright, in font units: its advance down the column, the
54
+ * distance from the top of its cell to its baseline, and whether it has a vertical alternate.
55
+ * Optional (opentype.js lacks it): vertical text then uses the ascender and descender.
56
+ */
57
+ getVerticalMetrics?(char: string): {
58
+ advance: number;
59
+ originY: number;
60
+ alternate: boolean;
61
+ };
50
62
  names?: {
51
63
  fontFamily?: Record<string, string>;
52
64
  postScriptName?: Record<string, string>;
@@ -42,11 +42,23 @@ export interface OutlineFontFace {
42
42
  getAdvanceWidth(text: string, fontSize: number, options?: {
43
43
  kerning?: boolean;
44
44
  }): number;
45
+ /** `vertical`: the font's vertical alternates (`vert`), where it has them (faces without `getVerticalMetrics` ignore it). */
45
46
  getPath(text: string, x: number, y: number, fontSize: number, options?: {
46
47
  kerning?: boolean;
48
+ vertical?: boolean;
47
49
  }): {
48
50
  commands: OutlinePathCommand[];
49
51
  };
52
+ /**
53
+ * Vertical metrics of one character set upright, in font units: its advance down the column, the
54
+ * distance from the top of its cell to its baseline, and whether it has a vertical alternate.
55
+ * Optional (opentype.js lacks it): vertical text then uses the ascender and descender.
56
+ */
57
+ getVerticalMetrics?(char: string): {
58
+ advance: number;
59
+ originY: number;
60
+ alternate: boolean;
61
+ };
50
62
  names?: {
51
63
  fontFamily?: Record<string, string>;
52
64
  postScriptName?: Record<string, string>;