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.
- package/README.md +26 -0
- package/THIRD_PARTY_NOTICES.md +5 -2
- package/docs/layout-regressions.md +65 -0
- package/docs/pagination-regressions.md +89 -0
- package/package.json +2 -1
- package/vendor/README.md +35 -0
- package/vendor/blitz-dom/Cargo.toml +113 -0
- package/vendor/blitz-dom/LICENSE-APACHE +176 -0
- package/vendor/blitz-dom/LICENSE-MIT +23 -0
- package/vendor/blitz-dom/assets/default.css +1142 -0
- package/vendor/blitz-dom/assets/moz-bullet-font.otf +0 -0
- package/vendor/blitz-dom/src/accessibility.rs +255 -0
- package/vendor/blitz-dom/src/config.rs +97 -0
- package/vendor/blitz-dom/src/cssom.rs +971 -0
- package/vendor/blitz-dom/src/debug.rs +206 -0
- package/vendor/blitz-dom/src/document.rs +3395 -0
- package/vendor/blitz-dom/src/events/driver.rs +427 -0
- package/vendor/blitz-dom/src/events/focus.rs +40 -0
- package/vendor/blitz-dom/src/events/ime.rs +31 -0
- package/vendor/blitz-dom/src/events/keyboard.rs +174 -0
- package/vendor/blitz-dom/src/events/mod.rs +315 -0
- package/vendor/blitz-dom/src/events/pointer.rs +849 -0
- package/vendor/blitz-dom/src/font_metrics.rs +177 -0
- package/vendor/blitz-dom/src/form.rs +463 -0
- package/vendor/blitz-dom/src/html.rs +38 -0
- package/vendor/blitz-dom/src/iframe.rs +186 -0
- package/vendor/blitz-dom/src/layout/construct.rs +1328 -0
- package/vendor/blitz-dom/src/layout/damage.rs +642 -0
- package/vendor/blitz-dom/src/layout/inline.rs +1160 -0
- package/vendor/blitz-dom/src/layout/list.rs +237 -0
- package/vendor/blitz-dom/src/layout/mod.rs +969 -0
- package/vendor/blitz-dom/src/layout/paint_tree.rs +462 -0
- package/vendor/blitz-dom/src/layout/replaced.rs +439 -0
- package/vendor/blitz-dom/src/layout/table.rs +967 -0
- package/vendor/blitz-dom/src/layout/text_transform.rs +979 -0
- package/vendor/blitz-dom/src/layout/writing_mode.rs +437 -0
- package/vendor/blitz-dom/src/lib.rs +160 -0
- package/vendor/blitz-dom/src/mutator.rs +1818 -0
- package/vendor/blitz-dom/src/net.rs +649 -0
- package/vendor/blitz-dom/src/node/attributes.rs +60 -0
- package/vendor/blitz-dom/src/node/custom_widget.rs +178 -0
- package/vendor/blitz-dom/src/node/element.rs +1106 -0
- package/vendor/blitz-dom/src/node/mod.rs +30 -0
- package/vendor/blitz-dom/src/node/node.rs +2036 -0
- package/vendor/blitz-dom/src/node/scrollbar.rs +237 -0
- package/vendor/blitz-dom/src/node/serialize.rs +225 -0
- package/vendor/blitz-dom/src/node/stylo_data.rs +142 -0
- package/vendor/blitz-dom/src/node/svg.rs +265 -0
- package/vendor/blitz-dom/src/node/text.rs +804 -0
- package/vendor/blitz-dom/src/query_selector.rs +275 -0
- package/vendor/blitz-dom/src/resolve.rs +474 -0
- package/vendor/blitz-dom/src/resolved_style.rs +797 -0
- package/vendor/blitz-dom/src/scrolling.rs +780 -0
- package/vendor/blitz-dom/src/selection.rs +122 -0
- package/vendor/blitz-dom/src/stylo.rs +1518 -0
- package/vendor/blitz-dom/src/stylo_device.rs +88 -0
- package/vendor/blitz-dom/src/stylo_to_cursor_icon.rs +49 -0
- package/vendor/blitz-dom/src/stylo_to_kurbo.rs +129 -0
- package/vendor/blitz-dom/src/stylo_to_parley.rs +655 -0
- package/vendor/blitz-dom/src/traversal.rs +600 -0
- package/vendor/blitz-dom/src/tree.rs +136 -0
- package/vendor/blitz-dom/src/url.rs +63 -0
- package/vendor/blitz-dom/src/util.rs +256 -0
- 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
|
+
}
|