| 1 | # The page: editor and canvas |
| 2 | |
| 3 | A OneNote page is a free canvas, but it doesn't feel like a drawing app. You |
| 4 | click anywhere and type, and a text box (an *outline*) appears and grows as you |
| 5 | write. Outlines wrap, lists indent, tags hang in the margin. `canvas` is the |
| 6 | crate that makes a page behave that way and look the way OneNote 2010 draws |
| 7 | it. It depends on `onestore`'s page model and on `draw`. It knows nothing |
| 8 | about storage, windows or the interface kit, which is why the same page runs |
| 9 | inside the desktop app and inside a UIKit view on iOS. |
| 10 | |
| 11 | ## Layers |
| 12 | |
| 13 | ```text |
| 14 | onestore::page::Page stored identities, formatting, unknown content kept aside |
| 15 | │ |
| 16 | canvas::document text outlines as paragraphs of rich text, UTF-16 positions |
| 17 | canvas::editor CanvasEditor: edits, selection, composition, undo, the ops each edit lowers to |
| 18 | canvas::layout, outline shaping (parley), Windows line metrics, outline and table geometry |
| 19 | │ |
| 20 | canvas::gpu::page PageScene: the page as draw primitives; pictures decoded off-thread |
| 21 | canvas::interaction PageView: hit layers, drags, grid, handles, key routing, scroll, zoom, AccessKit |
| 22 | │ |
| 23 | host translates platform events in; carries out the Requests that come back |
| 24 | ``` |
| 25 | |
| 26 | A host feeds `PageView` pointer, key and text-input events. It gets back a |
| 27 | `Response` saying whether the page, the selection or only the view changed, |
| 28 | plus the occasional `Request` the page can't do itself: show a date picker, |
| 29 | read the clipboard, open a link. Everything platform-specific stays on the |
| 30 | host's side of that line, shortcuts included: the page takes editing keys, and |
| 31 | the host's command table runs the chords for formatting, tags, zoom and the |
| 32 | clipboard through the page's own methods. |
| 33 | |
| 34 | ## Editing emits ops |
| 35 | |
| 36 | `CanvasEditor` holds the working page. When an edit changes what's stored, the |
| 37 | editor records the `onestore::op`s the change lowers to at that moment. The |
| 38 | host collects them and hands them to the section as one `Edit`. No page is |
| 39 | ever rebuilt or diffed to be saved, so the work of saving a keystroke follows |
| 40 | the size of the edit, not the size of the page. |
| 41 | |
| 42 | Undo lives in the editor, not in storage. Each history entry keeps the inverse |
| 43 | of the change it made. Undoing applies that inverse and emits its ops as a new |
| 44 | edit, so the file only ever moves forward, like OneNote's. Undo steps are |
| 45 | OneNote 2010's too: typing and backspaces at one caret are a single step until |
| 46 | the caret moves or another kind of edit comes between, though each keystroke's |
| 47 | ops still reach storage as it is typed. Undoing a deletion |
| 48 | brings back the original identities, so internal links to those paragraphs |
| 49 | survive an undo. |
| 50 | |
| 51 | Each page keeps its history while the window is open, as in OneNote 2010: the |
| 52 | desktop app parks a page's editor when it is left and takes it up again, through |
| 53 | the same reload in place, when the page opens. Between those edits the app keeps |
| 54 | what was done to pages and sections (new, deleted, moved and indented pages, page |
| 55 | and section names, sections moved), and Undo takes back whichever came last: the |
| 56 | open page's own step, or the last such action in its section or notebook, |
| 57 | showing the page it changes. Unlike OneNote, an edit doesn't end what actions Undo |
| 58 | can reach, so New Page, a typed title, Undo, Undo takes the title and then the |
| 59 | page. Each step back is computed against the section as it is then, one edit of |
| 60 | its own; a step whose page is gone, or holds work it didn't make, is passed over. |
| 61 | |
| 62 | Input-method composition (marked text) stays inside the editor until it |
| 63 | commits, and only then becomes ops. When another client changes the open page, |
| 64 | the editor compares the stored page with what it last read plus the ops it has |
| 65 | handed out, and reloads in place. |
| 66 | |
| 67 | ## OneNote-faithful geometry |
| 68 | |
| 69 | The goal is that a page looks the same in Snowbound as in OneNote: same wraps, |
| 70 | same line heights, same places. That took measuring, not guessing. |
| 71 | |
| 72 | - **Stored geometry is not laid-out geometry.** An outline's stored width and |
| 73 | height are constraints and hints. The displayed box comes from layout, so the |
| 74 | canvas lays out as OneNote does and never treats a stored size as a clip. |
| 75 | - **Line boxes use Windows metrics.** OneNote measures lines with a font's |
| 76 | Windows ascent and descent, not the typographic or horizontal-header values |
| 77 | most text stacks pick. The difference is small per line and large per page. |
| 78 | Drawing and hit-testing share these line boxes. |
| 79 | - **Substitutes match metrics.** Where the system lacks Calibri, Arial, Times |
| 80 | New Roman or Courier New, bundled metric-compatible substitutes (Carlito, |
| 81 | Arimo, Tinos, Cousine) stand in, under the stored font name. |
| 82 | - **OneNote's constants are OneNote's.** New outlines take OneNote's default |
| 83 | width. Dragging snaps to its grid, anchored at the page's margin origin, unless |
| 84 | Snap To Grid (the shape gallery's last item, kept between launches) is off. Tags |
| 85 | sit in a column to the left of the text with OneNote's spacing, and a tag's |
| 86 | colour paints the whole paragraph as it does there. |
| 87 | - **A right-to-left page is the same frame, seen from its right.** OneNote 2010 |
| 88 | keeps its positions left to right like any page's, from a margin origin some |
| 89 | 10,800 pt out, and opens it scrolled to the right end of its content, keeping |
| 90 | that edge as the window resizes. The canvas does the same and stores clicks, |
| 91 | strokes and drags as on any page (`corpus/rtl-page`). |
| 92 | |
| 93 | These rules were established against OneNote's own output: its XML export |
| 94 | gives outline sizes, its PDF export gives exact line breaks, and screenshots |
| 95 | give placement. The comparators in `tools/canvas` (with the probes |
| 96 | `layout-probe` and `page-probe`) keep checking them. |
| 97 | |
| 98 | Equations draw in two dimensions from the tree `onestore::page::Math` parses, |
| 99 | each in a space kept in its line of text, so text and links around them keep |
| 100 | their look and the line grows to hold them. They are edited as OneNote's equation editor edits them: Alt+= starts one, |
| 101 | typing is its linear format (UnicodeMath), and a space builds up what it ends. |
| 102 | Linear and Professional switch an equation between the forms, and OneNote |
| 103 | stores both. Links follow OneNote too: a typed URL links itself when a space or |
| 104 | Enter ends it, the Link dialog stores its address in a hidden field code before |
| 105 | the label, and a click or Enter opens a link. URL text shows as a link, as |
| 106 | OneNote links it when it opens a page, without being stored as one. Ink draws stroke by stroke in page |
| 107 | coordinates. Page templates' background art is recreated as vector art and |
| 108 | recognised by the stored picture's hash. OneNote's bitmaps aren't shipped. |
| 109 | |
| 110 | ## Attached files |
| 111 | |
| 112 | A file sits in an outline's flow as OneNote draws it: its icon over its name, |
| 113 | extension hidden, in a 54-point column. Attaching splits the caret's paragraph |
| 114 | around the file, and the text after the caret follows it with the caret, as |
| 115 | OneNote 2010 attaches and drops files. Every new file stores the icon OneNote |
| 116 | stores beside it, because OneNote draws a broken picture for a file without |
| 117 | one: the host's system icon where it has one, the canvas's blank page |
| 118 | otherwise. A double click or the context menu's Open asks the host to open a |
| 119 | copy; Save As writes the bytes where the user chooses. |
| 120 | |
| 121 | Attached or dropped where a click on blank page left the caret, the file goes |
| 122 | on the page itself, as OneNote 2010 places it: the same column at the caret's |
| 123 | grid point, outside any outline. It selects, drags on the grid, deletes and |
| 124 | moves with Insert Space as a picture does, and opens and saves as above. |
| 125 | |
| 126 | A tag on a picture or file is stored on the object itself, as OneNote 2010 stores |
| 127 | the tag Ctrl+1 gives a selected one, and drawn in a column left of it, centred on |
| 128 | it; a click checks its box (`corpus/object-tags`). A picture's link follows on |
| 129 | Ctrl+click, Command+click on macOS, while a click selects it, as OneNote's |
| 130 | tooltip says (`corpus/picture-link`). |
| 131 | |
| 132 | OneNote 2010's Ctrl+1 leaves a bulleted or numbered paragraph's list beside its |
| 133 | new To Do box, and it has no command that converts a list. Make To-Do List, in |
| 134 | the page's context menu and the palette, trades the selected paragraphs' bullets |
| 135 | and numbers for the first check box tag in the user's list as one undo step; |
| 136 | Make Bulleted List trades the tag back for a bullet (`corpus/to-do-list`). |
| 137 | OneNote's Enter never carries a tag, and on an empty tagged paragraph it opens a |
| 138 | plain one above (`corpus/structural-probe`). Snowbound's Enter continues a to-do |
| 139 | list as a list continues: the new paragraph takes an unchecked copy of each check |
| 140 | box, and Enter on an empty item drops them. |
| 141 | |
| 142 | An outline holding only pictures or files takes a paragraph after them where a |
| 143 | click beside them lands, as OneNote 2010 adds one when typing there. It is |
| 144 | stored with the first edit that reaches it, and undoing back to it empty takes it |
| 145 | out again (`corpus/object-outline`). |
| 146 | |
| 147 | A file printout's pages are pictures whose stored data is the printout's XPS |
| 148 | package. The page draws the PNG OneNote rendered of each |
| 149 | (WebPictureContainer14), framed in grey as OneNote frames them, and moves them as |
| 150 | pictures. A printout page deleted and brought back by undo returns as the page it |
| 151 | was: its XPS package, the PNG and the properties tying it to the printout, written |
| 152 | back as read (`corpus/printout`). |
| 153 | |
| 154 | Pictures go in as OneNote 2010 pastes and inserts them. At a caret in text the |
| 155 | paragraph splits around the picture, as around a file. On blank page the picture |
| 156 | lies on the page at the caret, and the caret moves to the grid row below it. |
| 157 | From the title it joins the outline where the body starts, or lies two grid rows |
| 158 | below the page's content when none starts there. A picture takes the size its |
| 159 | resolution gives it, or 96 dpi without one. Paste takes files first (a picture |
| 160 | file as its picture, as a dropped one goes in), then Snowbound's own copy, then |
| 161 | a web page, its formatted text, lists, tables and pictures in order as one undo |
| 162 | step, then text, then a picture: Finder offers a copied file's name and icon |
| 163 | beside it, and other apps a picture of copied text. |
| 164 | |
| 165 | Copy offers what OneNote 2010 does beside text, HTML with each run's formatting |
| 166 | inline, lists as `ul` and `ol` and tables bordered, which OneNote and Word paste |
| 167 | as copied (`corpus/clipboard`). Snowbound's own format, a `Clip` of paragraphs |
| 168 | and the definitions they name as JSON, carries styles, tags and pictures too. |
| 169 | Several paragraphs go between the halves of the caret's paragraph, an empty |
| 170 | half dropped; a title takes the text alone. |
| 171 | |
| 172 | ## Recordings |
| 173 | |
| 174 | Record Audio and Record Video work as OneNote 2010's do. The caret's paragraph |
| 175 | splits around a line saying when recording started, in OneNote's grey `cite` |
| 176 | style, and the caret goes on below it. Text written while recording links to |
| 177 | the moment it was written; text written while paused links to nothing, and the |
| 178 | pause is left out of later moments. On Stop, the file goes in above that line, |
| 179 | named after the page. Hovering a linked note or a recording shows OneNote's blue |
| 180 | play button in the margin, and a click asks the host to play from five seconds |
| 181 | before that moment, OneNote's default rewind. While a recording plays, See |
| 182 | Playback highlights the note linked last at or before the moment playing, |
| 183 | scrolling it into view when it changes. |
| 184 | |
| 185 | The host records audio as 16 kHz WAV and stores it as IMA ADPCM, and video as |
| 186 | AVI of Motion JPEG at 320 by 240 and 15 pictures a second with PCM sound, both |
| 187 | of which OneNote plays (`corpus/recording/video`); `canvas::recording` makes both, |
| 188 | so every host stores the same bytes. GStreamer writes that AVI |
| 189 | directly; on macOS a capture session records a movie that `AVAssetReader` reads |
| 190 | back into it off the main thread after Stop, the transport saying so and the page |
| 191 | editable meanwhile. Mac OS X 10.6, without AVFoundation, opens recordings in the |
| 192 | system's player and does not record. Snowbound plays both itself: the sound through the platform's |
| 193 | player, decoded from IMA ADPCM first, and a video's pictures in the transport |
| 194 | over the page, which also carries OneNote's Pause, Stop, ten-second and |
| 195 | ten-minute skips, Seek To and See Playback. |
| 196 | |
| 197 | ## Drawing |
| 198 | |
| 199 | The Draw tab's tools work as OneNote 2010's do with a mouse (`corpus/ink-tools`). |
| 200 | Every stroke and every shape is a drawing of its own, added on top of the page |
| 201 | when the pen lifts: one edit and one undo step, published as one appended |
| 202 | revision, or in one with the strokes queued behind it during a sync round trip, |
| 203 | as keystrokes are. Shapes are ink too. Their corners snap to the placement grid, and the |
| 204 | drawing keeps the shape's kind and anchors beside its strokes, which OneNote |
| 205 | edits it by. A highlighter's rectangular tip multiplies what lies beneath, so |
| 206 | text under it stays dark. The stroke eraser takes whole drawings, or only the |
| 207 | strokes it touches in an older drawing of several. The lasso picks the drawings |
| 208 | with most of their points inside it. They move by their offset, as OneNote |
| 209 | moves ink, and Delete removes them. Escape returns to Select & Type, where a |
| 210 | click on ink picks it. The default pen draws in the open section's accent at |
| 211 | the light theme's shade, so a stroke stores one real colour and shows it in both |
| 212 | themes. |
| 213 | |
| 214 | A pen that reports pressure (a tablet on macOS, the Apple Pencil) draws and stores it as |
| 215 | OneNote 2010 does with pressure sensitivity on: each point's width is the pen's times |
| 216 | 0.25 plus 1.5 times the pressure, and the stroke keeps NormalPressure beside X and Y |
| 217 | (`corpus/ink-pressure`). A mouse, trackpad or finger draws at the pen's width, and so |
| 218 | does every pen with OneNote's "Use pen pressure sensitivity" turned off (Options > |
| 219 | Advanced on the desktop, the pen's colour menu on iOS; on by default). winit reports no |
| 220 | tablet pressure on Linux. |
| 221 | |
| 222 | ## Tables and selections across them |
| 223 | |
| 224 | Columns size as OneNote 2010 sizes them (`corpus/table-widths`). An unlocked |
| 225 | column fits its widest line plus 4.347 pt, never under a new column's 37.11 pt, |
| 226 | widening and narrowing with each edit, which stores the width in the same |
| 227 | revision as its text. A table stops at the outline's width and its cells wrap |
| 228 | from there. Dragging a column's right border resizes that column alone, the |
| 229 | columns after it moving with it, down to 37.11 pt; on release it is one edit |
| 230 | and one undo step, and the column is locked, so typing no longer fits it. |
| 231 | |
| 232 | Note tags on a table sit in the tag column centred on it, and a click checks its |
| 233 | box (`corpus/table-tags`). A selection crossing a table's edge deletes as |
| 234 | OneNote 2010 deletes one: nothing joins across the edge, cells inside are |
| 235 | emptied, and where the selection runs on past the table its rows inside go, the |
| 236 | table with them when all do. What replaces the selection goes in at its start |
| 237 | (`corpus/cross-container`). Tab and Link leave such a selection alone, and |
| 238 | Shift+Enter a caret inside a link, as OneNote's do; Alt+= across paragraphs |
| 239 | makes each paragraph's part an equation, and a deletion between two equations |
| 240 | joins them where neither seam lies inside an object (`corpus/equation-join`). |
| 241 | An edit replacing a selection with an end inside an equation's object (a |
| 242 | fraction, a script, a root) takes the whole object first, as OneNote's equation |
| 243 | editor selects, and across paragraphs an object so taken at its paragraph's end |
| 244 | takes that end too; a placeholder ("Type equation here.") is taken whole |
| 245 | (`corpus/equation-select`). |
| 246 | |
| 247 | ## Styles and themes |
| 248 | |
| 249 | The Styles gallery is OneNote 2010's: Heading 1 to 6, Page Title, Citation, Quote, Code |
| 250 | and Normal, stored under OneNote's names (`h1`, `PageTitle`, `cite`, `blockquote`, `code`, |
| 251 | `p`). Applying one gives the paragraph the page's style object of that definition and |
| 252 | clears its character formatting but links, fields and language, as OneNote's does; Enter |
| 253 | at a paragraph's end takes its style's NextStyle, so a heading is followed by Normal. |
| 254 | Ctrl+Alt+1 to 6 apply the headings, and Clear Formatting at a caret applies Normal. |
| 255 | |
| 256 | A theme gives the eleven styles a look, and a page wears it as its style objects: OneNote |
| 257 | draws a page by its own style objects, so OneNote users see the theme under the same |
| 258 | names. Changing a theme moves each styled paragraph to a new style object of the same name |
| 259 | (style objects are read-only) in one revision per page, and a page is brought to its |
| 260 | theme when it opens, so a notebook-wide change costs each page only when someone looks at |
| 261 | it. Which theme a notebook, section or page wears lives in the notebook's `.snowbound` |
| 262 | folder, beside the tags' art (`corpus/styles`). |
| 263 | |
| 264 | ## Content the editor doesn't understand |
| 265 | |
| 266 | Nothing is lost for being unfamiliar. A paragraph the canvas can't draw |
| 267 | becomes a labelled placeholder that keeps its place in the flow, and the rest |
| 268 | of its outline stays editable. An object the editor can't hold draws as stored |
| 269 | and stays read-only. Its bytes are never touched either way. Wherever a |
| 270 | remaining "can't edit this" state exists, the aim is to remove it by teaching |
| 271 | the editor the structure, not by flattening the content. |
| 272 | |
| 273 | ## Pictures and memory |
| 274 | |
| 275 | The page scene reads only picture headers when it's built. Pictures in and |
| 276 | near the view decode on a background thread at the size they're shown, within |
| 277 | a fixed memory budget, and pictures long out of view are let go. No number or |
| 278 | size of pictures can make a page fail to open. Opening a page happens on its |
| 279 | own thread too. The current page stays live until the new one is laid out and |
| 280 | the pictures it shows first are ready. |
| 281 | |
| 282 | ## Accessibility |
| 283 | |
| 284 | The page builds an AccessKit tree. Each text outline appears as its own |
| 285 | editable text area, whose runs carry the canvas's real line boxes and |
| 286 | character positions, so a screen reader's caret and selection land where the |
| 287 | eye does. Native selection and replacement actions go through the editor's |
| 288 | history like any other edit. The tree updates only while an assistive client |
| 289 | is listening, and never for a caret blink. The interface's own tree holds it at |
| 290 | the page's box (see [the interface kit](ui.md#accessibility-and-the-keyboard)). |
| 291 | |
| 292 | ## Spelling |
| 293 | |
| 294 | Spelling is checked as OneNote 2010 checks it with its default proofing |
| 295 | options. Words in capitals, words with digits, and Internet and file addresses |
| 296 | go unchecked, and a word repeating the one before it is marked as repeated. |
| 297 | Each run is checked in the language it stores, so a French paragraph takes the |
| 298 | French dictionary and an equation none. The host supplies the dictionary; |
| 299 | `canvas::spelling` checks each paragraph on a thread of its own when it first |
| 300 | comes into view, and keeps the result by the paragraph's text, so drawing never |
| 301 | waits on it. Marked words carry OneNote's red zigzag, one pixel thick at any |
| 302 | zoom and laid on the device's pixel grid so a 1× screen shows OneNote's exact |
| 303 | pixels, except the word still being typed. The context menu (the edit menu on |
| 304 | iOS) offers the dictionary's corrections, Ignore and Add to Dictionary, and the |
| 305 | Spelling pane (F7) walks the page's marked words. A correction is an ordinary |
| 306 | edit, one revision. Nothing about spelling is stored in the page. |
| 307 | |
| 308 | ## Printing and PDF |
| 309 | |
| 310 | `canvas::print` lays pages on paper as OneNote 2010 prints them (`corpus/print`): between |
| 311 | half-inch top and bottom margins with the margin origin an inch in, the whole page shrunk |
| 312 | when its content reaches past the paper's right edge, and each sheet after the first |
| 313 | starting at the line of text or picture the one before would have cut. Rule lines and |
| 314 | template art print across the paper; the page colour does not. The footer names the |
| 315 | section and numbers the sheets. `draw::pdf` writes the same primitives the screen paints |
| 316 | as PDF: text in subset fonts whose ToUnicode maps come from the laid-out text, so it |
| 317 | selects and searches, ligatures included; ink and shapes as paths; pictures and tag |
| 318 | icons as images. On the desktop, Print and Export as PDF first show OneNote's Print Preview |
| 319 | and Settings, less the preview: the range (page, page group, section, and for a PDF the |
| 320 | notebook), paper, orientation, fitting to the paper's width and the footer. Print then hands |
| 321 | the PDF to AppKit's print panel, the XDG print portal or the shell's print verb for PDFs. |
| 322 | On iOS the page menu prints the page through the print sheet or shares its PDF. |
| 323 | |
| 324 | ## Search, dates, conflicts |
| 325 | |
| 326 | Smaller modules follow the same pattern of reproducing OneNote's behaviour |
| 327 | precisely. `search` matches the way OneNote 2010 searches: word prefixes, |
| 328 | ignoring case and diacritics, title matches first, and the text OneNote recognised |
| 329 | in pictures, which it stores in them, as OneNote's search finds it. `date` edits the title's |
| 330 | date and time fields the way OneNote stores a changed page date. `conflict` |
| 331 | shows conflict pages with OneNote's highlight. |