@pixerate/editor-svelte 0.141.0 → 0.142.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.
@@ -1,13 +1,13 @@
1
1
  <script lang="ts">
2
2
  import ImageColor from './controls/ImageColor.svelte';
3
3
  import { imageChipClass } from './styles.js';
4
- import type { ImageEditorController, TextAlign, TextEditSession, TextStyle, TypeToolOptions } from '@pixerate/editor/image-editor';
5
- import { TEXT_ALIGNS } from '@pixerate/editor/image-editor';
4
+ import type { ImageEditorController, TextAlign, TextEditSession, TextOrientation, TextStyle, TypeToolOptions } from '@pixerate/editor/image-editor';
5
+ import { TEXT_ALIGNS, TEXT_ORIENTATIONS } from '@pixerate/editor/image-editor';
6
6
  import { untrack } from 'svelte';
7
7
 
8
8
  /**
9
- * The type tool's options: font family (from the editor's fonts), size, bold, italic, colour and
10
- * alignment. While text is being edited they style the selection (or the whole text); with a
9
+ * The type tool's options: font family (from the editor's fonts), size, bold, italic, colour,
10
+ * alignment and orientation (horizontal or vertical type, with upright Latin in vertical text). While text is being edited they style the selection (or the whole text); with a
11
11
  * type layer active they style all of it; otherwise they set what the next text uses.
12
12
  */
13
13
  interface Props {
@@ -32,6 +32,7 @@
32
32
  return untrack(() => s.subscribe(() => tick++));
33
33
  });
34
34
  const ALIGN_LABELS: Record<TextAlign, string> = { left: 'Left', center: 'Centre', right: 'Right', justify: 'Justify' };
35
+ const ORIENTATION_LABELS: Record<TextOrientation, string> = { horizontal: 'Horizontal', vertical: 'Vertical' };
35
36
 
36
37
 
37
38
  const view = $derived.by(() => {
@@ -45,9 +46,18 @@
45
46
  ...(here.fontWeight ? { fontWeight: here.fontWeight } : {}),
46
47
  ...(session?.alive ? { italic: Boolean(here.italic), color: here.color ?? options.color } : {}),
47
48
  };
49
+ const typeId = !session && active?.type === 'type' ? active.id : null;
50
+ // The orientation of the text being edited or the active type layer, else the tool's.
51
+ const editedId = session?.alive ? session.layerId : typeId;
52
+ const edited = editedId ? controller.getLayer(editedId) : null;
53
+ const orientation: TextOrientation = edited?.type === 'type' ? (edited.orientation ?? 'horizontal') : (options.orientation ?? 'horizontal');
54
+ const upright = Boolean(session?.alive ? here.upright : edited?.type === 'type' && edited.runs[0]?.style.upright);
48
55
  return {
49
56
  shown,
50
- typeId: !session && active?.type === 'type' ? active.id : null,
57
+ editedId,
58
+ orientation,
59
+ upright,
60
+ typeId,
51
61
  missing: active?.type === 'type' ? (active.missingFonts ?? []) : [],
52
62
  fonts: controller.listFonts(),
53
63
  };
@@ -73,6 +83,24 @@
73
83
  error = e instanceof Error ? e.message : String(e);
74
84
  }
75
85
  }
86
+ function orient(next: TextOrientation) {
87
+ onchange({ ...options, orientation: next });
88
+ try {
89
+ error = null;
90
+ if (view.editedId) controller.setTextOrientation(view.editedId, next);
91
+ } catch (e) {
92
+ error = e instanceof Error ? e.message : String(e);
93
+ }
94
+ }
95
+ function toggleUpright() {
96
+ try {
97
+ error = null;
98
+ if (session?.alive) session.setStyle({ upright: !view.upright });
99
+ else if (view.typeId) controller.setTextStyle(view.typeId, { upright: !view.upright });
100
+ } catch (e) {
101
+ error = e instanceof Error ? e.message : String(e);
102
+ }
103
+ }
76
104
  const value = (e: Event) => (e.target as HTMLInputElement).value;
77
105
  </script>
78
106
 
@@ -108,7 +136,15 @@
108
136
  <button type="button" aria-pressed={options.align === a} class={imageChipClass(options.align === a)} onclick={() => style({ align: a })}>{ALIGN_LABELS[a]}</button>
109
137
  {/each}
110
138
  </div>
111
- <p class="text-[10px] text-muted-foreground">Click to type point text, drag for a paragraph box, click text to edit it. Esc finishes.</p>
139
+ <div class="flex gap-1" role="toolbar" aria-label="Orientation">
140
+ {#each TEXT_ORIENTATIONS as o (o)}
141
+ <button type="button" aria-pressed={view.orientation === o} class={imageChipClass(view.orientation === o)} onclick={() => orient(o)}>{ORIENTATION_LABELS[o]}</button>
142
+ {/each}
143
+ {#if view.orientation === 'vertical' && view.editedId}
144
+ <button type="button" aria-pressed={view.upright} class={imageChipClass(view.upright)} title="Set Latin upright, one letter under the other (else turned sideways)" onclick={toggleUpright}>Upright Latin</button>
145
+ {/if}
146
+ </div>
147
+ <p class="text-[10px] text-muted-foreground">Click to type point text (vertical: a column down from the click), drag for a paragraph box, click text to edit it. Esc finishes.</p>
112
148
  {#if view.missing.length > 0}
113
149
  <p role="status" class="text-amber-600" data-testid="type-missing-fonts">Missing {view.missing.length === 1 ? 'font' : 'fonts'}: {view.missing.join(', ')}</p>
114
150
  {/if}
@@ -1,7 +1,7 @@
1
1
  import type { ImageEditorController, TextEditSession, TypeToolOptions } from '@pixerate/editor/image-editor';
2
2
  /**
3
- * The type tool's options: font family (from the editor's fonts), size, bold, italic, colour and
4
- * alignment. While text is being edited they style the selection (or the whole text); with a
3
+ * The type tool's options: font family (from the editor's fonts), size, bold, italic, colour,
4
+ * alignment and orientation (horizontal or vertical type, with upright Latin in vertical text). While text is being edited they style the selection (or the whole text); with a
5
5
  * type layer active they style all of it; otherwise they set what the next text uses.
6
6
  */
7
7
  interface Props {
@@ -3,6 +3,7 @@
3
3
  VECTOR_FONT_WEIGHTS,
4
4
  VECTOR_TEXT_ALIGN_OPTIONS,
5
5
  applyVectorParagraphStyle,
6
+ applyVectorTextOrientation,
6
7
  applyVectorTextStyle,
7
8
  getVectorTextPanel,
8
9
  type VectorEditorController,
@@ -12,7 +13,8 @@
12
13
 
13
14
  /**
14
15
  * Character and paragraph style of text: family, weight, size, italic, underline, strike-through,
15
- * tracking, leading, alignment and point / area text. It edits the selected characters while text
16
+ * tracking, leading, alignment, horizontal / vertical orientation (with upright Latin in vertical
17
+ * text) and point / area text. It edits the selected characters while text
16
18
  * is being edited, else the selected text, else the defaults for new text. Type on a Path turns a
17
19
  * selected path into path text, and Create Outlines turns text into paths (with an outline
18
20
  * shaper). Each change is one undo step.
@@ -108,6 +110,22 @@
108
110
  <button type="button" class={buttonClass} aria-label="Underline" aria-pressed={f.underline === true} onclick={() => style({ underline: !f.underline })}><u>U</u></button>
109
111
  <button type="button" class={buttonClass} aria-label="Strike through" aria-pressed={f.strikethrough === true} onclick={() => style({ strikethrough: !f.strikethrough })}><s>S</s></button>
110
112
  </div>
113
+ {#if panel.target !== 'defaults'}
114
+ <div class="flex flex-wrap items-center gap-1" role="group" aria-label="Orientation">
115
+ <button type="button" class={buttonClass} aria-pressed={panel.orientation === 'horizontal'} title="Horizontal text: lines left to right" onclick={() => applyVectorTextOrientation(controller, 'horizontal')}>Horizontal</button>
116
+ <button
117
+ type="button"
118
+ class={buttonClass}
119
+ aria-pressed={panel.orientation === 'vertical'}
120
+ disabled={!panel.canOrient}
121
+ title={panel.canOrient ? 'Vertical text: columns top to bottom, right to left' : 'Text on a path cannot be vertical'}
122
+ onclick={() => applyVectorTextOrientation(controller, 'vertical')}>Vertical</button
123
+ >
124
+ {#if panel.orientation === 'vertical'}
125
+ <button type="button" class={buttonClass} aria-pressed={f.upright === true} title="Set Latin upright, one letter under the other (else turned sideways)" onclick={() => style({ upright: !f.upright })}>Upright Latin</button>
126
+ {/if}
127
+ </div>
128
+ {/if}
111
129
  <div class="flex items-center gap-1">
112
130
  <label class="flex flex-1 items-center gap-1 text-xs text-foreground" title="Tracking (1/1000 em)">
113
131
  <span class="text-[11px] text-muted-foreground">VA</span>
@@ -1,7 +1,8 @@
1
1
  import { type VectorEditorController } from '@pixerate/editor/vector';
2
2
  /**
3
3
  * Character and paragraph style of text: family, weight, size, italic, underline, strike-through,
4
- * tracking, leading, alignment and point / area text. It edits the selected characters while text
4
+ * tracking, leading, alignment, horizontal / vertical orientation (with upright Latin in vertical
5
+ * text) and point / area text. It edits the selected characters while text
5
6
  * is being edited, else the selected text, else the defaults for new text. Type on a Path turns a
6
7
  * selected path into path text, and Create Outlines turns text into paths (with an outline
7
8
  * shaper). Each change is one undo step.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pixerate/editor-svelte",
3
- "version": "0.141.0",
3
+ "version": "0.142.0",
4
4
  "description": "Svelte bindings and prompt/rich-text components for @pixerate/editor",
5
5
  "author": "Pixerate",
6
6
  "license": "MIT",
@@ -165,7 +165,7 @@
165
165
  "**/*.css"
166
166
  ],
167
167
  "dependencies": {
168
- "@pixerate/editor": "0.192.0"
168
+ "@pixerate/editor": "0.193.0"
169
169
  },
170
170
  "peerDependencies": {
171
171
  "@dagrejs/dagre": "^1.1.4",