blitz-pdf 0.1.2 → 0.1.4

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 (64) hide show
  1. package/README.md +26 -0
  2. package/THIRD_PARTY_NOTICES.md +5 -2
  3. package/docs/layout-regressions.md +65 -0
  4. package/docs/pagination-regressions.md +89 -0
  5. package/package.json +2 -1
  6. package/vendor/README.md +35 -0
  7. package/vendor/blitz-dom/Cargo.toml +113 -0
  8. package/vendor/blitz-dom/LICENSE-APACHE +176 -0
  9. package/vendor/blitz-dom/LICENSE-MIT +23 -0
  10. package/vendor/blitz-dom/assets/default.css +1142 -0
  11. package/vendor/blitz-dom/assets/moz-bullet-font.otf +0 -0
  12. package/vendor/blitz-dom/src/accessibility.rs +255 -0
  13. package/vendor/blitz-dom/src/config.rs +97 -0
  14. package/vendor/blitz-dom/src/cssom.rs +971 -0
  15. package/vendor/blitz-dom/src/debug.rs +206 -0
  16. package/vendor/blitz-dom/src/document.rs +3395 -0
  17. package/vendor/blitz-dom/src/events/driver.rs +427 -0
  18. package/vendor/blitz-dom/src/events/focus.rs +40 -0
  19. package/vendor/blitz-dom/src/events/ime.rs +31 -0
  20. package/vendor/blitz-dom/src/events/keyboard.rs +174 -0
  21. package/vendor/blitz-dom/src/events/mod.rs +315 -0
  22. package/vendor/blitz-dom/src/events/pointer.rs +849 -0
  23. package/vendor/blitz-dom/src/font_metrics.rs +177 -0
  24. package/vendor/blitz-dom/src/form.rs +463 -0
  25. package/vendor/blitz-dom/src/html.rs +38 -0
  26. package/vendor/blitz-dom/src/iframe.rs +186 -0
  27. package/vendor/blitz-dom/src/layout/construct.rs +1328 -0
  28. package/vendor/blitz-dom/src/layout/damage.rs +642 -0
  29. package/vendor/blitz-dom/src/layout/inline.rs +1160 -0
  30. package/vendor/blitz-dom/src/layout/list.rs +237 -0
  31. package/vendor/blitz-dom/src/layout/mod.rs +969 -0
  32. package/vendor/blitz-dom/src/layout/paint_tree.rs +462 -0
  33. package/vendor/blitz-dom/src/layout/replaced.rs +439 -0
  34. package/vendor/blitz-dom/src/layout/table.rs +967 -0
  35. package/vendor/blitz-dom/src/layout/text_transform.rs +979 -0
  36. package/vendor/blitz-dom/src/layout/writing_mode.rs +437 -0
  37. package/vendor/blitz-dom/src/lib.rs +160 -0
  38. package/vendor/blitz-dom/src/mutator.rs +1818 -0
  39. package/vendor/blitz-dom/src/net.rs +649 -0
  40. package/vendor/blitz-dom/src/node/attributes.rs +60 -0
  41. package/vendor/blitz-dom/src/node/custom_widget.rs +178 -0
  42. package/vendor/blitz-dom/src/node/element.rs +1106 -0
  43. package/vendor/blitz-dom/src/node/mod.rs +30 -0
  44. package/vendor/blitz-dom/src/node/node.rs +2036 -0
  45. package/vendor/blitz-dom/src/node/scrollbar.rs +237 -0
  46. package/vendor/blitz-dom/src/node/serialize.rs +225 -0
  47. package/vendor/blitz-dom/src/node/stylo_data.rs +142 -0
  48. package/vendor/blitz-dom/src/node/svg.rs +265 -0
  49. package/vendor/blitz-dom/src/node/text.rs +804 -0
  50. package/vendor/blitz-dom/src/query_selector.rs +275 -0
  51. package/vendor/blitz-dom/src/resolve.rs +474 -0
  52. package/vendor/blitz-dom/src/resolved_style.rs +797 -0
  53. package/vendor/blitz-dom/src/scrolling.rs +780 -0
  54. package/vendor/blitz-dom/src/selection.rs +122 -0
  55. package/vendor/blitz-dom/src/stylo.rs +1518 -0
  56. package/vendor/blitz-dom/src/stylo_device.rs +88 -0
  57. package/vendor/blitz-dom/src/stylo_to_cursor_icon.rs +49 -0
  58. package/vendor/blitz-dom/src/stylo_to_kurbo.rs +129 -0
  59. package/vendor/blitz-dom/src/stylo_to_parley.rs +655 -0
  60. package/vendor/blitz-dom/src/traversal.rs +600 -0
  61. package/vendor/blitz-dom/src/tree.rs +136 -0
  62. package/vendor/blitz-dom/src/url.rs +63 -0
  63. package/vendor/blitz-dom/src/util.rs +256 -0
  64. package/wasm/blitz_pdf_bg.wasm +0 -0
@@ -0,0 +1,804 @@
1
+ use blitz_traits::{
2
+ events::{BlitzImeEvent, BlitzKeyEvent},
3
+ node_id::NodeId,
4
+ shell::ShellProvider,
5
+ };
6
+ use keyboard_types::{Key, Modifiers};
7
+ use parley::{ContentWidths, FontContext, LayoutContext};
8
+
9
+ use crate::util::ACTION_MOD;
10
+
11
+ #[derive(Debug, Clone, Copy, Default, PartialEq)]
12
+ /// Parley Brush type for Blitz which contains the Blitz node id
13
+ pub struct TextBrush {
14
+ /// The node id for the span
15
+ pub id: NodeId,
16
+ }
17
+
18
+ impl TextBrush {
19
+ pub(crate) fn from_id(id: NodeId) -> Self {
20
+ Self { id }
21
+ }
22
+ }
23
+
24
+ #[derive(Clone, Default)]
25
+ pub struct TextLayout {
26
+ pub text: String,
27
+ pub content_widths: Option<ContentWidths>,
28
+ pub layout: parley::layout::Layout<TextBrush>,
29
+ /// Block-axis offset (in CSS px) of the line boxes from the top of the container's content
30
+ /// box, as applied by `align-content`.
31
+ pub block_offset: f32,
32
+ /// The out-of-flow positions for which some inline span with an inline box below it is a
33
+ /// containing block (as `SPAN_*_CB` flags). The inline root claims those boxes in its place.
34
+ /// Set by each `RunMode::PerformLayout` run of inline layout.
35
+ pub span_cb_flags: stylo_taffy::StyleFlags,
36
+ }
37
+
38
+ impl TextLayout {
39
+ pub fn new() -> Self {
40
+ Default::default()
41
+ }
42
+
43
+ pub fn content_widths(&mut self) -> ContentWidths {
44
+ *self
45
+ .content_widths
46
+ .get_or_insert_with(|| self.layout.calculate_content_widths())
47
+ }
48
+ }
49
+
50
+ impl std::fmt::Debug for TextLayout {
51
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
52
+ write!(f, "TextLayout")
53
+ }
54
+ }
55
+
56
+ // TODO: support keypress events
57
+ pub enum GeneratedTextInputEvent {
58
+ Input,
59
+ Select,
60
+ PreEditChange,
61
+ Submit,
62
+ }
63
+
64
+ pub struct TextInputData {
65
+ /// A parley TextEditor instance
66
+ pub editor: Box<parley::PlainEditor<TextBrush>>,
67
+ /// Whether the input is a singleline or multiline input
68
+ pub is_multiline: bool,
69
+ /// The scroll offset of the text content within the input, in CSS (unscaled) pixels.
70
+ ///
71
+ /// For single-line inputs this is a horizontal offset; for multi-line inputs it is a
72
+ /// vertical offset. It is kept up to date so that the caret remains visible within the
73
+ /// input's content box.
74
+ pub scroll_offset: f32,
75
+ }
76
+
77
+ // FIXME: Implement Clone for PlainEditor
78
+ impl Clone for TextInputData {
79
+ fn clone(&self) -> Self {
80
+ TextInputData::new(self.is_multiline)
81
+ }
82
+ }
83
+
84
+ impl TextInputData {
85
+ pub fn new(is_multiline: bool) -> Self {
86
+ let editor = Box::new(parley::PlainEditor::new(16.0));
87
+ Self {
88
+ editor,
89
+ is_multiline,
90
+ scroll_offset: 0.0,
91
+ }
92
+ }
93
+
94
+ pub fn set_text(
95
+ &mut self,
96
+ font_ctx: &mut FontContext,
97
+ layout_ctx: &mut LayoutContext<TextBrush>,
98
+ text: &str,
99
+ ) {
100
+ if self.editor.text() != text {
101
+ self.editor.set_text(text);
102
+ self.editor.driver(font_ctx, layout_ctx).refresh_layout();
103
+ }
104
+ }
105
+
106
+ /// Recompute [`Self::scroll_offset`] so that the caret stays visible within the input's
107
+ /// content box.
108
+ ///
109
+ /// `content_box_width` and `content_box_height` are the dimensions of the input's content
110
+ /// box in CSS (unscaled) pixels.
111
+ pub fn clamp_scroll_offset(&mut self, content_box_width: f32, content_box_height: f32) {
112
+ let Some(layout) = self.editor.try_layout() else {
113
+ return;
114
+ };
115
+ // Parley lays out at the editor's scale, so its geometry is in scaled (device) pixels.
116
+ // We convert into CSS (unscaled) pixels to match `scroll_offset` and the content box.
117
+ let scale = layout.scale();
118
+
119
+ // The caret geometry relative to the start of the text content.
120
+ let Some(caret) = self.editor.cursor_geometry(1.5) else {
121
+ return;
122
+ };
123
+
124
+ // Caret bounds and content/viewport extents along the scrolling axis (CSS pixels).
125
+ let (caret_start, caret_end, content, viewport) = if self.is_multiline {
126
+ (
127
+ caret.y0 as f32 / scale,
128
+ caret.y1 as f32 / scale,
129
+ layout.height() / scale,
130
+ content_box_height,
131
+ )
132
+ } else {
133
+ (
134
+ caret.x0 as f32 / scale,
135
+ caret.x1 as f32 / scale,
136
+ layout.full_width() / scale,
137
+ content_box_width,
138
+ )
139
+ };
140
+
141
+ let mut offset = self.scroll_offset;
142
+
143
+ // Scroll so that both edges of the caret are within the visible region.
144
+ if caret_end > offset + viewport {
145
+ offset = caret_end - viewport;
146
+ }
147
+ if caret_start < offset {
148
+ offset = caret_start;
149
+ }
150
+
151
+ // Never scroll past the content, and never scroll into negative space. The content
152
+ // extent includes the caret so that a caret at the very end remains fully visible
153
+ // (its rendered width extends slightly past the text).
154
+ let max_offset = (content.max(caret_end) - viewport).max(0.0);
155
+ self.scroll_offset = offset.clamp(0.0, max_offset);
156
+ }
157
+
158
+ /// The maximum valid value of [`Self::scroll_offset`] (in CSS pixels) given the input's
159
+ /// content box, i.e. the extent by which the text content overflows the content box along
160
+ /// the input's scroll axis.
161
+ ///
162
+ /// `content_box_width` and `content_box_height` are the dimensions of the input's content
163
+ /// box in CSS (unscaled) pixels.
164
+ pub fn max_scroll_offset(&self, content_box_width: f32, content_box_height: f32) -> f32 {
165
+ let Some(layout) = self.editor.try_layout() else {
166
+ return 0.0;
167
+ };
168
+ let scale = layout.scale();
169
+ let (content, viewport) = if self.is_multiline {
170
+ (layout.height() / scale, content_box_height)
171
+ } else {
172
+ (layout.full_width() / scale, content_box_width)
173
+ };
174
+ (content - viewport).max(0.0)
175
+ }
176
+
177
+ /// Scroll the input's text content by `delta` CSS pixels along its scroll axis (horizontal
178
+ /// for single-line inputs, vertical for multi-line inputs), clamping to the scrollable
179
+ /// range.
180
+ ///
181
+ /// Returns the portion of `delta` that could not be consumed (because the input was already
182
+ /// scrolled to its limit), so the caller can bubble it up to an ancestor scroller.
183
+ pub fn scroll_by(
184
+ &mut self,
185
+ delta: f32,
186
+ content_box_width: f32,
187
+ content_box_height: f32,
188
+ ) -> f32 {
189
+ let max_offset = self.max_scroll_offset(content_box_width, content_box_height);
190
+ if max_offset <= 0.0 {
191
+ return delta;
192
+ }
193
+
194
+ // Match the sign convention used for block scrolling: a positive delta decreases the
195
+ // scroll offset.
196
+ let new_offset = (self.scroll_offset - delta).clamp(0.0, max_offset);
197
+ let consumed = self.scroll_offset - new_offset;
198
+ self.scroll_offset = new_offset;
199
+ delta - consumed
200
+ }
201
+
202
+ pub(crate) fn apply_keypress_event(
203
+ &mut self,
204
+ font_ctx: &mut FontContext,
205
+ layout_ctx: &mut LayoutContext<TextBrush>,
206
+ shell_provider: &dyn ShellProvider,
207
+ event: BlitzKeyEvent,
208
+ ) -> Option<GeneratedTextInputEvent> {
209
+ // Do nothing if it is a keyup event
210
+ if !event.state.is_pressed() {
211
+ return None;
212
+ }
213
+
214
+ let mods = event.modifiers;
215
+ let shift = mods.contains(Modifiers::SHIFT);
216
+ let action_mod = mods.contains(ACTION_MOD);
217
+
218
+ let is_multiline = self.is_multiline;
219
+ let editor = &mut self.editor;
220
+ let mut driver = editor.driver(font_ctx, layout_ctx);
221
+ match event.key {
222
+ Key::Character(c) if action_mod && matches!(c.as_str(), "c" | "x" | "v") => {
223
+ match c.to_lowercase().as_str() {
224
+ "c" => {
225
+ if let Some(text) = driver.editor.selected_text() {
226
+ let _ = shell_provider.set_clipboard_text(text.to_owned());
227
+ }
228
+ }
229
+ "x" => {
230
+ if let Some(text) = driver.editor.selected_text() {
231
+ let _ = shell_provider.set_clipboard_text(text.to_owned());
232
+ driver.delete_selection()
233
+ }
234
+ }
235
+ "v" => {
236
+ let text = shell_provider.get_clipboard_text().unwrap_or_default();
237
+ driver.insert_or_replace_selection(&text)
238
+ }
239
+ _ => unreachable!(),
240
+ }
241
+
242
+ return Some(GeneratedTextInputEvent::Input);
243
+ }
244
+ Key::Character(c) if action_mod && matches!(c.to_lowercase().as_str(), "a") => {
245
+ if shift {
246
+ driver.collapse_selection()
247
+ } else {
248
+ driver.select_all()
249
+ }
250
+ return Some(GeneratedTextInputEvent::Select);
251
+ }
252
+ Key::ArrowLeft => {
253
+ if action_mod {
254
+ if shift {
255
+ driver.select_word_left()
256
+ } else {
257
+ driver.move_word_left()
258
+ }
259
+ } else if shift {
260
+ driver.select_left()
261
+ } else {
262
+ driver.move_left()
263
+ }
264
+ return Some(GeneratedTextInputEvent::Select);
265
+ }
266
+ Key::ArrowRight => {
267
+ if action_mod {
268
+ if shift {
269
+ driver.select_word_right()
270
+ } else {
271
+ driver.move_word_right()
272
+ }
273
+ } else if shift {
274
+ driver.select_right()
275
+ } else {
276
+ driver.move_right()
277
+ }
278
+ return Some(GeneratedTextInputEvent::Select);
279
+ }
280
+ Key::ArrowUp => {
281
+ if shift {
282
+ driver.select_up()
283
+ } else {
284
+ driver.move_up()
285
+ }
286
+ return Some(GeneratedTextInputEvent::Select);
287
+ }
288
+ Key::ArrowDown => {
289
+ if shift {
290
+ driver.select_down()
291
+ } else {
292
+ driver.move_down()
293
+ }
294
+ return Some(GeneratedTextInputEvent::Select);
295
+ }
296
+ Key::Home => {
297
+ if action_mod {
298
+ if shift {
299
+ driver.select_to_text_start()
300
+ } else {
301
+ driver.move_to_text_start()
302
+ }
303
+ } else if shift {
304
+ driver.select_to_line_start()
305
+ } else {
306
+ driver.move_to_line_start()
307
+ }
308
+ return Some(GeneratedTextInputEvent::Select);
309
+ }
310
+ Key::End => {
311
+ if action_mod {
312
+ if shift {
313
+ driver.select_to_text_end()
314
+ } else {
315
+ driver.move_to_text_end()
316
+ }
317
+ } else if shift {
318
+ driver.select_to_line_end()
319
+ } else {
320
+ driver.move_to_line_end()
321
+ }
322
+ return Some(GeneratedTextInputEvent::Select);
323
+ }
324
+ Key::Delete => {
325
+ if action_mod {
326
+ driver.delete_word()
327
+ } else {
328
+ driver.delete()
329
+ }
330
+ return Some(GeneratedTextInputEvent::Input);
331
+ }
332
+
333
+ // On macOS this is handled by the apple standard keybindings
334
+ #[cfg(not(target_os = "macos"))]
335
+ Key::Backspace => {
336
+ if action_mod {
337
+ driver.backdelete_word()
338
+ } else {
339
+ driver.backdelete()
340
+ }
341
+ return Some(GeneratedTextInputEvent::Input);
342
+ }
343
+
344
+ Key::Character(c) if c == "\n" => {
345
+ if is_multiline {
346
+ driver.insert_or_replace_selection("\n");
347
+ return Some(GeneratedTextInputEvent::Input);
348
+ } else {
349
+ return Some(GeneratedTextInputEvent::Submit);
350
+ }
351
+ }
352
+ Key::Enter => {
353
+ if is_multiline {
354
+ driver.insert_or_replace_selection("\n");
355
+ return Some(GeneratedTextInputEvent::Input);
356
+ } else {
357
+ return Some(GeneratedTextInputEvent::Submit);
358
+ }
359
+ }
360
+ Key::Character(s)
361
+ if !mods.contains(Modifiers::CONTROL) && !mods.contains(Modifiers::SUPER) =>
362
+ {
363
+ driver.insert_or_replace_selection(&s);
364
+ return Some(GeneratedTextInputEvent::Input);
365
+ }
366
+ _ => {}
367
+ };
368
+
369
+ None
370
+ }
371
+
372
+ pub(crate) fn apply_apple_standard_keybinding(
373
+ &mut self,
374
+ font_ctx: &mut FontContext,
375
+ layout_ctx: &mut LayoutContext<TextBrush>,
376
+ shell_provider: &dyn ShellProvider,
377
+ command: &str,
378
+ ) -> Option<GeneratedTextInputEvent> {
379
+ let editor = &mut self.editor;
380
+ let mut driver = editor.driver(font_ctx, layout_ctx);
381
+ let is_multiline = self.is_multiline;
382
+
383
+ match command {
384
+ // Inserting Content
385
+
386
+ // Inserts a backtab character.
387
+ "insertBacktab:" => {}
388
+ // Inserts a container break, such as a new page break.
389
+ "insertContainerBreak:" => {}
390
+ // Inserts a double quotation mark without substituting a curly quotation mark.
391
+ "insertDoubleQuoteIgnoringSubstitution:" => {
392
+ driver.insert_or_replace_selection("\"");
393
+ return Some(GeneratedTextInputEvent::Input);
394
+ }
395
+ // Inserts a line break character.
396
+ "insertLineBreak:" => {
397
+ driver.insert_or_replace_selection("\n");
398
+ return Some(GeneratedTextInputEvent::Input);
399
+ }
400
+ // Inserts a newline character.
401
+ "insertNewline:" => {
402
+ if is_multiline {
403
+ driver.insert_or_replace_selection("\n");
404
+ return Some(GeneratedTextInputEvent::Input);
405
+ } else {
406
+ return Some(GeneratedTextInputEvent::Submit);
407
+ }
408
+ }
409
+ // Inserts a newline character without invoking the field editor’s normal handling to end editing.
410
+ "insertNewlineIgnoringFieldEditor:" => {
411
+ driver.insert_or_replace_selection("\n");
412
+ return Some(GeneratedTextInputEvent::Input);
413
+ }
414
+ // Inserts a paragraph separator.
415
+ "insertParagraphSeparator:" => {
416
+ driver.insert_or_replace_selection("\n");
417
+ return Some(GeneratedTextInputEvent::Input);
418
+ }
419
+ "insertSingleQuoteIgnoringSubstitution:" => {
420
+ driver.insert_or_replace_selection("'");
421
+ return Some(GeneratedTextInputEvent::Input);
422
+ }
423
+ // Inserts a tab character.
424
+ "insertTab:" | "insertTabIgnoringFieldEditor:" => {
425
+ // Ignore for now seeing as parley has poor support for laying out tabs
426
+ }
427
+ // Inserts the text you specify.
428
+ "insertText:" => {}
429
+
430
+ // Deleting Content
431
+
432
+ // Deletes content moving backward from the current insertion point.
433
+ // TODO: handle deleteBackwardByDecomposingPreviousCharacter separately
434
+ "deleteBackward:" | "deleteBackwardByDecomposingPreviousCharacter:" => {
435
+ driver.backdelete();
436
+ return Some(GeneratedTextInputEvent::Input);
437
+ }
438
+ "deleteForward:" => {
439
+ driver.delete();
440
+ return Some(GeneratedTextInputEvent::Input);
441
+ }
442
+ // Deletes content from the insertion point to the beginning of the current line.
443
+ "deleteToBeginningOfLine:" => {
444
+ if driver.editor.raw_selection().is_collapsed() {
445
+ driver.select_to_line_start();
446
+ }
447
+ driver.delete_selection();
448
+ return Some(GeneratedTextInputEvent::Input);
449
+ }
450
+ // Deletes content from the insertion point to the beginning of the current paragraph.
451
+ "deleteToEndOfLine:" => {
452
+ if driver.editor.raw_selection().is_collapsed() {
453
+ driver.select_to_line_end();
454
+ }
455
+ driver.delete_selection();
456
+ return Some(GeneratedTextInputEvent::Input);
457
+ }
458
+ "deleteToBeginningOfParagraph:" => {
459
+ if driver.editor.raw_selection().is_collapsed() {
460
+ driver.select_to_hard_line_start();
461
+ }
462
+ driver.delete_selection();
463
+ return Some(GeneratedTextInputEvent::Input);
464
+ }
465
+
466
+ // Deletes content from the insertion point to the end of the current line.
467
+ "deleteToEndOfParagraph:" => {
468
+ if driver.editor.raw_selection().is_collapsed() {
469
+ driver.select_to_hard_line_end();
470
+ }
471
+ driver.delete_selection();
472
+ return Some(GeneratedTextInputEvent::Input);
473
+ }
474
+ // Deletes content from the insertion point to the end of the current paragraph.
475
+ "deleteWordBackward:" => {
476
+ driver.backdelete_word();
477
+ return Some(GeneratedTextInputEvent::Input);
478
+ }
479
+ // Deletes the word preceding the current insertion point.
480
+ "deleteWordForward:" => {
481
+ driver.delete_word();
482
+ return Some(GeneratedTextInputEvent::Input);
483
+ }
484
+ // Deletes the current selection, placing it in a temporary buffer, such as the Clipboard.
485
+ "yank:" => {
486
+ if let Some(text) = driver.editor.selected_text() {
487
+ let _ = shell_provider.set_clipboard_text(text.to_owned());
488
+ driver.delete_selection();
489
+ return Some(GeneratedTextInputEvent::Input);
490
+ }
491
+ }
492
+
493
+ // Moving the Insertion Pointer
494
+
495
+ // Moves the insertion pointer backward in the current content.
496
+ "moveBackward:" => {
497
+ driver.move_left(); // TODO: Bidi-aware
498
+ return Some(GeneratedTextInputEvent::Select);
499
+ }
500
+
501
+ // Moves the insertion pointer down in the current content.
502
+ "moveDown:" => {
503
+ driver.move_down();
504
+ return Some(GeneratedTextInputEvent::Select);
505
+ }
506
+ // Moves the insertion pointer forward in the current content.
507
+ "moveForward:" => {
508
+ driver.move_right();
509
+ return Some(GeneratedTextInputEvent::Select);
510
+ } // TODO: Bidi-aware
511
+
512
+ // Moves the insertion pointer left in the current content.
513
+ "moveLeft:" => {
514
+ driver.move_left();
515
+ return Some(GeneratedTextInputEvent::Select);
516
+ }
517
+ // Moves the insertion pointer right in the current content.
518
+ "moveRight:" => {
519
+ driver.move_right();
520
+ return Some(GeneratedTextInputEvent::Select);
521
+ }
522
+ // Moves the insertion pointer up in the current content.
523
+ "moveUp:" => {
524
+ driver.move_up();
525
+ return Some(GeneratedTextInputEvent::Select);
526
+ }
527
+
528
+ // Modifying the Selection
529
+
530
+ // Extends the selection to include the content before the current selection.
531
+ "moveBackwardAndModifySelection:" => {
532
+ driver.select_left(); // TODO: Bidi-aware
533
+ return Some(GeneratedTextInputEvent::Select);
534
+ }
535
+ // Extends the selection to include the content below the current selection.
536
+ "moveDownAndModifySelection:" => {
537
+ driver.select_down();
538
+ return Some(GeneratedTextInputEvent::Select);
539
+ }
540
+ // Extends the selection to include the content after the current selection.
541
+ "moveForwardAndModifySelection:" => {
542
+ driver.select_right(); // TODO: Bidi-aware
543
+ return Some(GeneratedTextInputEvent::Select);
544
+ }
545
+ // Extends the selection to include the content to the left of the current selection.
546
+ "moveLeftAndModifySelection:" => {
547
+ driver.select_left();
548
+ return Some(GeneratedTextInputEvent::Select);
549
+ }
550
+ // Extends the selection to include the content to the right of the current selection.
551
+ "moveRightAndModifySelection:" => {
552
+ driver.select_right();
553
+ return Some(GeneratedTextInputEvent::Select);
554
+ }
555
+ // Extends the selection to include the content above the current selection.
556
+ "moveUpAndModifySelection:" => {
557
+ driver.select_up();
558
+ return Some(GeneratedTextInputEvent::Select);
559
+ }
560
+
561
+ // Changing the Selection
562
+ "selectAll:" => {
563
+ driver.select_all();
564
+ return Some(GeneratedTextInputEvent::Select);
565
+ }
566
+ "selectLine:" => {
567
+ driver.move_to_line_start();
568
+ driver.select_to_line_end();
569
+ return Some(GeneratedTextInputEvent::Select);
570
+ }
571
+ "selectParagraph:" => {
572
+ driver.move_to_hard_line_start();
573
+ driver.select_to_hard_line_end();
574
+ return Some(GeneratedTextInputEvent::Select);
575
+ }
576
+ "selectWord:" => {
577
+ // TODO
578
+ }
579
+
580
+ // Moving the Selection in Documents
581
+ "moveToBeginningOfDocument:" => {
582
+ driver.move_to_text_start();
583
+ return Some(GeneratedTextInputEvent::Select);
584
+ }
585
+ "moveToBeginningOfDocumentAndModifySelection:" => {
586
+ driver.select_to_text_start();
587
+ return Some(GeneratedTextInputEvent::Select);
588
+ }
589
+ "moveToEndOfDocument:" => {
590
+ driver.move_to_text_end();
591
+ return Some(GeneratedTextInputEvent::Select);
592
+ }
593
+ "moveToEndOfDocumentAndModifySelection:" => {
594
+ driver.move_to_text_end();
595
+ return Some(GeneratedTextInputEvent::Select);
596
+ }
597
+
598
+ // Moving the Selection in Paragraphs
599
+ "moveParagraphBackwardAndModifySelection:" => {}
600
+ "moveParagraphForwardAndModifySelection:" => {}
601
+ "moveToBeginningOfParagraph:" => {
602
+ driver.move_to_hard_line_start();
603
+ return Some(GeneratedTextInputEvent::Select);
604
+ }
605
+ "moveToBeginningOfParagraphAndModifySelection:" => {
606
+ driver.select_to_hard_line_start();
607
+ return Some(GeneratedTextInputEvent::Select);
608
+ }
609
+ "moveToEndOfParagraph:" => {
610
+ driver.move_to_hard_line_end();
611
+ return Some(GeneratedTextInputEvent::Select);
612
+ }
613
+ "moveToEndOfParagraphAndModifySelection:" => {
614
+ driver.select_to_hard_line_end();
615
+ return Some(GeneratedTextInputEvent::Select);
616
+ }
617
+
618
+ // Moving the Selection in Lines of Text
619
+ "moveToBeginningOfLine:" => {
620
+ driver.move_to_line_start();
621
+ return Some(GeneratedTextInputEvent::Select);
622
+ }
623
+ "moveToBeginningOfLineAndModifySelection:" => {
624
+ driver.select_to_line_start();
625
+ return Some(GeneratedTextInputEvent::Select);
626
+ }
627
+ "moveToEndOfLine:" => {
628
+ driver.move_to_line_end();
629
+ return Some(GeneratedTextInputEvent::Select);
630
+ }
631
+ "moveToEndOfLineAndModifySelection:" => {
632
+ driver.select_to_line_end();
633
+ return Some(GeneratedTextInputEvent::Select);
634
+ }
635
+ "moveToLeftEndOfLine:" => {
636
+ driver.move_to_text_start();
637
+ return Some(GeneratedTextInputEvent::Select);
638
+ }
639
+ "moveToLeftEndOfLineAndModifySelection:" => {
640
+ driver.select_to_line_start();
641
+ return Some(GeneratedTextInputEvent::Select);
642
+ }
643
+ "moveToRightEndOfLine:" => {
644
+ driver.move_to_line_end();
645
+ return Some(GeneratedTextInputEvent::Select);
646
+ }
647
+ "moveToRightEndOfLineAndModifySelection:" => {
648
+ driver.select_to_line_end();
649
+ return Some(GeneratedTextInputEvent::Select);
650
+ }
651
+
652
+ // Moving the Selection by Word Boundaries
653
+ "moveWordBackward:" => {
654
+ driver.move_word_left();
655
+ return Some(GeneratedTextInputEvent::Select);
656
+ }
657
+ "moveWordBackwardAndModifySelection:" => {
658
+ driver.select_word_left();
659
+ return Some(GeneratedTextInputEvent::Select);
660
+ }
661
+ "moveWordForward:" => {
662
+ driver.move_word_right();
663
+ return Some(GeneratedTextInputEvent::Select);
664
+ }
665
+ "moveWordForwardAndModifySelection:" => {
666
+ driver.select_word_right();
667
+ return Some(GeneratedTextInputEvent::Select);
668
+ }
669
+ "moveWordLeft:" => {
670
+ driver.move_word_left();
671
+ return Some(GeneratedTextInputEvent::Select);
672
+ }
673
+ "moveWordLeftAndModifySelection:" => {
674
+ driver.select_word_left();
675
+ return Some(GeneratedTextInputEvent::Select);
676
+ }
677
+ "moveWordRight:" => {
678
+ driver.move_word_right();
679
+ return Some(GeneratedTextInputEvent::Select);
680
+ }
681
+ "moveWordRightAndModifySelection:" => {
682
+ driver.select_word_right();
683
+ return Some(GeneratedTextInputEvent::Select);
684
+ }
685
+
686
+ // Scrolling Content
687
+
688
+ // Scrolls the content down by a page.
689
+ "scrollPageDown:" => {}
690
+ // Scrolls the content up by a page.
691
+ "scrollPageUp:" => {}
692
+ // Scrolls the content down by a line.
693
+ "scrollLineDown:" => {}
694
+ // Scrolls the content up by a line.
695
+ "scrollLineUp:" => {}
696
+ // Scrolls the content to the beginning of the document.
697
+ "scrollToBeginningOfDocument:" => {}
698
+ // Scrolls the content to the end of the document.
699
+ "scrollToEndOfDocument:" => {}
700
+ // Moves the visible content region down by a page.
701
+ "pageDown:" => {}
702
+ // Moves the visible content region up by a page.
703
+ "pageUp:" => {}
704
+ // Moves the visible content region down by a page, and extends the current selection.
705
+ "pageDownAndModifySelection:" => {}
706
+ // Moves the visible content region up by a page, and extends the current selection.
707
+ "pageUpAndModifySelection:" => {}
708
+ // Moves the visible content region so the current selection is visually centered.
709
+ "centerSelectionInVisibleArea:" => {}
710
+
711
+ // Transposing Elements
712
+
713
+ // Transposes the content around the current selection.
714
+ "transpose:" => {}
715
+ // Transposes the words around the current selection.
716
+ "transposeWords:" => {}
717
+
718
+ // Indenting Content
719
+ // Indents the content at the current selection.
720
+ "indent:" => {}
721
+
722
+ // Canceling Operations
723
+ // Cancels the current operation.
724
+ "cancelOperation:" => {}
725
+
726
+ // Supporting QuickLook
727
+ // Invokes QuickLook to preview the current selection.
728
+ "quickLookPreviewItems:" => {}
729
+
730
+ // Supporting Writing Directions
731
+ "makeBaseWritingDirectionLeftToRight:" => {}
732
+ "makeBaseWritingDirectionNatural:" => {}
733
+ "makeBaseWritingDirectionRightToLeft:" => {}
734
+ "makeTextWritingDirectionLeftToRight:" => {}
735
+ "makeTextWritingDirectionNatural:" => {}
736
+ "makeTextWritingDirectionRightToLeft:" => {}
737
+
738
+ // Changing Capitalization
739
+ "capitalizeWord:" => {}
740
+ "changeCaseOfLetter:" => {}
741
+ "lowercaseWord:" => {}
742
+ "uppercaseWord:" => {}
743
+
744
+ // Supporting Marked Selections
745
+ "setMark:" => {}
746
+ "selectToMark:" => {}
747
+ "deleteToMark:" => {}
748
+ "swapWithMark:" => {}
749
+
750
+ // Supporting Autocomplete
751
+ "complete:" => {}
752
+
753
+ // Instance Methods
754
+ "showContextMenuForSelection:" => {}
755
+
756
+ // Unknown command
757
+ _ => {}
758
+ };
759
+
760
+ None
761
+ }
762
+
763
+ pub(crate) fn apply_ime_event(
764
+ &mut self,
765
+ font_ctx: &mut FontContext,
766
+ layout_ctx: &mut LayoutContext<TextBrush>,
767
+ event: BlitzImeEvent,
768
+ ) -> Option<GeneratedTextInputEvent> {
769
+ let editor = &mut self.editor;
770
+ let mut driver = editor.driver(font_ctx, layout_ctx);
771
+
772
+ match event {
773
+ BlitzImeEvent::Enabled => {
774
+ // Do nothing
775
+ None
776
+ }
777
+ BlitzImeEvent::Disabled => {
778
+ driver.clear_compose();
779
+ Some(GeneratedTextInputEvent::PreEditChange)
780
+ }
781
+ BlitzImeEvent::Commit(text) => {
782
+ driver.insert_or_replace_selection(&text);
783
+ Some(GeneratedTextInputEvent::Input)
784
+ }
785
+ BlitzImeEvent::Preedit(text, cursor) => {
786
+ if text.is_empty() {
787
+ driver.clear_compose();
788
+ } else {
789
+ driver.set_compose(&text, cursor);
790
+ }
791
+ Some(GeneratedTextInputEvent::PreEditChange)
792
+ }
793
+ BlitzImeEvent::DeleteSurrounding {
794
+ before_bytes,
795
+ after_bytes,
796
+ } => {
797
+ let _ = before_bytes;
798
+ let _ = after_bytes;
799
+ // TODO
800
+ None
801
+ }
802
+ }
803
+ }
804
+ }