Oregami
Repositories/oxedyne/fe2o3

oxedyne/fe2o3/fe2o3_sbj/src/kinds.rs

36.5 KiB, 13 runs

created by r1870400018:22220, which is this file's identity for as long as the history lasts, whatever it is later renamed to

download · who wrote it · its history

1//! The v0 node vocabulary, and the schema each kind obeys.
2//!
3//! A node is a JDAT `usr` daticle: a `u16` kind code followed by the node's payload. The code sits
4//! in front of the payload on the wire, so a decoder knows what a node is before it reads a byte of
5//! it, and an unknown or forbidden kind is refused without inspecting its contents.
6
7use crate::{
8 SCHEMA_APP,
9 SCHEMA_CARD,
10 SCHEMA_CHROME,
11 SCHEMA_DOC,
12 SCHEMA_POST,
13};
14
15use oxedyne_fe2o3_core::prelude::*;
16use oxedyne_fe2o3_jdat::prelude::*;
17
18/// The type a field must carry, checked against the decoded daticle.
19#[derive(Clone, Copy, Debug, PartialEq, Eq)]
20pub enum FieldType {
21 /// A UTF-8 string.
22 Str,
23 /// An unsigned 8-bit integer.
24 U8,
25 /// A signed 8-bit integer.
26 I8,
27 /// An unsigned 32-bit integer.
28 U32,
29 /// A boolean.
30 Bool,
31 /// A fixed 32-byte string, the width of a v0 content hash.
32 Hash32,
33 /// A typed link address: a single-entry map naming a `name` or a `hash`. See [`check_address`].
34 Address,
35 /// A non-empty list of nodes, walked and validated like any other, and carried in a field rather
36 /// than in `children`. It is what a `surface` names its semantic alternative by (§4.2).
37 Nodes,
38}
39
40/// The schema a payload declares, and with it the vocabulary the payload is held to (`SPEC.md` §4.2).
41///
42/// The schema is the enforcement. "A document is never a program" is not a rule written on top of the
43/// format; it is the fact that `oxeweb/doc/0` admits the kinds 1 to 13 and nothing else, that the
44/// schema sits in the envelope, and that the envelope is signed (§1.3), so a payload cannot be
45/// re-labelled into a vocabulary its author never claimed.
46///
47/// Each schema's admitted set is closed. A chrome tree may carry an `edit`, because the address bar
48/// is one; an application's tree may carry an `edit` and a `surface`, because a game's pane is one;
49/// and a document may carry neither, because a document that could name either would be a program.
50///
51/// A schema fixes two vocabularies, not one: the node kinds it admits ([`Schema::admits`]) and the
52/// style properties it admits ([`Schema::admits_style`]). Both are closed, and both are the schema's
53/// alone. What a property MEANS, and the width it is written at, are the same in every schema; only
54/// whether a tree may name it at all depends on which schema the envelope declared.
55#[derive(Clone, Copy, Debug, PartialEq, Eq)]
56pub enum Schema {
57 /// An oxeweb document: the kinds 1 to 13, neither reserved kind, and the seven document style
58 /// properties.
59 Doc,
60 /// The browser's own chrome: the document kinds and style properties, the `edit` node, and the
61 /// interface style properties.
62 Chrome,
63 /// An application's tree: everything a chrome admits, and the `surface` node.
64 App,
65}
66
67impl Schema {
68
69 /// The name an envelope declares this schema by.
70 pub fn name(&self) -> &'static str {
71 match self {
72 Self::Doc => SCHEMA_DOC,
73 Self::Chrome => SCHEMA_CHROME,
74 Self::App => SCHEMA_APP,
75 }
76 }
77
78 /// The schema a name declares, or an error naming the schema it was handed.
79 ///
80 /// A payload declaring a schema this build does not read is refused rather than read as though it
81 /// were one of these, since the container carries any schema at all (§1.2) and a reader that
82 /// validates three must refuse the rest.
83 ///
84 /// These are the schemas whose payload is a node tree. The container carries others whose payload
85 /// is not — a post and a card are flat records — and those are reached through `doc::Payload`
86 /// rather than here, because there is no tree in them for this validator to walk.
87 pub fn from_name(name: &str) -> Outcome<Self> {
88 match name {
89 SCHEMA_DOC => Ok(Self::Doc),
90 SCHEMA_CHROME => Ok(Self::Chrome),
91 SCHEMA_APP => Ok(Self::App),
92 _ => Err(err!(
93 "Schema '{}' is none of the node-tree schemas this validator reads: '{}', '{}', \
94 and '{}'. A payload that is a record rather than a tree, such as '{}' or '{}', is \
95 read through `doc::read_artefact` and written through `doc::write_artefact`.",
96 name, SCHEMA_DOC, SCHEMA_CHROME, SCHEMA_APP, SCHEMA_POST, SCHEMA_CARD;
97 Invalid, Input, Mismatch)),
98 }
99 }
100
101 /// Whether this schema admits the given reserved kind.
102 ///
103 /// This is the whole of the reserved kinds' admission rule, and it is a table rather than a
104 /// special case anywhere else: a surface is refused in a document because `oxeweb/doc/0` does not
105 /// admit it here, and for no other reason.
106 pub fn admits(&self, reserved: ReservedKind) -> bool {
107 match (self, reserved) {
108 (Self::Doc, _) => false,
109 (Self::Chrome, ReservedKind::Edit) => true,
110 (Self::Chrome, ReservedKind::Icon) => true,
111 (Self::Chrome, ReservedKind::Surface) => false,
112 (Self::App, _) => true,
113 }
114 }
115
116 /// Whether this schema admits the given style property (§4.4).
117 ///
118 /// This is the whole of the style vocabulary's admission rule, and it is a table for the same
119 /// reason [`Schema::admits`] is one: `grid` is refused in a document because `oxeweb/doc/0` does
120 /// not admit it here, and for no other reason.
121 ///
122 /// The v0 document vocabulary is frozen at the eight properties of [`STYLE_FIELDS`]. A published
123 /// document cannot name an interface property, whatever a later reader learns to draw, so a
124 /// document's appearance is settled by the vocabulary it was published under.
125 pub fn admits_style(&self, prop: &StyleField) -> bool {
126 match (self, prop.scope) {
127 (Self::Doc, StyleScope::Doc) => true,
128 (Self::Doc, StyleScope::Interface) => false,
129 (Self::Chrome, _) => true,
130 (Self::App, _) => true,
131 }
132 }
133
134 /// The style-record property of the given name that this schema admits, if it admits one.
135 ///
136 /// A property this schema does not admit comes back as `None` exactly as an unknown one does, so
137 /// no caller can reach a property by name without passing the admission rule first. A caller that
138 /// must tell the two apart, to say which it refused and why, asks [`known_style_field`] as well.
139 pub fn style_field(&self, name: &str) -> Option<&'static StyleField> {
140 match known_style_field(name) {
141 Some(field) if self.admits_style(field) => Some(field),
142 _ => None,
143 }
144 }
145
146 /// The tree this schema describes, as an error message names it.
147 pub fn tree_label(&self) -> &'static str {
148 match self {
149 Self::Doc => "a document",
150 Self::Chrome => "a chrome tree",
151 Self::App => "an application tree",
152 }
153 }
154
155 /// The vocabulary this schema admits, as an error message spells it.
156 pub fn admits_label(&self) -> &'static str {
157 match self {
158 Self::Doc => "the kinds 1 to 13 and no others",
159 Self::Chrome => "the kinds 1 to 13 and the reserved kinds 'edit' and 'icon', and no \
160 others",
161 Self::App => "the kinds 1 to 13 and the reserved kinds 'edit', 'surface' and 'icon', \
162 and no others",
163 }
164 }
165
166 /// The style vocabulary this schema admits, as an error message spells it.
167 pub fn style_admits_label(&self) -> &'static str {
168 match self {
169 Self::Doc => "the eight document properties 'fill', 'size', 'lang', 'dir', 'bg', \
170 'pad', 'align' and 'radius', and no others",
171 Self::Chrome => "the eight document properties and the interface properties 'grid', \
172 'pack', 'grow', 'max', 'border', 'shadow', 'nowrap' and 'ends', and no others",
173 Self::App => "the eight document properties and the interface properties 'grid', \
174 'pack', 'grow', 'max', 'border', 'shadow', 'nowrap' and 'ends', and no others",
175 }
176 }
177}
178
179/// One field of a node's payload map.
180#[derive(Clone, Copy, Debug)]
181pub struct Field {
182 /// The map key.
183 pub name: &'static str,
184 /// The type the value must carry.
185 pub typ: FieldType,
186 /// Whether the field may be omitted.
187 pub opt: bool,
188}
189
190/// What a node may contain.
191#[derive(Clone, Copy, Debug, PartialEq, Eq)]
192pub enum Content {
193 /// No children at all.
194 None,
195 /// Flow content: sections, paragraphs, headings, lists, boxes, images.
196 Flow,
197 /// Inline content: text runs, emphasis, links.
198 Inline,
199 /// List items only.
200 Items,
201}
202
203/// The v0 node kinds. Deliberately small; growth is a versioned decision.
204#[derive(Clone, Copy, Debug, PartialEq, Eq)]
205pub enum NodeKind {
206 /// Root of a document.
207 Doc,
208 /// A titled division.
209 Section,
210 /// A paragraph.
211 Para,
212 /// A heading, levels 1 to 6.
213 Heading,
214 /// An ordered or unordered list.
215 List,
216 /// One item of a list.
217 Item,
218 /// A generic structural box.
219 Boxx,
220 /// An image, referenced by content hash.
221 Image,
222 /// A run of text. Its payload is a string rather than a map.
223 Text,
224 /// Emphasised inline content.
225 Emph,
226 /// A link to a name or a hash address.
227 Link,
228 /// A preserved run of source code.
229 Code,
230 /// A block quotation.
231 Quote,
232}
233
234impl NodeKind {
235
236 /// The wire code for this kind.
237 pub fn code(&self) -> u16 {
238 match self {
239 Self::Doc => 1,
240 Self::Section => 2,
241 Self::Para => 3,
242 Self::Heading => 4,
243 Self::List => 5,
244 Self::Item => 6,
245 Self::Boxx => 7,
246 Self::Image => 8,
247 Self::Text => 9,
248 Self::Emph => 10,
249 Self::Link => 11,
250 Self::Code => 12,
251 Self::Quote => 13,
252 }
253 }
254
255 /// The kind for a wire code, or an error naming the code the document schema does not admit.
256 ///
257 /// A reserved code (§4.2) is not a document kind, so it is an error here like any other code
258 /// outside 1..=13. The error says which of the two it is, because the two are refused for
259 /// opposite reasons: an unknown code may be admitted by a fallback (§4.5), and a reserved code
260 /// never is.
261 pub fn from_code(code: u16) -> Outcome<Self> {
262 match code {
263 1 => Ok(Self::Doc),
264 2 => Ok(Self::Section),
265 3 => Ok(Self::Para),
266 4 => Ok(Self::Heading),
267 5 => Ok(Self::List),
268 6 => Ok(Self::Item),
269 7 => Ok(Self::Boxx),
270 8 => Ok(Self::Image),
271 9 => Ok(Self::Text),
272 10 => Ok(Self::Emph),
273 11 => Ok(Self::Link),
274 12 => Ok(Self::Code),
275 13 => Ok(Self::Quote),
276 _ => match ReservedKind::from_code(code) {
277 Some(reserved) => Err(err!(
278 "Node kind code {} is the reserved kind '{}', which is legal in {} and never \
279 in a document (SPEC.md §4.2).", code, reserved.label(), reserved.legal_in();
280 Invalid, Input)),
281 None => Err(err!(
282 "Unknown node kind code {}, the v0 document vocabulary runs from 1 to 13, and \
283 the codes 14 to 16 are reserved.", code;
284 Invalid, Input)),
285 },
286 }
287 }
288
289 /// The label used in JDAT's text form, e.g. `(heading|{..})`.
290 pub fn label(&self) -> &'static str {
291 match self {
292 Self::Doc => "doc",
293 Self::Section => "section",
294 Self::Para => "para",
295 Self::Heading => "heading",
296 Self::List => "list",
297 Self::Item => "item",
298 Self::Boxx => "box",
299 Self::Image => "image",
300 Self::Text => "text",
301 Self::Emph => "emph",
302 Self::Link => "link",
303 Self::Code => "code",
304 Self::Quote => "quote",
305 }
306 }
307
308 /// The fields this kind's payload map must and may carry.
309 pub fn fields(&self) -> &'static [Field] {
310 match self {
311 Self::Doc => &[
312 Field { name: "title", typ: FieldType::Str, opt: false },
313 Field { name: "lang", typ: FieldType::Str, opt: false },
314 ],
315 Self::Section => &[
316 Field { name: "title", typ: FieldType::Str, opt: true },
317 ],
318 Self::Para => &[],
319 Self::Heading => &[
320 Field { name: "level", typ: FieldType::U8, opt: false },
321 ],
322 Self::List => &[
323 Field { name: "ordered", typ: FieldType::Bool, opt: false },
324 ],
325 Self::Item => &[],
326 // A box declares no fields of its own. The universal `style` field is not listed here,
327 // nor on any other kind, because §4.4 gives it to every map payload alike, and both the
328 // canonical check and the validator handle it before they consult this table.
329 Self::Boxx => &[],
330 Self::Image => &[
331 Field { name: "hash", typ: FieldType::Hash32, opt: false },
332 Field { name: "alt", typ: FieldType::Str, opt: false },
333 Field { name: "w", typ: FieldType::U32, opt: true },
334 Field { name: "h", typ: FieldType::U32, opt: true },
335 ],
336 Self::Text => &[],
337 Self::Emph => &[
338 Field { name: "strong", typ: FieldType::Bool, opt: false },
339 ],
340 Self::Link => &[
341 Field { name: "to", typ: FieldType::Address, opt: false },
342 ],
343 Self::Code => &[
344 Field { name: "lang", typ: FieldType::Str, opt: true },
345 Field { name: "text", typ: FieldType::Str, opt: false },
346 ],
347 Self::Quote => &[
348 Field { name: "cite", typ: FieldType::Str, opt: true },
349 ],
350 }
351 }
352
353 /// What this kind may contain.
354 pub fn content(&self) -> Content {
355 match self {
356 Self::Doc => Content::Flow,
357 Self::Section => Content::Flow,
358 Self::Para => Content::Inline,
359 Self::Heading => Content::Inline,
360 Self::List => Content::Items,
361 Self::Item => Content::Flow,
362 Self::Boxx => Content::Flow,
363 Self::Image => Content::None,
364 Self::Text => Content::None,
365 Self::Emph => Content::Inline,
366 Self::Link => Content::Inline,
367 Self::Code => Content::None,
368 Self::Quote => Content::Flow,
369 }
370 }
371
372 /// Whether this kind is flow content, permitted where a section's children go.
373 pub fn is_flow(&self) -> bool {
374 matches!(self,
375 Self::Section | Self::Para | Self::Heading | Self::List | Self::Boxx | Self::Image
376 | Self::Code | Self::Quote)
377 }
378
379 /// Whether this kind is inline content, permitted inside a paragraph.
380 pub fn is_inline(&self) -> bool {
381 matches!(self, Self::Text | Self::Emph | Self::Link)
382 }
383
384 /// Whether a child of the given kind may appear inside this one.
385 pub fn allows(&self, child: &Self) -> bool {
386 match self.content() {
387 Content::None => false,
388 Content::Flow => child.is_flow(),
389 Content::Inline => child.is_inline(),
390 Content::Items => matches!(child, Self::Item),
391 }
392 }
393
394 /// Whether this kind's payload is a bare string rather than a map. Only `text` is.
395 pub fn payload_is_str(&self) -> bool {
396 matches!(self, Self::Text)
397 }
398
399 /// Whether this kind must carry at least one child, the `+` of the SPEC §4.2 vocabulary.
400 ///
401 /// A list with no items is a construction error rather than intent, so the format refuses it. A
402 /// document or section may be empty, since a stub with only a title is a legitimate thing to
403 /// publish.
404 pub fn requires_child(&self) -> bool {
405 matches!(self, Self::List)
406 }
407}
408
409/// A node kind the format reserves, and which the document schema admits nowhere (`SPEC.md` §4.2).
410///
411/// These are the engine's own facilities: an editable text field, a pane an application paints, and
412/// the engine's own icons. The chrome's address bar and an application's form field reach the same
413/// `edit`, and a document may name none of the three, which is what makes "a document is never a
414/// program" structural rather than conventional. Which trees may name them is [`Schema::admits`], and
415/// nothing else.
416///
417/// An `icon` is reserved for a reason of its own, and it is not that an icon is dangerous. A document
418/// carries its pictures as an `image`, which is a content hash: the picture is the author's, it is
419/// held, and it is the same picture wherever it is read. An icon is the opposite -- a name the tree
420/// reaches the *reader's* drawing by. A document naming one would be letting whichever reader opened
421/// it supply its content, and two readers would show two documents. Style may be the reader's, because
422/// style is how a thing looks; content may not, because content is what the author said.
423///
424/// The codes are known here rather than left outside the vocabulary on purpose. A code this version
425/// has never heard of may be admitted by a fallback (§4.5), because the reader cannot know what it
426/// means and a fallback lets it render something faithful anyway. A reserved code is the opposite
427/// case: the reader knows exactly what it has been handed, and knows the schema it was handed under
428/// does not admit it, so it refuses it whether or not a fallback is offered. Knowing the code is what
429/// makes that refusal possible, and what stops a document smuggling a `surface` past a reader that
430/// has not yet learnt what code 15 means.
431#[derive(Clone, Copy, Debug, PartialEq, Eq)]
432pub enum ReservedKind {
433 /// An editable text field, code 14.
434 Edit,
435 /// A pane an application paints, code 15.
436 Surface,
437 /// One of the engine's own icons, code 16.
438 Icon,
439}
440
441impl ReservedKind {
442
443 /// The wire code this kind is reserved at.
444 pub fn code(&self) -> u16 {
445 match self {
446 Self::Edit => 14,
447 Self::Surface => 15,
448 Self::Icon => 16,
449 }
450 }
451
452 /// The reserved kind a wire code names, or `None` if the code reserves nothing.
453 pub fn from_code(code: u16) -> Option<Self> {
454 match code {
455 14 => Some(Self::Edit),
456 15 => Some(Self::Surface),
457 16 => Some(Self::Icon),
458 _ => None,
459 }
460 }
461
462 /// The label an error message names this kind by.
463 pub fn label(&self) -> &'static str {
464 match self {
465 Self::Edit => "edit",
466 Self::Surface => "surface",
467 Self::Icon => "icon",
468 }
469 }
470
471 /// The label with its indefinite article, so that a refusal reads as English.
472 pub fn with_article(&self) -> &'static str {
473 match self {
474 Self::Edit => "an edit",
475 Self::Surface => "a surface",
476 Self::Icon => "an icon",
477 }
478 }
479
480 /// The trees this kind is legal in, which never include a document.
481 pub fn legal_in(&self) -> &'static str {
482 match self {
483 Self::Edit => "a chrome or an application tree",
484 Self::Surface => "an application tree",
485 Self::Icon => "a chrome or an application tree",
486 }
487 }
488
489 /// The fields this kind's payload map must and may carry.
490 ///
491 /// An `edit` carries the name the shell keeps its state under, and the hint it shows while it is
492 /// empty. The name is required, since a field the shell cannot key state by can never hold any.
493 ///
494 /// A `surface` carries the content hash of the application module it is a pane for, and the
495 /// semantic alternative that stands in for that application. It carries **no geometry at all**: no
496 /// width, no height, no minimum, no aspect. A surface is laid out like any box, by the host, in
497 /// the flow of the tree, and an application learns the size it was given rather than stating the
498 /// size it wants. An application that could size itself could size itself over the trust band, and
499 /// the band's whole guarantee would be gone; a field the format does not have cannot be asked for.
500 ///
501 /// The universal `style` field (§4.4) is legal on both, as it is on every map payload, and is not
502 /// listed here for the same reason it is not listed on a document kind.
503 pub fn fields(&self) -> &'static [Field] {
504 match self {
505 Self::Edit => &[
506 Field { name: "name", typ: FieldType::Str, opt: false },
507 Field { name: "placeholder", typ: FieldType::Str, opt: true },
508 ],
509 Self::Surface => &[
510 Field { name: "app", typ: FieldType::Hash32, opt: false },
511 Field { name: KEY_ALT, typ: FieldType::Nodes, opt: false },
512 ],
513 Self::Icon => &[
514 Field { name: "name", typ: FieldType::Str, opt: false },
515 ],
516 }
517 }
518
519 /// What this kind may contain, which is nothing: every reserved kind is a leaf.
520 pub fn content(&self) -> Content {
521 Content::None
522 }
523
524 /// The content class a parent must admit to hold this kind, which is where it may sit.
525 ///
526 /// This is not [`Self::content`] read backwards. That says what a kind may hold; this says what
527 /// may hold it, and the two are independent -- every reserved kind is a leaf, and they do not all
528 /// sit in the same place.
529 ///
530 /// An `edit` and a `surface` are blocks: a field is a line of its own and a pane is a rectangle,
531 /// and each sits exactly where a `box` may. An `icon` is a glyph, and sits exactly where a `text`
532 /// run may -- in the line, on the baseline, at the size and in the colour of whatever it stands
533 /// beside. A bar's button is an icon and a word next to each other, and an icon that were flow
534 /// content could only ever be a block above the word.
535 pub fn sits_in(&self) -> Content {
536 match self {
537 Self::Edit => Content::Flow,
538 Self::Surface => Content::Flow,
539 Self::Icon => Content::Inline,
540 }
541 }
542
543 /// Why this kind carries no children, as a refusal spells it.
544 pub fn no_children(&self) -> &'static str {
545 match self {
546 Self::Edit => "an editable field is one line of text with a caret in it, and not a \
547 container",
548 Self::Surface => "a surface's content is the application's, and its 'alt' is what stands \
549 in for the application when it is not running. A surface is a leaf that happens to \
550 be alive",
551 Self::Icon => "an icon is a glyph the engine draws, and a glyph holds nothing",
552 }
553 }
554}
555
556/// The icons the engine draws, which is the whole of what an `icon` node may name (§4.2).
557///
558/// The set is closed, and a name outside it is refused rather than drawn as a gap. An icon is not
559/// content the tree supplies but a name the tree *reaches* the engine's own drawing by, so a name the
560/// engine has never heard of is a caller's mistake, not a payload's, and there is nothing faithful to
561/// render in its place. That is the same reason a reserved code is refused where an unknown code may
562/// be admitted by a fallback (§4.5): a fallback exists for what the reader cannot know, and the reader
563/// knows exactly which icons it has.
564///
565/// The names are browser actions, which is the second reason a document cannot name one: `back` means
566/// nothing in a document, and a document that could say it would be reaching for a facility it does
567/// not have.
568///
569/// Growing this set is a versioned decision, as growing the kinds is. A chrome naming an icon this
570/// build does not draw is a bug in the chrome, and the validator is where it should be caught.
571pub const ICON_NAMES: &[&str] = &[
572 "back", // Back to the previously held document.
573 "forward", // Forward again, after going back.
574 "home", // To the library, which is the home.
575 "add", // Open another tab.
576 "close", // Close this tab.
577 "find", // Search what is held: by name, by hash, by author.
578 "reload", // Reload the current document.
579 "menu", // Open the browser's menu.
580 "more", // Open the overflow of a control that holds more than it shows.
581 "page", // A document standing in for a tab whose favicon is not yet known.
582 "panel", // Toggle the side panel rail.
583 "bookmark", // Mark the held document, or open the marks.
584 "download", // Save the held document, or open what has been saved.
585 "history", // The documents held before this one, in time.
586 "note", // Annotate the held document.
587 "tile", // Split the view into tiled panes.
588 "grip", // The drag handle of a dockable bar: a two-by-three of dots.
589 "minimise", // Send the window to the taskbar.
590 "maximise", // Fill the screen with the window.
591 "restore", // Return a maximised window to its former size.
592];
593
594/// Whether the engine draws an icon of this name.
595pub fn known_icon(name: &str) -> bool {
596 ICON_NAMES.contains(&name)
597}
598
599/// The icon set an error message spells, so a refusal names what it would have taken.
600pub fn icon_names_label() -> String {
601 ICON_NAMES.join("', '")
602}
603
604/// The map key under which a node's children are carried.
605pub const KEY_CHILDREN: &'static str = "children";
606/// The map key under which a node names a document style (§4.4). Permitted on any map payload.
607pub const KEY_STYLE: &'static str = "style";
608/// The `doc` node's map key for the document style table (§4.4).
609pub const KEY_STYLES: &'static str = "styles";
610/// The map key under which an unknown kind carries its fallback (§4.5).
611pub const KEY_FALLBACK: &'static str = "fallback";
612/// The `surface` node's map key for its semantic alternative (§4.2).
613///
614/// The alternative is inert content, always: it is what a screen reader reads, what a search indexes,
615/// and what the reader sees when the application is not running, was granted no drawing capability,
616/// or was stopped for misbehaving. It is not the same key as an `image`'s `alt`, which is a string;
617/// this one is a non-empty list of nodes.
618pub const KEY_ALT: &'static str = "alt";
619/// The `link` node's map key for its typed address (§4.3).
620pub const KEY_TO: &'static str = "to";
621/// The address-map key selecting a NAMES name.
622pub const ADDR_NAME: &'static str = "name";
623/// The address-map key selecting a content hash.
624pub const ADDR_HASH: &'static str = "hash";
625
626/// Returns the children of a node payload, or an empty slice if it declares none.
627pub fn children_of(payload: &Dat) -> Outcome<Vec<Dat>> {
628 match payload {
629 Dat::Map(map) => {
630 match map.get(&dat!(KEY_CHILDREN)) {
631 None => Ok(Vec::new()),
632 Some(Dat::List(v)) => Ok(v.clone()),
633 Some(d) => Err(err!(
634 "Node children must be a list, found {:?}.", d.kind();
635 Invalid, Input)),
636 }
637 },
638 _ => Ok(Vec::new()),
639 }
640}
641
642// ┌───────────────────────────────────────────────────────────────────────────┐
643// │ STYLING (§4.4) │
644// └───────────────────────────────────────────────────────────────────────────┘
645
646/// The semantic palette names a colour property may carry. The reader's theme resolves them.
647///
648/// `line` is the rule between one region and the next: a colour quieter than the muted ink, for a surface
649/// that draws its own edge. An outline in a text colour reads as a box drawn AROUND something rather than
650/// as the edge OF it, so a border wants a name of its own.
651pub const PALETTE: [&'static str; 5] = ["ink", "muted", "accent", "bg", "line"];
652/// The text-direction values.
653pub const DIRECTIONS: [&'static str; 2] = ["ltr", "rtl"];
654/// The alignment values.
655pub const ALIGNMENTS: [&'static str; 4] = ["start", "center", "end", "justify"];
656
657/// How a style-record property's value is checked.
658#[derive(Clone, Copy, Debug, PartialEq, Eq)]
659pub enum StyleCheck {
660 /// A palette name (`str`), one of [`PALETTE`].
661 Palette,
662 /// A type scale step (`i8`); 0 is the reader's base size.
663 ScaleStep,
664 /// A language tag (`str`), BCP-47.
665 Lang,
666 /// A direction (`str`), one of [`DIRECTIONS`].
667 Direction,
668 /// A spacing scale index (`u8`).
669 Spacing,
670 /// An alignment (`str`), one of [`ALIGNMENTS`].
671 Alignment,
672 /// A grid's minimum tile width, as a PERCENTAGE of a base size (`u16`), so a measure that does not
673 /// fall on a whole em can still be named.
674 Tile,
675 /// A share of the room a packed row has left over (`u8`); 0 takes none of it.
676 Share,
677 /// A border: a palette name and a width in pixels, as a two-element list. See [`check_border`].
678 Border,
679 /// How far a node is lifted off the surface behind it (`u8`); 0 is lying flat on it.
680 Elevation,
681}
682
683/// Which schemas a style property belongs to (§4.4).
684///
685/// It is the property's own scope rather than a list kept on the schema, so a property is written
686/// down once, beside the check its value is held to, and cannot be admitted somewhere its author
687/// never wrote.
688#[derive(Clone, Copy, Debug, PartialEq, Eq)]
689pub enum StyleScope {
690 /// The v0 document vocabulary, which every schema admits and which is frozen.
691 Doc,
692 /// The interface vocabulary: admitted by a chrome and an application tree, and by no document.
693 Interface,
694}
695
696impl StyleScope {
697
698 /// The trees a property of this scope is legal in, as an error message names them.
699 pub fn legal_in(&self) -> &'static str {
700 match self {
701 Self::Doc => "any tree",
702 Self::Interface => "a chrome or an application tree",
703 }
704 }
705}
706
707/// One property of a style record.
708#[derive(Clone, Copy, Debug)]
709pub struct StyleField {
710 /// The map key.
711 pub name: &'static str,
712 /// How the value is checked.
713 pub check: StyleCheck,
714 /// Whether the property flows down to descendants until overridden.
715 pub inherited: bool,
716 /// The schemas that admit the property. See [`Schema::admits_style`].
717 pub scope: StyleScope,
718}
719
720/// The document style vocabulary (§4.4): the eight properties `oxeweb/doc/0` admits, and no others.
721///
722/// **This list is frozen.** A published document is signed under the vocabulary of its schema, and
723/// its address is the hash of its bytes, so the set of properties it could ever have named is settled
724/// the day it is published. Growing this list would change what an existing document is allowed to
725/// mean; growth belongs in a schema version, or in [`INTERFACE_STYLE_FIELDS`], which no document may
726/// name.
727///
728/// # It grew once, before v0 was published, and this is what was asked
729///
730/// `radius` was an interface property and is now a document one. The question put was whether a corner
731/// radius is a property a DOCUMENT legitimately has, and the answer turns on three things.
732///
733/// It decorates a surface the document already draws. A style may name `bg` and `pad`, so a tinted,
734/// padded box is already a thing a document makes; `radius` says what shape that box's corners are. It
735/// adds no element, no geometry and no authority.
736///
737/// It has a meaning in prose. That is what tells it from the six it left behind -- `grid`, `pack`,
738/// `grow`, `max`, `nowrap`, `ends` are the layout of a BAR and a SHELF and mean nothing in a document,
739/// while a soft-cornered callout, pull-quote or listing is ordinary typography and has been for forty
740/// years. A property whose absence every document would have to work around forever, for a rule that
741/// was not protecting anything, is a property in the wrong list.
742///
743/// And the rule it seemed to break is not the rule that does the work. "A document must not dress as an
744/// interface" is real, but what enforces it is ADDRESSING: the band and the bars are painted into
745/// pixmaps a document is never handed, so it cannot draw there whatever it names. A document with `bg`,
746/// `pad`, `fill` and `align` can already draw a filled box with a word in it -- a corner is a rounding,
747/// not the difference between plausible and implausible -- and a document that drew a perfect forgery
748/// of a control could still make nothing happen by it, because a document has no script and a link goes
749/// to an address or nowhere. §4.4 says as much in its own words: a rounded corner is not a capability.
750///
751/// **`border` and `shadow` stayed where they are, and the distinction is the point.** A shadow is
752/// ELEVATION -- the language of a thing that lifts toward the hand -- and a paragraph claiming to float
753/// above its page is a document dressing as an interface in exactly the way the rule means. A border is
754/// the line round a control with an edge, which is the spec's own phrase for what a chrome draws. It was
755/// not asked about and it is not moved. One property moved because one property was argued.
756pub const STYLE_FIELDS: [StyleField; 8] = [
757 StyleField { name: "fill", check: StyleCheck::Palette, inherited: true, scope: StyleScope::Doc },
758 StyleField { name: "size", check: StyleCheck::ScaleStep, inherited: true, scope: StyleScope::Doc },
759 StyleField { name: "lang", check: StyleCheck::Lang, inherited: true, scope: StyleScope::Doc },
760 StyleField { name: "dir", check: StyleCheck::Direction, inherited: true, scope: StyleScope::Doc },
761 StyleField { name: "bg", check: StyleCheck::Palette, inherited: false, scope: StyleScope::Doc },
762 StyleField { name: "pad", check: StyleCheck::Spacing, inherited: false, scope: StyleScope::Doc },
763 StyleField { name: "align", check: StyleCheck::Alignment, inherited: false, scope: StyleScope::Doc },
764 StyleField { name: "radius", check: StyleCheck::Spacing, inherited: false, scope: StyleScope::Doc },
765];
766
767/// The interface style vocabulary (§4.4): what a chrome or an application tree may name, and a
768/// document may not.
769///
770/// The browser's own chrome is an SBJ tree laid out by the same engine as a document, and it is a
771/// real interface: a library of tiles, a navigation bar, a control with an edge. These are what it
772/// takes to draw one, and they are here rather than in [`STYLE_FIELDS`] because a document is not an
773/// interface and must not be able to dress as one.
774///
775/// Every one of them is self-only. A property that cannot flow down cannot reach a node that did not
776/// name it, so admitting three more of them widens what a chrome may say about itself and nothing
777/// else.
778pub const INTERFACE_STYLE_FIELDS: [StyleField; 8] = [
779 StyleField { name: "grid", check: StyleCheck::Tile, inherited: false, scope: StyleScope::Interface },
780 StyleField { name: "pack", check: StyleCheck::Tile, inherited: false, scope: StyleScope::Interface },
781 StyleField { name: "grow", check: StyleCheck::Share, inherited: false, scope: StyleScope::Interface },
782 StyleField { name: "max", check: StyleCheck::Tile, inherited: false, scope: StyleScope::Interface },
783 StyleField { name: "border", check: StyleCheck::Border, inherited: false, scope: StyleScope::Interface },
784 StyleField { name: "shadow", check: StyleCheck::Elevation, inherited: false, scope: StyleScope::Interface },
785 StyleField { name: "nowrap", check: StyleCheck::Share, inherited: false, scope: StyleScope::Interface },
786 StyleField { name: "ends", check: StyleCheck::Spacing, inherited: false, scope: StyleScope::Interface },
787];
788
789/// Returns the style-record property of the given name in ANY schema's vocabulary, if it is one.
790///
791/// This answers what a property IS -- its width, its check, its scope -- and never whether a tree may
792/// name it. It exists for the canonical encoding check, which has no schema and needs none: a
793/// property's wire type does not depend on who may name it, so `grid` is a `u8` in every tree, and
794/// pinning that width is the same work in all three schemas.
795///
796/// **A validator must not call this.** Admission is [`Schema::style_field`], which asks this and then
797/// applies the schema's rule; calling this instead would admit `grid` in a document.
798pub fn known_style_field(name: &str) -> Option<&'static StyleField> {
799 STYLE_FIELDS.iter()
800 .chain(INTERFACE_STYLE_FIELDS.iter())
801 .find(|f| f.name == name)
802}
803
804/// A decoded, validated border: the line's palette name, and how thick it is.
805#[derive(Clone, Copy, Debug, PartialEq, Eq)]
806pub struct Border<'a> {
807 /// The palette name the line is drawn in. The reader's theme resolves it, as it does any other.
808 pub colour: &'a str,
809 /// The width of the line, in pixels.
810 pub width: u8,
811}
812
813/// Checks a style's `border` value: a two-element list of a palette name and a width in pixels (§4.4).
814///
815/// A border is one property and not two: a colour with no width draws nothing, and a width with no
816/// colour draws nothing, so the two are named together or not at all, and a half-written border
817/// cannot be spelt.
818///
819/// The width is in pixels rather than on the spacing scale that `pad` and `radius` ride, because a
820/// border is a boundary and not a measure of room: a hairline is one pixel at any text size.
821pub fn check_border(d: &Dat) -> Outcome<Border<'_>> {
822 let list = match d {
823 Dat::List(list) => list,
824 other => return Err(err!(
825 "A border is a palette name and a width in pixels, written as a two-element list, \
826 e.g. [\"muted\", (u8|1)]; found a {:?}.", other.kind();
827 Invalid, Input)),
828 };
829 let (colour, width) = match list.as_slice() {
830 [Dat::Str(colour), Dat::U8(width)] => (colour.as_str(), *width),
831 _ => return Err(err!(
832 "A border is a palette name and a width in pixels, in that order, written as a \
833 two-element list, e.g. [\"muted\", (u8|1)]; found a list of {} element(s).", list.len();
834 Invalid, Input)),
835 };
836 if !PALETTE.contains(&colour) {
837 return Err(err!(
838 "A border is drawn in a palette colour ({}), but one names '{}'.",
839 PALETTE.join(", "), colour;
840 Invalid, Input));
841 }
842 Ok(Border {
843 colour,
844 width,
845 })
846}
847
848// ┌───────────────────────────────────────────────────────────────────────────┐
849// │ LINK ADDRESSES (§4.3) │
850// └───────────────────────────────────────────────────────────────────────────┘
851
852/// A decoded, validated link address.
853#[derive(Clone, Debug, PartialEq, Eq)]
854pub enum Address {
855 /// A NAMES name.
856 Name(String),
857 /// A 32-byte content hash.
858 Hash(Vec<u8>),
859}
860
861/// Checks a `link`'s `to` value: a map with exactly one entry, `name` (str) or `hash` (b32).
862///
863/// Returning the typed address means the decoder tells a name from a hash, and a malformed target
864/// is refused here rather than misread by the renderer.
865pub fn check_address(d: &Dat) -> Outcome<Address> {
866 let map = match d {
867 Dat::Map(map) => map,
868 other => return Err(err!(
869 "A link address is a single-entry map, found a {:?}.", other.kind();
870 Invalid, Input)),
871 };
872 if map.len() != 1 {
873 return Err(err!(
874 "A link address is a single-entry map naming a name or a hash, found {} entries.",
875 map.len();
876 Invalid, Input));
877 }
878 match map.get(&dat!(ADDR_NAME)) {
879 Some(Dat::Str(s)) => return Ok(Address::Name(s.clone())),
880 Some(other) => return Err(err!(
881 "A link address \"name\" is a str, found a {:?}.", other.kind();
882 Invalid, Input)),
883 None => (),
884 }
885 match map.get(&dat!(ADDR_HASH)) {
886 Some(Dat::B32(b)) => Ok(Address::Hash(b.to_vec())),
887 Some(other) => Err(err!(
888 "A link address \"hash\" is a b32, found a {:?}.", other.kind();
889 Invalid, Input)),
890 None => Err(err!(
891 "A link address names \"name\" or \"hash\", found neither."; Invalid, Input)),
892 }
893}