1use 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
74pub const MB_PARA: u8 = 0;
76pub const MB_HEADING: u8 = 1;
78pub const MB_CODE: u8 = 2;
80pub const MB_RULE: u8 = 3;
82pub const MB_ROW: u8 = 4;
84
85pub const MS_BOLD: u16 = 1;
87pub const MS_ITALIC: u16 = 2;
89pub const MS_STRIKE: u16 = 4;
91pub const MS_CODE: u16 = 8;
93pub const MS_UNDER: u16 = 16;
95
96pub const MK_NONE: u8 = 0;
98pub const MK_BULLET: u8 = 1;
100pub const MK_NUMBER: u8 = 2;
102
103pub const MA_NONE: u8 = 0;
105pub const MA_LEFT: u8 = 1;
107pub const MA_CENTER: u8 = 2;
109pub const MA_RIGHT: u8 = 3;
111
112pub const MF_TEXT: u8 = 0;
115pub const MF_PASSWORD: u8 = 1;
117pub const MF_CHECKBOX: u8 = 2;
119pub const MF_RADIO: u8 = 3;
121pub const MF_HIDDEN: u8 = 4;
123pub const MF_TEXTAREA: u8 = 5;
125pub const MF_SELECT: u8 = 6;
127pub const MF_SUBMIT: u8 = 7;
129pub const MF_RESET: u8 = 8;
131pub const MF_BUTTON: u8 = 9;
133
134const INPUT_MAX: usize = 16 << 20; const NEST_MAX: usize = 32; const 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; #[derive(Clone, Debug, Default, PartialEq, Eq)]
146pub struct MdSpan {
147 pub a: u32,
149 pub b: u32,
150 pub style: u16,
152 pub link: u32,
154 pub image: u32,
156 pub color: u32,
158 pub bg: u32,
159 pub field: u32,
161}
162
163#[derive(Clone, Debug, Default, PartialEq)]
165pub struct MdBlock {
166 pub kind: u8,
168 pub level: u8,
170 pub text: String,
173 pub spans: Vec<MdSpan>,
175 pub quote: u8,
177 pub list: u8,
179 pub path: u32,
182 pub marker: u8,
184 pub number: u32,
186 pub delim: u8,
187 pub task: u8,
189 pub space: u8,
191 pub lang: String,
193 pub cells: Vec<(u32, u32)>,
195 pub table: u32,
197 pub head: bool,
198 pub align: u8,
200 pub node: MdNode,
203 pub boxes: Vec<u32>,
205}
206
207#[derive(Clone, Debug, Default, PartialEq, Eq)]
210pub struct MdBox {
211 pub margin: [i16; 4],
212 pub padding: [i16; 4],
213 pub border: [u8; 4],
216 pub border_color: [u32; 4],
217 pub bg: u32,
219 pub mset: u8,
222 pub depth: u8,
224 pub node: MdNode,
226}
227
228#[derive(Clone, Debug, Default, PartialEq, Eq)]
230pub struct MdImage {
231 pub src: String,
232 pub alt: String,
233 pub title: String,
234 pub w: u32,
236 pub h: u32,
237}
238
239#[derive(Clone, Debug, Default, PartialEq, Eq)]
241pub struct MdTable {
242 pub aligns: Vec<u8>,
243}
244
245#[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#[derive(Clone, Debug, Default, PartialEq, Eq)]
256pub struct MdForm {
257 pub action: String,
260 pub method: String,
261 pub id: String,
262 pub name: String,
263 pub node: MdNode,
265}
266
267#[derive(Clone, Debug, Default, PartialEq, Eq)]
269pub struct MdOption {
270 pub value: String,
271 pub label: String,
272}
273
274#[derive(Clone, Debug, Default, PartialEq, Eq)]
276pub struct MdField {
277 pub kind: u8,
279 pub form: u32,
281 pub name: String,
282 pub id: String,
283 pub value: String,
286 pub label: String,
288 pub placeholder: String,
289 pub checked: bool,
291 pub options: Vec<MdOption>,
293 pub selected: i32,
294 pub size: u32,
297 pub rows: u32,
298 pub maxlength: u32,
300 pub disabled: bool,
301 pub readonly: bool,
302 pub node: MdNode,
305}
306
307impl MdField {
308 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#[derive(Clone, Copy, Debug, PartialEq, Eq)]
322pub struct MdFieldSpot {
323 pub block: usize,
324 pub a: u32,
325 pub field: u32,
326}
327
328#[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 boxes: Vec<MdBox>,
342 box_ends: Vec<(u32, u32)>,
343 pub(crate) dom: Dom,
346 sheet: Rc<Sheet>,
347 linked: BTreeMap<String, String>,
349 cut: bool,
350 change: Option<DocChange>,
351}
352
353#[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 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 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
398fn 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
412fn 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
434fn 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
454fn 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
479fn 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#[derive(Clone)]
497struct Cont {
498 quote: bool,
499 indent: usize,
501 ordered: bool,
502 number: u32,
503 delim: u8,
504 task: u8,
505 filed: bool,
506 empty: bool,
508}
509
510enum 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#[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 blank: bool,
549 truncated: bool,
550 table_cells: usize,
552}
553
554fn 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
578fn 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
599fn 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 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
622fn 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
644fn 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 out.push(t[start..].trim().replace("\\|", "|"));
672 out
673}
674
675fn 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 if v.len() == 1 && !t.contains('|') {
702 return None;
703 }
704 Some(v)
705}
706
707fn 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
736fn 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
750fn 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 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
804fn 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
853fn 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 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 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 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 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 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; }
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 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 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 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 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 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 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 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 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
1238const 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 ivs: usize,
1266}
1267
1268struct 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 dead: usize,
1288 ivs: Vec<Iv>,
1289 html: Vec<(String, usize, u16, u32)>,
1291 ticks: BTreeMap<usize, Vec<usize>>,
1293 budget: usize,
1295 no_cmt: bool,
1297 links: &'a mut Vec<String>,
1298 images: &'a mut Vec<MdImage>,
1299}
1300
1301fn 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 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 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 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 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 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 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 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 if found.is_none() && i - br.src <= LABEL_MAX && !self.defs.is_empty() && self.budget > 0 {
1572 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 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 self.dead = self.brackets.len();
1631 }
1632 end
1633 }
1634
1635 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 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 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 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 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 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 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 fn output(mut self) -> (String, Vec<MdSpan>) {
1845 self.emphasis();
1846 let n = self.nodes.len();
1847 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
1912fn 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
1919fn 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 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 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 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; 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 = ¶s[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 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 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 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 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
2087fn 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 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
2149fn 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
2165fn 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
2176impl MdDoc {
2179 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 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 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 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 pub(crate) fn take_change(&mut self) -> Option<DocChange> {
2345 self.change.take()
2346 }
2347
2348 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 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 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 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 pub fn sheet_links(&self) -> &[String] {
2419 self.sheet.links()
2420 }
2421
2422 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 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 pub fn boxes(&self) -> &[MdBox] {
2457 &self.boxes
2458 }
2459
2460 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 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 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 pub fn blocks(&self) -> &[MdBlock] {
2515 &self.blocks
2516 }
2517
2518 pub fn links(&self) -> &[String] {
2520 &self.links
2521 }
2522
2523 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 pub fn images(&self) -> &[MdImage] {
2530 &self.images
2531 }
2532
2533 pub fn tables(&self) -> &[MdTable] {
2535 &self.tables
2536 }
2537
2538 pub fn link_spots(&self) -> &[MdLinkSpot] {
2540 &self.spots
2541 }
2542
2543 pub fn forms(&self) -> &[MdForm] {
2545 &self.forms
2546 }
2547
2548 pub fn fields(&self) -> &[MdField] {
2550 &self.fields
2551 }
2552
2553 pub fn field_spots(&self) -> &[MdFieldSpot] {
2555 &self.fspots
2556 }
2557
2558 pub fn truncated(&self) -> bool {
2560 self.truncated
2561 }
2562
2563 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 pub fn plain(&self) -> String {
2571 self.plain_range(TextPos::default(), self.end())
2572 }
2573
2574 pub fn to_html(&self) -> String {
2576 self.html_range(TextPos::default(), self.end())
2577 }
2578
2579 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 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 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 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 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 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("&"),
2879 '<' => out.push_str("<"),
2880 '>' => out.push_str(">"),
2881 '"' => out.push_str("""),
2882 '\u{FFFC}' => {}
2883 _ => out.push(c),
2884 }
2885 }
2886}