Skip to main content

ui/
md_doc.rs

1// md_doc -- the document of the Markdown / HTML viewer (md_view.rs): blocks of formatted text,
2// read from Markdown (CommonMark with GitHub's tables, task lists, strikethrough and bare
3// links) or from HTML (the reader of the rich-text editor, rich_html.rs)
4//
5//   MdDoc::markdown(s) / MdDoc::html(s)  parse; never panics, takes linear time: nesting, input
6//                                        size and the inline parser's work are capped
7//   doc.blocks() -> &[MdBlock]           the blocks in reading order (see MdBlock)
8//   doc.links() / images() / tables()    link targets (MdSpan::link - 1), images (MdSpan::image
9//                                        - 1: src, alt, title, size), table column alignments
10//   doc.plain() / doc.to_html()          everything as text (list markers, cells by tabs) / HTML
11//   doc.plain_range(a, b) / html_range(a, b)   a part: TextPos { line: block, byte }
12//   doc.link_spots()                     every link in reading order (block, bytes, link)
13//   doc.forms() / fields() / field_spots()   the forms of HTML, their fields (MdSpan::field - 1;
14//                                        hidden ones have no character; their first values:
15//                                        md_view keeps the current ones) and where the visible
16//                                        ones stand in reading order; field.caption(): a
17//                                        button's caption
18//   doc.truncated()                      a limit cut the input
19//   HTML documents keep their element tree (md_dom.rs: queries, changes of text, attributes,
20//   classes and structure; MdNode handles that stay the same across changes); the blocks,
21//   fields and forms name their elements (MdBlock::node, MdField::node, MdForm::node).
22//   doc.update() -> the blocks were derived again after changes: in place when every change
23//                                        lies inside block elements that show one paragraph of
24//                                        text by themselves (no links, images, fields, blocks in
25//                                        it: O(that paragraph)), else all of them (linear, no
26//                                        layout); md_view.rs keeps its place, selection, focus,
27//                                        the fields' values and the layouts that still hold
28//   doc.field_of(n) -> the field of element n
29//   doc.boxes()                          the boxes of CSS (style sheets, rich_html.rs): a block
30//                                        element's margins, padding, borders and background
31//                                        around its blocks (MdBlock::boxes, outermost first)
32//
33// The model: a block is a paragraph, a heading (level 1..6), a code block (its text, the
34// language of the fence), a rule or a table row (cells: byte ranges of the text, '\t' between
35// them).  Its place: `quote` (block quotes around it), `list` (lists around it), `marker` /
36// `number` / `task` (it starts a list item), `space` (the gap before it: 0 none, 1 a tight
37// list's, 2 a paragraph's).  Its text holds the visible characters (an image is one U+FFFC,
38// a hard break '\n'); spans give ranges their style (MS_*), link, image and colours.
39//
40// Markdown: ATX and setext headings, paragraphs with lazy continuation lines, block quotes,
41// bullet (- + *) and ordered (1. 1)) lists with start numbers, tight / loose spacing and task
42// items ([ ] / [x]), fenced (``` ~~~ with an info string) and indented code, thematic breaks,
43// GFM tables (alignment row, escaped pipes), HTML blocks (through the HTML reader), link
44// reference definitions; inline: backslash escapes, entities, code spans, emphasis and strong
45// emphasis (the delimiter algorithm with the rule of three), ~~strikethrough~~, links and
46// images (inline, full / collapsed / shortcut references, titles), autolinks (<...>, bare
47// http:// https:// www.), hard breaks (two spaces, backslash) and soft breaks, a little inline
48// HTML (br, b / strong, i / em, s / del, u / ins, code / kbd, a href, img).  Other tags are
49// dropped, their text stays.  Tabs expand to 4 columns.
50//
51// Forms (HTML, also in Markdown's HTML blocks): <form action method> (forms do not nest),
52// <input> of type text (and the other text types: email, search, url, tel, number ...),
53// password, checkbox, radio, hidden, submit, image (a submit button), reset and button,
54// <textarea> (its text read raw, one leading line end dropped), <select> with <option value
55// selected> (one choice), <button type> (its text the caption), <label> (around a field or
56// for= its id: the field's name; aria-label wins, title is the last resort) and form= (a field
57// outside its form).  A field is one U+FFFC in the text with a span of its own (MdSpan::field);
58// a disabled check box at the start of a list item is that item's task box (as Markdown's
59// "[x]" is written out by html_range).
60
61use alloc::collections::BTreeMap;
62use alloc::format;
63use alloc::rc::Rc;
64use alloc::string::String;
65use alloc::vec;
66use alloc::vec::Vec;
67
68use crate::ed_core::TextPos;
69use crate::i18n::tr;
70use crate::md_dom::{Dom, MdNode, NIL};
71use crate::rich_doc::*;
72use crate::rich_html::{attrs, dom_para, dom_read, entity, html_read, HExt, HRead, Sheet};
73
74/// MdBlock::kind: a paragraph
75pub const MB_PARA: u8 = 0;
76/// a heading (MdBlock::level 1 ..= 6)
77pub const MB_HEADING: u8 = 1;
78/// a code block (MdBlock::lang: the fence's language)
79pub const MB_CODE: u8 = 2;
80/// a thematic break (no text)
81pub const MB_RULE: u8 = 3;
82/// a table row (MdBlock::cells, table, head)
83pub const MB_ROW: u8 = 4;
84
85/// MdSpan::style: strong
86pub const MS_BOLD: u16 = 1;
87/// emphasis
88pub const MS_ITALIC: u16 = 2;
89/// struck through
90pub const MS_STRIKE: u16 = 4;
91/// code (monospace)
92pub const MS_CODE: u16 = 8;
93/// underlined (HTML)
94pub const MS_UNDER: u16 = 16;
95
96/// MdBlock::marker: no list item starts here
97pub const MK_NONE: u8 = 0;
98/// a bullet item
99pub const MK_BULLET: u8 = 1;
100/// a numbered item (MdBlock::number, delim)
101pub const MK_NUMBER: u8 = 2;
102
103/// MdTable::aligns: not given
104pub const MA_NONE: u8 = 0;
105/// left (start)
106pub const MA_LEFT: u8 = 1;
107/// centred
108pub const MA_CENTER: u8 = 2;
109/// right (end)
110pub const MA_RIGHT: u8 = 3;
111
112/// MdField::kind: a one-line text field (<input> of type text, email, search, url, tel,
113/// number and every type not named below)
114pub const MF_TEXT: u8 = 0;
115/// a password field (its text never leaves the view but in the form's data)
116pub const MF_PASSWORD: u8 = 1;
117/// a check box
118pub const MF_CHECKBOX: u8 = 2;
119/// a radio button: one of the radios of its form with its name
120pub const MF_RADIO: u8 = 3;
121/// a hidden field (no character in the text)
122pub const MF_HIDDEN: u8 = 4;
123/// a text area (<textarea>: several lines)
124pub const MF_TEXTAREA: u8 = 5;
125/// a drop-down list (<select>)
126pub const MF_SELECT: u8 = 6;
127/// a submit button (<button>, <input type=submit> / image)
128pub const MF_SUBMIT: u8 = 7;
129/// a reset button: the form's fields get their first values back
130pub const MF_RESET: u8 = 8;
131/// a button without an action of its own (<button type=button>, <input type=button>)
132pub const MF_BUTTON: u8 = 9;
133
134const INPUT_MAX: usize = 16 << 20; // bytes read (the rest is cut)
135const NEST_MAX: usize = 32; // block quotes and lists inside each other
136const BLOCKS_MAX: usize = 1 << 20;
137const LABEL_MAX: usize = 999;
138const PARENS_MAX: usize = 32;
139const HTML_STACK: usize = 64;
140const COLS_MAX: usize = 128;
141const CELLS_MAX: usize = 1 << 21;
142const FIELDS_MAX: usize = 0xFFFE; // form fields of a document (the view's ids: md_field_id)
143
144/// a range of a block's text with its style
145#[derive(Clone, Debug, Default, PartialEq, Eq)]
146pub struct MdSpan {
147    /// bytes a .. b of the block's text
148    pub a: u32,
149    pub b: u32,
150    /// MS_*
151    pub style: u16,
152    /// a link: index + 1 into MdDoc::links (0: none)
153    pub link: u32,
154    /// an image (the text is U+FFFC): index + 1 into MdDoc::images (0: none)
155    pub image: u32,
156    /// HTML colours 0xAARRGGBB (0: the theme's)
157    pub color: u32,
158    pub bg: u32,
159    /// a form field (the text is U+FFFC): index + 1 into MdDoc::fields (0: none)
160    pub field: u32,
161}
162
163/// a block of the document (see the module header)
164#[derive(Clone, Debug, Default, PartialEq)]
165pub struct MdBlock {
166    /// MB_*
167    pub kind: u8,
168    /// headings: 1 ..= 6
169    pub level: u8,
170    /// the visible text (U+FFFC an image, '\n' a hard break or a code line's end, '\t' between
171    /// cells)
172    pub text: String,
173    /// styled ranges, ascending, not overlapping (the text between them is plain)
174    pub spans: Vec<MdSpan>,
175    /// block quotes around it
176    pub quote: u8,
177    /// lists around it (0: none)
178    pub list: u8,
179    /// the containers from the outside in: bit i set = the i-th is a block quote, clear = a
180    /// list
181    pub path: u32,
182    /// MK_*: a list item starts with this block
183    pub marker: u8,
184    /// MK_NUMBER: the item's number and its delimiter ('.' or ')')
185    pub number: u32,
186    pub delim: u8,
187    /// a task item: 0 none, 1 open, 2 done
188    pub task: u8,
189    /// the gap before it: 0 none, 1 a tight list's, 2 a paragraph's
190    pub space: u8,
191    /// code blocks: the language of the fence ("" none)
192    pub lang: String,
193    /// table rows: the cells' byte ranges
194    pub cells: Vec<(u32, u32)>,
195    /// table rows: index into MdDoc::tables, a header row
196    pub table: u32,
197    pub head: bool,
198    /// paragraphs: 0 start, 1 centre, 2 end (HTML)
199    pub align: u8,
200    /// HTML: the element whose content it shows (the innermost block element: p, li, h1, the
201    /// row of a cell ...; MdNode::NONE: none, Markdown)
202    pub node: MdNode,
203    /// HTML: the boxes of CSS around it (indices of MdDoc::boxes, outermost first)
204    pub boxes: Vec<u32>,
205}
206
207/// a box of CSS around blocks: what a block element's style gives (MdBlock::boxes); lengths
208/// in px at 100 % text, the sides top, right, bottom, left
209#[derive(Clone, Debug, Default, PartialEq, Eq)]
210pub struct MdBox {
211    pub margin: [i16; 4],
212    pub padding: [i16; 4],
213    /// the borders' widths (a style none: 0; every visible style is drawn solid) and colours
214    /// (0xAARRGGBB; 0: the text's)
215    pub border: [u8; 4],
216    pub border_color: [u32; 4],
217    /// the background (0: none)
218    pub bg: u32,
219    /// the margins CSS gave (bit 1 << side): at the top and bottom they take the place of the
220    /// gap between blocks
221    pub mset: u8,
222    /// the block quotes and lists around its element (its place among their indents)
223    pub depth: u8,
224    /// its element
225    pub node: MdNode,
226}
227
228/// an image of the document
229#[derive(Clone, Debug, Default, PartialEq, Eq)]
230pub struct MdImage {
231    pub src: String,
232    pub alt: String,
233    pub title: String,
234    /// size given by HTML attributes (px, 0: the picture's)
235    pub w: u32,
236    pub h: u32,
237}
238
239/// a table: its columns' alignment (MA_*)
240#[derive(Clone, Debug, Default, PartialEq, Eq)]
241pub struct MdTable {
242    pub aligns: Vec<u8>,
243}
244
245/// a link in reading order: block, bytes a .. b of its text, the link (index + 1)
246#[derive(Clone, Copy, Debug, PartialEq, Eq)]
247pub struct MdLinkSpot {
248    pub block: usize,
249    pub a: u32,
250    pub b: u32,
251    pub link: u32,
252}
253
254/// a form of HTML
255#[derive(Clone, Debug, Default, PartialEq, Eq)]
256pub struct MdForm {
257    /// where its data goes (as written: the app resolves it) and how ("get" / "post": lower
258    /// case, "get" when not given)
259    pub action: String,
260    pub method: String,
261    pub id: String,
262    pub name: String,
263    /// its <form> element (an HTML document)
264    pub node: MdNode,
265}
266
267/// a choice of a drop-down list: the value sent, the text shown
268#[derive(Clone, Debug, Default, PartialEq, Eq)]
269pub struct MdOption {
270    pub value: String,
271    pub label: String,
272}
273
274/// a field of a form (its first value: the view keeps the current one, MdViewState)
275#[derive(Clone, Debug, Default, PartialEq, Eq)]
276pub struct MdField {
277    /// MF_*
278    pub kind: u8,
279    /// its form: index + 1 into MdDoc::forms (0: none -- it sends nothing)
280    pub form: u32,
281    pub name: String,
282    pub id: String,
283    /// text fields: the text; check boxes, radios, buttons: the value sent ("on" for check
284    /// boxes and radios without one)
285    pub value: String,
286    /// the name screen readers say (aria-label, the <label>, title); buttons: the caption
287    pub label: String,
288    pub placeholder: String,
289    /// check boxes and radios: checked at first
290    pub checked: bool,
291    /// drop-down lists: the choices, the one chosen at first (-1: none)
292    pub options: Vec<MdOption>,
293    pub selected: i32,
294    /// text fields: the width in characters (size; text areas: cols; 0: the default), text
295    /// areas: the lines (rows)
296    pub size: u32,
297    pub rows: u32,
298    /// at most this many characters (0: no limit)
299    pub maxlength: u32,
300    pub disabled: bool,
301    pub readonly: bool,
302    /// its element (<input>, <select> ... of an HTML document; the view keeps its value while
303    /// the element stays)
304    pub node: MdNode,
305}
306
307impl MdField {
308    /// a button's caption: its own, else "Senden" / "Zurücksetzen" (submit / reset, in the UI
309    /// language); "" for the other kinds
310    pub fn caption(&self) -> &str {
311        match self.kind {
312            MF_SUBMIT | MF_RESET | MF_BUTTON if !self.label.is_empty() => &self.label,
313            MF_SUBMIT => tr("Senden"),
314            MF_RESET => tr("Zurücksetzen"),
315            _ => "",
316        }
317    }
318}
319
320/// a visible field in reading order: block, byte of its U+FFFC, the field (index + 1)
321#[derive(Clone, Copy, Debug, PartialEq, Eq)]
322pub struct MdFieldSpot {
323    pub block: usize,
324    pub a: u32,
325    pub field: u32,
326}
327
328/// the document (see the module header)
329#[derive(Clone, Debug, Default)]
330pub struct MdDoc {
331    blocks: Vec<MdBlock>,
332    links: Vec<String>,
333    images: Vec<MdImage>,
334    tables: Vec<MdTable>,
335    spots: Vec<MdLinkSpot>,
336    forms: Vec<MdForm>,
337    fields: Vec<MdField>,
338    fspots: Vec<MdFieldSpot>,
339    truncated: bool,
340    // the boxes of CSS and the first and last block of each
341    boxes: Vec<MdBox>,
342    box_ends: Vec<(u32, u32)>,
343    // an HTML document's element tree (md_dom.rs; none for Markdown) and its style sheets, the
344    // input was cut, the changes of the blocks the view has not taken yet
345    pub(crate) dom: Dom,
346    sheet: Rc<Sheet>,
347    // the texts of its <link rel="stylesheet"> elements, by their addresses as written (set_sheet)
348    linked: BTreeMap<String, String>,
349    cut: bool,
350    change: Option<DocChange>,
351}
352
353/// what MdDoc::update changed, for md_view.rs: the blocks before `range.0` are the same,
354/// range.0 .. + range.1 became range.0 .. + range.2, the ones after moved; `redo` (new
355/// indices) changed in place.  full: links, images, fields, forms and tables were numbered
356/// again (images, spots: those before the first change)
357#[derive(Clone, Debug, Default)]
358pub(crate) struct DocChange {
359    pub range: Option<(usize, usize, usize)>,
360    pub redo: Vec<usize>,
361    pub full: bool,
362    pub images: Vec<MdImage>,
363    pub spots: Vec<MdLinkSpot>,
364}
365
366impl DocChange {
367    // this change, then `n` (in the blocks this one made) -> both in one
368    fn then(self, n: DocChange) -> DocChange {
369        let range = match (self.range, n.range) {
370            (Some((a1, o1, n1)), Some((a2, o2, n2))) => {
371                let lo = a1.min(a2);
372                let hi = (a1 + n1).max(a2 + o2);
373                Some((lo, hi - n1 + o1 - lo, hi - o2 + n2 - lo))
374            }
375            (r, None) | (None, r) => r,
376        };
377        // the first one's blocks changed in place: where the second one put them
378        let mut redo: Vec<usize> = Vec::new();
379        for &x in &self.redo {
380            match n.range {
381                Some((a, o, nn)) if x >= a + o => redo.push(x - o + nn),
382                Some((a, _, _)) if x >= a => {}
383                _ => redo.push(x),
384            }
385        }
386        redo.extend(n.redo.iter().copied());
387        if let Some((a, _, nn)) = range {
388            redo.retain(|&x| x < a || x >= a + nn);
389        }
390        redo.sort_unstable();
391        redo.dedup();
392        let full = self.full || n.full;
393        let (images, spots) = if self.full { (self.images, self.spots) } else { (n.images, n.spots) };
394        DocChange { range, redo, full, images, spots }
395    }
396}
397
398// ------------------------------------------------------------------ small helpers
399
400fn is_punct(c: char) -> bool {
401    c.is_ascii_punctuation() || (!c.is_alphanumeric() && !c.is_whitespace() && !c.is_control() && c as u32 > 127)
402}
403
404fn blank(s: &str) -> bool {
405    s.bytes().all(|b| b == b' ' || b == b'\t')
406}
407
408fn indent_of(s: &str) -> usize {
409    s.bytes().take_while(|&b| b == b' ').count()
410}
411
412// a line with its tabs expanded to 4 columns
413fn expand_tabs(s: &str) -> String {
414    if !s.contains('\t') {
415        return String::from(s);
416    }
417    let mut out = String::with_capacity(s.len() + 8);
418    let mut col = 0;
419    for c in s.chars() {
420        if c == '\t' {
421            let n = 4 - col % 4;
422            for _ in 0..n {
423                out.push(' ');
424            }
425            col += n;
426        } else {
427            out.push(c);
428            col += 1;
429        }
430    }
431    out
432}
433
434// the label of a reference, compared case-insensitively with blanks collapsed
435fn norm_label(s: &str) -> String {
436    let mut out = String::new();
437    let mut sp = false;
438    for c in s.trim().chars() {
439        if c.is_whitespace() {
440            sp = true;
441            continue;
442        }
443        if sp && !out.is_empty() {
444            out.push(' ');
445        }
446        sp = false;
447        for l in c.to_lowercase() {
448            out.push(l);
449        }
450    }
451    out
452}
453
454// backslash escapes and entities of a link destination or title
455fn unescape(s: &str) -> String {
456    let b = s.as_bytes();
457    let mut out = String::with_capacity(s.len());
458    let mut i = 0;
459    while i < b.len() {
460        if b[i] == b'\\' && i + 1 < b.len() && b[i + 1].is_ascii_punctuation() {
461            out.push(b[i + 1] as char);
462            i += 2;
463            continue;
464        }
465        if b[i] == b'&' {
466            if let Some((c, n)) = entity_at(&s[i..]) {
467                out.push(c);
468                i += n;
469                continue;
470            }
471        }
472        let c = s[i..].chars().next().unwrap_or(' ');
473        out.push(c);
474        i += c.len_utf8();
475    }
476    out
477}
478
479// an entity at the start of s -> (character, bytes)
480fn entity_at(s: &str) -> Option<(char, usize)> {
481    let b = s.as_bytes();
482    let end = b.iter().take(34).position(|&c| c == b';')?;
483    if end < 2 {
484        return None;
485    }
486    let name = &s[1..end];
487    if !name.bytes().all(|c| c.is_ascii_alphanumeric() || c == b'#') {
488        return None;
489    }
490    entity(name).map(|c| (c, end + 1))
491}
492
493// ------------------------------------------------------------------ the block parser
494
495// a container: a block quote or a list item
496#[derive(Clone)]
497struct Cont {
498    quote: bool,
499    // list items: the content column, the marker, whether its first block was filed
500    indent: usize,
501    ordered: bool,
502    number: u32,
503    delim: u8,
504    task: u8,
505    filed: bool,
506    // the list item began with a blank line (it may then hold one more)
507    empty: bool,
508}
509
510// a leaf being read
511enum Leaf {
512    None,
513    Para(Vec<String>),
514    Fence { ch: u8, len: usize, indent: usize, info: String, lines: Vec<String> },
515    Code(Vec<String>),
516    Table { aligns: Vec<u8>, rows: Vec<(Vec<String>, bool)> },
517    Html(Vec<String>),
518}
519
520// a block before its inline content is parsed
521#[derive(Default)]
522struct Raw {
523    kind: u8,
524    level: u8,
525    src: String,
526    cells: Vec<String>,
527    html: bool,
528    quote: u8,
529    list: u8,
530    path: u32,
531    marker: u8,
532    number: u32,
533    delim: u8,
534    task: u8,
535    space: u8,
536    lang: String,
537    table: u32,
538    head: bool,
539}
540
541struct BlockParser {
542    conts: Vec<Cont>,
543    leaf: Leaf,
544    raws: Vec<Raw>,
545    defs: BTreeMap<String, (String, String)>,
546    tables: Vec<MdTable>,
547    // a blank line since the last block (the next one gets a paragraph's gap)
548    blank: bool,
549    truncated: bool,
550    // the cells of all tables so far (a row always costs the table's width)
551    table_cells: usize,
552}
553
554// a list marker at the start of s (after up to 3 blanks) -> (ordered, bullet / delim, number,
555// bytes of indent + marker)
556fn list_marker(s: &str) -> Option<(bool, u8, u32, usize)> {
557    let b = s.as_bytes();
558    let sp = indent_of(s);
559    if sp > 3 || sp >= b.len() {
560        return None;
561    }
562    let c = b[sp];
563    let after = |k: usize| k >= b.len() || b[k] == b' ';
564    if matches!(c, b'-' | b'+' | b'*') {
565        return after(sp + 1).then_some((false, c, 0, sp + 1));
566    }
567    let digits = b[sp..].iter().take_while(|c| c.is_ascii_digit()).count();
568    if (1..=9).contains(&digits) {
569        let k = sp + digits;
570        if k < b.len() && (b[k] == b'.' || b[k] == b')') && after(k + 1) {
571            let n = s[sp..k].parse::<u32>().unwrap_or(0);
572            return Some((true, b[k], n, k + 1));
573        }
574    }
575    None
576}
577
578// a thematic break: 3 or more of * - _ (blanks between them)
579fn is_rule(s: &str) -> bool {
580    if indent_of(s) > 3 {
581        return false;
582    }
583    let t = s.trim();
584    let Some(c) = t.bytes().next() else { return false };
585    if !matches!(c, b'*' | b'-' | b'_') {
586        return false;
587    }
588    let mut n = 0;
589    for b in t.bytes() {
590        if b == c {
591            n += 1;
592        } else if b != b' ' {
593            return false;
594        }
595    }
596    n >= 3
597}
598
599// an ATX heading -> (level, content)
600fn atx(s: &str) -> Option<(u8, &str)> {
601    if indent_of(s) > 3 {
602        return None;
603    }
604    let t = s.trim_start();
605    let n = t.bytes().take_while(|&b| b == b'#').count();
606    if !(1..=6).contains(&n) {
607        return None;
608    }
609    let rest = &t[n..];
610    if !rest.is_empty() && !rest.starts_with(' ') {
611        return None;
612    }
613    let mut c = rest.trim();
614    // a closing sequence of #s (after a blank, or the whole content)
615    let k = c.trim_end_matches('#');
616    if k.is_empty() || k.ends_with(' ') {
617        c = k.trim_end();
618    }
619    Some((n as u8, c))
620}
621
622// a fence opening -> (char, length, info)
623fn fence_open(s: &str) -> Option<(u8, usize, String)> {
624    let sp = indent_of(s);
625    if sp > 3 {
626        return None;
627    }
628    let t = &s[sp..];
629    let c = *t.as_bytes().first()?;
630    if c != b'`' && c != b'~' {
631        return None;
632    }
633    let n = t.bytes().take_while(|&b| b == c).count();
634    if n < 3 {
635        return None;
636    }
637    let info = t[n..].trim();
638    if c == b'`' && info.contains('`') {
639        return None;
640    }
641    Some((c, n, unescape(info)))
642}
643
644// the cells of a table row (outer pipes optional, \| is no separator)
645fn split_cells(s: &str) -> Vec<String> {
646    let mut t = s.trim();
647    if let Some(r) = t.strip_prefix('|') {
648        t = r;
649    }
650    if t.ends_with('|') && !t.ends_with("\\|") {
651        t = &t[..t.len() - 1];
652    }
653    let b = t.as_bytes();
654    let mut out = Vec::new();
655    let (mut start, mut i) = (0, 0);
656    while i < b.len() {
657        if b[i] == b'\\' && i + 1 < b.len() {
658            i += 2;
659            continue;
660        }
661        if b[i] == b'|' {
662            out.push(t[start..i].trim().replace("\\|", "|"));
663            start = i + 1;
664            if out.len() >= COLS_MAX {
665                return out;
666            }
667        }
668        i += 1;
669    }
670    // (an escaped pipe is a pipe, also inside code spans)
671    out.push(t[start..].trim().replace("\\|", "|"));
672    out
673}
674
675// a delimiter row of a table -> the alignments
676fn delim_row(s: &str) -> Option<Vec<u8>> {
677    if indent_of(s) > 3 || !s.contains('-') {
678        return None;
679    }
680    let t = s.trim();
681    if !t.contains('|') && !t.starts_with(':') && !t.starts_with('-') {
682        return None;
683    }
684    let cells = split_cells(s);
685    let mut v = Vec::new();
686    for c in cells {
687        let l = c.starts_with(':');
688        let r = c.ends_with(':');
689        let core = c.trim_matches(':');
690        if core.is_empty() || !core.bytes().all(|b| b == b'-') {
691            return None;
692        }
693        v.push(match (l, r) {
694            (true, true) => MA_CENTER,
695            (true, false) => MA_LEFT,
696            (false, true) => MA_RIGHT,
697            _ => MA_NONE,
698        });
699    }
700    // a single "---" without pipes is a setext underline or a rule
701    if v.len() == 1 && !t.contains('|') {
702        return None;
703    }
704    Some(v)
705}
706
707// an HTML block starts (a block-level tag or a comment)
708fn html_start(s: &str) -> bool {
709    if indent_of(s) > 3 {
710        return false;
711    }
712    let t = s.trim_start();
713    if t.starts_with("<!--") {
714        return true;
715    }
716    let Some(r) = t.strip_prefix('<') else { return false };
717    let r = r.strip_prefix('/').unwrap_or(r);
718    let n = r.bytes().take_while(|b| b.is_ascii_alphanumeric()).count();
719    if n == 0 {
720        return false;
721    }
722    let name = r[..n].to_ascii_lowercase();
723    let end = r.as_bytes().get(n).copied();
724    if !matches!(end, None | Some(b' ') | Some(b'>') | Some(b'/')) {
725        return false;
726    }
727    matches!(
728        name.as_str(),
729        "address" | "article" | "aside" | "blockquote" | "center" | "dd" | "details" | "dialog" | "div" | "dl" | "dt" | "fieldset"
730            | "figcaption" | "figure" | "footer" | "form" | "h1" | "h2" | "h3" | "h4" | "h5" | "h6" | "header" | "hr" | "legend" | "li"
731            | "main" | "nav" | "ol" | "p" | "pre" | "section" | "summary" | "table" | "tbody" | "td" | "tfoot" | "th" | "thead" | "tr"
732            | "ul" | "img" | "picture"
733    )
734}
735
736// link reference definitions at the start of a paragraph's text -> the rest
737fn take_defs(src: &str, defs: &mut BTreeMap<String, (String, String)>) -> String {
738    let mut rest = src;
739    while rest.starts_with('[') {
740        let Some((label, dest, title, after)) = parse_def(rest) else { break };
741        let key = norm_label(&label);
742        if !key.is_empty() {
743            defs.entry(key).or_insert((dest, title));
744        }
745        rest = after;
746    }
747    String::from(rest)
748}
749
750// one definition: [label]: dest "title" (to the end of its line)
751fn parse_def(s: &str) -> Option<(String, String, String, &str)> {
752    let b = s.as_bytes();
753    let mut i = 1;
754    while i < b.len() && b[i] != b']' {
755        if b[i] == b'[' || i > LABEL_MAX {
756            return None;
757        }
758        if b[i] == b'\\' {
759            i += 1;
760        }
761        i += 1;
762    }
763    if i >= b.len() || b.get(i + 1) != Some(&b':') {
764        return None;
765    }
766    let label = &s[1..i];
767    if label.trim().is_empty() {
768        return None;
769    }
770    let mut k = i + 2;
771    let ws = |k: &mut usize, nl: bool| {
772        let mut seen = false;
773        while *k < b.len() && (b[*k] == b' ' || (nl && !seen && b[*k] == b'\n')) {
774            if b[*k] == b'\n' {
775                seen = true;
776            }
777            *k += 1;
778        }
779    };
780    ws(&mut k, true);
781    let (dest, mut k) = parse_dest(s, k)?;
782    let line_end = |k: usize| -> Option<usize> {
783        let mut j = k;
784        while j < b.len() && b[j] == b' ' {
785            j += 1;
786        }
787        (j >= b.len() || b[j] == b'\n').then_some((j + 1).min(b.len()))
788    };
789    let before = k;
790    // a title on the same line or the next
791    let mut t = k;
792    ws(&mut t, true);
793    if t > before && t < b.len() && matches!(b[t], b'"' | b'\'' | b'(') {
794        if let Some((title, e)) = parse_title(s, t) {
795            if let Some(end) = line_end(e) {
796                return Some((String::from(label), dest, title, &s[end..]));
797            }
798        }
799    }
800    k = line_end(k)?;
801    Some((String::from(label), dest, String::new(), &s[k..]))
802}
803
804// a link destination at s[k..] -> (destination, the byte after it)
805fn parse_dest(s: &str, k: usize) -> Option<(String, usize)> {
806    let b = s.as_bytes();
807    if k < b.len() && b[k] == b'<' {
808        let mut j = k + 1;
809        while j < b.len() && b[j] != b'>' {
810            if b[j] == b'\n' || b[j] == b'<' {
811                return None;
812            }
813            if b[j] == b'\\' {
814                j += 1;
815            }
816            j += 1;
817        }
818        if j >= b.len() {
819            return None;
820        }
821        return Some((unescape(&s[k + 1..j]), j + 1));
822    }
823    let mut j = k;
824    let mut depth = 0usize;
825    while j < b.len() {
826        let c = b[j];
827        if c == b'\\' && j + 1 < b.len() && b[j + 1].is_ascii_punctuation() {
828            j += 2;
829            continue;
830        }
831        if c <= b' ' {
832            break;
833        }
834        if c == b'(' {
835            depth += 1;
836            if depth > PARENS_MAX {
837                return None;
838            }
839        } else if c == b')' {
840            if depth == 0 {
841                break;
842            }
843            depth -= 1;
844        }
845        j += 1;
846    }
847    if depth != 0 || j == k {
848        return None;
849    }
850    Some((unescape(&s[k..j]), j))
851}
852
853// a link title at s[k..] ("..", '..', (..)) -> (title, the byte after it)
854fn parse_title(s: &str, k: usize) -> Option<(String, usize)> {
855    let b = s.as_bytes();
856    let close = match b.get(k)? {
857        b'"' => b'"',
858        b'\'' => b'\'',
859        b'(' => b')',
860        _ => return None,
861    };
862    let mut j = k + 1;
863    while j < b.len() && b[j] != close {
864        if b[j] == b'\\' {
865            j += 1;
866        }
867        j += 1;
868    }
869    if j >= b.len() {
870        return None;
871    }
872    Some((unescape(&s[k + 1..j]), j + 1))
873}
874
875impl BlockParser {
876    fn new() -> BlockParser {
877        BlockParser { conts: Vec::new(), leaf: Leaf::None, raws: Vec::new(), defs: BTreeMap::new(),         tables: Vec::new(), blank: false, truncated: false, table_cells: 0 }
878    }
879
880    // a raw block in the place of the open containers
881    fn file(&mut self, mut r: Raw) {
882        if self.raws.len() >= BLOCKS_MAX {
883            self.truncated = true;
884            return;
885        }
886        r.quote = self.conts.iter().filter(|c| c.quote).count() as u8;
887        r.list = self.conts.iter().filter(|c| !c.quote).count() as u8;
888        r.path = self.conts.iter().enumerate().filter(|(_, c)| c.quote).fold(0, |p, (i, _)| p | 1u32 << (i & 31));
889        let tight = r.list > 0 && self.raws.last().is_some_and(|p| p.list > 0) && !self.blank;
890        r.space = if self.raws.is_empty() { 0 } else if tight { 1 } else { 2 };
891        if let Some(c) = self.conts.iter_mut().rev().find(|c| !c.quote) {
892            if !c.filed {
893                c.filed = true;
894                r.marker = if c.ordered { MK_NUMBER } else { MK_BULLET };
895                r.number = c.number;
896                r.delim = c.delim;
897                r.task = c.task;
898            }
899        }
900        self.blank = false;
901        self.raws.push(r);
902    }
903
904    // close the open leaf
905    fn close_leaf(&mut self) {
906        match core::mem::replace(&mut self.leaf, Leaf::None) {
907            Leaf::None => {}
908            Leaf::Para(lines) => {
909                let mut src = lines.join("\n");
910                let t = src.trim_end().len();
911                src.truncate(t);
912                let rest = take_defs(&src, &mut self.defs);
913                if !rest.trim().is_empty() {
914                    self.file(Raw { kind: MB_PARA, src: rest, ..Raw::default() });
915                }
916            }
917            Leaf::Fence { info, lines, .. } => {
918                let lang = String::from(info.split_whitespace().next().unwrap_or(""));
919                self.file(Raw { kind: MB_CODE, src: lines.join("\n"), lang, ..Raw::default() });
920            }
921            Leaf::Code(mut lines) => {
922                while lines.last().is_some_and(|l| blank(l)) {
923                    lines.pop();
924                }
925                if !lines.is_empty() {
926                    self.file(Raw { kind: MB_CODE, src: lines.join("\n"), ..Raw::default() });
927                }
928            }
929            Leaf::Table { aligns, rows } => {
930                let t = self.tables.len() as u32;
931                let n = aligns.len();
932                self.tables.push(MdTable { aligns });
933                for (ri, (mut cells, head)) in rows.into_iter().enumerate() {
934                    cells.resize(n, String::new());
935                    self.file(Raw { kind: MB_ROW, cells, table: t, head, ..Raw::default() });
936                    // the rows of a table follow each other without a gap
937                    if ri > 0 {
938                        if let Some(x) = self.raws.last_mut() {
939                            x.space = 0;
940                        }
941                    }
942                }
943            }
944            Leaf::Html(lines) => {
945                self.file(Raw { kind: MB_PARA, src: lines.join("\n"), html: true, ..Raw::default() });
946            }
947        }
948    }
949
950    // close the containers from index n on (an item without a block files an empty one)
951    fn close_conts(&mut self, n: usize) {
952        if self.conts.len() <= n {
953            return;
954        }
955        self.close_leaf();
956        while self.conts.len() > n {
957            let last = self.conts.len() - 1;
958            if !self.conts[last].quote && !self.conts[last].filed {
959                self.file(Raw { kind: MB_PARA, ..Raw::default() });
960            }
961            self.conts.pop();
962        }
963    }
964
965    fn line(&mut self, raw: &str) {
966        let ln = expand_tabs(raw);
967        let mut p = 0usize;
968        let mut matched = 0;
969        // the open containers this line continues
970        for k in 0..self.conts.len() {
971            let rest = &ln[p..];
972            let c = &self.conts[k];
973            if c.quote {
974                let sp = indent_of(rest);
975                if sp <= 3 && rest.as_bytes().get(sp) == Some(&b'>') {
976                    p += sp + 1;
977                    if ln.as_bytes().get(p) == Some(&b' ') {
978                        p += 1;
979                    }
980                    matched += 1;
981                    continue;
982                }
983                break;
984            }
985            if blank(rest) {
986                if c.empty && !c.filed {
987                    break; // an item may begin with one blank line only
988                }
989                matched += 1;
990                continue;
991            }
992            if indent_of(rest) >= c.indent {
993                p += c.indent;
994                matched += 1;
995                continue;
996            }
997            break;
998        }
999        let rest = String::from(&ln[p..]);
1000        // a fence goes on while all its containers do
1001        if let Leaf::Fence { ch, len, indent, lines, .. } = &mut self.leaf {
1002            if matched == self.conts.len() {
1003                let sp = indent_of(&rest);
1004                let t = &rest[sp..];
1005                let n = t.bytes().take_while(|&b| b == *ch).count();
1006                if sp <= 3 && n >= *len && t[n..].trim().is_empty() {
1007                    self.close_leaf();
1008                    return;
1009                }
1010                let cut = sp.min(*indent);
1011                lines.push(String::from(&rest[cut..]));
1012                return;
1013            }
1014        }
1015        // a lazy continuation line of a paragraph
1016        if matched < self.conts.len() && matches!(self.leaf, Leaf::Para(_)) && !blank(&rest) && !Self::starts_block(&rest, true) {
1017            if let Leaf::Para(lines) = &mut self.leaf {
1018                lines.push(String::from(rest.trim_start()));
1019            }
1020            return;
1021        }
1022        self.close_conts(matched);
1023        let mut rest = rest;
1024        // new containers
1025        loop {
1026            if self.conts.len() >= NEST_MAX {
1027                break;
1028            }
1029            let sp = indent_of(&rest);
1030            if sp >= 4 && !matches!(self.leaf, Leaf::Para(_)) {
1031                break;
1032            }
1033            if sp <= 3 && rest.as_bytes().get(sp) == Some(&b'>') {
1034                self.close_leaf();
1035                let mut q = sp + 1;
1036                if rest.as_bytes().get(q) == Some(&b' ') {
1037                    q += 1;
1038                }
1039                rest = String::from(&rest[q..]);
1040                self.conts.push(Cont { quote: true, indent: 0, ordered: false, number: 0, delim: 0, task: 0, filed: true, empty: false });
1041                continue;
1042            }
1043            if is_rule(&rest) {
1044                break;
1045            }
1046            if let Some((ordered, ch, number, mlen)) = list_marker(&rest) {
1047                let after = &rest[mlen..];
1048                let empty = blank(after);
1049                if matches!(self.leaf, Leaf::Para(_)) && (empty || (ordered && number != 1)) {
1050                    break;
1051                }
1052                self.close_leaf();
1053                let pad = indent_of(after);
1054                let (indent, skip) = if empty {
1055                    (mlen + 1, after.len())
1056                } else if pad > 4 {
1057                    (mlen + 1, 1)
1058                } else {
1059                    (mlen + pad, pad)
1060                };
1061                let mut body = String::from(&after[skip.min(after.len())..]);
1062                // a task item: [ ] or [x]
1063                let mut task = 0;
1064                let bb = body.as_bytes();
1065                if bb.len() >= 3 && bb[0] == b'[' && bb[2] == b']' && matches!(bb[1], b' ' | b'x' | b'X') && (bb.len() == 3 || bb[3] == b' ') {
1066                    task = if bb[1] == b' ' { 1 } else { 2 };
1067                    body = String::from(body[3.min(body.len())..].trim_start());
1068                }
1069                self.conts.push(Cont {
1070                    quote: false,
1071                    indent,
1072                    ordered,
1073                    number,
1074                    delim: if ordered { ch } else { 0 },
1075                    task,
1076                    filed: false,
1077                    empty,
1078                });
1079                rest = body;
1080                if empty {
1081                    break;
1082                }
1083                continue;
1084            }
1085            break;
1086        }
1087        self.leaf_line(rest);
1088    }
1089
1090    // the line's text starts another block (it cannot be a lazy continuation)
1091    // (lazy: the paragraph is not in the line's innermost container, so any list item starts
1092    // a block; else only one that may interrupt a paragraph)
1093    fn starts_block(s: &str, lazy: bool) -> bool {
1094        let sp = indent_of(s);
1095        if sp > 3 {
1096            return false;
1097        }
1098        let t = &s[sp..];
1099        t.starts_with('>')
1100            || is_rule(s)
1101            || atx(s).is_some()
1102            || fence_open(s).is_some()
1103            || list_marker(s).is_some_and(|m| lazy || (!blank(&s[m.3..]) && (!m.0 || m.2 == 1)))
1104            || html_start(s)
1105    }
1106
1107    fn leaf_line(&mut self, rest: String) {
1108        if blank(&rest) {
1109            match &mut self.leaf {
1110                Leaf::Code(lines) => lines.push(String::new()),
1111                Leaf::None => {}
1112                _ => self.close_leaf(),
1113            }
1114            self.blank = true;
1115            return;
1116        }
1117        let sp = indent_of(&rest);
1118        if sp >= 4 {
1119            match &mut self.leaf {
1120                Leaf::Para(lines) => {
1121                    lines.push(String::from(rest.trim_start()));
1122                    return;
1123                }
1124                Leaf::Code(lines) => {
1125                    lines.push(String::from(&rest[4..]));
1126                    return;
1127                }
1128                Leaf::Table { .. } | Leaf::Html(_) => {}
1129                _ => {
1130                    self.close_leaf();
1131                    self.leaf = Leaf::Code(vec![String::from(&rest[4..])]);
1132                    return;
1133                }
1134            }
1135        }
1136        if matches!(self.leaf, Leaf::Code(_)) {
1137            self.close_leaf();
1138        }
1139        if let Leaf::Html(lines) = &mut self.leaf {
1140            lines.push(rest);
1141            return;
1142        }
1143        if let Some((ch, len, info)) = fence_open(&rest) {
1144            self.close_leaf();
1145            self.leaf = Leaf::Fence { ch, len, indent: sp, info, lines: Vec::new() };
1146            return;
1147        }
1148        if let Some((level, text)) = atx(&rest) {
1149            self.close_leaf();
1150            let src = String::from(text);
1151            self.file(Raw { kind: MB_HEADING, level, src, ..Raw::default() });
1152            return;
1153        }
1154        // a setext underline turns the paragraph into a heading
1155        if sp <= 3 {
1156            if let Leaf::Para(lines) = &self.leaf {
1157                let t = rest.trim();
1158                let lv = if !t.is_empty() && t.bytes().all(|b| b == b'=') {
1159                    1
1160                } else if !t.is_empty() && t.bytes().all(|b| b == b'-') {
1161                    2
1162                } else {
1163                    0
1164                };
1165                if lv > 0 {
1166                    let src = lines.join("\n");
1167                    let rest_src = take_defs(&src, &mut self.defs);
1168                    self.leaf = Leaf::None;
1169                    if rest_src.trim().is_empty() {
1170                        // only definitions: the line is text (or a rule)
1171                        if lv == 2 && is_rule(&rest) {
1172                            self.file(Raw { kind: MB_RULE, ..Raw::default() });
1173                        } else {
1174                            self.leaf = Leaf::Para(vec![String::from(rest.trim())]);
1175                        }
1176                        return;
1177                    }
1178                    self.file(Raw { kind: MB_HEADING, level: lv, src: String::from(rest_src.trim_end()), ..Raw::default() });
1179                    return;
1180                }
1181            }
1182        }
1183        if is_rule(&rest) {
1184            self.close_leaf();
1185            self.file(Raw { kind: MB_RULE, ..Raw::default() });
1186            return;
1187        }
1188        // a table: a paragraph's last line followed by its delimiter row
1189        if let Leaf::Para(lines) = &mut self.leaf {
1190            if let (Some(aligns), Some(last)) = (delim_row(&rest), lines.last()) {
1191                let head = split_cells(last);
1192                if head.len() == aligns.len() && last.contains('|') {
1193                    lines.pop();
1194                    if lines.is_empty() {
1195                        self.leaf = Leaf::None;
1196                    } else {
1197                        self.close_leaf();
1198                    }
1199                    self.table_cells = self.table_cells.saturating_add(aligns.len());
1200                    self.leaf = Leaf::Table { aligns, rows: vec![(head, true)] };
1201                    return;
1202                }
1203            }
1204        }
1205        if let Leaf::Table { aligns, rows } = &mut self.leaf {
1206            if !blank(&rest) && !Self::starts_block(&rest, false) {
1207                if rows.len() < BLOCKS_MAX && self.table_cells + aligns.len() <= CELLS_MAX {
1208                    self.table_cells += aligns.len();
1209                    rows.push((split_cells(&rest), false));
1210                } else {
1211                    self.truncated = true;
1212                }
1213                return;
1214            }
1215            self.close_leaf();
1216        }
1217        if html_start(&rest) {
1218            self.close_leaf();
1219            self.leaf = Leaf::Html(vec![rest]);
1220            return;
1221        }
1222        match &mut self.leaf {
1223            Leaf::Para(lines) => lines.push(String::from(rest.trim_start())),
1224            _ => {
1225                self.close_leaf();
1226                self.leaf = Leaf::Para(vec![String::from(rest.trim_start())]);
1227            }
1228        }
1229    }
1230
1231    fn finish(mut self) -> (Vec<Raw>, BTreeMap<String, (String, String)>, Vec<MdTable>, bool) {
1232        self.close_conts(0);
1233        self.close_leaf();
1234        (self.raws, self.defs, self.tables, self.truncated)
1235    }
1236}
1237
1238// ------------------------------------------------------------------ the inline parser
1239
1240const N_TEXT: u8 = 0;
1241const N_CODE: u8 = 1;
1242const N_IMG: u8 = 2;
1243const N_SKIP: u8 = 3;
1244
1245struct Node {
1246    t: String,
1247    k: u8,
1248    img: u32,
1249}
1250
1251struct Delim {
1252    node: usize,
1253    ch: u8,
1254    n: usize,
1255    orig: usize,
1256    open: bool,
1257    close: bool,
1258}
1259
1260struct Bracket {
1261    node: usize,
1262    src: usize,
1263    image: bool,
1264    // the intervals there were when it opened (those before end at or before its node)
1265    ivs: usize,
1266}
1267
1268// a style or link over nodes a .. b
1269struct Iv {
1270    a: usize,
1271    b: usize,
1272    style: u16,
1273    link: u32,
1274    color: u32,
1275    bg: u32,
1276}
1277
1278struct Inline<'a> {
1279    s: &'a str,
1280    b: &'a [u8],
1281    defs: &'a BTreeMap<String, (String, String)>,
1282    nodes: Vec<Node>,
1283    pend: String,
1284    delims: Vec<Delim>,
1285    brackets: Vec<Bracket>,
1286    // brackets below this index are no link openers any more (a link formed after them)
1287    dead: usize,
1288    ivs: Vec<Iv>,
1289    // inline HTML elements open: (tag, node, style, link)
1290    html: Vec<(String, usize, u16, u32)>,
1291    // backtick runs by length: their starts, ascending
1292    ticks: BTreeMap<usize, Vec<usize>>,
1293    // bytes the link and tag parsers may still scan (adversarial input stays linear)
1294    budget: usize,
1295    // a comment was not closed: no later one is
1296    no_cmt: bool,
1297    links: &'a mut Vec<String>,
1298    images: &'a mut Vec<MdImage>,
1299}
1300
1301// the char before byte i / at byte i
1302fn char_before(s: &str, i: usize) -> Option<char> {
1303    s[..i].chars().next_back()
1304}
1305
1306fn char_at(s: &str, i: usize) -> Option<char> {
1307    s.get(i..).and_then(|t| t.chars().next())
1308}
1309
1310impl<'a> Inline<'a> {
1311    fn new(s: &'a str, defs: &'a BTreeMap<String, (String, String)>, links: &'a mut Vec<String>, images: &'a mut Vec<MdImage>) -> Inline<'a> {
1312        let b = s.as_bytes();
1313        let mut ticks: BTreeMap<usize, Vec<usize>> = BTreeMap::new();
1314        let mut i = 0;
1315        while i < b.len() {
1316            if b[i] == b'`' {
1317                let n = b[i..].iter().take_while(|&&c| c == b'`').count();
1318                ticks.entry(n).or_default().push(i);
1319                i += n;
1320            } else {
1321                i += 1;
1322            }
1323        }
1324        Inline {
1325            s,
1326            b,
1327            defs,
1328            nodes: Vec::new(),
1329            pend: String::new(),
1330            delims: Vec::new(),
1331            brackets: Vec::new(),
1332            dead: 0,
1333            ivs: Vec::new(),
1334            html: Vec::new(),
1335            ticks,
1336            budget: s.len() * 8 + 4096,
1337            no_cmt: false,
1338            links,
1339            images,
1340        }
1341    }
1342
1343    fn flush(&mut self) {
1344        if !self.pend.is_empty() {
1345            let t = core::mem::take(&mut self.pend);
1346            self.nodes.push(Node { t, k: N_TEXT, img: 0 });
1347        }
1348    }
1349
1350    fn push(&mut self, t: String, k: u8) -> usize {
1351        self.flush();
1352        self.nodes.push(Node { t, k, img: 0 });
1353        self.nodes.len() - 1
1354    }
1355
1356    fn link_id(&mut self, url: String) -> u32 {
1357        self.links.push(url);
1358        self.links.len() as u32
1359    }
1360
1361    fn run(&mut self) {
1362        let (s, b) = (self.s, self.b);
1363        let mut i = 0;
1364        while i < b.len() {
1365            let c = b[i];
1366            match c {
1367                b'\\' => {
1368                    if i + 1 < b.len() && b[i + 1].is_ascii_punctuation() {
1369                        self.pend.push(b[i + 1] as char);
1370                        i += 2;
1371                    } else if i + 1 < b.len() && b[i + 1] == b'\n' {
1372                        self.push(String::from("\n"), N_TEXT);
1373                        i += 2;
1374                        i += b[i..].iter().take_while(|&&c| c == b' ').count();
1375                    } else {
1376                        self.pend.push('\\');
1377                        i += 1;
1378                    }
1379                }
1380                b'`' => {
1381                    let n = b[i..].iter().take_while(|&&c| c == b'`').count();
1382                    let close = self.ticks.get(&n).and_then(|v| {
1383                        let k = v.partition_point(|&p| p <= i);
1384                        v.get(k).copied()
1385                    });
1386                    match close {
1387                        Some(j) => {
1388                            let mut t: String = s[i + n..j].chars().map(|c| if c == '\n' { ' ' } else { c }).collect();
1389                            if t.len() >= 2 && t.starts_with(' ') && t.ends_with(' ') && !t.bytes().all(|c| c == b' ') {
1390                                t = String::from(&t[1..t.len() - 1]);
1391                            }
1392                            self.push(t, N_CODE);
1393                            i = j + n;
1394                        }
1395                        None => {
1396                            for _ in 0..n {
1397                                self.pend.push('`');
1398                            }
1399                            i += n;
1400                        }
1401                    }
1402                }
1403                b'*' | b'_' | b'~' => {
1404                    let n = b[i..].iter().take_while(|&&x| x == c).count();
1405                    let before = char_before(s, i);
1406                    let after = char_at(s, i + n);
1407                    let ws = |c: Option<char>| c.is_none_or(|c| c.is_whitespace());
1408                    let pu = |c: Option<char>| c.is_some_and(is_punct);
1409                    let left = !ws(after) && (!pu(after) || ws(before) || pu(before));
1410                    let right = !ws(before) && (!pu(before) || ws(after) || pu(after));
1411                    let (open, close) = if c == b'_' {
1412                        (left && (!right || pu(before)), right && (!left || pu(after)))
1413                    } else {
1414                        (left, right)
1415                    };
1416                    let node = self.push(String::from(&s[i..i + n]), N_TEXT);
1417                    if (open || close) && (c != b'~' || n <= 2) {
1418                        self.delims.push(Delim { node, ch: c, n, orig: n, open, close });
1419                    }
1420                    i += n;
1421                }
1422                b'[' => {
1423                    let node = self.push(String::from("["), N_TEXT);
1424                    self.brackets.push(Bracket { node, src: i + 1, image: false, ivs: self.ivs.len() });
1425                    i += 1;
1426                }
1427                b'!' if b.get(i + 1) == Some(&b'[') => {
1428                    let node = self.push(String::from("!["), N_TEXT);
1429                    self.brackets.push(Bracket { node, src: i + 2, image: true, ivs: self.ivs.len() });
1430                    i += 2;
1431                }
1432                b']' => i = self.close_bracket(i),
1433                b'<' => i = self.angle(i),
1434                b'&' => match entity_at(&s[i..]) {
1435                    Some((ch, n)) => {
1436                        self.pend.push(ch);
1437                        i += n;
1438                    }
1439                    None => {
1440                        self.pend.push('&');
1441                        i += 1;
1442                    }
1443                },
1444                b'\n' => {
1445                    let hard = self.pend.ends_with("  ");
1446                    let t = self.pend.trim_end_matches(' ').len();
1447                    self.pend.truncate(t);
1448                    self.push(String::from(if hard { "\n" } else { " " }), N_TEXT);
1449                    i += 1;
1450                    i += b[i..].iter().take_while(|&&c| c == b' ').count();
1451                }
1452                b'h' | b'H' | b'w' | b'W' => {
1453                    if let Some(n) = self.bare_link(i) {
1454                        i += n;
1455                    } else {
1456                        self.pend.push(c as char);
1457                        i += 1;
1458                    }
1459                }
1460                _ => {
1461                    // plain text up to the next special byte
1462                    let j = b[i..]
1463                        .iter()
1464                        .position(|&x| matches!(x, b'\\' | b'`' | b'*' | b'_' | b'~' | b'[' | b'!' | b']' | b'<' | b'&' | b'\n' | b'h' | b'H' | b'w' | b'W'))
1465                        .map_or(b.len(), |k| i + k.max(1));
1466                    let j = (j..=b.len()).find(|&k| s.is_char_boundary(k)).unwrap_or(b.len());
1467                    self.pend.push_str(&s[i..j]);
1468                    i = j;
1469                }
1470            }
1471        }
1472        self.flush();
1473    }
1474
1475    // http:// https:// www. at i (after a blank or the start) -> bytes taken
1476    fn bare_link(&mut self, i: usize) -> Option<usize> {
1477        let (s, b) = (self.s, self.b);
1478        if let Some(p) = char_before(s, i) {
1479            if !(p.is_whitespace() || matches!(p, '(' | '*' | '_' | '~' | '"' | '\'')) {
1480                return None;
1481            }
1482        }
1483        let rest = &b[i..];
1484        let pre = |p: &[u8]| rest.len() > p.len() && rest[..p.len()].eq_ignore_ascii_case(p);
1485        let www = pre(b"www.");
1486        if !(www || pre(b"http://") || pre(b"https://")) {
1487            return None;
1488        }
1489        let mut e = i;
1490        while e < b.len() && !b[e].is_ascii_whitespace() && b[e] != b'<' {
1491            e += 1;
1492        }
1493        while !s.is_char_boundary(e) {
1494            e -= 1;
1495        }
1496        // trailing punctuation is no part of it, nor an unbalanced ')' (the counts are kept, the
1497        // trimmed punctuation holds no parentheses)
1498        let opens = s[i..e].matches('(').count();
1499        let mut closes = s[i..e].matches(')').count();
1500        loop {
1501            let t = &s[i..e];
1502            let Some(last) = t.chars().next_back() else { break };
1503            if matches!(last, '?' | '!' | '.' | ',' | ':' | '*' | '_' | '~' | '\'' | '"' | ';') {
1504                e -= last.len_utf8();
1505                continue;
1506            }
1507            if last == ')' && closes > opens {
1508                closes -= 1;
1509                e -= 1;
1510                continue;
1511            }
1512            break;
1513        }
1514        let t = &s[i..e];
1515        let host = if www { t.get(4..).unwrap_or("") } else { t.split("://").nth(1).unwrap_or("") };
1516        if host.is_empty() || (www && !host.contains('.')) {
1517            return None;
1518        }
1519        let url = if www { format!("http://{}", t) } else { String::from(t) };
1520        let link = self.link_id(url);
1521        let node = self.push(String::from(t), N_TEXT);
1522        self.ivs.push(Iv { a: node, b: node + 1, style: 0, link, color: 0, bg: 0 });
1523        Some(e - i)
1524    }
1525
1526    // ']' at i: a link or image, or the text "]" -> the next byte
1527    fn close_bracket(&mut self, i: usize) -> usize {
1528        let Some(br) = self.brackets.pop() else {
1529            self.pend.push(']');
1530            return i + 1;
1531        };
1532        // a link formed after this bracket opened: it opens no link
1533        let idx = self.brackets.len();
1534        let dead = idx < self.dead;
1535        self.dead = self.dead.min(idx);
1536        if dead && !br.image {
1537            self.pend.push(']');
1538            return i + 1;
1539        }
1540        let (s, b) = (self.s, self.b);
1541        let mut found: Option<(String, String, usize)> = None;
1542        // inline: (dest "title")
1543        if b.get(i + 1) == Some(&b'(') && self.budget > 0 {
1544            let start = i + 2;
1545            let r = (|| {
1546                let mut k = start;
1547                let ws = |k: &mut usize| {
1548                    while *k < b.len() && (b[*k] == b' ' || b[*k] == b'\n') {
1549                        *k += 1;
1550                    }
1551                };
1552                ws(&mut k);
1553                let (dest, mut k) = if b.get(k) == Some(&b')') { (String::new(), k) } else { parse_dest(s, k)? };
1554                let before = k;
1555                ws(&mut k);
1556                let mut title = String::new();
1557                if k > before && k < b.len() && matches!(b[k], b'"' | b'\'' | b'(') {
1558                    let (t, e) = parse_title(s, k)?;
1559                    title = t;
1560                    k = e;
1561                    ws(&mut k);
1562                }
1563                (b.get(k) == Some(&b')')).then_some((dest, title, k + 1))
1564            })();
1565            // what the attempt scanned counts against the budget
1566            let used = r.as_ref().map_or(b.len() - start, |x| x.2 - start);
1567            self.budget = self.budget.saturating_sub(used.min(b.len()));
1568            found = r;
1569        }
1570        // references: [label], [], or the text itself
1571        if found.is_none() && i - br.src <= LABEL_MAX && !self.defs.is_empty() && self.budget > 0 {
1572            // (nested brackets would read the same label bytes again and again)
1573            self.budget = self.budget.saturating_sub(i - br.src + 1);
1574            let text_label = &s[br.src..i];
1575            let mut label = String::from(text_label);
1576            let mut end = i + 1;
1577            if b.get(i + 1) == Some(&b'[') {
1578                let lim = (i + 2 + LABEL_MAX + 1).min(b.len());
1579                if let Some(k) = b[i + 2..lim].iter().position(|&c| c == b']' || c == b'[') {
1580                    let k = i + 2 + k;
1581                    if b[k] == b']' {
1582                        if k > i + 2 {
1583                            label = String::from(&s[i + 2..k]);
1584                        }
1585                        end = k + 1;
1586                    }
1587                }
1588            }
1589            if let Some((d, t)) = self.defs.get(&norm_label(&label)) {
1590                found = Some((d.clone(), t.clone(), end));
1591            }
1592        }
1593        let Some((dest, title, end)) = found else {
1594            self.pend.push(']');
1595            return i + 1;
1596        };
1597        self.flush();
1598        let a = br.node + 1;
1599        if br.image {
1600            // the alt text: the plain text inside (without delimiter runs); the nodes give way
1601            // to one image (they go: nested images stay linear)
1602            let first = self.delims.partition_point(|d| d.node < a);
1603            let runs: Vec<usize> = self.delims[first..].iter().map(|d| d.node).collect();
1604            let mut alt = String::new();
1605            for (k, n) in self.nodes[a..].iter().enumerate() {
1606                if (n.k == N_TEXT || n.k == N_CODE) && runs.binary_search(&(a + k)).is_err() {
1607                    alt.push_str(&n.t);
1608                }
1609            }
1610            self.nodes.truncate(a);
1611            let tail = self.ivs.split_off(br.ivs.min(self.ivs.len()));
1612            self.ivs.extend(tail.into_iter().filter(|iv| iv.a < a).map(|mut iv| {
1613                iv.b = iv.b.min(a);
1614                iv
1615            }));
1616            self.html.retain(|h| h.1 < a);
1617            self.delims.truncate(first);
1618            self.images.push(MdImage { src: dest, alt, title, w: 0, h: 0 });
1619            let img = self.images.len() as u32;
1620            let nd = &mut self.nodes[br.node];
1621            nd.k = N_IMG;
1622            nd.img = img;
1623            nd.t = String::from("\u{FFFC}");
1624        } else {
1625            let link = self.link_id(dest);
1626            self.nodes[br.node].k = N_SKIP;
1627            let bn = self.nodes.len();
1628            self.ivs.push(Iv { a, b: bn, style: 0, link, color: 0, bg: 0 });
1629            // no links inside links: the brackets before it open none
1630            self.dead = self.brackets.len();
1631        }
1632        end
1633    }
1634
1635    // '<' at i: an autolink, inline HTML or the text "<" -> the next byte
1636    fn angle(&mut self, i: usize) -> usize {
1637        let (s, b) = (self.s, self.b);
1638        let lim = (i + 2048).min(b.len());
1639        let end = b[i + 1..lim].iter().position(|&c| c == b'>' || c == b'<' || c == b'\n').map(|k| i + 1 + k);
1640        if let Some(e) = end.filter(|&e| b[e] == b'>') {
1641            let inner = &s[i + 1..e];
1642            // autolinks: scheme:... or an e-mail address
1643            let sch = inner.bytes().take_while(|c| c.is_ascii_alphanumeric() || matches!(c, b'+' | b'.' | b'-')).count();
1644            if (2..=32).contains(&sch) && inner.as_bytes()[0].is_ascii_alphabetic() && inner.as_bytes().get(sch) == Some(&b':') && !inner.contains(' ') {
1645                let link = self.link_id(String::from(inner));
1646                let node = self.push(String::from(inner), N_TEXT);
1647                self.ivs.push(Iv { a: node, b: node + 1, style: 0, link, color: 0, bg: 0 });
1648                return e + 1;
1649            }
1650            if let Some((user, host)) = inner.split_once('@') {
1651                if !user.is_empty() && host.contains('.') && !inner.contains(' ') && !host.contains('@') {
1652                    let link = self.link_id(format!("mailto:{}", inner));
1653                    let node = self.push(String::from(inner), N_TEXT);
1654                    self.ivs.push(Iv { a: node, b: node + 1, style: 0, link, color: 0, bg: 0 });
1655                    return e + 1;
1656                }
1657            }
1658        }
1659        // comments (once one is not closed, no later one is)
1660        if s[i..].starts_with("<!--") && !self.no_cmt {
1661            match s[i + 4..].find("-->") {
1662                Some(k) => return i + 4 + k + 3,
1663                None => self.no_cmt = true,
1664            }
1665        }
1666        // a tag (quotes respected; the scans count against the budget)
1667        if self.budget == 0 {
1668            self.pend.push('<');
1669            return i + 1;
1670        }
1671        let mut q = 0u8;
1672        let mut gt = None;
1673        let mut scanned = 0;
1674        for (k, &c) in b[i + 1..lim].iter().enumerate() {
1675            scanned = k + 1;
1676            if q != 0 {
1677                if c == q {
1678                    q = 0;
1679                }
1680            } else if c == b'"' || c == b'\'' {
1681                q = c;
1682            } else if c == b'>' {
1683                gt = Some(i + 1 + k);
1684                break;
1685            } else if c == b'<' {
1686                break;
1687            }
1688        }
1689        self.budget = self.budget.saturating_sub(scanned);
1690        let Some(gt) = gt else {
1691            self.pend.push('<');
1692            return i + 1;
1693        };
1694        let inner = &s[i + 1..gt];
1695        let closing = inner.starts_with('/');
1696        let body = inner.trim_start_matches('/');
1697        let nl = body.bytes().take_while(|c| c.is_ascii_alphanumeric()).count();
1698        if nl == 0 || !body.as_bytes()[0].is_ascii_alphabetic() {
1699            self.pend.push('<');
1700            return i + 1;
1701        }
1702        let name = body[..nl].to_ascii_lowercase();
1703        let style = match name.as_str() {
1704            "b" | "strong" => MS_BOLD,
1705            "i" | "em" | "cite" => MS_ITALIC,
1706            "s" | "del" | "strike" => MS_STRIKE,
1707            "u" | "ins" => MS_UNDER,
1708            "code" | "kbd" | "tt" | "samp" => MS_CODE,
1709            _ => 0,
1710        };
1711        match name.as_str() {
1712            "br" => {
1713                self.push(String::from("\n"), N_TEXT);
1714            }
1715            "img" if !closing => {
1716                let at = attrs(&body[nl..]);
1717                let get = |k: &str| at.iter().find(|a| a.0 == k).map(|a| a.1.clone()).unwrap_or_default();
1718                let num = |k: &str| get(k).trim().trim_end_matches("px").parse::<u32>().unwrap_or(0).min(100_000);
1719                self.images.push(MdImage { src: get("src"), alt: get("alt"), title: get("title"), w: num("width"), h: num("height") });
1720                let img = self.images.len() as u32;
1721                let node = self.push(String::from("\u{FFFC}"), N_IMG);
1722                self.nodes[node].img = img;
1723            }
1724            _ if style != 0 || name == "a" => {
1725                self.flush();
1726                if closing {
1727                    if let Some(k) = self.html.iter().rposition(|h| h.0 == name) {
1728                        let (_, a, st, link) = self.html.remove(k);
1729                        let bn = self.nodes.len();
1730                        if a < bn {
1731                            self.ivs.push(Iv { a, b: bn, style: st, link, color: 0, bg: 0 });
1732                        }
1733                    }
1734                } else if self.html.len() < HTML_STACK {
1735                    let link = if name == "a" {
1736                        let at = attrs(&body[nl..]);
1737                        match at.iter().find(|a| a.0 == "href") {
1738                            Some(h) => self.link_id(h.1.clone()),
1739                            None => 0,
1740                        }
1741                    } else {
1742                        0
1743                    };
1744                    let a = self.nodes.len();
1745                    self.html.push((name, a, style, link));
1746                }
1747            }
1748            _ => {}
1749        }
1750        gt + 1
1751    }
1752
1753    // the delimiter runs become emphasis (CommonMark's "process emphasis")
1754    fn emphasis(&mut self) {
1755        let n = self.delims.len();
1756        if n == 0 {
1757            return;
1758        }
1759        const NONE: usize = usize::MAX;
1760        let mut prev: Vec<usize> = (0..n).map(|i| if i == 0 { NONE } else { i - 1 }).collect();
1761        let mut next: Vec<usize> = (0..n).map(|i| if i + 1 < n { i + 1 } else { NONE }).collect();
1762        let mut gone = vec![false; n];
1763        // the lowest opener worth looking at, by char, length % 3, closer can open
1764        let mut bottom = [[[0usize; 2]; 3]; 3];
1765        let ci = |c: u8| match c {
1766            b'*' => 0,
1767            b'_' => 1,
1768            _ => 2,
1769        };
1770        let unlink = |k: usize, prev: &mut Vec<usize>, next: &mut Vec<usize>, gone: &mut Vec<bool>| {
1771            gone[k] = true;
1772            let (p, x) = (prev[k], next[k]);
1773            if p != NONE {
1774                next[p] = x;
1775            }
1776            if x != NONE {
1777                prev[x] = p;
1778            }
1779        };
1780        let mut c = 0;
1781        while c != NONE {
1782            if gone[c] || !self.delims[c].close {
1783                c = next[c];
1784                continue;
1785            }
1786            let (ch, cn, corig, copen) = {
1787                let d = &self.delims[c];
1788                (d.ch, d.n, d.orig, d.open)
1789            };
1790            let key = (ci(ch), corig % 3, copen as usize);
1791            let lim = bottom[key.0][key.1][key.2];
1792            let mut o = prev[c];
1793            let mut found = NONE;
1794            while o != NONE && o >= lim {
1795                let d = &self.delims[o];
1796                if !gone[o] && d.open && d.ch == ch {
1797                    let three = ch != b'~' && (d.close || copen) && (d.orig + corig) % 3 == 0 && !(d.orig % 3 == 0 && corig % 3 == 0);
1798                    let tilde = ch == b'~' && d.n != cn;
1799                    if !three && !tilde {
1800                        found = o;
1801                        break;
1802                    }
1803                }
1804                o = prev[o];
1805            }
1806            if found == NONE {
1807                bottom[key.0][key.1][key.2] = c;
1808                let nx = next[c];
1809                if !copen {
1810                    unlink(c, &mut prev, &mut next, &mut gone);
1811                }
1812                c = nx;
1813                continue;
1814            }
1815            let o = found;
1816            let used = if ch == b'~' { cn } else if cn >= 2 && self.delims[o].n >= 2 { 2 } else { 1 };
1817            let style = if ch == b'~' { MS_STRIKE } else if used == 2 { MS_BOLD } else { MS_ITALIC };
1818            let (on, cnode) = (self.delims[o].node, self.delims[c].node);
1819            self.ivs.push(Iv { a: on + 1, b: cnode, style, link: 0, color: 0, bg: 0 });
1820            // the runs between them are text now
1821            let mut k = next[o];
1822            while k != NONE && k != c {
1823                let nx = next[k];
1824                unlink(k, &mut prev, &mut next, &mut gone);
1825                k = nx;
1826            }
1827            self.delims[o].n -= used;
1828            let t = &mut self.nodes[on].t;
1829            t.truncate(t.len() - used);
1830            self.delims[c].n -= used;
1831            self.nodes[cnode].t.drain(..used);
1832            if self.delims[o].n == 0 {
1833                unlink(o, &mut prev, &mut next, &mut gone);
1834            }
1835            if self.delims[c].n == 0 {
1836                let nx = next[c];
1837                unlink(c, &mut prev, &mut next, &mut gone);
1838                c = nx;
1839            }
1840        }
1841    }
1842
1843    // the text and its spans
1844    fn output(mut self) -> (String, Vec<MdSpan>) {
1845        self.emphasis();
1846        let n = self.nodes.len();
1847        // events by node: (node, start?, interval)
1848        let mut ev: Vec<(usize, bool, usize)> = Vec::with_capacity(self.ivs.len() * 2);
1849        for (k, iv) in self.ivs.iter().enumerate() {
1850            if iv.a < iv.b && iv.a < n {
1851                ev.push((iv.a, true, k));
1852                ev.push((iv.b.min(n), false, k));
1853            }
1854        }
1855        ev.sort_by_key(|e| (e.0, e.1));
1856        let mut counts = [0u32; 16];
1857        let mut links: Vec<usize> = Vec::new();
1858        let mut text = String::new();
1859        let mut spans: Vec<MdSpan> = Vec::new();
1860        let mut ei = 0;
1861        for (k, node) in self.nodes.iter().enumerate() {
1862            while ei < ev.len() && ev[ei].0 == k {
1863                let (_, start, iv) = ev[ei];
1864                let v = &self.ivs[iv];
1865                for (bit, c) in counts.iter_mut().enumerate() {
1866                    if v.style & (1 << bit) != 0 {
1867                        if start {
1868                            *c += 1;
1869                        } else {
1870                            *c = c.saturating_sub(1);
1871                        }
1872                    }
1873                }
1874                if v.link != 0 || v.color != 0 || v.bg != 0 {
1875                    if start {
1876                        links.push(iv);
1877                    } else if let Some(p) = links.iter().rposition(|&x| x == iv) {
1878                        links.remove(p);
1879                    }
1880                }
1881                ei += 1;
1882            }
1883            if node.k == N_SKIP || (node.t.is_empty() && node.k != N_IMG) {
1884                continue;
1885            }
1886            let mut style = 0u16;
1887            for (bit, &c) in counts.iter().enumerate() {
1888                if c > 0 {
1889                    style |= 1 << bit;
1890                }
1891            }
1892            if node.k == N_CODE {
1893                style |= MS_CODE;
1894            }
1895            let link = links.iter().rev().map(|&i| self.ivs[i].link).find(|&l| l != 0).unwrap_or(0);
1896            let a = text.len() as u32;
1897            text.push_str(&node.t);
1898            let b = text.len() as u32;
1899            let img = if node.k == N_IMG { node.img } else { 0 };
1900            if style == 0 && link == 0 && img == 0 {
1901                continue;
1902            }
1903            match spans.last_mut() {
1904                Some(l) if l.b == a && l.style == style && l.link == link && img == 0 && l.image == 0 => l.b = b,
1905                _ => spans.push(MdSpan { a, b, style, link, image: img, color: 0, bg: 0, field: 0 }),
1906            }
1907        }
1908        (text, spans)
1909    }
1910}
1911
1912// inline Markdown -> (text, spans)
1913fn inline(src: &str, defs: &BTreeMap<String, (String, String)>, links: &mut Vec<String>, images: &mut Vec<MdImage>) -> (String, Vec<MdSpan>) {
1914    let mut p = Inline::new(src, defs, links, images);
1915    p.run();
1916    p.output()
1917}
1918
1919// ------------------------------------------------------------------ HTML
1920
1921// paragraphs of the HTML reader -> blocks (inside `quote` / `list` containers of Markdown) ->
1922// the block of every paragraph that is a paragraph or heading of its own (NIL: none)
1923fn html_blocks(h: HRead, doc: &mut MdDoc, quote: u8, list: u8, path: u32, first_space: u8) -> Vec<u32> {
1924    let depth = (quote + list) as u32;
1925    // the HTML's own quotes inside the Markdown's containers, its lists inside them
1926    let path_of = |dq: u8| path | (((1u64 << dq.min(31)) - 1) << depth.min(31)) as u32;
1927    let HRead { paras, ext, forms, fields, nodes, boxes, para_boxes, rule_boxes, .. } = h;
1928    let mut para_blk = vec![NIL; paras.len()];
1929    // the boxes of CSS (their indices count from the ones before)
1930    let box0 = doc.boxes.len() as u32;
1931    doc.boxes.extend(boxes);
1932    let chain = |v: Option<&Vec<u32>>| -> Vec<u32> { v.map_or(Vec::new(), |v| v.iter().map(|&b| b + box0).collect()) };
1933    let mut rules = rule_boxes.iter().peekable();
1934    // the forms and fields (MdField::form and HExt::Field::n count from the ones before)
1935    let (form0, field0) = (doc.forms.len() as u32, doc.fields.len() as u32);
1936    let room = FIELDS_MAX.saturating_sub(doc.fields.len());
1937    doc.forms.extend(forms);
1938    doc.fields.extend(fields.into_iter().take(room).map(|mut f| {
1939        if f.form > 0 {
1940            f.form += form0;
1941        }
1942        f
1943    }));
1944    let mut by: BTreeMap<usize, Vec<&HExt>> = BTreeMap::new();
1945    for (at, e) in &ext {
1946        by.entry(*at).or_default().push(e);
1947    }
1948    let mut table_map: BTreeMap<u32, u32> = BTreeMap::new();
1949    let mut pre_open: Option<(u32, usize)> = None; // (pre block, its MdBlock index)
1950    let n = paras.len();
1951    let mut first = true;
1952    let space = |first: &mut bool, s: u8| -> u8 {
1953        if core::mem::take(first) { first_space } else { s }
1954    };
1955    for k in 0..=n {
1956        let items: Vec<&HExt> = by.get(&k).cloned().unwrap_or_default();
1957        let mut rb = Vec::new();
1958        while rules.peek().is_some_and(|r| r.0 <= k) {
1959            if let Some(r) = rules.next() {
1960                rb = chain(Some(&r.1));
1961            }
1962        }
1963        if items.iter().any(|e| matches!(e, HExt::Rule)) {
1964            let sp = space(&mut first, 2);
1965            doc.push_block(MdBlock { kind: MB_RULE, quote, list, path, space: sp, boxes: rb, ..MdBlock::default() });
1966            pre_open = None;
1967        }
1968        if k == n {
1969            break;
1970        }
1971        let p = &paras[k];
1972        let mut b = MdBlock { quote, list, path, node: nodes.get(k).copied().unwrap_or_default(), boxes: chain(para_boxes.get(k)), ..MdBlock::default() };
1973        // the images and fields of the paragraph by byte (their U+FFFC gets a span of its own):
1974        // byte, index + 1, a field
1975        let mut imgs: Vec<(usize, u32, bool)> = Vec::new();
1976        for e in &items {
1977            match e {
1978                HExt::Image { byte, src, alt, w, h } => {
1979                    doc.images.push(MdImage { src: src.clone(), alt: alt.clone(), title: String::new(), w: *w, h: *h });
1980                    imgs.push((*byte, doc.images.len() as u32, false));
1981                }
1982                HExt::Field { byte: Some(byte), n } if *n < room => imgs.push((*byte, field0 + *n as u32 + 1, true)),
1983                _ => {}
1984            }
1985        }
1986        imgs.sort_unstable();
1987        // text and spans from the runs
1988        para_text(p, &imgs, doc, &mut b);
1989        if p.style == PS_QUOTE {
1990            b.quote = quote + 1;
1991        }
1992        b.align = match p.align {
1993            RA_CENTER => 1,
1994            RA_RIGHT => 2,
1995            _ => 0,
1996        };
1997        if p.list != LS_NONE {
1998            b.list = list + p.level + 1;
1999        }
2000        let mut sp = 2;
2001        let mut pre = None;
2002        for e in &items {
2003            match e {
2004                HExt::Heading(l) => {
2005                    b.kind = MB_HEADING;
2006                    b.level = *l;
2007                }
2008                HExt::Quote(d) => b.quote = quote + d,
2009                HExt::Item(num) => {
2010                    b.marker = if *num > 0 { MK_NUMBER } else { MK_BULLET };
2011                    b.number = *num;
2012                    b.delim = b'.';
2013                    sp = if b.list > 0 && doc.blocks.last().is_some_and(|l| l.list > 0) { 1 } else { 2 };
2014                }
2015                HExt::Task(done) => b.task = if *done { 2 } else { 1 },
2016                HExt::Pre { block, lang } => pre = Some((*block, lang.clone())),
2017                HExt::Row { table, head, cells } => {
2018                    let next_t = doc.tables.len() as u32;
2019                    let t = *table_map.entry(*table).or_insert(next_t);
2020                    if t == next_t {
2021                        doc.tables.push(MdTable { aligns: Vec::new() });
2022                    }
2023                    b.kind = MB_ROW;
2024                    b.table = t;
2025                    b.head = *head;
2026                    let len = b.text.len();
2027                    for (ci, &(start, al)) in cells.iter().enumerate().take(COLS_MAX) {
2028                        let end = cells.get(ci + 1).map_or(len, |c| c.0.saturating_sub(1));
2029                        b.cells.push((start.min(len) as u32, end.max(start).min(len) as u32));
2030                        let tb = &mut doc.tables[t as usize];
2031                        if tb.aligns.len() <= ci {
2032                            tb.aligns.resize(ci + 1, MA_NONE);
2033                        }
2034                        if al != 0 && tb.aligns[ci] == MA_NONE {
2035                            tb.aligns[ci] = al;
2036                        }
2037                    }
2038                    sp = if doc.blocks.last().is_some_and(|l| l.kind == MB_ROW && l.table == t) { 0 } else { 2 };
2039                }
2040                _ => {}
2041            }
2042        }
2043        if b.list > 0 && b.marker == MK_NONE && p.list != LS_NONE {
2044            sp = 1;
2045        }
2046        b.path = path_of(b.quote.saturating_sub(quote));
2047        // the lines of one <pre> make one code block
2048        if let Some((blk, lang)) = pre {
2049            match pre_open {
2050                Some((pb, idx)) if pb == blk && idx < doc.blocks.len() => {
2051                    let cb = &mut doc.blocks[idx];
2052                    cb.text.push('\n');
2053                    cb.text.push_str(&expand_tabs(&b.text));
2054                }
2055                _ => {
2056                    b.text = expand_tabs(&b.text);
2057                    b.kind = MB_CODE;
2058                    b.lang = lang;
2059                    b.spans.clear();
2060                    b.space = space(&mut first, 2);
2061                    doc.push_block(b);
2062                    pre_open = Some((blk, doc.blocks.len() - 1));
2063                }
2064            }
2065            continue;
2066        }
2067        pre_open = None;
2068        if b.kind == MB_ROW {
2069            trim_cells(&mut b);
2070        }
2071        b.space = space(&mut first, sp);
2072        let (kind, before) = (b.kind, doc.blocks.len());
2073        doc.push_block(b);
2074        if doc.blocks.len() > before && (kind == MB_PARA || kind == MB_HEADING) {
2075            para_blk[k] = before as u32;
2076        }
2077    }
2078    // a line end before </pre> shows no line
2079    for b in doc.blocks.iter_mut().filter(|b| b.kind == MB_CODE) {
2080        while b.text.ends_with('\n') {
2081            b.text.pop();
2082        }
2083    }
2084    para_blk
2085}
2086
2087// the text and spans of a paragraph of the HTML reader (one link index for the runs of one
2088// link; imgs: byte, index + 1, a field -- their U+FFFC gets a span of its own)
2089fn para_text(p: &Para, imgs: &[(usize, u32, bool)], doc: &mut MdDoc, b: &mut MdBlock) {
2090    let mut ii = 0;
2091    let mut last_link: Option<(usize, u32)> = None;
2092    for (t, f) in p.runs() {
2093        let a0 = b.text.len();
2094        b.text.push_str(t);
2095        let e0 = b.text.len();
2096        let mut style = 0;
2097        if f.flags & RF_BOLD != 0 {
2098            style |= MS_BOLD;
2099        }
2100        if f.flags & RF_ITALIC != 0 {
2101            style |= MS_ITALIC;
2102        }
2103        if f.flags & RF_UNDER != 0 {
2104            style |= MS_UNDER;
2105        }
2106        if f.flags & RF_STRIKE != 0 {
2107            style |= MS_STRIKE;
2108        }
2109        if f.font.eq_ignore_ascii_case("monospace") {
2110            style |= MS_CODE;
2111        }
2112        let link = if f.link.is_empty() {
2113            0
2114        } else {
2115            match last_link {
2116                Some((end, l)) if end == a0 && doc.link(l) == f.link => l,
2117                _ => {
2118                    doc.links.push(f.link.clone());
2119                    doc.links.len() as u32
2120                }
2121            }
2122        };
2123        last_link = (link != 0).then_some((e0, link));
2124        let styled = style != 0 || link != 0 || f.color != 0 || f.bg != 0;
2125        let span = |a: usize, e: usize, image: u32| MdSpan { a: a as u32, b: e as u32, style, link, image, color: f.color, bg: f.bg, field: 0 };
2126        let mut s0 = a0;
2127        while ii < imgs.len() && imgs[ii].0 < e0 {
2128            let (ib, obj, field) = imgs[ii];
2129            ii += 1;
2130            if ib >= s0 && b.text.get(ib..ib + 3) == Some("\u{FFFC}") {
2131                if ib > s0 && styled {
2132                    b.spans.push(span(s0, ib, 0));
2133                }
2134                if field {
2135                    // (a field has no style or link of its own)
2136                    b.spans.push(MdSpan { a: ib as u32, b: ib as u32 + 3, field: obj, ..MdSpan::default() });
2137                } else {
2138                    b.spans.push(span(ib, ib + 3, obj));
2139                }
2140                s0 = ib + 3;
2141            }
2142        }
2143        if s0 < e0 && styled {
2144            b.spans.push(span(s0, e0, 0));
2145        }
2146    }
2147}
2148
2149// two blocks look alike: the same but for the numbers of their links, images, fields and table
2150fn alike(x: &MdBlock, y: &MdBlock) -> bool {
2151    x.kind == y.kind
2152        && x.level == y.level
2153        && x.text == y.text
2154        && (x.quote, x.list, x.path, x.marker, x.number, x.delim, x.task, x.space, x.head, x.align) == (y.quote, y.list, y.path, y.marker, y.number, y.delim, y.task, y.space, y.head, y.align)
2155        && x.lang == y.lang
2156        && x.cells == y.cells
2157        && x.node == y.node
2158        && x.boxes.len() == y.boxes.len()
2159        && x.spans.len() == y.spans.len()
2160        && x.spans.iter().zip(&y.spans).all(|(s, t)| {
2161            (s.a, s.b, s.style, s.color, s.bg) == (t.a, t.b, t.style, t.color, t.bg) && (s.link != 0) == (t.link != 0) && (s.image != 0) == (t.image != 0) && (s.field != 0) == (t.field != 0)
2162        })
2163}
2164
2165// the cells of a row without the blanks around them
2166fn trim_cells(b: &mut MdBlock) {
2167    for c in b.cells.iter_mut() {
2168        let t = &b.text[c.0 as usize..c.1 as usize];
2169        let lead = t.len() - t.trim_start().len();
2170        let trail = t.len() - t.trim_end().len();
2171        c.0 += lead as u32;
2172        c.1 = c.1.saturating_sub(trail as u32).max(c.0);
2173    }
2174}
2175
2176// ------------------------------------------------------------------ the document
2177
2178impl MdDoc {
2179    /// a document from Markdown (see the module header)
2180    pub fn markdown(s: &str) -> MdDoc {
2181        let mut doc = MdDoc::default();
2182        let mut s = s;
2183        if s.len() > INPUT_MAX {
2184            let mut k = INPUT_MAX;
2185            while !s.is_char_boundary(k) {
2186                k -= 1;
2187            }
2188            s = &s[..k];
2189            doc.truncated = true;
2190        }
2191        let s = s.strip_prefix('\u{FEFF}').unwrap_or(s);
2192        let mut bp = BlockParser::new();
2193        let mut rest = s;
2194        while !rest.is_empty() {
2195            let (line, next) = match rest.find(['\n', '\r']) {
2196                Some(i) => {
2197                    let skip = if rest[i..].starts_with("\r\n") { 2 } else { 1 };
2198                    (&rest[..i], &rest[i + skip..])
2199                }
2200                None => (rest, ""),
2201            };
2202            if line.contains('\0') {
2203                bp.line(&line.replace('\0', "\u{FFFD}"));
2204            } else {
2205                bp.line(line);
2206            }
2207            rest = next;
2208        }
2209        let (raws, defs, tables, trunc) = bp.finish();
2210        doc.truncated |= trunc;
2211        doc.tables = tables;
2212        for r in raws {
2213            if r.html {
2214                html_blocks(html_read(&r.src), &mut doc, r.quote, r.list, r.path, r.space);
2215                continue;
2216            }
2217            let mut b = MdBlock {
2218                kind: r.kind,
2219                level: r.level,
2220                quote: r.quote,
2221                list: r.list,
2222                path: r.path,
2223                marker: r.marker,
2224                number: r.number,
2225                delim: r.delim,
2226                task: r.task,
2227                space: r.space,
2228                lang: r.lang,
2229                table: r.table,
2230                head: r.head,
2231                ..MdBlock::default()
2232            };
2233            match r.kind {
2234                MB_CODE => b.text = r.src,
2235                MB_RULE => {}
2236                MB_ROW => {
2237                    for (ci, c) in r.cells.iter().enumerate() {
2238                        if ci > 0 {
2239                            b.text.push('\t');
2240                        }
2241                        let (t, spans) = inline(c, &defs, &mut doc.links, &mut doc.images);
2242                        let off = b.text.len() as u32;
2243                        let t = t.replace('\t', " ").replace('\n', " ");
2244                        b.text.push_str(&t);
2245                        b.cells.push((off, b.text.len() as u32));
2246                        b.spans.extend(spans.into_iter().map(|mut s| {
2247                            s.a += off;
2248                            s.b += off;
2249                            s
2250                        }));
2251                    }
2252                }
2253                _ => {
2254                    let (t, spans) = inline(&r.src, &defs, &mut doc.links, &mut doc.images);
2255                    b.text = t;
2256                    b.spans = spans;
2257                }
2258            }
2259            doc.push_block(b);
2260        }
2261        doc.finish();
2262        doc
2263    }
2264
2265    /// a document from HTML: what the rich-text editor reads (paragraphs, headings h1 .. h6,
2266    /// lists, block quotes, formats, links, colours) and rules, images, preformatted blocks
2267    /// (<pre><code class="language-x">), tables and forms (see the module header)
2268    pub fn html(s: &str) -> MdDoc {
2269        let mut s = s;
2270        let mut cut = false;
2271        if s.len() > INPUT_MAX {
2272            let mut k = INPUT_MAX;
2273            while !s.is_char_boundary(k) {
2274                k -= 1;
2275            }
2276            s = &s[..k];
2277            cut = true;
2278        }
2279        let mut doc = MdDoc { dom: Dom::parse(s), cut, ..MdDoc::default() };
2280        doc.derive();
2281        doc
2282    }
2283
2284    // the blocks, links, images, tables, forms and fields of the element tree (all of them);
2285    // the block elements that show one paragraph by themselves note its block
2286    fn derive(&mut self) {
2287        let mut dom = core::mem::take(&mut self.dom);
2288        let mut d = MdDoc::default();
2289        let sheet = Rc::new(Sheet::of(&dom, &self.linked));
2290        let mut h = dom_read(&dom, &sheet, true);
2291        let ranges = core::mem::take(&mut h.ranges);
2292        let para_blk = html_blocks(h, &mut d, 0, 0, 0, 0);
2293        d.finish();
2294        for n in dom.nodes.iter_mut() {
2295            n.blk = NIL;
2296        }
2297        for (e, p0, p1) in ranges {
2298            if p1 == p0 + 1 {
2299                if let Some(&b) = para_blk.get(p0 as usize) {
2300                    dom.nodes[e as usize].blk = b;
2301                }
2302            }
2303        }
2304        dom.changed = false;
2305        dom.touched = Some(Vec::new());
2306        self.blocks = d.blocks;
2307        self.links = d.links;
2308        self.images = d.images;
2309        self.tables = d.tables;
2310        self.spots = d.spots;
2311        self.forms = d.forms;
2312        self.fields = d.fields;
2313        self.fspots = d.fspots;
2314        self.boxes = d.boxes;
2315        self.box_ends = d.box_ends;
2316        self.sheet = sheet;
2317        self.truncated = self.cut || d.truncated || dom.truncated;
2318        self.dom = dom;
2319    }
2320
2321    /// derive the blocks (fields, forms, links ...) again after changes of the element tree
2322    /// (md_dom.rs; the view does it every frame) -> there were changes.  In place when every
2323    /// change lies inside block elements that show one paragraph of plain formatted text by
2324    /// themselves (O(those paragraphs)), else all of them (linear in the document).
2325    pub fn update(&mut self) -> bool {
2326        if !self.dom.changed {
2327            return false;
2328        }
2329        let touched = self.dom.touched.take();
2330        let ch = match touched.and_then(|t| self.update_fast(&t)) {
2331            Some(redo) => DocChange { redo, ..DocChange::default() },
2332            None => self.update_full(),
2333        };
2334        self.dom.changed = false;
2335        self.dom.touched = Some(Vec::new());
2336        self.change = Some(match self.change.take() {
2337            Some(c) => c.then(ch),
2338            None => ch,
2339        });
2340        true
2341    }
2342
2343    // (md_view.rs) what update changed since the view took it last
2344    pub(crate) fn take_change(&mut self) -> Option<DocChange> {
2345        self.change.take()
2346    }
2347
2348    // changes inside block elements that show one paragraph of plain formatted text by
2349    // themselves: those blocks again, in place -> the blocks that changed (None: not so)
2350    fn update_fast(&mut self, touched: &[(u32, bool)]) -> Option<Vec<usize>> {
2351        let mut leaves: Vec<(u32, usize)> = Vec::new();
2352        for &(e, own) in touched {
2353            let d = &self.dom;
2354            // (a node freed or out of the document shows nothing)
2355            if d.nodes.get(e as usize).is_none_or(|n| n.serial == 0) || !d.attached(e) {
2356                continue;
2357            }
2358            let l = d.leaf_of(e, own)?;
2359            if !leaves.iter().any(|x| x.0 == l) {
2360                leaves.push((l, d.nodes[l as usize].blk as usize));
2361            }
2362        }
2363        let mut new: Vec<(usize, MdBlock)> = Vec::new();
2364        for &(l, b) in &leaves {
2365            let old = self.blocks.get(b)?;
2366            if !matches!(old.kind, MB_PARA | MB_HEADING) || old.spans.iter().any(|s| s.link != 0 || s.image != 0 || s.field != 0) {
2367                return None;
2368            }
2369            let p = dom_para(&self.dom, &self.sheet, l)?;
2370            let mut nb = MdBlock { text: String::new(), spans: Vec::new(), node: self.dom.handle(l), ..old.clone() };
2371            let mut scratch = MdDoc::default();
2372            para_text(&p, &[], &mut scratch, &mut nb);
2373            if !scratch.links.is_empty() {
2374                return None;
2375            }
2376            new.push((b, nb));
2377        }
2378        let mut redo = Vec::new();
2379        for (b, nb) in new {
2380            if self.blocks[b] != nb {
2381                self.blocks[b] = nb;
2382                redo.push(b);
2383            }
2384        }
2385        redo.sort_unstable();
2386        redo.dedup();
2387        Some(redo)
2388    }
2389
2390    // every block again: the range that differs from the blocks before, the blocks after it that
2391    // only changed in place (their links, images or fields numbered otherwise)
2392    fn update_full(&mut self) -> DocChange {
2393        let old = core::mem::take(&mut self.blocks);
2394        let images = core::mem::take(&mut self.images);
2395        let spots = core::mem::take(&mut self.spots);
2396        let old_boxes = core::mem::take(&mut self.boxes);
2397        self.derive();
2398        let new = &self.blocks;
2399        let (on, nn) = (old.len(), new.len());
2400        // (the boxes around two blocks are the same)
2401        let same = |x: &MdBlock, y: &MdBlock| x.boxes.len() == y.boxes.len() && x.boxes.iter().zip(&y.boxes).all(|(&i, &j)| old_boxes.get(i as usize) == self.boxes.get(j as usize));
2402        let mut a = 0;
2403        while a < on && a < nn && old[a] == new[a] && same(&old[a], &new[a]) {
2404            a += 1;
2405        }
2406        let mut s = 0;
2407        while s < on - a && s < nn - a && alike(&old[on - 1 - s], &new[nn - 1 - s]) {
2408            s += 1;
2409        }
2410        let redo: Vec<usize> = (nn - s..nn).filter(|&j| old[j - nn + on] != new[j] || !same(&old[j - nn + on], &new[j])).collect();
2411        let range = (a, on - a - s, nn - a - s);
2412        DocChange { range: (range.1 > 0 || range.2 > 0).then_some(range), redo, full: true, images, spots }
2413    }
2414
2415    /// the addresses the document's <link rel="stylesheet"> elements name, as written, each once,
2416    /// in document order (media other than screen / all left out); the view reads nothing by
2417    /// itself: the app fetches them and answers with set_sheet
2418    pub fn sheet_links(&self) -> &[String] {
2419        self.sheet.links()
2420    }
2421
2422    /// the text of a linked style sheet (None: it could not be had, its rules go): it counts where
2423    /// its <link> stands among the <style> elements, from the next update on (a text for an
2424    /// address no link names is kept for a link that comes later)
2425    pub fn set_sheet(&mut self, href: &str, css: Option<&str>) {
2426        let href = href.trim();
2427        match css {
2428            Some(c) => {
2429                if self.linked.get(href).is_some_and(|x| x == c) {
2430                    return;
2431                }
2432                self.linked.insert(String::from(href), String::from(c));
2433            }
2434            None => {
2435                if self.linked.remove(href).is_none() {
2436                    return;
2437                }
2438            }
2439        }
2440        if self.sheet.links().iter().any(|l| l == href) {
2441            self.dom.changed = true;
2442            self.dom.touched = None;
2443        }
2444    }
2445
2446    /// what the cascade gives an element ("property:value;" of the rules that match and its style
2447    /// attribute, inherited properties not among them); empty for none, Markdown or a gone node
2448    pub fn style_of(&self, n: MdNode) -> String {
2449        match self.elem(n) {
2450            Some(i) => self.sheet.style_of(&self.dom, i),
2451            None => String::new(),
2452        }
2453    }
2454
2455    /// the boxes of CSS (MdBlock::boxes)
2456    pub fn boxes(&self) -> &[MdBox] {
2457        &self.boxes
2458    }
2459
2460    // the first and last block of box i
2461    pub(crate) fn box_ends(&self, i: u32) -> (usize, usize) {
2462        self.box_ends.get(i as usize).map_or((0, 0), |e| (e.0 as usize, e.1 as usize))
2463    }
2464
2465    /// the field whose element is n (an index of fields())
2466    pub fn field_of(&self, n: MdNode) -> Option<usize> {
2467        if n.is_none() {
2468            return None;
2469        }
2470        self.fields.iter().position(|f| f.node == n)
2471    }
2472
2473    fn push_block(&mut self, b: MdBlock) {
2474        if self.blocks.len() < BLOCKS_MAX {
2475            self.blocks.push(b);
2476        } else {
2477            self.truncated = true;
2478        }
2479    }
2480
2481    // the links and fields in reading order, the first and last block of every box
2482    fn finish(&mut self) {
2483        if let Some(b) = self.blocks.first_mut() {
2484            b.space = 0;
2485        }
2486        self.box_ends = vec![(u32::MAX, 0); self.boxes.len()];
2487        for (k, b) in self.blocks.iter().enumerate() {
2488            for &i in &b.boxes {
2489                if let Some(e) = self.box_ends.get_mut(i as usize) {
2490                    e.0 = e.0.min(k as u32);
2491                    e.1 = e.1.max(k as u32);
2492                }
2493            }
2494        }
2495        self.spots.clear();
2496        self.fspots.clear();
2497        for (k, b) in self.blocks.iter().enumerate() {
2498            for s in &b.spans {
2499                if s.field != 0 && (s.field as usize) <= self.fields.len() {
2500                    self.fspots.push(MdFieldSpot { block: k, a: s.a, field: s.field });
2501                }
2502                if s.link == 0 {
2503                    continue;
2504                }
2505                match self.spots.last_mut() {
2506                    Some(l) if l.block == k && l.link == s.link && l.b == s.a => l.b = s.b,
2507                    _ => self.spots.push(MdLinkSpot { block: k, a: s.a, b: s.b, link: s.link }),
2508                }
2509            }
2510        }
2511    }
2512
2513    /// the blocks in reading order
2514    pub fn blocks(&self) -> &[MdBlock] {
2515        &self.blocks
2516    }
2517
2518    /// the link targets (MdSpan::link - 1)
2519    pub fn links(&self) -> &[String] {
2520        &self.links
2521    }
2522
2523    /// the target of MdSpan::link (index + 1); "" for 0
2524    pub fn link(&self, link: u32) -> &str {
2525        if link == 0 { "" } else { self.links.get(link as usize - 1).map_or("", |s| s.as_str()) }
2526    }
2527
2528    /// the images (MdSpan::image - 1)
2529    pub fn images(&self) -> &[MdImage] {
2530        &self.images
2531    }
2532
2533    /// the tables (MdBlock::table)
2534    pub fn tables(&self) -> &[MdTable] {
2535        &self.tables
2536    }
2537
2538    /// every link in reading order (adjacent spans of one link joined)
2539    pub fn link_spots(&self) -> &[MdLinkSpot] {
2540        &self.spots
2541    }
2542
2543    /// the forms (MdField::form - 1)
2544    pub fn forms(&self) -> &[MdForm] {
2545        &self.forms
2546    }
2547
2548    /// the fields of the forms in document order (MdSpan::field - 1)
2549    pub fn fields(&self) -> &[MdField] {
2550        &self.fields
2551    }
2552
2553    /// every visible field in reading order
2554    pub fn field_spots(&self) -> &[MdFieldSpot] {
2555        &self.fspots
2556    }
2557
2558    /// a limit cut the input (size, blocks)
2559    pub fn truncated(&self) -> bool {
2560        self.truncated
2561    }
2562
2563    /// the place after the last character
2564    pub fn end(&self) -> TextPos {
2565        let n = self.blocks.len();
2566        if n == 0 { TextPos::default() } else { TextPos::new(n - 1, self.blocks[n - 1].text.len()) }
2567    }
2568
2569    /// the whole document as text
2570    pub fn plain(&self) -> String {
2571        self.plain_range(TextPos::default(), self.end())
2572    }
2573
2574    /// the whole document as HTML
2575    pub fn to_html(&self) -> String {
2576        self.html_range(TextPos::default(), self.end())
2577    }
2578
2579    // the text of block k from a to b with images as their alt texts, buttons as their captions
2580    // (other fields as nothing: their values are the view's)
2581    pub(crate) fn block_text(&self, k: usize, a: usize, b: usize) -> String {
2582        let bl = &self.blocks[k];
2583        let t = &bl.text;
2584        let (a, b) = (clamp_char(t, a), clamp_char(t, b.max(a)));
2585        let mut out = String::new();
2586        let mut i = a;
2587        for s in &bl.spans {
2588            if (s.image == 0 && s.field == 0) || (s.b as usize) <= a || (s.a as usize) >= b {
2589                continue;
2590            }
2591            out.push_str(&t[i..s.a as usize]);
2592            if s.image != 0 {
2593                out.push_str(&self.images.get(s.image as usize - 1).map_or(String::new(), |m| m.alt.clone()));
2594            } else if let Some(f) = self.fields.get(s.field as usize - 1) {
2595                out.push_str(f.caption());
2596            }
2597            i = s.b as usize;
2598        }
2599        out.push_str(&t[i.min(b)..b]);
2600        out
2601    }
2602
2603    /// the text of a .. b: blocks on lines of their own (a blank line before paragraphs),
2604    /// list markers ("- ", "1. ", "[x] "), cells by tabs, images as their alt texts, buttons
2605    /// as their captions (the other fields: nothing)
2606    pub fn plain_range(&self, a: TextPos, b: TextPos) -> String {
2607        let (a, b) = if a <= b { (a, b) } else { (b, a) };
2608        let mut out = String::new();
2609        if self.blocks.is_empty() {
2610            return out;
2611        }
2612        let last = b.line.min(self.blocks.len() - 1);
2613        for k in a.line..=last {
2614            let bl = &self.blocks[k];
2615            let s = if k == a.line { a.byte } else { 0 };
2616            let e = if k == b.line { b.byte } else { bl.text.len() };
2617            if k > a.line {
2618                out.push('\n');
2619                if bl.space >= 2 {
2620                    out.push('\n');
2621                }
2622            }
2623            if s == 0 {
2624                for _ in 1..bl.list {
2625                    out.push_str("  ");
2626                }
2627                match bl.marker {
2628                    MK_BULLET => out.push_str("- "),
2629                    MK_NUMBER => out.push_str(&format!("{}{} ", bl.number, bl.delim as char)),
2630                    _ if bl.list > 0 => out.push_str("  "),
2631                    _ => {}
2632                }
2633                match bl.task {
2634                    1 => out.push_str("[ ] "),
2635                    2 => out.push_str("[x] "),
2636                    _ => {}
2637                }
2638                if bl.kind == MB_RULE && k != last {
2639                    out.push_str("---");
2640                }
2641            }
2642            out.push_str(&self.block_text(k, s, e));
2643        }
2644        out
2645    }
2646
2647    /// the HTML of a .. b: h1 .. h6, p, ul / ol / li (task items with a disabled check box),
2648    /// blockquote, pre / code (class language-x), hr, table / tr / th / td (align), strong,
2649    /// em, del, u, code, a href, img src alt, colours as span styles; of forms only the
2650    /// buttons' captions (as text)
2651    pub fn html_range(&self, a: TextPos, b: TextPos) -> String {
2652        let (a, b) = if a <= b { (a, b) } else { (b, a) };
2653        let mut out = String::new();
2654        if self.blocks.is_empty() {
2655            return out;
2656        }
2657        let last = b.line.min(self.blocks.len() - 1);
2658        // open containers: quotes, lists (ordered?), the open <li>, the table
2659        let mut quotes = 0u8;
2660        let mut lists: Vec<(bool, bool)> = Vec::new();
2661        let mut table: Option<u32> = None;
2662        for k in a.line..=last {
2663            let bl = &self.blocks[k];
2664            let s = if k == a.line { a.byte } else { 0 };
2665            let e = if k == b.line { b.byte } else { bl.text.len() };
2666            if bl.kind != MB_ROW || table != Some(bl.table) {
2667                if table.take().is_some() {
2668                    out.push_str("</table>");
2669                }
2670            }
2671            // lists deeper than this block's close (an item of the other kind closes its list)
2672            loop {
2673                let depth = bl.list as usize;
2674                let pop = match lists.last() {
2675                    Some(_) if lists.len() > depth => true,
2676                    Some(l) if lists.len() == depth && bl.marker != MK_NONE => l.0 != (bl.marker == MK_NUMBER),
2677                    _ => false,
2678                };
2679                if !pop {
2680                    break;
2681                }
2682                if let Some((ord, li)) = lists.pop() {
2683                    if li {
2684                        out.push_str("</li>");
2685                    }
2686                    out.push_str(if ord { "</ol>" } else { "</ul>" });
2687                }
2688            }
2689            while quotes > bl.quote {
2690                out.push_str("</blockquote>");
2691                quotes -= 1;
2692            }
2693            while quotes < bl.quote {
2694                out.push_str("<blockquote>");
2695                quotes += 1;
2696            }
2697            while lists.len() < bl.list as usize {
2698                let ord = bl.marker == MK_NUMBER && lists.len() + 1 == bl.list as usize;
2699                if ord && bl.number != 1 {
2700                    out.push_str(&format!("<ol start=\"{}\">", bl.number));
2701                } else {
2702                    out.push_str(if ord { "<ol>" } else { "<ul>" });
2703                }
2704                lists.push((ord, false));
2705            }
2706            if bl.marker != MK_NONE {
2707                if let Some(l) = lists.last_mut() {
2708                    if l.1 {
2709                        out.push_str("</li>");
2710                    }
2711                    out.push_str("<li>");
2712                    l.1 = true;
2713                }
2714                match bl.task {
2715                    1 => out.push_str("<input type=\"checkbox\" disabled> "),
2716                    2 => out.push_str("<input type=\"checkbox\" checked disabled> "),
2717                    _ => {}
2718                }
2719            }
2720            match bl.kind {
2721                MB_HEADING => {
2722                    let l = bl.level.clamp(1, 6);
2723                    out.push_str(&format!("<h{}>", l));
2724                    self.inline_html(k, s, e, &mut out);
2725                    out.push_str(&format!("</h{}>", l));
2726                }
2727                MB_CODE => {
2728                    if bl.lang.is_empty() {
2729                        out.push_str("<pre><code>");
2730                    } else {
2731                        out.push_str("<pre><code class=\"language-");
2732                        esc(&bl.lang, &mut out);
2733                        out.push_str("\">");
2734                    }
2735                    esc(&bl.text[clamp_char(&bl.text, s)..clamp_char(&bl.text, e)], &mut out);
2736                    out.push_str("</code></pre>");
2737                }
2738                MB_RULE => out.push_str("<hr>"),
2739                MB_ROW => {
2740                    if table.is_none() {
2741                        out.push_str("<table>");
2742                        table = Some(bl.table);
2743                    }
2744                    out.push_str("<tr>");
2745                    let aligns = self.tables.get(bl.table as usize).map(|t| t.aligns.as_slice()).unwrap_or(&[]);
2746                    for (ci, &(ca, cb)) in bl.cells.iter().enumerate() {
2747                        let (ca, cb) = (ca as usize, cb as usize);
2748                        if cb < s || ca > e {
2749                            continue;
2750                        }
2751                        let tag = if bl.head { "th" } else { "td" };
2752                        match aligns.get(ci).copied().unwrap_or(MA_NONE) {
2753                            MA_LEFT => out.push_str(&format!("<{} align=\"left\">", tag)),
2754                            MA_CENTER => out.push_str(&format!("<{} align=\"center\">", tag)),
2755                            MA_RIGHT => out.push_str(&format!("<{} align=\"right\">", tag)),
2756                            _ => out.push_str(&format!("<{}>", tag)),
2757                        }
2758                        self.inline_html(k, ca.max(s), cb.min(e), &mut out);
2759                        out.push_str(&format!("</{}>", tag));
2760                    }
2761                    out.push_str("</tr>");
2762                }
2763                _ => {
2764                    match bl.align {
2765                        1 => out.push_str("<p style=\"text-align:center\">"),
2766                        2 => out.push_str("<p style=\"text-align:right\">"),
2767                        _ => out.push_str("<p>"),
2768                    }
2769                    self.inline_html(k, s, e, &mut out);
2770                    out.push_str("</p>");
2771                }
2772            }
2773        }
2774        if table.is_some() {
2775            out.push_str("</table>");
2776        }
2777        while let Some((ord, li)) = lists.pop() {
2778            if li {
2779                out.push_str("</li>");
2780            }
2781            out.push_str(if ord { "</ol>" } else { "</ul>" });
2782        }
2783        while quotes > 0 {
2784            out.push_str("</blockquote>");
2785            quotes -= 1;
2786        }
2787        out
2788    }
2789
2790    // the inline HTML of block k's bytes a .. b
2791    fn inline_html(&self, k: usize, a: usize, b: usize, out: &mut String) {
2792        let bl = &self.blocks[k];
2793        let t = &bl.text;
2794        let (a, b) = (clamp_char(t, a), clamp_char(t, b.max(a)));
2795        let mut i = a;
2796        let plain = |out: &mut String, s: &str| {
2797            for (n, part) in s.split('\n').enumerate() {
2798                if n > 0 {
2799                    out.push_str("<br>");
2800                }
2801                esc(part, out);
2802            }
2803        };
2804        for s in &bl.spans {
2805            let (sa, sb) = (s.a as usize, s.b as usize);
2806            if sb <= a || sa >= b {
2807                continue;
2808            }
2809            let (sa, sb) = (clamp_char(t, sa.max(a)), clamp_char(t, sb.min(b)));
2810            if sa > i {
2811                plain(out, &t[i..sa]);
2812            }
2813            if s.image != 0 {
2814                if let Some(m) = self.images.get(s.image as usize - 1) {
2815                    out.push_str("<img src=\"");
2816                    esc(&m.src, out);
2817                    out.push_str("\" alt=\"");
2818                    esc(&m.alt, out);
2819                    out.push_str("\">");
2820                }
2821                i = sb;
2822                continue;
2823            }
2824            if s.field != 0 {
2825                if let Some(f) = self.fields.get(s.field as usize - 1) {
2826                    esc(f.caption(), out);
2827                }
2828                i = sb;
2829                continue;
2830            }
2831            let mut close: Vec<&str> = Vec::new();
2832            if s.link != 0 {
2833                out.push_str("<a href=\"");
2834                esc(self.link(s.link), out);
2835                out.push_str("\">");
2836                close.push("</a>");
2837            }
2838            if s.color != 0 || s.bg != 0 {
2839                out.push_str("<span style=\"");
2840                if s.color != 0 {
2841                    out.push_str(&format!("color:#{:06x};", s.color & 0xFF_FFFF));
2842                }
2843                if s.bg != 0 {
2844                    out.push_str(&format!("background-color:#{:06x};", s.bg & 0xFF_FFFF));
2845                }
2846                out.push_str("\">");
2847                close.push("</span>");
2848            }
2849            for (bit, o, c) in [(MS_BOLD, "<strong>", "</strong>"), (MS_ITALIC, "<em>", "</em>"), (MS_STRIKE, "<del>", "</del>"), (MS_UNDER, "<u>", "</u>"), (MS_CODE, "<code>", "</code>")] {
2850                if s.style & bit != 0 {
2851                    out.push_str(o);
2852                    close.push(c);
2853                }
2854            }
2855            plain(out, &t[sa..sb]);
2856            for c in close.iter().rev() {
2857                out.push_str(c);
2858            }
2859            i = sb;
2860        }
2861        if i < b {
2862            plain(out, &t[i..b]);
2863        }
2864    }
2865}
2866
2867fn clamp_char(t: &str, i: usize) -> usize {
2868    let mut i = i.min(t.len());
2869    while !t.is_char_boundary(i) {
2870        i -= 1;
2871    }
2872    i
2873}
2874
2875fn esc(s: &str, out: &mut String) {
2876    for c in s.chars() {
2877        match c {
2878            '&' => out.push_str("&amp;"),
2879            '<' => out.push_str("&lt;"),
2880            '>' => out.push_str("&gt;"),
2881            '"' => out.push_str("&quot;"),
2882            '\u{FFFC}' => {}
2883            _ => out.push(c),
2884        }
2885    }
2886}