|
| 1 | +//! Design notes, comparisons, and long-term considerations. |
| 2 | +//! |
| 3 | +//! Parley’s core (`parley`) is intentionally paragraph-focused and pure: it owns shaping, |
| 4 | +//! line breaking, bidi, and alignment. This crate builds a higher-level layer for composing |
| 5 | +//! many paragraphs/surfaces and for modeling multi-selection/navigation. |
| 6 | +//! |
| 7 | +//! ## Inspiration and Comparisons |
| 8 | +//! |
| 9 | +//! ### Apple TextKit 1/2 |
| 10 | +//! |
| 11 | +//! - Layering: `NSTextStorage` (attributed storage) → `NSLayoutManager`/`NSTextLayoutManager` |
| 12 | +//! (shaping/line breaking) → `NSTextContainer` (geometry/regions). Storage can flow through |
| 13 | +//! multiple containers for columns/pages. |
| 14 | +//! - Selection/navigation: First-class objects (`NSTextSelection`, `NSTextSelectionNavigation`) |
| 15 | +//! with anchor/focus, affinity, and granularity (character/word/line/paragraph). |
| 16 | +//! - Attachments: `NSTextAttachment` uses object-replacement semantics, which maps to Parley |
| 17 | +//! inline boxes and helps serialization (U+FFFC placeholder). |
| 18 | +//! - Takeaways for us: |
| 19 | +//! - Preserve a strict separation: storage vs. layout vs. region. |
| 20 | +//! - Model selection/navigation as reusable services that operate over regions/surfaces. |
| 21 | +//! - Treat inline objects as real selection units and serialization boundaries. |
| 22 | +//! |
| 23 | +//! ### Windows DirectWrite + TSF |
| 24 | +//! |
| 25 | +//! - DirectWrite: paragraph-centric layouts (`IDWriteTextLayout`) composed by host toolkits |
| 26 | +//! into documents. Justification, trimming, and typographic knobs exposed as explicit options. |
| 27 | +//! - TSF (Text Services Framework): composition (IME) modeled as ranges; selection kept in sync; |
| 28 | +//! UTF‑16 code unit indexing as lingua franca. |
| 29 | +//! - Takeaways: |
| 30 | +//! - Keep layout per-paragraph; composition/selection are ranges in storage. |
| 31 | +//! - Provide explicit UTF‑8↔UTF‑16 conversions at the edges for platform interoperability. |
| 32 | +//! - Don’t bake editing into layout; keep layout pure and reusable. |
| 33 | +//! |
| 34 | +//! ### Android (Spannable, MovementMethod, InputConnection) |
| 35 | +//! |
| 36 | +//! - Ranges: Spans with inclusive/exclusive flags survive edits; replacement spans for attachments. |
| 37 | +//! - Navigation: MovementMethod separates navigation from widgets, enabling reuse across views. |
| 38 | +//! - IME: InputConnection is an explicit bridge for composition, commit, and selection updates. |
| 39 | +//! - Precompute: Precomputed/Measured text validates doing shaping/measurement off the UI thread |
| 40 | +//! and reusing results (like `LayoutContext`). |
| 41 | +//! - Cautions: span proliferation and watchers can become hot paths; cross-widget selection is |
| 42 | +//! not first-class. |
| 43 | +//! - Takeaways: |
| 44 | +//! - Favor compact range tables over callback-heavy span objects. |
| 45 | +//! - Keep navigation and IME bridges separate, explicit, and host-driven. |
| 46 | +//! - Make async/precomputed layout a supported pattern via caches + generations. |
| 47 | +//! |
| 48 | +//! ### WPF/Web (brief) |
| 49 | +//! |
| 50 | +//! - WPF: `TextContainer`/`TextPointer`/`TextSelection` abstractions, flow across regions. |
| 51 | +//! - Web: `Range` across nodes, selection APIs that treat node boundaries as hard breaks; rich |
| 52 | +//! serialization policies matter. |
| 53 | +//! - Takeaways: |
| 54 | +//! - Encapsulate positions as abstract pointers, not raw indices. |
| 55 | +//! - Provide serialization policies (logical/visual order, boundary separators). |
| 56 | +//! |
| 57 | +//! ## Key Choices for a 10+ Year Horizon |
| 58 | +//! |
| 59 | +//! - Keep editor/navigation policies out of `Layout`; implement them in this crate. |
| 60 | +//! - Favor explicit types for locations/ranges across surfaces, with conversions to platform units. |
| 61 | +//! - Treat surface boundaries as hard boundaries for movement/word/line granularity. |
| 62 | +//! - Provide read-only aggregation (copy/search/AX) across surfaces by default; editing across |
| 63 | +//! multiple surfaces is opt-in and typically limited to a single active caret. |
| 64 | +//! |
| 65 | +//! ## Future Work |
| 66 | +//! |
| 67 | +//! ### Surface Flow and Ordering |
| 68 | +//! |
| 69 | +//! Parley surfaces are intentionally minimal. Explicit flow is modeled by `flow::TextFlow`: |
| 70 | +//! - Each `FlowItem` encodes a `block_id`, a `rect` (for hit-testing/geometry), and a `join` |
| 71 | +//! policy for serialization. |
| 72 | +//! - Order in the `TextFlow` defines cross-block navigation and concatenation semantics. |
| 73 | +//! - This mirrors TextKit’s ordered container array. |
| 74 | +//! |
| 75 | +//! Guidance: |
| 76 | +//! - Provide non-overlapping `rect`s in the flow for deterministic hit-testing. |
| 77 | +//! - Use large widths/heights when you don’t care about precise bounds; the flow determines order. |
| 78 | +//! - For multi-column/page or virtualized layouts, build the `TextFlow` with the intended reading |
| 79 | +//! order and rects for each visible block. |
| 80 | +//! - Concatenation: use a uniform `join` via [`crate::flow::TextFlow::from_vertical_stack`] or assign |
| 81 | +//! per-item `join` policies (e.g., `Space` for inline, `Newline` for block boundaries). |
| 82 | +//! |
| 83 | +//! ### 1) TextLocation and TextRange (positions across surfaces) |
| 84 | +//! |
| 85 | +//! - Purpose: decouple pointer/range representations from raw indices; enable safe conversions to |
| 86 | +//! platform units and stable identity across edits. |
| 87 | +//! - Shape: |
| 88 | +//! - `TextLocation { surface_id, utf8: usize }` with helpers: `to_utf16()`, `from_utf16()`. |
| 89 | +//! - `TextRange { start: TextLocation, end: TextLocation }`, normalized with methods for |
| 90 | +//! granularity expansion (to cluster/word/line/paragraph) using the surface’s layout. |
| 91 | +//! - Invariants: |
| 92 | +//! - Always at character boundaries in UTF‑8; conversion to UTF‑16 is lossy only in units, not |
| 93 | +//! in meaning. |
| 94 | +//! - Stable `surface_id` and monotonic ordering within a surface. |
| 95 | +//! - Interop: |
| 96 | +//! - Map to TSF/AppKit APIs that require UTF‑16 indices; provide zero‑allocation conversions. |
| 97 | +//! - Serialize as absolute (surface_id, byte_offset) to avoid ambiguity when text changes. |
| 98 | +//! - Integration: |
| 99 | +//! - Backed by the same hit-testing code as current `Cursor`/`Selection`. |
| 100 | +//! - Acts as the wire type for accessibility and IME bridges. |
| 101 | +//! |
| 102 | +//! ### 2) SelectionNavigation (surface‑crossing caret movement) |
| 103 | +//! |
| 104 | +//! - Purpose: unify navigation semantics (move/extend by cluster/word/line/paragraph) across |
| 105 | +//! multiple surfaces in visual order, mirroring Apple’s `NSTextSelectionNavigation`. |
| 106 | +//! - Current scaffold: |
| 107 | +//! - Implemented in `crate::selection_navigation` with `move_left`/`move_right` that cross |
| 108 | +//! surface boundaries by jumping to the end/start of adjacent surfaces. |
| 109 | +//! - Inside a surface, movement delegates to Parley’s `Cursor` logic. |
| 110 | +//! - Tests cover crossing from the end of one surface to the start of the next, and vice versa. |
| 111 | +//! - Next steps: |
| 112 | +//! - Vertical movement preserving `h_pos`: `move_line_up`/`move_line_down`. |
| 113 | +//! - Word/paragraph granularity: `move_word_left/right`, `hard_line_start/end`. |
| 114 | +//! - Extend variants (Shift-modify): introduce an anchor caret in `SelectionSet` or pass a |
| 115 | +//! transient anchor so movement can grow/shrink the nearest segment or add cross-surface |
| 116 | +//! segments. |
| 117 | +//! - Surface ordering predicates: allow clients to supply ordering beyond y-offset (e.g., columns). |
| 118 | +//! - Semantics to retain: |
| 119 | +//! - Affinity and bidi: respect `Cursor` affinity at line ends; treat surface edges as hard |
| 120 | +//! boundaries; never span a cluster across surfaces. |
| 121 | +//! - Vertical movement: maintain a sticky `h_pos` in global coordinates; when moving across |
| 122 | +//! surfaces, compute target y in the next surface via its `y_offset` and line metrics. |
| 123 | +//! - Word/line boundaries: use per-surface rules; when crossing a surface, land at the first/last |
| 124 | +//! selectable boundary inside the target surface. |
| 125 | +//! - Testing: |
| 126 | +//! - Golden tests for bidi line ends, RTL/LTR boundaries, mixed metrics; property tests for |
| 127 | +//! inverse moves where appropriate. |
| 128 | +//! |
| 129 | +//! ### 3) Document Storage and Incremental Relayout |
| 130 | +//! |
| 131 | +//! - Purpose: provide a concrete “document” built from paragraphs with efficient editing, |
| 132 | +//! invalidation, and relayout, while still exposing each paragraph as a `TextBlock`. |
| 133 | +//! - Storage: |
| 134 | +//! - Use a rope or gap buffer for large texts; maintain paragraph boundaries as a side table of |
| 135 | +//! byte ranges with stable paragraph IDs. |
| 136 | +//! - Inclusive/exclusive range flags for styles, composition, and annotations that survive edits. |
| 137 | +//! - Invalidation: |
| 138 | +//! - When editing, detect impacted paragraphs (splits/merges on newlines) and only rebuild |
| 139 | +//! affected layouts; reuse shaped results when attributes allow. |
| 140 | +//! - Employ generations to coordinate caches (fonts, shaping, line breaking) and async rebuilds. |
| 141 | +//! - Layout: |
| 142 | +//! - Build each paragraph’s `Layout` via `LayoutContext`; cache glyph/cluster data. |
| 143 | +//! - Support async layout: produce placeholder metrics/geometry, swap in final results when ready. |
| 144 | +//! - Regions: |
| 145 | +//! - Flow paragraphs into regions (columns/pages) by assigning `y_offset`s; each paragraph is a |
| 146 | +//! `TextBlock` with its own layout and text slice. |
| 147 | +//! - Attachments: |
| 148 | +//! - Represent as inline boxes using object‑replacement semantics (U+FFFC) for selection and |
| 149 | +//! serialization; geometry carried by the inline box. |
| 150 | +//! |
| 151 | +//! This module contains only documentation. |
0 commit comments