1use alloc::collections::{BTreeMap, BTreeSet};
109use alloc::format;
110use alloc::rc::Rc;
111use alloc::string::String;
112use alloc::vec::Vec;
113
114use gfx::Surface;
115use img_svg::Svg;
116use text::segment;
117use text::{FS_SYN_BOLD, FS_SYN_ITALIC};
118
119use crate::cmd::{Draw, DrawRef, Gfx};
120use crate::consts::*;
121use crate::ed_core::{word_range, TextPos, TextSel};
122use crate::highlight::{Highlighter, Token};
123use crate::i18n::tr;
124use crate::imgview::SvgDraw;
125use crate::keys::*;
126use crate::list::{anim_decay, anim_steps};
127use crate::md_doc::*;
128use crate::md_dom::MdNode;
129use crate::theme::color_mix;
130use crate::value::part_id;
131use crate::vlist::Rows;
132use crate::{Sp, Ui};
133
134pub const MV_NO_FRAME: u32 = 1;
136pub const MV_OPEN_LINKS: u32 = 2;
138pub const MV_RTL: u32 = 4;
140pub const MV_LTR: u32 = 8;
142
143pub const MI_LOCAL: u8 = 0;
145pub const MI_ASK: u8 = 1;
147pub const MI_APP: u8 = 2;
149
150const NAT_W: i32 = 480;
151const NAT_H: i32 = 320;
152const PAD: i32 = 16;
153const PAD_BARE: i32 = 4;
154const PARA_GAP: i32 = 12;
155const TIGHT_GAP: i32 = 4;
156const LIST_W: i32 = 28;
157const QUOTE_W: i32 = 18;
158const CODE_PX: i32 = 12;
159const CODE_PY: i32 = 10;
160const CODE_PAD: i32 = 3;
161const CELL_PX: i32 = 10;
162const CELL_PY: i32 = 6;
163const RULE_H: i32 = 20;
164const LINE_DIP: i32 = 40;
165const WHEEL_DIP: i32 = 48;
166const IMG_BYTES: usize = 32 << 20;
167const HEAD_SCALE: [u32; 6] = [200, 150, 125, 100, 88, 85];
168const TABLE_SAMPLE: usize = 200;
169const LAYS_KEEP: usize = 400;
170const LABEL_MAX: usize = 4000;
171const FIELD_CHARS: u32 = 20; const FIELD_ROWS: u32 = 2; const FIELD_GAP: i32 = 2; const FIELD_CAP: u32 = 1 << 20; #[derive(Clone, Debug, Default)]
178pub struct MdEv {
179 pub ev: u32,
181 pub link: Option<String>,
183 pub hover: Option<String>,
185 pub field: Option<usize>,
188 pub submit: Option<MdSubmit>,
190 pub click: Option<MdNode>,
192}
193
194#[derive(Clone, Debug, Default, PartialEq, Eq)]
196pub struct MdSubmit {
197 pub form: usize,
199 pub action: String,
200 pub method: String,
201 pub fields: Vec<(String, String)>,
203 pub button: Option<usize>,
205}
206
207#[derive(Clone)]
209enum Img {
210 Unread,
211 Failed,
212 Pending(Rc<str>),
213 Bitmap(Rc<Surface>),
214 Vector(Rc<Svg>),
215}
216
217enum Req {
219 Asked,
220 Got(Img),
221}
222
223#[derive(Clone, Default)]
227struct Val {
228 text: String,
229 on: bool,
230 sel: i32,
231 kind: u8,
232 node: MdNode,
233 dirty: bool,
234}
235
236#[derive(Clone, Copy, PartialEq, Eq)]
238enum Stop {
239 Link(usize),
240 Field(usize),
241}
242
243pub fn md_field_id(id: u32, i: usize) -> u32 {
245 (id.wrapping_mul(0x9E37_79B1) & 0x0FFF_0000) | 0xE000_0000 | (i as u32 + 1) & 0xFFFF
246}
247
248fn field_of(id: u32, fid: u32, n: usize) -> Option<usize> {
250 let k = (fid & 0xFFFF) as usize;
251 (fid & 0xFFFF_0000 == md_field_id(id, 0) & 0xFFFF_0000 && k >= 1 && k <= n).then(|| k - 1)
252}
253
254fn field_name(f: &MdField) -> &str {
256 if !f.label.is_empty() {
257 &f.label
258 } else if !f.placeholder.is_empty() {
259 &f.placeholder
260 } else {
261 &f.name
262 }
263}
264
265fn has_scheme(s: &str) -> bool {
267 match s.find(':') {
268 Some(i) if i >= 2 => s.as_bytes()[0].is_ascii_alphabetic() && s[..i].bytes().all(|c| c.is_ascii_alphanumeric() || matches!(c, b'+' | b'-' | b'.')),
269 _ => false,
270 }
271}
272
273fn img_at(src: &str, base: &str, mode: u8) -> (Option<String>, Option<String>) {
276 let src = src.trim();
277 let low = src.to_ascii_lowercase();
278 if src.is_empty() || low.starts_with("javascript:") {
279 return (None, None);
280 }
281 let remote = low.starts_with("http:") || low.starts_with("https:") || low.starts_with("data:");
282 let path = if src.contains("://") || src.starts_with('/') || base.is_empty() || (mode == MI_APP && has_scheme(src)) {
283 String::from(src)
284 } else {
285 let s = src.strip_prefix("./").unwrap_or(src);
286 if base.ends_with('/') { format!("{}{}", base, s) } else { format!("{}/{}", base, s) }
287 };
288 match mode {
289 MI_APP => (None, Some(path)),
290 MI_ASK if remote => (None, Some(String::from(src))),
291 _ if remote => (None, None),
292 _ => (Some(path), None),
293 }
294}
295
296fn img_file_ok(path: &str) -> bool {
300 let b = path.as_bytes();
301 if b.len() >= 2 && matches!(b[0], b'/' | b'\\') && matches!(b[1], b'/' | b'\\') {
302 return false;
303 }
304 #[cfg(any(windows, target_os = "linux", target_vendor = "apple"))]
305 if matches!(plat::vfs::route(path), plat::vfs::Route::Os) {
306 return plat::fs::stat(path).is_some_and(|m| m.is_file() && m.size <= IMG_BYTES as u64);
307 }
308 true
309}
310
311fn img_decode(d: &[u8], name: &str) -> Img {
314 if d.len() > IMG_BYTES {
315 return Img::Failed;
316 }
317 let low = name.to_ascii_lowercase();
318 let head = core::str::from_utf8(&d[..d.len().min(256)]).unwrap_or("");
319 let svg = low.ends_with(".svg") || low.ends_with(".svgz") || head.trim_start_matches('\u{FEFF}').trim_start().starts_with('<');
320 if svg {
321 Svg::parse(d).map(|s| Img::Vector(Rc::new(s))).unwrap_or(Img::Failed)
322 } else {
323 img_core::io::decode(d).map(|s| Img::Bitmap(Rc::new(s))).unwrap_or(Img::Failed)
324 }
325}
326
327#[derive(Clone)]
330struct Piece {
331 a: u32,
332 b: u32,
333 x: i32,
334 w: i32,
335 font: u32,
336 size: i32,
337 asc: i32,
338 style: u16,
339 color: u32,
340 bg: u32,
341 link: u32,
342 image: u32,
343 ih: i32,
344 syn: u8,
345 shaped: bool,
346 field: u32,
347}
348
349struct LRow {
351 a: u32,
352 b: u32,
353 y: i32,
354 h: i32,
355 base: i32,
356 pieces: Vec<Piece>,
357 stops: Vec<(u32, i32)>,
358}
359
360impl LRow {
361 fn hit(&self, x: i32) -> u32 {
362 self.stops.iter().min_by_key(|s| (s.1 - x).abs()).map_or(self.a, |s| s.0)
363 }
364}
365
366#[derive(Default)]
368struct Flow {
369 rows: Vec<LRow>,
370 h: i32,
371 w: i32,
372}
373
374struct FBox {
376 x: i32,
377 y: i32,
378 w: i32,
379 flow: Flow,
380}
381
382struct BLay {
384 h: i32,
386 gap: i32,
387 ch: i32,
388 x: i32,
390 w: i32,
391 boxes: Vec<FBox>,
393 bars: Vec<i32>,
395 css: Vec<CssBox>,
397 mx: i32,
398}
399
400struct CssBox {
403 b: u32,
404 x: i32,
405 w: i32,
406 y0: i32,
407 y1: i32,
408 first: bool,
409 last: bool,
410}
411
412#[derive(Clone, Copy, Default, PartialEq)]
414struct Faces {
415 f: [[u32; 4]; 2],
416 size: i32,
417}
418
419impl Faces {
420 fn get(&self, mono: bool, bold: bool, italic: bool) -> u32 {
421 self.f[mono as usize][bold as usize | (italic as usize) << 1]
422 }
423}
424
425#[derive(Clone)]
427struct Seg {
428 a: usize,
429 b: usize,
430 font: u32,
431 size: i32,
432 asc: i32,
433 desc: i32,
434 lh: i32,
435 style: u16,
436 color: u32,
437 bg: u32,
438 link: u32,
439 image: u32,
440 iw: i32,
441 ih: i32,
442 syn: u8,
443 shaped: bool,
444 pad: i32,
446 field: u32,
449}
450
451#[derive(Default)]
453struct Cache {
454 key: (i32, i32, u32, bool, Faces),
455 lays: BTreeMap<usize, Rc<BLay>>,
456 cols: BTreeMap<u32, Rc<Vec<i32>>>,
457 rows_of: BTreeMap<u32, Vec<usize>>,
458 imgs: Vec<Img>,
459 scaled: BTreeMap<(usize, i32, i32), Rc<Surface>>,
460 svgs: BTreeMap<(usize, u32), Rc<SvgDraw>>,
461 mode: u8,
464 req: BTreeMap<String, Req>,
465 queue: Vec<String>,
466}
467
468pub struct MdViewState {
470 doc: MdDoc,
471 src: (u8, u64, usize),
473 pub base: String,
475 pub label: String,
477 pub images: u8,
479 img_block: Vec<usize>,
481 vals: Vec<Val>,
484 stops: Vec<Stop>,
485 group: Vec<usize>,
486 ffocus: Option<usize>,
487 rows: Rows,
488 sel: TextSel,
489 link: Option<usize>,
490 lc: Cache,
491 sized: bool,
493 auto_rtl: Option<bool>,
495 follow: bool,
498 drag: u8,
499 dragged: bool,
500 word: (TextPos, TextPos),
501 press_link: Option<usize>,
502 pending: i32,
503 t0: i32,
504 reveal: Option<(usize, i32, i32)>,
506 sb_y: i32,
508 sb_pos: i32,
509 refocus: Option<(usize, Option<usize>)>,
512 reguess: bool,
513}
514
515impl Default for MdViewState {
516 fn default() -> MdViewState {
517 MdViewState::new()
518 }
519}
520
521fn text_hash(s: &str) -> u64 {
523 let b = s.as_bytes();
524 let mut h: u64 = 0xcbf2_9ce4_8422_2325;
525 let mut it = b.chunks_exact(8);
526 for c in &mut it {
527 let v = u64::from_le_bytes([c[0], c[1], c[2], c[3], c[4], c[5], c[6], c[7]]);
528 h = (h ^ v).wrapping_mul(0x0000_0100_0000_01B3).rotate_left(29);
529 }
530 for &x in it.remainder() {
531 h = (h ^ x as u64).wrapping_mul(0x0000_0100_0000_01B3);
532 }
533 h ^ b.len() as u64
534}
535
536fn rtl_char(c: char) -> bool {
538 matches!(c as u32, 0x0590..=0x08FF | 0xFB1D..=0xFDFF | 0xFE70..=0xFEFF | 0x10800..=0x10FFF | 0x1E800..=0x1EFFF)
539}
540
541fn text_rtl(s: &str) -> Option<bool> {
543 s.chars().find(|c| c.is_alphabetic()).map(rtl_char)
544}
545
546fn opaque(c: u32) -> u32 {
547 if c >> 24 == 0 { c | 0xFF00_0000 } else { c }
548}
549
550fn on_bg(bg: u32) -> (u32, u32, u32) {
553 let (r, g, b) = ((bg >> 16) & 255, (bg >> 8) & 255, bg & 255);
554 if r * 2126 + g * 7152 + b * 722 > 255 * 5500 {
555 (0xFF1B_1B1B, 0xFF5C_5C5C, 0xFF0A_55B0)
556 } else {
557 (0xFFF2_F2F2, 0xFFBE_BEBE, 0xFF8A_B4F8)
558 }
559}
560
561impl MdViewState {
562 pub fn new() -> MdViewState {
564 MdViewState {
565 doc: MdDoc::default(),
566 src: (0, 0, 0),
567 base: String::new(),
568 label: String::new(),
569 images: MI_LOCAL,
570 img_block: Vec::new(),
571 vals: Vec::new(),
572 stops: Vec::new(),
573 group: Vec::new(),
574 ffocus: None,
575 rows: Rows::default(),
576 sel: TextSel::default(),
577 link: None,
578 lc: Cache::default(),
579 sized: false,
580 auto_rtl: None,
581 follow: false,
582 drag: 0,
583 dragged: false,
584 word: (TextPos::default(), TextPos::default()),
585 press_link: None,
586 pending: 0,
587 t0: 0,
588 reveal: None,
589 sb_y: i32::MIN,
590 sb_pos: 0,
591 refocus: None,
592 reguess: false,
593 }
594 }
595
596 pub fn doc(&self) -> &MdDoc {
598 &self.doc
599 }
600
601 pub fn doc_mut(&mut self) -> &mut MdDoc {
605 &mut self.doc
606 }
607
608 pub fn update(&mut self) -> bool {
611 self.sync()
612 }
613
614 pub fn set_markdown(&mut self, s: &str) {
616 let key = (1, text_hash(s), s.len());
617 if key != self.src {
618 self.src = key;
619 self.set_doc_inner(MdDoc::markdown(s));
620 }
621 }
622
623 pub fn set_html(&mut self, s: &str) {
625 let key = (2, text_hash(s), s.len());
626 if key != self.src {
627 self.src = key;
628 self.set_doc_inner(MdDoc::html(s));
629 }
630 }
631
632 pub fn set_doc(&mut self, doc: MdDoc) {
634 self.src = (3, 0, 0);
635 self.set_doc_inner(doc);
636 }
637
638 fn set_doc_inner(&mut self, doc: MdDoc) {
641 self.doc = doc;
642 self.doc.update();
643 self.doc.take_change();
644 self.refocus = None;
645 self.sel = TextSel::default();
646 self.link = None;
647 self.press_link = None;
648 self.drag = 0;
649 self.reveal = None;
650 self.lc.lays.clear();
651 self.lc.cols.clear();
652 self.lc.scaled.clear();
653 self.lc.svgs.clear();
654 self.lc.imgs = alloc::vec![Img::Unread; self.doc.images().len()];
655 self.blocks_index();
656 if !self.lc.req.is_empty() {
658 let used: BTreeSet<String> = self.doc.images().iter().filter_map(|m| img_at(&m.src, &self.base, self.lc.mode).1).collect();
659 self.lc.req.retain(|a, _| used.contains(a));
660 self.lc.queue.retain(|a| used.contains(a));
661 }
662 self.fields_init(&[]);
663 self.ffocus = None;
664 self.sized = false;
665 self.auto_rtl = self.doc.blocks().iter().filter(|b| b.kind != MB_CODE).find_map(|b| text_rtl(&b.text));
666 }
667
668 fn blocks_index(&mut self) {
670 self.lc.rows_of.clear();
671 self.img_block = alloc::vec![0; self.doc.images().len()];
672 for (k, b) in self.doc.blocks().iter().enumerate() {
673 if b.kind == MB_ROW {
674 self.lc.rows_of.entry(b.table).or_default().push(k);
675 }
676 for s in b.spans.iter().filter(|s| s.image != 0) {
677 if let Some(x) = self.img_block.get_mut(s.image as usize - 1) {
678 *x = k;
679 }
680 }
681 }
682 }
683
684 fn fields_init(&mut self, old: &[Val]) {
687 let by: BTreeMap<MdNode, &Val> = old.iter().filter(|v| !v.node.is_none()).map(|v| (v.node, v)).collect();
688 self.vals = self
689 .doc
690 .fields()
691 .iter()
692 .map(|f| match by.get(&f.node) {
693 Some(v) if v.dirty && v.kind == f.kind => {
694 let mut v = (*v).clone();
695 if v.sel >= f.options.len() as i32 {
696 v.sel = f.selected;
697 }
698 v
699 }
700 _ => Val {
701 text: if f.kind == MF_CHECKBOX || f.kind == MF_RADIO { String::new() } else { f.value.clone() },
702 on: f.checked,
703 sel: f.selected,
704 kind: f.kind,
705 node: f.node,
706 dirty: false,
707 },
708 })
709 .collect();
710 let mut first: BTreeMap<(u32, &str), usize> = BTreeMap::new();
711 self.group = alloc::vec![0; self.doc.fields().len()];
712 for (i, f) in self.doc.fields().iter().enumerate() {
713 if f.kind == MF_RADIO && !f.name.is_empty() {
714 self.group[i] = *first.entry((f.form, f.name.as_str())).or_insert(i) + 1;
715 }
716 }
717 let mut checked: BTreeMap<usize, usize> = BTreeMap::new();
720 for i in 0..self.vals.len() {
721 if self.vals[i].on && self.group[i] != 0 {
722 match checked.get(&self.group[i]).copied() {
723 Some(p) if self.vals[p].dirty && !self.vals[i].dirty => self.vals[i].on = false,
724 Some(p) => {
725 self.vals[p].on = false;
726 checked.insert(self.group[i], i);
727 }
728 None => {
729 checked.insert(self.group[i], i);
730 }
731 }
732 }
733 }
734 let (links, fields) = (self.doc.link_spots(), self.doc.field_spots());
735 self.stops = Vec::with_capacity(links.len() + fields.len());
736 let (mut a, mut b) = (0, 0);
737 while a < links.len() || b < fields.len() {
738 let take_link = b >= fields.len() || (a < links.len() && (links[a].block, links[a].a) <= (fields[b].block, fields[b].a));
739 if take_link {
740 self.stops.push(Stop::Link(a));
741 a += 1;
742 } else {
743 self.stops.push(Stop::Field(fields[b].field as usize - 1));
744 b += 1;
745 }
746 }
747 }
748
749 fn sync(&mut self) -> bool {
754 self.doc.update();
755 let Some(ch) = self.doc.take_change() else { return false };
756 let n = self.doc.blocks().len();
757 let range = ch.range;
759 let map = |k: usize| -> (usize, bool) {
760 match range {
761 Some((a, o, nn)) if k >= a + o => (k - o + nn, false),
762 Some((a, o, nn)) if k >= a => (a + (k - a).min(nn.saturating_sub(1)), o != nn),
763 _ => (k, false),
764 }
765 };
766 if let Some((a, o, nn)) = range {
767 let (top, off) = (self.rows.top, self.rows.off);
769 if o != nn {
770 self.rows.remove(a, o);
771 self.rows.insert(a, nn);
772 } else {
773 for k in a..a + o {
774 self.rows.forget(k);
775 }
776 }
777 self.rows.top = map(top).0.min(n.saturating_sub(1));
778 self.rows.off = off;
779 let lays = core::mem::take(&mut self.lc.lays);
780 for (k, l) in lays {
781 if k < a {
782 self.lc.lays.insert(k, l);
783 } else if k >= a + o {
784 self.lc.lays.insert(k - o + nn, l);
785 }
786 }
787 self.lc.lays.remove(&a.wrapping_sub(1));
789 self.lc.lays.remove(&(a + nn));
790 self.reguess = true;
791 }
792 for &k in &ch.redo {
793 self.lc.lays.remove(&k);
795 self.lc.lays.remove(&k.wrapping_sub(1));
796 self.lc.lays.remove(&(k + 1));
797 }
798 if ch.full {
799 let mut pics: BTreeMap<&str, Img> = BTreeMap::new();
801 for (m, img) in ch.images.iter().zip(self.lc.imgs.iter()) {
802 pics.entry(m.src.as_str()).or_insert_with(|| img.clone());
803 }
804 self.lc.imgs = self.doc.images().iter().map(|m| pics.get(m.src.as_str()).cloned().unwrap_or(Img::Unread)).collect();
805 self.lc.scaled.clear();
806 self.lc.svgs.clear();
807 self.lc.cols.clear();
808 let rows: Vec<usize> = self.lc.lays.keys().copied().filter(|&k| self.doc.blocks().get(k).is_some_and(|b| b.kind == MB_ROW)).collect();
809 for k in rows {
810 self.lc.lays.remove(&k);
811 }
812 self.blocks_index();
813 let old = core::mem::take(&mut self.vals);
814 let focus = self.ffocus.and_then(|i| old.get(i)).map(|v| v.node);
815 self.fields_init(&old);
816 if let Some(i) = self.ffocus {
817 let j = focus.and_then(|nd| self.doc.field_of(nd));
818 if j != Some(i) {
819 self.refocus = Some((i, j));
820 self.ffocus = j;
821 }
822 }
823 self.link = self.link.and_then(|k| {
824 let s = ch.spots.get(k)?;
825 let (b, moved) = map(s.block);
826 if moved {
827 return None;
828 }
829 self.doc.link_spots().iter().position(|t| t.block == b && t.a == s.a && t.link != 0)
830 });
831 self.press_link = None;
832 }
833 let mp = |p: TextPos| {
835 let (line, moved) = map(p.line);
836 TextPos::new(line, if moved { 0 } else { p.byte })
837 };
838 self.sel = TextSel::new(self.clamp(mp(self.sel.anchor)), self.clamp(mp(self.sel.caret)));
839 self.word = (self.clamp(mp(self.word.0)), self.clamp(mp(self.word.1)));
840 self.reveal = self.reveal.map(|(b, y0, y1)| (map(b).0, y0, y1));
841 self.auto_rtl = self.doc.blocks().iter().filter(|b| b.kind != MB_CODE).find_map(|b| text_rtl(&b.text));
842 true
843 }
844
845 pub fn selection(&self) -> TextSel {
847 self.sel
848 }
849
850 pub fn set_selection(&mut self, sel: TextSel) {
852 self.sel = TextSel::new(self.clamp(sel.anchor), self.clamp(sel.caret));
853 }
854
855 pub fn select_all(&mut self) {
857 self.sel = TextSel::new(TextPos::default(), self.doc.end());
858 }
859
860 pub fn selected_text(&self) -> String {
862 if self.sel.is_empty() {
863 return String::new();
864 }
865 self.doc.plain_range(self.sel.start(), self.sel.end())
866 }
867
868 pub fn selected_html(&self) -> String {
870 if self.sel.is_empty() {
871 return String::new();
872 }
873 self.doc.html_range(self.sel.start(), self.sel.end())
874 }
875
876 pub fn scroll_to_block(&mut self, i: usize) {
878 self.rows.top = i.min(self.doc.blocks().len().saturating_sub(1));
879 self.rows.off = 0;
880 self.pending = 0;
881 self.reveal = None;
882 self.follow = false;
883 }
884
885 pub fn scroll_to_end(&mut self) {
887 self.pending = 0;
888 self.reveal = None;
889 self.follow = true;
890 }
891
892 pub fn first_visible(&self) -> (usize, i32) {
894 (self.rows.top, self.rows.off)
895 }
896
897 pub fn focused_link(&self) -> Option<usize> {
899 self.link
900 }
901
902 pub fn image_requests(&mut self) -> Vec<String> {
907 core::mem::take(&mut self.lc.queue)
908 }
909
910 pub fn set_image(&mut self, addr: &str, data: Option<&[u8]>) -> bool {
914 let img = data.map_or(Img::Failed, |d| img_decode(d, addr));
915 self.lc.req.insert(String::from(addr), Req::Got(img.clone()));
916 self.lc.queue.retain(|a| a != addr);
917 let mut hit = false;
918 for i in 0..self.lc.imgs.len() {
919 if matches!(&self.lc.imgs[i], Img::Pending(a) if &**a == addr) {
920 self.lc.imgs[i] = img.clone();
921 self.lc.scaled.retain(|k, _| k.0 != i);
922 self.lc.svgs.retain(|k, _| k.0 != i);
923 let k = self.img_block.get(i).copied().unwrap_or(0);
926 match self.doc.blocks().get(k) {
927 Some(b) if b.kind == MB_ROW => {
928 self.lc.cols.remove(&b.table);
929 for r in self.lc.rows_of.get(&b.table).into_iter().flatten() {
930 self.lc.lays.remove(r);
931 }
932 }
933 _ => {
934 self.lc.lays.remove(&k);
935 }
936 }
937 hit = true;
938 }
939 }
940 hit
941 }
942
943 pub fn field_text(&self, i: usize) -> &str {
947 self.vals.get(i).map_or("", |v| v.text.as_str())
948 }
949
950 pub fn field_checked(&self, i: usize) -> bool {
952 self.vals.get(i).is_some_and(|v| v.on)
953 }
954
955 pub fn field_selected(&self, i: usize) -> i32 {
957 self.vals.get(i).map_or(-1, |v| v.sel)
958 }
959
960 pub fn set_field_text(&mut self, i: usize, s: &str) {
963 if let Some(v) = self.vals.get_mut(i) {
964 v.text = String::from(s);
965 v.dirty = true;
966 }
967 }
968
969 pub fn set_field_checked(&mut self, i: usize, on: bool) {
971 if i >= self.vals.len() {
972 return;
973 }
974 let g = self.group[i];
975 if on && g != 0 {
976 for (v, &h) in self.vals.iter_mut().zip(self.group.iter()) {
977 if h == g {
978 v.on = false;
979 v.dirty = true;
980 }
981 }
982 }
983 self.vals[i].on = on;
984 self.vals[i].dirty = true;
985 }
986
987 pub fn set_field_selected(&mut self, i: usize, k: i32) {
989 let n = self.doc.fields().get(i).map_or(0, |f| f.options.len()) as i32;
990 if let Some(v) = self.vals.get_mut(i) {
991 v.sel = if k >= 0 && k < n { k } else { -1 };
992 v.dirty = true;
993 }
994 }
995
996 pub fn form_data(&self, form: usize, button: Option<usize>) -> Vec<(String, String)> {
1000 let mut out = Vec::new();
1001 for (i, f) in self.doc.fields().iter().enumerate() {
1002 if f.form as usize != form + 1 || f.disabled || f.name.is_empty() {
1003 continue;
1004 }
1005 let v = &self.vals[i];
1006 let value = match f.kind {
1007 MF_CHECKBOX | MF_RADIO if v.on => f.value.clone(),
1008 MF_SELECT if v.sel >= 0 => f.options.get(v.sel as usize).map_or(String::new(), |o| o.value.clone()),
1009 MF_SUBMIT if button == Some(i) => f.value.clone(),
1010 MF_TEXT | MF_PASSWORD | MF_TEXTAREA | MF_HIDDEN => v.text.clone(),
1011 _ => continue,
1012 };
1013 out.push((f.name.clone(), value));
1014 }
1015 out
1016 }
1017
1018 pub fn reset_form(&mut self, form: usize) {
1020 for (i, f) in self.doc.fields().iter().enumerate() {
1021 if f.form as usize == form + 1 {
1022 let v = &mut self.vals[i];
1023 if f.kind != MF_CHECKBOX && f.kind != MF_RADIO {
1024 v.text = f.value.clone();
1025 }
1026 v.on = f.checked;
1027 v.sel = f.selected;
1028 v.dirty = false;
1029 }
1030 }
1031 }
1032
1033 pub fn focused_field(&self) -> Option<usize> {
1035 self.ffocus
1036 }
1037
1038 fn implicit(&self, i: usize) -> Option<MdSubmit> {
1041 let f = self.doc.fields().get(i)?;
1042 let form = (f.form as usize).checked_sub(1)?;
1043 let button = self.doc.fields().iter().position(|b| b.kind == MF_SUBMIT && b.form == f.form);
1044 if button.is_some_and(|b| self.doc.fields()[b].disabled) {
1045 return None;
1046 }
1047 Some(self.submit(form, button))
1048 }
1049
1050 fn submit(&self, form: usize, button: Option<usize>) -> MdSubmit {
1052 let fm = self.doc.forms().get(form);
1053 MdSubmit {
1054 form,
1055 action: fm.map_or(String::new(), |m| m.action.clone()),
1056 method: fm.map_or(String::from("get"), |m| m.method.clone()),
1057 fields: self.form_data(form, button),
1058 button,
1059 }
1060 }
1061
1062 fn step(&self, cur: Option<usize>, back: bool) -> Option<Stop> {
1066 let n = self.stops.len();
1067 let checked: BTreeMap<usize, usize> = (0..self.vals.len()).filter(|&j| self.group[j] != 0 && self.vals[j].on).map(|j| (self.group[j], j)).collect();
1068 let stop = |i: usize| {
1069 let Some(f) = self.doc.fields().get(i) else { return false };
1070 let g = self.group[i];
1071 !f.disabled && (g == 0 || checked.get(&g).copied().unwrap_or(g - 1) == i)
1072 };
1073 let mut k = cur;
1074 loop {
1075 k = match (k, back) {
1076 (None, false) => (n > 0).then_some(0),
1077 (None, true) => None,
1078 (Some(i), false) => (i + 1 < n).then_some(i + 1),
1079 (Some(i), true) => i.checked_sub(1),
1080 };
1081 let s = self.stops[k?];
1082 match s {
1083 Stop::Field(f) if !stop(f) => continue,
1084 _ => return Some(s),
1085 }
1086 }
1087 }
1088
1089 fn radio_step(&self, i: usize, back: bool) -> Option<usize> {
1091 let g = *self.group.get(i)?;
1092 if g == 0 {
1093 return None;
1094 }
1095 let members: Vec<usize> = (0..self.group.len()).filter(|&j| self.group[j] == g && !self.doc.fields()[j].disabled).collect();
1096 let p = members.iter().position(|&j| j == i)?;
1097 let n = members.len();
1098 Some(members[if back { (p + n - 1) % n } else { (p + 1) % n }])
1099 }
1100
1101 fn clamp(&self, p: TextPos) -> TextPos {
1102 let bl = self.doc.blocks();
1103 if bl.is_empty() {
1104 return TextPos::default();
1105 }
1106 let line = p.line.min(bl.len() - 1);
1107 let t = &bl[line].text;
1108 let mut b = p.byte.min(t.len());
1109 while !t.is_char_boundary(b) {
1110 b -= 1;
1111 }
1112 TextPos::new(line, b)
1113 }
1114}
1115
1116fn spot_of(spots: &[MdLinkSpot], k: usize, f: impl Fn(&MdLinkSpot) -> bool) -> Option<usize> {
1118 let i0 = spots.partition_point(|s| s.block < k);
1119 spots[i0..].iter().take_while(|s| s.block == k).position(f).map(|i| i0 + i)
1120}
1121
1122fn box_x(cx: i32, cw: i32, x: i32, w: i32, rtl: bool) -> i32 {
1124 if rtl { cx + cw - x - w } else { cx + x }
1125}
1126
1127impl Ui {
1128 #[cfg_attr(jfgui_dev, track_caller)]
1130 pub fn markdown_view(&mut self, id: u32, st: &mut MdViewState, text: &str, flags: u32) -> MdEv {
1131 let _src = self.src_scope();
1132 st.set_markdown(text);
1133 self.doc_view(id, st, flags)
1134 }
1135
1136 pub fn markdown_fit(&mut self, st: &mut MdViewState, text: &str, w_px: i32, flags: u32) -> i32 {
1139 st.set_markdown(text);
1140 let pad = self.dp(if flags & MV_NO_FRAME == 0 { PAD } else { PAD_BARE });
1141 let sbw = self.style.scrollbar_thickness(self);
1142 let hair = self.hair().max(1);
1143 let drtl = if flags & MV_RTL != 0 {
1144 true
1145 } else if flags & MV_LTR != 0 {
1146 false
1147 } else {
1148 st.auto_rtl.unwrap_or(self.rtl)
1149 };
1150 let cw = ((w_px - hair * 2).max(1) - pad * 2 - sbw).max(self.dp(80));
1152 st.sync();
1153 let fc = self.md_faces();
1154 let key = (self.wp.rt.dpi, cw, self.layout_scale(), drtl, fc);
1155 let n = st.doc.blocks().len();
1156 if key != st.lc.key || !st.sized || st.rows.len() != n {
1157 if key != st.lc.key {
1158 st.lc.lays.clear();
1159 st.lc.cols.clear();
1160 st.lc.scaled.clear();
1161 }
1162 st.lc.key = key;
1163 st.rows.forget_all();
1164 self.md_guess(st, cw, &fc);
1165 st.sized = true;
1166 }
1167 for k in 0..n {
1168 self.md_lay(st, k, cw, drtl, &fc);
1169 }
1170 let total = st.rows.total().clamp(0, i32::MAX as i64 / 2) as i32;
1171 self.px2dip(total + hair * 2) + 2
1172 }
1173
1174 #[cfg_attr(jfgui_dev, track_caller)]
1176 pub fn html_view(&mut self, id: u32, st: &mut MdViewState, html: &str, flags: u32) -> MdEv {
1177 let _src = self.src_scope();
1178 st.set_html(html);
1179 self.doc_view(id, st, flags)
1180 }
1181
1182 fn md_faces(&mut self) -> Faces {
1185 self.ts_font(TS_BODY);
1186 let (_, size) = self.text.get_font();
1187 let mut f = Faces { size, ..Faces::default() };
1188 for mono in 0..2 {
1189 for k in 0..4 {
1190 let fam = if mono == 1 { "monospace" } else { "" };
1191 f.f[mono][k] = self.rt_face(fam, k & 1 != 0, k & 2 != 0);
1192 }
1193 }
1194 f
1195 }
1196
1197 fn md_metrics(&mut self, font: u32, size: i32) -> (i32, i32, i32) {
1199 self.text.set_font(font, size);
1200 let (asc, desc) = (self.text.ascent(), self.text.descent());
1201 (asc, desc, self.text.line_h().max(asc + desc))
1202 }
1203
1204 fn md_img(&mut self, lc: &mut Cache, doc: &MdDoc, base: &str, i: usize) -> Img {
1207 if let Some(Img::Unread) = lc.imgs.get(i) {
1208 let src = doc.images().get(i).map_or("", |m| m.src.as_str()).trim();
1209 let img = match img_at(src, base, lc.mode) {
1210 (Some(path), _) => match img_file_ok(&path).then(|| plat::read_file(&path)).flatten() {
1211 Some(d) => img_decode(&d, src),
1212 None => Img::Failed,
1213 },
1214 (None, Some(addr)) => match lc.req.get(&addr) {
1215 Some(Req::Got(img)) => img.clone(),
1216 Some(Req::Asked) => Img::Pending(Rc::from(addr.as_str())),
1217 None => {
1218 lc.queue.push(addr.clone());
1219 let p = Img::Pending(Rc::from(addr.as_str()));
1220 lc.req.insert(addr, Req::Asked);
1221 p
1222 }
1223 },
1224 _ => Img::Failed,
1225 };
1226 lc.imgs[i] = img;
1227 }
1228 lc.imgs.get(i).cloned().unwrap_or(Img::Failed)
1229 }
1230
1231 fn md_img_size(&mut self, lc: &mut Cache, doc: &MdDoc, base: &str, i: usize, avail: i32, fc: &Faces) -> (i32, i32) {
1234 let m = doc.images().get(i);
1235 let (aw, ah) = m.map_or((0, 0), |m| (m.w as i32, m.h as i32));
1236 let nat = match self.md_img(lc, doc, base, i) {
1237 Img::Bitmap(sf) => Some((sf.w as f64, sf.h as f64)),
1238 Img::Vector(s) => Some(s.size()),
1239 Img::Pending(_) if aw > 0 && ah > 0 => Some((aw as f64, ah as f64)),
1240 _ => None,
1241 };
1242 let (w, h) = match nat {
1243 Some((w, h)) if w > 0.0 && h > 0.0 => {
1244 let (w, h) = match (aw > 0, ah > 0) {
1245 (true, true) => (aw as f64, ah as f64),
1246 (true, false) => (aw as f64, aw as f64 * h / w),
1247 (false, true) => (ah as f64 * w / h, ah as f64),
1248 _ => (w, h),
1249 };
1250 (self.dp((w + 0.5).min(100_000.0) as i32).max(1), self.dp((h + 0.5).min(100_000.0) as i32).max(1))
1251 }
1252 _ => {
1253 let alt = m.map_or("", |m| m.alt.as_str());
1255 let alt = if alt.is_empty() { tr("Bild") } else { alt };
1256 let (_, _, lh) = self.md_metrics(fc.get(false, false, false), fc.size);
1257 let tw = self.text.width(alt);
1258 (tw + self.dp(16), lh + self.dp(8))
1259 }
1260 };
1261 if w > avail && avail > 0 {
1262 (avail, ((h as i64 * avail as i64) / w as i64).max(1) as i32)
1263 } else {
1264 (w, h)
1265 }
1266 }
1267
1268 fn md_field_size(&mut self, doc: &MdDoc, i: usize, avail: i32, fc: &Faces) -> (i32, i32, i32, i32) {
1272 let Some(f) = doc.fields().get(i) else { return (1, 1, 1, 0) };
1273 let st = self.style;
1274 self.ts_font(TS_BODY);
1275 let cw = self.text.width("0").max(1);
1276 let (w, h) = match f.kind {
1277 MF_TEXT | MF_PASSWORD | MF_TEXTAREA => {
1278 let multi = f.kind == MF_TEXTAREA;
1279 let lh = self.ts_line(TS_BODY);
1280 let mut sp = Sp { value2: lh, flags: multi as u32, ..Sp::default() };
1281 let (_, h) = st.textbox_metrics(self, &mut sp);
1282 let n = if f.size > 0 { f.size.min(1000) } else { FIELD_CHARS } as i32;
1283 let w = sp.s[0] + sp.s[1] + cw * n;
1284 if multi {
1285 let rows = if f.rows > 0 { f.rows.min(200) } else { FIELD_ROWS } as i32;
1286 (w, sp.s[2] * 2 + lh * rows)
1287 } else {
1288 self.ctrl_size(w, h, 0, false)
1289 }
1290 }
1291 MF_CHECKBOX | MF_RADIO => {
1292 let mut sp = Sp::default();
1293 if f.kind == MF_CHECKBOX {
1294 st.checkbox_size(self, &mut sp);
1295 } else {
1296 st.radio_size(self, &mut sp);
1297 }
1298 (sp.s[0], sp.s[0])
1299 }
1300 MF_SELECT => {
1301 self.ts_font(TS_BODY);
1302 let widest = f.options.iter().map(|o| self.text.width(&o.label)).max().unwrap_or(0);
1303 let mut sp = Sp { value: widest, ..Sp::default() };
1304 let (w, h) = st.combo_size(self, &mut sp);
1305 self.ctrl_size(w, h, 0, false)
1306 }
1307 MF_HIDDEN => (1, 1),
1308 _ => {
1309 let mut sp = Sp { label: Some(f.caption()), ..Sp::default() };
1310 let (w, h) = st.button_size(self, &mut sp);
1311 self.ctrl_size(w, h, 0, false)
1312 }
1313 };
1314 let (w, h) = (w.min(avail.max(1)).max(1), h.max(1));
1315 let (asc, desc, _) = self.md_metrics(fc.get(false, false, false), fc.size);
1316 let up = ((h + asc - desc) / 2).clamp(0, h);
1317 let gap = self.dp(FIELD_GAP);
1318 (w, h, up + gap, h - up + gap)
1319 }
1320
1321 #[allow(clippy::too_many_arguments)]
1323 fn md_segs(&mut self, lc: &mut Cache, doc: &MdDoc, base: &str, k: usize, a: usize, b: usize, scale: u32, bold: bool, avail: i32, fc: &Faces) -> Vec<Seg> {
1324 let bl = &doc.blocks()[k];
1325 let text = bl.text.as_str();
1326 let size = (fc.size as i64 * scale as i64 / 100) as i32;
1327 let mut out: Vec<Seg> = Vec::new();
1328 let mut metr: Vec<((u32, i32), (i32, i32, i32))> = Vec::new();
1329 let mut add = |ui: &mut Ui, lc: &mut Cache, s0: usize, s1: usize, sp: Option<&MdSpan>| {
1330 if s0 >= s1 {
1331 return;
1332 }
1333 let style = sp.map_or(0, |s| s.style);
1334 let code = style & MS_CODE != 0;
1335 let (bo, it) = (bold || style & MS_BOLD != 0, style & MS_ITALIC != 0);
1336 let shaped = text[s0..s1].chars().any(rtl_char);
1337 let mut font = fc.get(code, bo, it);
1338 if shaped {
1339 font = ui.md_rtl_font(font, &text[s0..s1], bo, it);
1340 }
1341 let sz = if code { size * 9 / 10 } else { size };
1342 let m = match metr.iter().find(|m| m.0 == (font, sz)) {
1343 Some(m) => m.1,
1344 None => {
1345 let m = ui.md_metrics(font, sz);
1346 metr.push(((font, sz), m));
1347 m
1348 }
1349 };
1350 let image = sp.map_or(0, |s| s.image);
1351 let (iw, ih) = if image != 0 { ui.md_img_size(lc, doc, base, image as usize - 1, avail, fc) } else { (0, 0) };
1352 let field = sp.map_or(0, |s| s.field);
1354 let (iw, ih, m) = if field != 0 {
1355 let (w, h, up, down) = ui.md_field_size(doc, field as usize - 1, avail, fc);
1356 (w, h, (up, down, up + down))
1357 } else {
1358 (iw, ih, m)
1359 };
1360 out.push(Seg {
1361 a: s0,
1362 b: s1,
1363 font,
1364 size: sz,
1365 asc: m.0,
1366 desc: m.1,
1367 lh: m.2,
1368 style,
1369 color: sp.map_or(0, |s| s.color),
1370 bg: sp.map_or(0, |s| s.bg),
1371 link: sp.map_or(0, |s| s.link),
1372 image,
1373 iw,
1374 ih,
1375 syn: 0,
1376 shaped,
1377 pad: if code { ui.dp_text(CODE_PAD) } else { 0 },
1378 field,
1379 });
1380 };
1381 let mut i = a;
1382 for s in &bl.spans {
1383 let (sa, sb) = ((s.a as usize).max(a), (s.b as usize).min(b));
1384 if sb <= sa {
1385 continue;
1386 }
1387 if sa > i {
1388 add(self, lc, i, sa, None);
1389 }
1390 add(self, lc, sa.max(i), sb, Some(s));
1391 i = i.max(sb);
1392 }
1393 if i < b {
1394 add(self, lc, i, b, None);
1395 }
1396 out
1397 }
1398
1399 fn md_rtl_font(&mut self, font: u32, s: &str, bold: bool, italic: bool) -> u32 {
1403 let Some(c) = s.chars().find(|&c| rtl_char(c) && c.is_alphabetic()) else { return font };
1404 if self.text.has(font, c as u32) {
1405 return font;
1406 }
1407 let Some(f) = self.text.fallback_for(c as u32) else { return font };
1408 let flags = if bold { FS_SYN_BOLD } else { 0 } | if italic { FS_SYN_ITALIC } else { 0 };
1409 if flags == 0 {
1410 return f;
1411 }
1412 let r = self.text.style(f, flags);
1413 if r >= 0 { r as u32 } else { f }
1414 }
1415
1416 fn md_code_segs(&mut self, text: &str, lang: &str, fc: &Faces) -> Vec<Seg> {
1418 let h = Highlighter::for_name(lang);
1419 let font = fc.get(true, false, false);
1420 let size = fc.size * 9 / 10;
1421 let (asc, desc, lh) = self.md_metrics(font, size);
1422 let fb = fc.get(true, true, false);
1423 let (basc, bdesc, blh) = self.md_metrics(fb, size);
1424 let theme = self.code_theme();
1425 let mut out: Vec<Seg> = Vec::new();
1426 let mut push = |a: usize, b: usize, kind: u8| {
1427 if a >= b {
1428 return;
1429 }
1430 let bold = theme.bold & (1 << kind) != 0;
1431 if let Some(l) = out.last_mut() {
1432 if l.b == a && l.syn == kind + 1 {
1433 l.b = b;
1434 return;
1435 }
1436 }
1437 let (f, m) = if bold { (fb, (basc, bdesc, blh)) } else { (font, (asc, desc, lh)) };
1438 out.push(Seg {
1439 a,
1440 b,
1441 font: f,
1442 size,
1443 asc: m.0,
1444 desc: m.1,
1445 lh: m.2,
1446 style: if theme.italic & (1 << kind) != 0 { MS_ITALIC } else { 0 },
1447 color: 0,
1448 bg: 0,
1449 link: 0,
1450 image: 0,
1451 iw: 0,
1452 ih: 0,
1453 syn: kind + 1,
1454 shaped: false,
1455 pad: 0,
1456 field: 0,
1457 });
1458 };
1459 let mut state = 0;
1460 let mut toks: Vec<Token> = Vec::new();
1461 let mut o = 0;
1462 for line in text.split('\n') {
1463 state = h.line(line, state, &mut toks);
1464 let mut i = 0;
1465 for t in &toks {
1466 let (ta, tb) = (t.start.min(line.len()), t.end.min(line.len()));
1467 if ta > i {
1468 push(o + i, o + ta, 0);
1469 }
1470 push(o + ta.max(i), o + tb, t.kind);
1471 i = i.max(tb);
1472 }
1473 push(o + i, o + line.len(), 0);
1474 if o + line.len() < text.len() {
1475 push(o + line.len(), o + line.len() + 1, 0);
1476 }
1477 o += line.len() + 1;
1478 }
1479 out
1480 }
1481
1482 #[allow(clippy::too_many_arguments)]
1487 fn md_flow(&mut self, text: &str, a: usize, b: usize, segs: &[Seg], avail: i32, align: u8, rtl: bool, empty: (i32, i32, i32), stops_too: bool) -> Flow {
1488 let tb = text.as_bytes();
1489 let shaping0 = self.text.set_shaping(false);
1490 let avail = avail.max(1);
1491 let lim = (avail as i64) << 8;
1492 let width = |ui: &mut Ui, x: usize, y: usize| -> i64 {
1494 let mut pen = 0i64;
1495 let k0 = segs.partition_point(|s| s.b <= x);
1496 for s in &segs[k0..] {
1497 if s.a >= y {
1498 break;
1499 }
1500 let (s0, s1) = (s.a.max(x), s.b.min(y));
1501 if s0 >= s1 {
1502 continue;
1503 }
1504 if s.image != 0 || s.field != 0 {
1505 pen += (s.iw as i64) << 8;
1506 continue;
1507 }
1508 ui.text.set_font(s.font, s.size);
1509 ui.text.set_shaping(s.shaped);
1510 pen += ui.text.measure_n(&tb[s0..], s1 - s0) as i64;
1511 if s.pad > 0 {
1512 pen += (((s0 == s.a) as i64) + ((s1 == s.b) as i64)) * ((s.pad as i64) << 8);
1513 }
1514 }
1515 pen
1516 };
1517 let vis = |e: usize, s: usize| {
1519 let mut v = e;
1520 while v > s && matches!(tb[v - 1], b' ' | b'\t' | b'\n') {
1521 v -= 1;
1522 }
1523 v
1524 };
1525 let sub = &text[a..b];
1526 let mut rows_ab: Vec<(usize, usize)> = Vec::new();
1527 if !sub.is_empty() {
1528 let ops: Vec<(usize, bool)> = segment::line_breaks(sub).into_iter().map(|o| (o.0 + a, o.1)).collect();
1529 let mut start = a;
1530 let mut x = 0i64;
1531 let mut prev = a;
1532 for &(o, mand) in &ops {
1533 if o <= prev {
1534 continue;
1535 }
1536 let atom = width(self, prev, vis(o, prev));
1537 if x + atom > lim && prev > start {
1538 rows_ab.push((start, prev));
1539 start = prev;
1540 x = 0;
1541 }
1542 if x == 0 && atom > lim {
1543 let (mut s, mut acc, mut e) = (start, 0i64, start);
1545 let ve = vis(o, start);
1546 while e < ve {
1547 let n = segment::grapheme_next(text, e).min(ve);
1548 let gw = width(self, e, n);
1549 if acc + gw > lim && e > s {
1550 rows_ab.push((s, e));
1551 s = e;
1552 acc = 0;
1553 }
1554 acc += gw;
1555 e = n;
1556 }
1557 start = s;
1558 }
1559 x += width(self, prev.max(start), o);
1560 prev = o;
1561 if mand && o < b {
1562 rows_ab.push((start, o));
1563 start = o;
1564 x = 0;
1565 }
1566 }
1567 if start < b || rows_ab.is_empty() {
1568 rows_ab.push((start, b));
1569 }
1570 } else {
1571 rows_ab.push((a, a));
1572 }
1573 let mut flow = Flow::default();
1574 let mut y = 0;
1575 for &(ra, rb) in &rows_ab {
1576 let mut pieces: Vec<Piece> = Vec::new();
1577 let (mut asc, mut desc, mut lh) = (0, 0, 0);
1578 let mut pen = 0i64;
1579 let k0 = segs.partition_point(|s| s.b <= ra);
1580 for s in &segs[k0..] {
1581 if s.a >= rb {
1582 break;
1583 }
1584 let (s0, s1) = (s.a.max(ra), s.b.min(rb));
1585 if s0 >= s1 {
1586 continue;
1587 }
1588 if s.pad > 0 && s0 == s.a {
1589 pen += (s.pad as i64) << 8;
1590 }
1591 let base = Piece {
1592 a: s0 as u32,
1593 b: s1 as u32,
1594 x: ((pen + 128) >> 8) as i32,
1595 w: 0,
1596 font: s.font,
1597 size: s.size,
1598 asc: s.asc,
1599 style: s.style,
1600 color: s.color,
1601 bg: s.bg,
1602 link: s.link,
1603 image: s.image,
1604 ih: s.ih,
1605 syn: s.syn,
1606 shaped: s.shaped,
1607 field: s.field,
1608 };
1609 if s.field != 0 {
1610 asc = asc.max(s.asc);
1611 desc = desc.max(s.desc);
1612 lh = lh.max(s.lh);
1613 pieces.push(Piece { w: s.iw, ..base });
1614 pen += (s.iw as i64) << 8;
1615 continue;
1616 }
1617 if s.image != 0 {
1618 asc = asc.max(s.ih);
1619 lh = lh.max(s.ih);
1620 pieces.push(Piece { w: s.iw, ..base });
1621 pen += (s.iw as i64) << 8;
1622 continue;
1623 }
1624 asc = asc.max(s.asc);
1625 desc = desc.max(s.desc);
1626 lh = lh.max(s.lh);
1627 self.text.set_font(s.font, s.size);
1628 self.text.set_shaping(s.shaped);
1629 let w = self.text.measure_n(&tb[s0..], s1 - s0) as i64;
1630 pieces.push(Piece { w: ((w + 255) >> 8) as i32, ..base });
1631 pen += w;
1632 if s.pad > 0 && s1 == s.b {
1633 pen += (s.pad as i64) << 8;
1634 }
1635 }
1636 if pieces.is_empty() {
1637 (asc, desc, lh) = empty;
1638 }
1639 let h = lh.max(asc + desc).max(1);
1640 let base = asc + (h - asc - desc) / 2;
1641 let ve = vis(rb, ra);
1642 let vis_w = ((width(self, ra, ve) + 255) >> 8) as i32;
1643 flow.w = flow.w.max(vis_w);
1644 if rtl {
1645 for p in pieces.iter_mut() {
1646 p.x = vis_w - p.x - p.w;
1647 }
1648 }
1649 let spare = (avail - vis_w).max(0);
1650 let off = match (align, rtl) {
1651 (1, _) => spare / 2,
1652 (0, true) | (2, false) => spare,
1653 _ => 0,
1654 };
1655 let mut stops: Vec<(u32, i32)> = Vec::new();
1658 for p in pieces.iter_mut() {
1659 p.x += off;
1660 if !stops_too {
1661 continue;
1662 }
1663 if p.image != 0 || p.field != 0 {
1664 let (l, r) = if rtl { (p.x + p.w, p.x) } else { (p.x, p.x + p.w) };
1665 stops.push((p.a, l));
1666 stops.push((p.b, r));
1667 continue;
1668 }
1669 let s = &tb[p.a as usize..p.b as usize];
1670 self.text.set_font(p.font, p.size);
1671 self.text.set_shaping(p.shaped);
1672 let long = s.len() > 512 && !p.shaped;
1673 let (mut g, mut acc) = (p.a as usize, 0i64);
1674 loop {
1675 let k = g - p.a as usize;
1676 let rel = if p.shaped {
1677 self.text.caret_x(s, k)
1678 } else if long {
1679 ((acc + 128) >> 8) as i32
1680 } else {
1681 (self.text.measure_n(s, k) + 128) >> 8
1682 };
1683 stops.push((g as u32, p.x + rel));
1684 if g >= p.b as usize {
1685 break;
1686 }
1687 let n = segment::grapheme_next(text, g).min(p.b as usize);
1688 if long {
1689 acc += self.text.measure_n(&tb[g..], n - g) as i64;
1690 }
1691 g = n;
1692 }
1693 }
1694 if stops.is_empty() {
1695 stops.push((ra as u32, if rtl { avail - off.min(avail) } else { off }));
1696 }
1697 stops.dedup_by_key(|s| s.0);
1698 flow.rows.push(LRow { a: ra as u32, b: rb as u32, y, h, base, pieces, stops });
1699 y += h;
1700 }
1701 flow.h = y;
1702 self.text.set_shaping(shaping0);
1703 flow
1704 }
1705
1706 fn md_cols(&mut self, lc: &mut Cache, doc: &MdDoc, base: &str, t: u32, cw: i32, fc: &Faces) -> Rc<Vec<i32>> {
1708 if let Some(c) = lc.cols.get(&t) {
1709 return c.clone();
1710 }
1711 let rows: Vec<usize> = lc.rows_of.get(&t).map(|v| v.iter().copied().take(TABLE_SAMPLE).collect()).unwrap_or_default();
1712 let ncols = doc.tables().get(t as usize).map_or(0, |x| x.aligns.len()).max(rows.iter().map(|&k| doc.blocks()[k].cells.len()).max().unwrap_or(0)).max(1);
1713 let pad = self.dp_text(CELL_PX) * 2;
1714 let mut nat = alloc::vec![self.dp(24); ncols];
1715 let empty = self.md_metrics(fc.get(false, false, false), fc.size);
1716 for &k in &rows {
1717 let bl = &doc.blocks()[k];
1718 for (c, &(ca, cb)) in bl.cells.iter().enumerate().take(ncols) {
1719 let segs = self.md_segs(lc, doc, base, k, ca as usize, cb as usize, 100, bl.head, cw, fc);
1720 let f = self.md_flow(&bl.text, ca as usize, cb as usize, &segs, 1 << 22, 0, false, empty, false);
1721 nat[c] = nat[c].max(f.w + pad);
1722 }
1723 }
1724 let total: i64 = nat.iter().map(|&w| w as i64).sum();
1725 let mut wd = nat.clone();
1726 if total > cw as i64 {
1727 let mut fixed = alloc::vec![false; ncols];
1729 loop {
1730 let free: i64 = cw as i64 - (0..ncols).filter(|&c| fixed[c]).map(|c| nat[c] as i64).sum::<i64>();
1731 let n = fixed.iter().filter(|f| !**f).count().max(1) as i64;
1732 let share = (free / n).max(self.dp(48) as i64);
1733 let mut changed = false;
1734 for c in 0..ncols {
1735 if !fixed[c] && (nat[c] as i64) <= share {
1736 fixed[c] = true;
1737 changed = true;
1738 }
1739 }
1740 if !changed {
1741 for c in 0..ncols {
1742 if !fixed[c] {
1743 wd[c] = share as i32;
1744 }
1745 }
1746 break;
1747 }
1748 }
1749 }
1750 let rc = Rc::new(wd);
1751 lc.cols.insert(t, rc.clone());
1752 rc
1753 }
1754
1755 #[allow(clippy::too_many_arguments)]
1757 fn md_block(&mut self, lc: &mut Cache, doc: &MdDoc, base: &str, k: usize, cw: i32, drtl: bool, fc: &Faces) -> BLay {
1758 let bl = &doc.blocks()[k];
1759 let depth = (bl.quote as usize + bl.list as usize).min(32);
1760 let css: Vec<(u32, &MdBox)> = bl.boxes.iter().filter_map(|&i| doc.boxes().get(i as usize).map(|b| (i, b))).collect();
1764 let lr = |v: [i32; 4]| if drtl { (v[1], v[3]) } else { (v[3], v[1]) };
1765 let mut geo: Vec<(u32, i32, i32)> = Vec::new();
1766 let (mut x, mut right, mut bi, mut mx) = (0, 0, 0, 0);
1767 let mut bars = Vec::new();
1768 for lv in 0..=depth {
1769 if lv == depth {
1770 mx = x;
1771 }
1772 while bi < css.len() && (css[bi].1.depth as usize <= lv || lv == depth) {
1773 let (i, b) = css[bi];
1774 let (ms, me) = lr(b.margin.map(|v| self.dp_text(v as i32)));
1775 let (ps, pe) = lr(b.padding.map(|v| self.dp_text(v as i32)));
1776 let (bs, be) = lr(b.border.map(|v| self.dp(v as i32)));
1777 x += ms;
1778 right += me;
1779 geo.push((i, x, right));
1780 x += bs + ps;
1781 right += be + pe;
1782 bi += 1;
1783 }
1784 if lv == depth {
1785 break;
1786 }
1787 if bl.path >> lv & 1 != 0 {
1788 bars.push(x + self.dp(2));
1789 x += self.dp_text(QUOTE_W);
1790 } else {
1791 x += self.dp_text(LIST_W);
1792 }
1793 }
1794 let w = (cw - x - right).max(self.dp(48));
1795 let mut gap = match bl.space {
1796 0 => 0,
1797 1 => self.dp_text(TIGHT_GAP),
1798 _ => self.dp_text(PARA_GAP),
1799 };
1800 let rtl = text_rtl(&bl.text).unwrap_or(drtl);
1801 let empty = self.md_metrics(fc.get(false, false, false), fc.size);
1802 let align = match (bl.align, rtl) {
1803 (1, _) => 1,
1804 (2, false) | (0, true) => 2,
1805 _ => 0,
1806 };
1807 let falign = if rtl { [0, 1, 0][align as usize] } else { align };
1809 let falign = if bl.align == 2 && rtl { 2 } else { falign };
1810 let mut boxes = Vec::new();
1811 let ch;
1812 match bl.kind {
1813 MB_HEADING => {
1814 let lv = bl.level.clamp(1, 6) as usize;
1815 if bl.space > 0 {
1816 gap = gap.max(self.dp_text(if lv <= 2 { 22 } else { 16 }));
1817 }
1818 let scale = HEAD_SCALE[lv - 1];
1819 let segs = self.md_segs(lc, doc, base, k, 0, bl.text.len(), scale, true, w, fc);
1820 let hm = self.md_metrics(fc.get(false, true, false), (fc.size as i64 * scale as i64 / 100) as i32);
1821 let flow = self.md_flow(&bl.text, 0, bl.text.len(), &segs, w, falign, rtl, hm, true);
1822 ch = flow.h + if lv <= 2 { self.dp(10) } else { 0 };
1823 boxes.push(FBox { x, y: 0, w, flow });
1824 }
1825 MB_CODE => {
1826 let (px, py) = (self.dp_text(CODE_PX), self.dp_text(CODE_PY));
1827 let segs = self.md_code_segs(&bl.text, &bl.lang, fc);
1828 let mono = self.md_metrics(fc.get(true, false, false), fc.size * 9 / 10);
1829 let flow = self.md_flow(&bl.text, 0, bl.text.len(), &segs, w - px * 2, 0, false, mono, true);
1830 ch = flow.h + py * 2;
1831 boxes.push(FBox { x: x + px, y: py, w: w - px * 2, flow });
1832 }
1833 MB_RULE => {
1834 ch = self.dp_text(RULE_H);
1835 }
1836 MB_ROW => {
1837 let cols = self.md_cols(lc, doc, base, bl.table, w, fc);
1838 let aligns = doc.tables().get(bl.table as usize).map(|t| t.aligns.clone()).unwrap_or_default();
1839 let (px, py) = (self.dp_text(CELL_PX), self.dp_text(CELL_PY));
1840 let mut cx = x;
1841 let mut hmax = 0;
1842 for (c, &cwd) in cols.iter().enumerate() {
1843 let (ca, cb) = bl.cells.get(c).map_or((bl.text.len(), bl.text.len()), |&(a, b)| (a as usize, b as usize));
1844 let segs = self.md_segs(lc, doc, base, k, ca, cb, 100, bl.head, cwd - px * 2, fc);
1845 let al = match aligns.get(c).copied().unwrap_or(MA_NONE) {
1847 MA_CENTER => 1,
1848 MA_RIGHT => if rtl { 0 } else { 2 },
1849 MA_LEFT => if rtl { 2 } else { 0 },
1850 _ => 0,
1851 };
1852 let flow = self.md_flow(&bl.text, ca, cb, &segs, cwd - px * 2, al, rtl, empty, true);
1853 hmax = hmax.max(flow.h);
1854 boxes.push(FBox { x: cx + px, y: py, w: cwd - px * 2, flow });
1855 cx += cwd;
1856 }
1857 ch = hmax + py * 2;
1858 if rtl != drtl {
1860 let tw = cx - x;
1861 for b in boxes.iter_mut() {
1862 b.x = x + tw - (b.x - x) - b.w;
1863 }
1864 }
1865 }
1866 _ => {
1867 let segs = self.md_segs(lc, doc, base, k, 0, bl.text.len(), 100, false, w, fc);
1868 let flow = self.md_flow(&bl.text, 0, bl.text.len(), &segs, w, falign, rtl, empty, true);
1869 ch = flow.h;
1870 boxes.push(FBox { x, y: 0, w, flow });
1871 }
1872 }
1873 if geo.is_empty() && (k == 0 || doc.blocks()[k - 1].boxes.is_empty()) {
1874 return BLay { h: gap + ch, gap, ch, x, w, boxes, bars, css: Vec::new(), mx };
1875 }
1876 let starts = |i: u32| doc.box_ends(i).0 == k;
1879 let ends = |i: u32| doc.box_ends(i).1 == k;
1880 let own_mt = css.iter().any(|&(i, b)| starts(i) && b.mset & 1 != 0);
1881 let prev_mb = k > 0 && doc.blocks()[k - 1].boxes.iter().any(|&i| doc.box_ends(i).1 == k - 1 && doc.boxes().get(i as usize).is_some_and(|b| b.mset & 4 != 0));
1882 if own_mt || prev_mb {
1883 gap = 0;
1884 }
1885 let mut y = gap;
1887 let mut out: Vec<CssBox> = Vec::with_capacity(geo.len());
1888 for &(i, bx, br) in &geo {
1889 let b = &doc.boxes()[i as usize];
1890 let first = starts(i);
1891 let y0 = if first { y + self.dp_text(b.margin[0] as i32) } else { 0 };
1892 if first {
1893 y = y0 + self.dp(b.border[0] as i32) + self.dp_text(b.padding[0] as i32);
1894 }
1895 out.push(CssBox { b: i, x: bx, w: (cw - bx - br).max(1), y0, y1: 0, first, last: ends(i) });
1896 }
1897 let top = y;
1898 let mut yb = top + ch;
1899 for g in out.iter_mut().rev() {
1900 if g.last {
1901 let b = &doc.boxes()[g.b as usize];
1902 yb += self.dp_text(b.padding[2] as i32) + self.dp(b.border[2] as i32);
1903 g.y1 = yb;
1904 yb += self.dp_text(b.margin[2] as i32);
1905 }
1906 }
1907 for g in out.iter_mut().filter(|g| !g.last) {
1908 g.y1 = yb;
1909 }
1910 BLay { h: yb, gap: top, ch, x, w, boxes, bars, css: out, mx }
1911 }
1912
1913 fn md_lay(&mut self, st: &mut MdViewState, k: usize, cw: i32, drtl: bool, fc: &Faces) -> Rc<BLay> {
1915 if let Some(l) = st.lc.lays.get(&k) {
1916 return l.clone();
1917 }
1918 let l = Rc::new(self.md_block(&mut st.lc, &st.doc, &st.base, k, cw, drtl, fc));
1919 st.rows.set(k, l.h);
1920 st.lc.lays.insert(k, l.clone());
1921 l
1922 }
1923
1924 fn md_guess(&mut self, st: &mut MdViewState, cw: i32, fc: &Faces) {
1926 let px = (fc.size >> 8).max(1);
1927 let lh = px * 14 / 10;
1928 let cpl = ((cw / ((px / 2).max(1))).max(8)) as usize;
1929 let (gp, gt) = (self.dp_text(PARA_GAP), self.dp_text(TIGHT_GAP));
1930 let (cpy, cell) = (self.dp_text(CODE_PY) * 2, self.dp_text(CELL_PY) * 2);
1931 let rule = self.dp_text(RULE_H);
1932 let n = st.doc.blocks().len();
1933 st.rows.resize(n);
1934 for k in 0..n {
1935 let b = &st.doc.blocks()[k];
1936 let gap = match b.space {
1937 0 => 0,
1938 1 => gt,
1939 _ => gp,
1940 };
1941 let h = match b.kind {
1942 MB_CODE => (b.text.bytes().filter(|&c| c == b'\n').count() as i32 + 1) * lh + cpy,
1943 MB_RULE => rule,
1944 MB_ROW => lh + cell,
1945 MB_HEADING => {
1946 let s = HEAD_SCALE[b.level.clamp(1, 6) as usize - 1] as i32;
1947 lh * s / 100 * (b.text.len() / cpl.max(1) + 1) as i32 + gp
1948 }
1949 _ => lh * ((b.text.len() / cpl.max(1)) as i32 + 1),
1950 };
1951 st.rows.guess(k, gap + h.max(1));
1952 }
1953 }
1954
1955 #[cfg_attr(jfgui_dev, track_caller)]
1959 pub fn doc_view(&mut self, id: u32, st: &mut MdViewState, flags: u32) -> MdEv {
1960 let _src = self.src_scope();
1961 let mut out = MdEv::default();
1962 let stflags = self.take_flags();
1963 let r = self.place(self.dp(NAT_W), self.dp(NAT_H));
1964 let frame = flags & MV_NO_FRAME == 0;
1965 let pad = self.dp(if frame { PAD } else { PAD_BARE });
1966 let sbw = self.style.scrollbar_thickness(self);
1967 let hair = self.hair().max(1);
1968 let drtl = if flags & MV_RTL != 0 {
1969 true
1970 } else if flags & MV_LTR != 0 {
1971 false
1972 } else {
1973 st.auto_rtl.unwrap_or(self.rtl)
1974 };
1975 let (vx, vy, vw, vh) = (r[0] + hair, r[1] + hair, (r[2] - hair * 2).max(1), (r[3] - hair * 2).max(1));
1977 let cw = (vw - pad * 2 - sbw).max(self.dp(80));
1978 let cx = if self.rtl { vx + pad + sbw } else { vx + pad };
1980 let view_h = vh;
1981 if st.lc.mode != st.images {
1983 st.lc.mode = st.images;
1984 st.lc.imgs.iter_mut().for_each(|m| *m = Img::Unread);
1985 st.lc.lays.clear();
1986 st.lc.cols.clear();
1987 st.lc.scaled.clear();
1988 st.lc.svgs.clear();
1989 }
1990 st.sync();
1993 if let Some((old, new)) = st.refocus.take() {
1994 let fid = md_field_id(id, old);
1995 if self.wp.ctx.focus == fid {
1996 let nid = new.map_or(id, |j| md_field_id(id, j));
1997 self.wp.ctx.focus = nid;
1998 self.tb_rekey(fid, nid);
1999 }
2000 }
2001 let fc = self.md_faces();
2003 let key = (self.wp.rt.dpi, cw, self.layout_scale(), drtl, fc);
2004 let n = st.doc.blocks().len();
2005 if key != st.lc.key || !st.sized || st.rows.len() != n {
2006 if key != st.lc.key {
2007 st.lc.lays.clear();
2008 st.lc.cols.clear();
2009 st.lc.scaled.clear();
2010 }
2011 st.lc.key = key;
2012 st.rows.forget_all();
2013 self.md_guess(st, cw, &fc);
2014 st.sized = true;
2015 st.rows.clamp(view_h);
2016 } else if st.reguess {
2017 self.md_guess(st, cw, &fc);
2019 st.rows.clamp(view_h);
2020 }
2021 st.reguess = false;
2022 self.wp.ctx.last_rect = r;
2024 let mut hb = HB_FOCUS | HB_OWNKEYS | HB_MOVES | CUR_IBEAM << HB_CURSOR_SHIFT;
2026 if stflags & ST_DISABLED != 0 {
2027 hb |= HB_DISABLED;
2028 }
2029 let hit0 = self.wp.ctx.hits[self.wp.ctx.hit_cur].len();
2030 let ev = self.behave(id, hb);
2031 let disabled = self.wp.ctx.last_state == SS_DISABLED as u32;
2032 let focused = !disabled && self.wp.ctx.focus == id;
2033 out.ev = ev & (EV_HOVER | EV_FOCUS);
2034 if !focused {
2035 st.link = None;
2036 }
2037 let sel0 = st.sel;
2038 let mut activate: Option<usize> = None;
2039 let mut clicked = false;
2040 if focused && n > 0 {
2042 activate = self.md_keys(st, id, view_h, cw, drtl, &fc);
2043 }
2044 if st.pending != 0 {
2046 let p0 = st.pending;
2047 if self.anim_off {
2048 st.pending = 0;
2049 } else {
2050 let steps = anim_steps(self.now_ms, &mut st.t0);
2051 st.pending = anim_decay(p0, 0, steps);
2052 }
2053 let p = st.rows.pos() + (p0 - st.pending) as i64;
2054 st.rows.set_pos(p);
2055 st.follow = false;
2056 if st.pending != 0 {
2057 self.animating = true;
2058 }
2059 }
2060 let (_, pan) = self.pan_take(id);
2062 if pan != 0 {
2063 st.pending = 0;
2064 st.reveal = None;
2065 st.follow = false;
2066 let p = st.rows.pos() + pan as i64;
2067 st.rows.set_pos(p);
2068 }
2069 let mut vis = self.md_walk(st, view_h, cw, drtl, &fc);
2071 let (mx, my) = (self.rtl_map(self.wp.ctx.mx, vx, vw), self.wp.ctx.my);
2073 let c = &self.wp.ctx;
2074 let press = c.pressed & (1 << BTN_LEFT) != 0 && ev & EV_HOVER != 0 && c.active == id;
2075 let held = ev & EV_HELD != 0;
2076 if press && !disabled {
2077 let p = self.md_hit(st, &vis, mx - cx, my - vy, cw, drtl);
2078 st.press_link = self.md_link_at(st, &vis, mx, my, cx, vy, cw, drtl);
2079 st.dragged = false;
2080 if let Some(p) = p {
2081 if ev & EV_DOUBLE != 0 && st.press_link.is_none() {
2082 let t = &st.doc.blocks()[p.line].text;
2083 let (a, b) = word_range(t, p.byte);
2084 st.word = (TextPos::new(p.line, a), TextPos::new(p.line, b));
2085 st.sel = TextSel::new(st.word.0, st.word.1);
2086 st.drag = 2;
2087 } else if self.mods & MOD_SHIFT != 0 {
2088 st.sel = TextSel::new(st.sel.anchor, p);
2089 st.drag = 1;
2090 } else {
2091 st.sel = TextSel::at(p);
2092 st.drag = 1;
2093 }
2094 }
2095 } else if held && st.drag != 0 {
2096 if ev & EV_DRAG != 0 {
2097 st.dragged = true;
2098 }
2099 let edge = if my < vy {
2101 my - vy
2102 } else if my >= vy + view_h {
2103 my - (vy + view_h) + 1
2104 } else {
2105 0
2106 };
2107 if edge != 0 && st.dragged {
2108 let d = edge.signum() * (edge.abs() / 2).clamp(self.dp(4), view_h / 4);
2109 let p = st.rows.pos() + d as i64;
2110 st.rows.set_pos(p.max(0));
2111 st.rows.clamp(view_h);
2112 st.follow = false;
2113 self.animating = true;
2114 vis = self.md_walk(st, view_h, cw, drtl, &fc);
2115 }
2116 let yy = my.clamp(vy, vy + view_h - 1);
2117 if let Some(p) = self.md_hit(st, &vis, mx - cx, yy - vy, cw, drtl) {
2118 if st.drag == 2 {
2119 let t = &st.doc.blocks()[p.line].text;
2120 let (a, b) = word_range(t, p.byte);
2121 let (wa, wb) = (TextPos::new(p.line, a), TextPos::new(p.line, b));
2122 st.sel = if wa < st.word.0 { TextSel::new(st.word.1, wa) } else { TextSel::new(st.word.0, wb.max(st.word.1)) };
2123 } else {
2124 st.sel = TextSel::new(st.sel.anchor, p);
2125 }
2126 }
2127 } else if !held {
2128 if ev & EV_ACT != 0 && !st.dragged {
2129 clicked = true;
2130 if let Some(k) = st.press_link {
2131 if self.md_link_at(st, &vis, mx, my, cx, vy, cw, drtl) == Some(k) {
2132 activate = Some(k);
2133 st.sel = TextSel::at(st.sel.caret);
2134 }
2135 }
2136 }
2137 st.drag = 0;
2138 st.press_link = None;
2139 }
2140 if let Some(k) = activate {
2141 if let Some(sp) = st.doc.link_spots().get(k).copied() {
2142 let url = String::from(st.doc.link(sp.link));
2143 if flags & MV_OPEN_LINKS != 0 {
2144 self.open_doc_link(&url);
2145 }
2146 out.link = Some(url);
2147 out.ev |= EV_ACT;
2148 out.click = (!clicked).then(|| st.doc.element_at(sp.block, sp.a as usize)).filter(|n| !n.is_none());
2149 self.repass();
2150 }
2151 }
2152 if clicked {
2153 let n = self.md_element_at(st, &vis, mx - cx, my - vy, cw, drtl);
2154 out.click = (!n.is_none()).then_some(n);
2155 self.repass();
2156 }
2157 if st.sel != sel0 {
2158 self.repass();
2159 }
2160 let st_ = self.style;
2162 if frame {
2163 let mut sp = Sp { flags: BOX_CARD, ..Sp::default() };
2164 sp.place(r);
2165 st_.box_deco(self, &sp);
2166 }
2167 self.clip_push(vx, vy, vw, vh);
2168 self.ltr_begin(vx, vw);
2169 let copy = st.rows.total() > view_h as i64;
2171 if copy {
2172 self.scroll_copy_begin(id, vx as i64, vy as i64 - st.rows.pos());
2173 }
2174 let bgc = self.md_bg(frame);
2175 let mut links: Vec<Link> = Vec::new();
2176 let mut fields: Vec<(usize, [i32; 4], u32)> = Vec::new();
2177 let mut hover: Option<usize> = None;
2178 let mut tab = None;
2179 for &(k, y) in &vis {
2180 let lay = st.lc.lays.get(&k).cloned();
2181 if let Some(lay) = lay {
2182 self.md_draw_block(&st.doc, &mut st.lc, st.sel, k, &lay, cx, vy + y, cw, drtl, focused, bgc, (vy, vy + view_h), &mut links, &mut fields, &mut tab);
2183 }
2184 }
2185 if let (Some(f), true) = (st.link, focused && self.wp.ctx.focus_visible) {
2187 for l in &links {
2188 if l.spot == f {
2189 let st_ = self.style;
2190 let lr = l.r;
2191 st_.focus_ring(self, [lr[0] - self.dp(1), lr[1], lr[2] + self.dp(2), lr[3]], 2);
2192 }
2193 }
2194 }
2195 if copy {
2196 self.scroll_copy_end();
2197 }
2198 self.ltr_end();
2199 for l in &links {
2201 let lr = l.logical;
2202 self.wp.ctx.last_rect = lr;
2203 let before = self.wp.ctx.hits[self.wp.ctx.hit_cur].len();
2204 self.hit(id, hb & ((1 << HB_CURSOR_SHIFT) - 1) | CUR_HAND << HB_CURSOR_SHIFT);
2205 if let Some(h) = self.wp.ctx.hits[self.wp.ctx.hit_cur].get_mut(before) {
2206 h.continuation = true;
2207 }
2208 let (pmx, pmy) = (self.wp.ctx.mx, self.wp.ctx.my);
2209 if ev & EV_HOVER != 0 && pmx >= lr[0] && pmx < lr[0] + lr[2] && pmy >= lr[1] && pmy < lr[1] + lr[3] {
2210 hover = Some(l.spot);
2211 }
2212 self.wp.ctx.last_state = if st.link == Some(l.spot) && focused { SS_FOCUSED as u32 } else { 0 };
2213 self.sem(K_LINK, 0, Some(&l.label), 0);
2214 }
2215 self.md_fields(st, id, &fields, disabled, view_h, cw, drtl, &fc, &mut out);
2217 self.clip_pop();
2218 if let Some(k) = hover {
2219 if let Some(sp) = st.doc.link_spots().get(k) {
2220 out.hover = Some(String::from(st.doc.link(sp.link)));
2221 }
2222 }
2223 let total = st.rows.total().min(i32::MAX as i64) as i32;
2225 if total > view_h {
2226 let sid = part_id(id, 1);
2230 let c = &self.wp.ctx;
2231 let (py, press) = (c.my, c.pressed & (1 << BTN_LEFT) != 0);
2232 let held = c.active == sid && c.mbtn & (1 << BTN_LEFT) != 0;
2233 let mut pos = if held && !press && py == st.sb_y { st.sb_pos } else { st.rows.pos().clamp(0, i32::MAX as i64) as i32 };
2234 self.next_rect_px(vx + vw - sbw, vy, sbw, vh);
2235 let sev = self.scrollbar(sid, &mut pos, total, view_h, true);
2236 if sev & EV_ACT != 0 && (press || py != st.sb_y) {
2237 st.rows.set_pos(pos as i64);
2238 st.rows.clamp(view_h);
2239 st.pending = 0;
2240 st.reveal = None;
2241 st.follow = false;
2242 self.repass();
2243 }
2244 st.sb_y = py;
2245 st.sb_pos = pos;
2246 }
2247 let wy = self.wp.ctx.wheel_y;
2249 let over = ev & EV_HOVER != 0 || field_of(id, self.wp.ctx.hot, st.doc.fields().len()).is_some();
2250 if over && wy != 0 {
2251 let d = wy.wrapping_neg().wrapping_mul(self.dp(WHEEL_DIP)) / 120;
2252 let max = (st.rows.total() - view_h as i64).max(0);
2253 let p = st.rows.pos();
2254 if (d < 0 && p > 0) || (d > 0 && p < max) {
2255 if st.pending == 0 {
2256 st.t0 = self.now_ms as i32;
2257 }
2258 st.pending += d;
2259 st.reveal = None;
2260 self.wp.ctx.wheel_y = 0;
2261 if self.anim_off {
2262 self.repass();
2263 } else {
2264 self.animating = true;
2265 }
2266 }
2267 }
2268 let max = (st.rows.total() - view_h as i64).clamp(0, i32::MAX as i64) as i32;
2270 let pos = st.rows.pos().clamp(0, i32::MAX as i64) as i32;
2271 self.pan_view(id, hit0, super::touch::AX_V, [0, pos], [0, max]);
2272 self.wp.ctx.last_rect = r;
2274 if self.focus_ring(ev) && st.link.is_none() {
2275 self.focus_ring_draw(ev);
2276 }
2277 let mut s = 0;
2278 if ev & EV_HOVER != 0 {
2279 s |= SS_HOVER as u32;
2280 }
2281 if focused {
2282 s |= SS_FOCUSED as u32;
2283 }
2284 if disabled {
2285 s |= SS_DISABLED as u32;
2286 }
2287 self.wp.ctx.last_state = s;
2288 let label = if st.label.is_empty() { String::from(tr("Dokument")) } else { st.label.clone() };
2289 let pos = self.px2dip(st.rows.pos().min(i32::MAX as i64) as i32);
2290 self.sem(K_BOX, id, Some(&label), pos);
2291 if st.lc.lays.len() > LAYS_KEEP {
2293 let top = st.rows.top;
2294 st.lc.lays.retain(|&k, _| k + LAYS_KEEP / 2 >= top && k <= top + LAYS_KEEP / 2);
2295 }
2296 self.wp.ctx.last_rect = r;
2297 out
2298 }
2299
2300 fn md_bg(&self, frame: bool) -> u32 {
2302 let bg = self.color(T_BG) | 0xFF00_0000;
2303 if !frame {
2304 return bg;
2305 }
2306 let c = self.color(T_CARD);
2307 let a = (c >> 24) as i32;
2308 color_mix(bg, c | 0xFF00_0000, a + (a >> 7))
2309 }
2310
2311 fn md_walk(&mut self, st: &mut MdViewState, view_h: i32, cw: i32, drtl: bool, fc: &Faces) -> Vec<(usize, i32)> {
2314 let n = st.doc.blocks().len();
2315 let mut vis = Vec::new();
2316 if n == 0 {
2317 return vis;
2318 }
2319 if st.reveal.is_some() {
2320 st.follow = false;
2321 }
2322 for _ in 0..6 {
2323 if st.follow {
2324 st.rows.set_pos(i64::MAX / 4);
2325 }
2326 if let Some((b, y0, y1)) = st.reveal {
2327 let b = b.min(n - 1);
2328 self.md_lay(st, b, cw, drtl, fc);
2330 let m = self.dp(8);
2331 let top = st.rows.prefix(b) - st.rows.pos();
2332 if top + (y0 as i64) < 0 || (y1 - y0) >= view_h {
2333 st.rows.set_pos(st.rows.prefix(b) + (y0 - m).max(0) as i64);
2334 } else if top + y1 as i64 > view_h as i64 {
2335 st.rows.set_pos(st.rows.prefix(b) + (y1 + m - view_h) as i64);
2336 }
2337 }
2338 st.rows.clamp(view_h);
2339 vis.clear();
2340 let (t0, o0) = (st.rows.top, st.rows.off);
2341 let mut k = st.rows.top;
2342 let mut y = -st.rows.off;
2343 while k < n && y < view_h {
2344 let lay = self.md_lay(st, k, cw, drtl, fc);
2345 vis.push((k, y));
2346 y += lay.h;
2347 k += 1;
2348 }
2349 st.rows.clamp(view_h);
2350 let shown = match st.reveal {
2351 Some((b, y0, y1)) => vis.iter().find(|v| v.0 == b).is_some_and(|v| v.1 + y0 >= 0 && v.1 + y1 <= view_h),
2352 None => true,
2353 };
2354 if shown {
2355 st.reveal = None;
2356 }
2357 if (st.rows.top, st.rows.off) == (t0, o0) && shown {
2358 break;
2359 }
2360 }
2361 st.reveal = None;
2362 let total = st.rows.total();
2364 if total > view_h as i64 {
2365 st.follow = st.rows.pos() + view_h as i64 >= total - 1;
2366 }
2367 vis
2368 }
2369
2370 fn md_hit(&self, st: &MdViewState, vis: &[(usize, i32)], x: i32, y: i32, cw: i32, drtl: bool) -> Option<TextPos> {
2372 let &(k, top) = vis.iter().rev().find(|v| v.1 <= y).or(vis.first())?;
2373 let lay = st.lc.lays.get(&k)?;
2374 let ly = y - top - lay.gap;
2375 if lay.boxes.is_empty() {
2376 return Some(TextPos::new(k, 0));
2377 }
2378 let bx = |b: &FBox| box_x(0, cw, b.x, b.w, drtl);
2380 let fb = lay
2381 .boxes
2382 .iter()
2383 .min_by_key(|b| {
2384 let l = bx(b);
2385 if x < l { l - x } else if x >= l + b.w { x - (l + b.w) + 1 } else { 0 }
2386 })?;
2387 let fy = ly - fb.y;
2388 let row = fb.flow.rows.iter().rposition(|r| r.y <= fy).unwrap_or(0);
2389 let r = fb.flow.rows.get(row)?;
2390 if fy >= fb.flow.h && k + 1 < st.doc.blocks().len() && ly >= lay.ch {
2391 return Some(TextPos::new(k, r.b as usize));
2392 }
2393 Some(TextPos::new(k, r.hit(x - bx(fb)) as usize))
2394 }
2395
2396 fn md_element_at(&self, st: &MdViewState, vis: &[(usize, i32)], x: i32, y: i32, cw: i32, drtl: bool) -> MdNode {
2399 let root = st.doc.root();
2400 let Some(&(k, top)) = vis.iter().rev().find(|v| v.1 <= y) else { return root };
2401 let Some(lay) = st.lc.lays.get(&k) else { return root };
2402 let ly = y - top - lay.gap;
2403 if ly < 0 || ly >= lay.ch {
2404 return root;
2405 }
2406 let block = st.doc.blocks().get(k).map_or(root, |b| b.node);
2407 for fb in &lay.boxes {
2408 let x0 = box_x(0, cw, fb.x, fb.w, drtl);
2409 let px = x - x0;
2410 for r in &fb.flow.rows {
2411 let ry = fb.y + r.y;
2412 if ly < ry || ly >= ry + r.h {
2413 continue;
2414 }
2415 for p in &r.pieces {
2416 if px >= p.x && px < p.x + p.w {
2417 let rtl = r.stops.iter().filter(|s| s.0 >= p.a && s.0 <= p.b).map(|s| (s.0, s.1)).min_by_key(|s| s.0).is_some_and(|s| s.1 > p.x);
2420 let at = r.stops.iter().filter(|s| s.0 >= p.a && s.0 < p.b && if rtl { s.1 >= px } else { s.1 <= px });
2421 let hit = if rtl { at.min_by_key(|s| s.1) } else { at.max_by_key(|s| s.1) };
2422 return hit.map_or(block, |s| st.doc.element_at(k, s.0 as usize));
2423 }
2424 }
2425 }
2426 }
2427 block
2428 }
2429
2430 #[allow(clippy::too_many_arguments)]
2432 fn md_link_at(&self, st: &MdViewState, vis: &[(usize, i32)], mx: i32, my: i32, cx: i32, vy: i32, cw: i32, drtl: bool) -> Option<usize> {
2433 let y = my - vy;
2434 let &(k, top) = vis.iter().rev().find(|v| v.1 <= y)?;
2435 let lay = st.lc.lays.get(&k)?;
2436 let ly = y - top - lay.gap;
2437 for fb in &lay.boxes {
2438 let x0 = box_x(cx, cw, fb.x, fb.w, drtl);
2439 for r in &fb.flow.rows {
2440 let ry = fb.y + r.y;
2441 if ly < ry || ly >= ry + r.h {
2442 continue;
2443 }
2444 for p in &r.pieces {
2445 if p.link != 0 && mx >= x0 + p.x && mx < x0 + p.x + p.w {
2446 return spot_of(st.doc.link_spots(), k, |s| s.link == p.link && s.a <= p.a && p.b <= s.b);
2447 }
2448 }
2449 }
2450 }
2451 None
2452 }
2453
2454 #[allow(clippy::too_many_arguments)]
2456 fn md_keys(&mut self, st: &mut MdViewState, id: u32, view_h: i32, cw: i32, drtl: bool, fc: &Faces) -> Option<usize> {
2457 let find = |ui: &Ui, vk: u32, need: u32, mask: u32| -> Option<usize> {
2458 ui.wp.ctx.keyq.iter().position(|&k| (k as i32) >= 0 && k & 0xFFFF == vk && (k >> 16) & mask == need)
2459 };
2460 let take = |ui: &mut Ui, i: usize| {
2461 if let Some(k) = ui.wp.ctx.keyq.get_mut(i) {
2462 *k |= 0x8000_0000;
2463 }
2464 };
2465 const M: u32 = MOD_SHIFT | MOD_CTRL | MOD_ALT;
2466 let line = self.dp(LINE_DIP);
2467 let page = (view_h - line).max(line);
2468 let mut d: i64 = 0;
2469 let mut act = None;
2470 if let Some(i) = find(self, VK_UP, 0, M) {
2471 take(self, i);
2472 d -= line as i64;
2473 }
2474 if let Some(i) = find(self, VK_DOWN, 0, M) {
2475 take(self, i);
2476 d += line as i64;
2477 }
2478 if let Some(i) = find(self, VK_PRIOR, 0, M & !MOD_SHIFT) {
2479 take(self, i);
2480 d -= page as i64;
2481 }
2482 if let Some(i) = find(self, VK_NEXT, 0, M & !MOD_SHIFT) {
2483 take(self, i);
2484 d += page as i64;
2485 }
2486 if let Some(i) = find(self, VK_SPACE, 0, MOD_CTRL | MOD_ALT) {
2487 let shift = (self.wp.ctx.keyq[i] >> 16) & MOD_SHIFT != 0;
2488 take(self, i);
2489 d += if shift { -(page as i64) } else { page as i64 };
2490 }
2491 let mut home = false;
2492 let mut end = false;
2493 if let Some(i) = find(self, VK_HOME, 0, MOD_SHIFT | MOD_ALT) {
2494 take(self, i);
2495 home = true;
2496 }
2497 if let Some(i) = find(self, VK_END, 0, MOD_SHIFT | MOD_ALT) {
2498 take(self, i);
2499 end = true;
2500 }
2501 if find(self, b'A' as u32, MOD_CTRL, M).map(|i| take(self, i)).is_some() {
2502 st.select_all();
2503 }
2504 if find(self, b'C' as u32, MOD_CTRL, M).or_else(|| find(self, VK_INSERT, MOD_CTRL, M)).map(|i| take(self, i)).is_some() && !st.sel.is_empty() {
2505 let (t, h) = (st.selected_text(), st.selected_html());
2506 self.clip_set_many(&[(CLIP_TEXT, t.as_bytes()), (CLIP_HTML, h.as_bytes())]);
2507 }
2508 if let Some(i) = find(self, VK_TAB, 0, MOD_CTRL | MOD_ALT) {
2510 let back = (self.wp.ctx.keyq[i] >> 16) & MOD_SHIFT != 0;
2511 let cur = st.link.and_then(|k| st.stops.iter().position(|&s| s == Stop::Link(k)));
2512 match st.step(cur, back) {
2513 Some(Stop::Link(k)) => {
2514 take(self, i);
2515 st.link = Some(k);
2516 self.wp.ctx.focus_visible = true;
2517 self.md_reveal_link(st, k, cw, drtl, fc);
2518 self.repass();
2519 }
2520 Some(Stop::Field(f)) => {
2521 take(self, i);
2522 st.link = None;
2523 self.md_focus_field(st, id, f, cw, drtl, fc);
2524 }
2525 None if st.link.is_some() && back => {
2526 take(self, i);
2527 st.link = None;
2528 self.repass();
2529 }
2530 None => st.link = None,
2531 }
2532 }
2533 if let Some(k) = st.link {
2534 if let Some(i) = find(self, VK_RETURN, 0, M) {
2535 take(self, i);
2536 act = Some(k);
2537 }
2538 if let Some(i) = find(self, VK_ESCAPE, 0, M) {
2539 take(self, i);
2540 st.link = None;
2541 self.repass();
2542 }
2543 }
2544 if home {
2545 st.rows.set_pos(0);
2546 st.pending = 0;
2547 st.follow = false;
2548 }
2549 if end {
2550 st.pending = 0;
2551 st.follow = true;
2552 }
2553 if d != 0 {
2554 st.rows.set_pos((st.rows.pos() + d).max(0));
2555 st.pending = 0;
2556 st.follow = false;
2557 }
2558 if home || end || d != 0 {
2559 st.reveal = None;
2560 self.wp.ctx.focus_visible = true;
2561 self.repass();
2562 }
2563 act
2564 }
2565
2566 fn md_focus_field(&mut self, st: &mut MdViewState, id: u32, f: usize, cw: i32, drtl: bool, fc: &Faces) {
2568 self.wp.ctx.focus = md_field_id(id, f);
2569 self.wp.ctx.focus_visible = true;
2570 st.ffocus = Some(f);
2571 self.repass();
2572 let Some(sp) = st.doc.field_spots().iter().find(|s| s.field as usize == f + 1).copied() else { return };
2573 let lay = self.md_lay(st, sp.block, cw, drtl, fc);
2574 let (mut y0, mut y1) = (i32::MAX, 0);
2575 for fb in &lay.boxes {
2576 for r in &fb.flow.rows {
2577 if r.pieces.iter().any(|p| p.field == sp.field) {
2578 y0 = y0.min(lay.gap + fb.y + r.y);
2579 y1 = y1.max(lay.gap + fb.y + r.y + r.h);
2580 }
2581 }
2582 }
2583 if y0 <= y1 {
2584 st.reveal = Some((sp.block, y0, y1));
2585 }
2586 }
2587
2588 fn md_key_at(&self, vk: u32, mask: u32) -> Option<usize> {
2591 self.wp.ctx.keyq.iter().position(|&k| (k as i32) >= 0 && k & 0xFFFF == vk && (k >> 16) & mask == 0)
2592 }
2593
2594 fn md_key_take(&mut self, i: usize) {
2596 if let Some(k) = self.wp.ctx.keyq.get_mut(i) {
2597 *k |= 0x8000_0000;
2598 }
2599 }
2600
2601 fn md_ctl(&mut self, fid: u32, kind: u8, r: [i32; 4], label: &str, on: bool, disabled: bool) -> u32 {
2605 self.wp.ctx.last_rect = r;
2606 let button = !matches!(kind, MF_CHECKBOX | MF_RADIO);
2607 let mut hb = HB_FOCUS | if button { 0 } else { HB_SPACE };
2608 if disabled {
2609 hb |= HB_DISABLED;
2610 }
2611 let ev = self.behave(fid, hb);
2612 let st = self.style;
2613 let mut sp = Sp { flags: if disabled { ST_DISABLED } else { 0 }, label: button.then_some(label), ..Sp::default() };
2614 match kind {
2615 MF_CHECKBOX => st.checkbox_size(self, &mut sp),
2616 MF_RADIO => st.radio_size(self, &mut sp),
2617 _ => st.button_size(self, &mut sp),
2618 };
2619 self.sp_placed(&mut sp, ev);
2620 sp.value = on as i32;
2621 match kind {
2622 MF_CHECKBOX => st.checkbox(self, &sp),
2623 MF_RADIO => st.radio(self, &sp),
2624 _ => st.button(self, &sp),
2625 }
2626 self.focus_ring_draw(ev);
2627 if on {
2628 self.wp.ctx.last_state |= SS_CHECKED as u32;
2629 }
2630 let k = match kind {
2631 MF_CHECKBOX => K_CHECKBOX,
2632 MF_RADIO => K_RADIO,
2633 _ => K_BUTTON,
2634 };
2635 self.sem(k, fid, Some(label), on as i32);
2636 ev
2637 }
2638
2639 #[allow(clippy::too_many_arguments)]
2644 fn md_fields(&mut self, st: &mut MdViewState, id: u32, drawn: &[(usize, [i32; 4], u32)], off: bool, view_h: i32, cw: i32, drtl: bool, fc: &Faces, out: &mut MdEv) {
2645 let n = st.doc.fields().len();
2646 if n == 0 {
2647 return;
2648 }
2649 const M: u32 = MOD_SHIFT | MOD_CTRL | MOD_ALT;
2650 let mut changed: Option<usize> = None;
2651 let mut submit: Option<MdSubmit> = None;
2652 let mut reset: Option<usize> = None;
2653 let mut moved: Option<usize> = None;
2654 for &(f, r, under) in drawn {
2655 if under != 0 {
2656 self.draw_color(under);
2657 self.draw_rect(r[0], r[1], r[2], r[3]);
2658 }
2659 let fid = md_field_id(id, f);
2660 let focused = self.wp.ctx.focus == fid;
2661 let h0 = self.wp.ctx.hits[self.wp.ctx.hit_cur].len();
2662 let fd = &st.doc.fields()[f];
2663 let (kind, form, disabled, maxlength) = (fd.kind, fd.form as usize, fd.disabled || off, fd.maxlength as usize);
2664 match kind {
2665 MF_TEXT | MF_PASSWORD | MF_TEXTAREA | MF_SELECT => {
2666 let name = field_name(fd);
2669 if !name.is_empty() {
2670 self.wp.ctx.last_rect = [r[0], r[1], r[2], 0];
2671 self.wp.ctx.last_state = 0;
2672 self.sem(K_TEXT, 0, Some(name), 0);
2673 }
2674 let enter = if focused && kind != MF_TEXTAREA && kind != MF_SELECT { self.md_key_at(VK_RETURN, 0) } else { None };
2675 let esc = focused && kind != MF_SELECT && self.md_key_at(VK_ESCAPE, M).is_some();
2676 if disabled {
2677 self.next_flags(ST_DISABLED);
2678 }
2679 self.next_rect_px(r[0], r[1], r[2], r[3]);
2680 let ev = if kind == MF_SELECT {
2681 let opts: Vec<&str> = fd.options.iter().map(|o| if o.label.is_empty() { " " } else { o.label.as_str() }).collect();
2682 self.combobox(fid, &mut st.vals[f].sel, &opts)
2683 } else {
2684 let mut flags = match kind {
2685 MF_PASSWORD => TB_PASSWORD,
2686 MF_TEXTAREA => TB_MULTI | TB_WRAP,
2687 _ => 0,
2688 };
2689 if fd.readonly {
2690 flags |= TB_READONLY;
2691 }
2692 let cap = if maxlength > 0 { (maxlength as u32 * 4 + 1).min(FIELD_CAP) } else { FIELD_CAP };
2693 let ph = (!fd.placeholder.is_empty()).then_some(fd.placeholder.as_str());
2694 self.textbox(fid, ph, &mut st.vals[f].text, cap | flags)
2695 };
2696 if ev & EV_ACT != 0 {
2697 st.vals[f].dirty = true;
2698 let t = &mut st.vals[f].text;
2700 if maxlength > 0 && t.chars().count() > maxlength {
2701 let cut = t.char_indices().nth(maxlength).map_or(t.len(), |c| c.0);
2702 t.truncate(cut);
2703 }
2704 changed = Some(f);
2705 }
2706 if enter.is_some() && ev & EV_COMMIT != 0 {
2707 submit = st.implicit(f);
2708 }
2709 if esc && self.wp.ctx.focus == 0 {
2710 self.wp.ctx.focus = id;
2712 self.wp.ctx.focus_visible = true;
2713 }
2714 }
2715 _ => {
2716 let label = if matches!(kind, MF_CHECKBOX | MF_RADIO) { field_name(fd) } else { fd.caption() };
2717 let on = st.vals[f].on;
2718 let ev = self.md_ctl(fid, kind, r, label, on, disabled);
2719 match kind {
2720 MF_CHECKBOX | MF_RADIO => {
2721 if focused {
2722 if let Some(i) = self.md_key_at(VK_RETURN, M) {
2723 self.md_key_take(i);
2724 submit = st.implicit(f);
2725 }
2726 }
2727 if focused && kind == MF_RADIO {
2728 let rtl = st.doc.field_spots().iter().find(|s| s.field as usize == f + 1).and_then(|s| st.doc.blocks().get(s.block)).and_then(|b| text_rtl(&b.text)).unwrap_or(drtl);
2732 for vk in [VK_LEFT, VK_UP, VK_RIGHT, VK_DOWN] {
2733 if let Some(i) = self.md_key_at(vk, M) {
2734 let k = self.rtl_text_key(self.wp.ctx.keyq[i]) & 0xFFFF;
2735 self.md_key_take(i);
2736 let back = k == VK_UP || (k != VK_DOWN && ((k == VK_LEFT) != rtl));
2737 moved = st.radio_step(f, back);
2738 break;
2739 }
2740 }
2741 }
2742 if ev & EV_ACT != 0 && (kind == MF_CHECKBOX || !on) {
2743 st.set_field_checked(f, !on);
2744 changed = Some(f);
2745 }
2746 }
2747 MF_SUBMIT if ev & EV_ACT != 0 => match form.checked_sub(1) {
2748 Some(fm) => submit = Some(st.submit(fm, Some(f))),
2749 None => changed = Some(f),
2750 },
2751 MF_RESET if ev & EV_ACT != 0 => {
2752 reset = form.checked_sub(1);
2753 changed = Some(f);
2754 }
2755 _ if ev & EV_ACT != 0 => changed = Some(f),
2756 _ => {}
2757 }
2758 }
2759 }
2760 let c = &mut self.wp.ctx;
2762 for h in c.hits[c.hit_cur][h0..].iter_mut() {
2763 if h.id == fid {
2764 h.continuation = true;
2765 }
2766 }
2767 }
2768 if let Some(fm) = reset {
2769 st.reset_form(fm);
2770 self.repass();
2771 }
2772 if let Some(g) = moved {
2773 st.set_field_checked(g, true);
2774 self.md_focus_field(st, id, g, cw, drtl, fc);
2775 changed = Some(g);
2776 }
2777 if let Some(f) = field_of(id, self.wp.ctx.focus, n) {
2780 if let Some(i) = self.md_key_at(VK_TAB, MOD_CTRL | MOD_ALT) {
2781 let back = (self.wp.ctx.keyq[i] >> 16) & MOD_SHIFT != 0;
2782 let cur = st.stops.iter().position(|&s| s == Stop::Field(f));
2783 match st.step(cur, back) {
2784 Some(Stop::Field(g)) => {
2785 self.md_key_take(i);
2786 self.md_focus_field(st, id, g, cw, drtl, fc);
2787 }
2788 s => {
2789 st.link = None;
2790 if let Some(Stop::Link(k)) = s {
2791 st.link = Some(k);
2792 self.md_reveal_link(st, k, cw, drtl, fc);
2793 }
2794 if s.is_some() || back {
2795 self.md_key_take(i);
2796 }
2797 self.wp.ctx.focus = id;
2798 self.wp.ctx.focus_visible = true;
2799 self.repass();
2800 }
2801 }
2802 }
2803 }
2804 if field_of(id, self.wp.ctx.focus, n).is_some() {
2807 for (vk, dir) in [(VK_PRIOR, -1i64), (VK_NEXT, 1)] {
2808 if let Some(i) = self.md_key_at(vk, MOD_CTRL | MOD_ALT) {
2809 self.md_key_take(i);
2810 let line = self.dp(LINE_DIP);
2811 st.rows.set_pos((st.rows.pos() + dir * (view_h - line).max(line) as i64).max(0));
2812 st.pending = 0;
2813 st.reveal = None;
2814 st.follow = false;
2815 self.repass();
2816 }
2817 }
2818 }
2819 st.ffocus = field_of(id, self.wp.ctx.focus, n);
2820 if changed.is_some() || submit.is_some() {
2821 self.repass();
2822 }
2823 out.field = changed;
2824 out.submit = submit;
2825 }
2826
2827 fn md_reveal_link(&mut self, st: &mut MdViewState, k: usize, cw: i32, drtl: bool, fc: &Faces) {
2829 let Some(sp) = st.doc.link_spots().get(k).copied() else { return };
2830 let lay = self.md_lay(st, sp.block, cw, drtl, fc);
2831 let (mut y0, mut y1) = (i32::MAX, 0);
2832 for fb in &lay.boxes {
2833 for r in &fb.flow.rows {
2834 if r.pieces.iter().any(|p| p.link == sp.link && p.a < sp.b && p.b > sp.a) {
2835 y0 = y0.min(lay.gap + fb.y + r.y);
2836 y1 = y1.max(lay.gap + fb.y + r.y + r.h);
2837 }
2838 }
2839 }
2840 if y0 <= y1 {
2841 st.reveal = Some((sp.block, y0, y1));
2842 }
2843 }
2844
2845 #[allow(clippy::too_many_arguments)]
2848 fn md_draw_block(&mut self, doc: &MdDoc, lc: &mut Cache, sel: TextSel, k: usize, lay: &BLay, cx: i32, top: i32, cw: i32, drtl: bool, focused: bool, bgc: u32, vis: (i32, i32), links: &mut Vec<Link>, fields: &mut Vec<(usize, [i32; 4], u32)>, tab: &mut Option<(u32, usize)>) {
2852 let hc = self.high_contrast;
2853 let bl = &doc.blocks()[k];
2854 let ctop = top + lay.gap;
2855 let mut tcol = self.color(T_TEXT) | 0xFF00_0000;
2856 let mut t2 = self.color(T_TEXT2) | 0xFF00_0000;
2857 let mut lcol = self.color(T_ACCENT_TEXT) | 0xFF00_0000;
2858 let bgc0 = bgc;
2859 let mut bgc = bgc;
2860 for g in &lay.css {
2863 let Some(b) = doc.boxes().get(g.b as usize) else { continue };
2864 let (x, y0, hh) = (box_x(cx, cw, g.x, g.w, drtl), top + g.y0, g.y1 - g.y0);
2865 if hh <= 0 {
2866 continue;
2867 }
2868 if b.bg != 0 && !hc {
2869 self.draw_color(b.bg);
2870 self.draw_rect(x, y0, g.w, hh);
2871 bgc = color_mix(bgc, b.bg | 0xFF00_0000, ((b.bg >> 24) as i32) + ((b.bg >> 31) as i32));
2872 }
2873 let bw = b.border.map(|v| self.dp(v as i32));
2874 let col = |s: usize| if hc || b.border_color[s] == 0 { tcol } else { b.border_color[s] };
2875 let edge = |ui: &mut Ui, s: usize, r: [i32; 4]| {
2876 if bw[s] > 0 && r[3] > 0 {
2877 ui.draw_color(col(s));
2878 ui.draw_rect(r[0], r[1], r[2], r[3]);
2879 }
2880 };
2881 edge(self, 3, [x, y0, bw[3], hh]);
2882 edge(self, 1, [x + g.w - bw[1], y0, bw[1], hh]);
2883 if g.first {
2884 edge(self, 0, [x, y0, g.w, bw[0]]);
2885 }
2886 if g.last {
2887 edge(self, 2, [x, top + g.y1 - bw[2], g.w, bw[2]]);
2888 }
2889 }
2890 let mut under = 0;
2892 if bgc != bgc0 && !hc {
2893 let theme_light = (tcol & 0xFF) < 0x80;
2894 (tcol, t2, lcol) = on_bg(bgc);
2895 if ((tcol & 0xFF) < 0x80) != theme_light {
2896 under = bgc0;
2897 }
2898 }
2899 let div = if hc { tcol } else { color_mix(bgc, tcol, 48) };
2900 let tint = color_mix(bgc, tcol, if self.theme_dark { 22 } else { 14 });
2901 let hair = self.hair().max(1);
2902 let prev_q = if k > 0 { doc.blocks()[k - 1].quote } else { 0 };
2904 let mut qn = 0;
2905 for &bx in &lay.bars {
2906 qn += 1;
2907 let bw = self.dp(3);
2908 let y0 = if prev_q >= qn && k > 0 { top } else { ctop };
2909 let x = box_x(cx, cw, bx, bw, drtl);
2910 self.draw_color(if hc { tcol } else { color_mix(bgc, tcol, 70) });
2911 self.draw_rect(x, y0, bw, ctop + lay.ch - y0);
2912 }
2913 let colx = box_x(cx, cw, lay.x, lay.w, drtl);
2914 match bl.kind {
2915 MB_CODE => {
2916 if hc {
2917 self.draw_color(tcol);
2918 self.draw_frame(colx, ctop, lay.w, lay.ch, self.dp(4), hair);
2919 } else {
2920 let th = self.code_theme();
2921 let fill = if th.bg != 0 { color_mix(bgc, tcol, if self.theme_dark { 18 } else { 10 }) } else { tint };
2922 self.cmd_rrect(colx, ctop, lay.w, lay.ch, self.dp(6), 0, fill, 0, 0);
2923 }
2924 }
2925 MB_RULE => {
2926 let t = self.dp(2).max(1);
2927 self.draw_color(div);
2928 self.draw_rect(colx, ctop + lay.ch / 2 - t / 2, lay.w, t);
2929 }
2930 MB_ROW => {
2931 let px = self.dp_text(CELL_PX);
2933 for fb in &lay.boxes {
2934 let x = box_x(cx, cw, fb.x - px, fb.w + px * 2, drtl);
2935 if bl.head && !hc {
2936 self.draw_color(tint);
2937 self.draw_rect(x, ctop, fb.w + px * 2, lay.ch);
2938 }
2939 self.draw_color(div);
2940 self.draw_frame(x, ctop, fb.w + px * 2 + hair, lay.ch + hair, 0, hair);
2941 }
2942 }
2943 MB_HEADING if bl.level <= 2 && !lay.css.iter().any(|g| g.last && doc.boxes().get(g.b as usize).is_some_and(|b| b.node == bl.node && b.border[2] > 0)) => {
2945 let fh = lay.boxes.first().map_or(0, |b| b.flow.h);
2946 self.draw_color(div);
2947 self.draw_rect(colx, ctop + fh + self.dp(6), lay.w, hair);
2948 }
2949 _ => {}
2950 }
2951 let (sa, sb) = sel.range();
2953 let selc = if focused { (self.color(T_ACCENT) & 0x00FF_FFFF) | 0x60 << 24 } else { (self.color(T_TEXT3) & 0x00FF_FFFF) | 0x38 << 24 };
2954 if sa < sb && sa.line <= k && sb.line >= k {
2955 let lo = if sa.line == k { sa.byte as u32 } else { 0 };
2956 let hi = if sb.line == k { sb.byte as u32 } else { u32::MAX };
2957 if bl.kind == MB_RULE {
2958 self.draw_color(selc);
2959 self.draw_rect(colx, ctop, lay.w, lay.ch);
2960 }
2961 for fb in &lay.boxes {
2962 let x0 = box_x(cx, cw, fb.x, fb.w, drtl);
2963 for r in &fb.flow.rows {
2964 let ry = ctop + fb.y + r.y;
2965 if ry + r.h <= vis.0 || ry >= vis.1 {
2966 continue;
2967 }
2968 for p in &r.pieces {
2969 let (a, b) = (p.a.max(lo), p.b.min(hi));
2970 if a >= b {
2971 continue;
2972 }
2973 let (mut xa, mut xb) = (i32::MAX, i32::MIN);
2974 for s in &r.stops {
2975 if s.0 >= a && s.0 <= b {
2976 xa = xa.min(s.1);
2977 xb = xb.max(s.1);
2978 }
2979 }
2980 if xa < xb {
2981 self.draw_color(selc);
2982 self.draw_rect(x0 + xa, ctop + fb.y + r.y, xb - xa, r.h);
2983 }
2984 }
2985 }
2986 }
2987 }
2988 let quote = bl.quote > 0;
2990 let theme = if bl.kind == MB_CODE { Some(self.code_theme()) } else { None };
2991 let first_base: Option<(i32, i32, u32, i32, i32)> = lay.boxes.first().and_then(|fb| {
2993 let r = fb.flow.rows.first()?;
2994 let (f, s, a) = r.pieces.iter().find(|p| p.image == 0 && p.field == 0).map_or((0, 0, 0), |p| (p.font, p.size, p.asc));
2995 Some((ctop + fb.y + r.y + r.base, box_x(cx, cw, fb.x, fb.w, drtl), f, s, a))
2996 });
2997 for fb in &lay.boxes {
2998 let x0 = box_x(cx, cw, fb.x, fb.w, drtl);
2999 for r in &fb.flow.rows {
3000 let ry = ctop + fb.y + r.y;
3001 if ry + r.h <= vis.0 || ry >= vis.1 {
3002 continue;
3003 }
3004 let base = ry + r.base;
3005 let mut prev_link: Option<([i32; 4], u32)> = None;
3006 for p in &r.pieces {
3007 let x = x0 + p.x;
3008 if p.image != 0 {
3009 self.md_draw_image(lc, doc, p.image as usize - 1, x, base - p.ih, p.w, p.ih, tcol, bgc);
3010 let alt = doc.images().get(p.image as usize - 1).map_or("", |m| m.alt.as_str());
3011 self.wp.ctx.last_rect = self.ltr_rect([x, base - p.ih, p.w, p.ih]);
3012 self.wp.ctx.last_state = 0;
3013 let label = if alt.is_empty() { tr("Bild") } else { alt };
3014 self.sem(K_IMAGE, 0, Some(label), 0);
3015 } else if p.field != 0 {
3016 let y = base - p.asc + self.dp(FIELD_GAP);
3018 fields.push((p.field as usize - 1, self.ltr_rect([x, y, p.w, p.ih]), under));
3019 } else {
3020 let link = p.link != 0;
3021 let col = if let Some(t) = &theme {
3022 t.tokens.get(p.syn.saturating_sub(1) as usize).copied().unwrap_or(tcol)
3023 } else if link && (p.color == 0 || hc) {
3024 lcol
3025 } else if p.color != 0 && !hc {
3026 opaque(p.color)
3027 } else if quote || (bl.kind == MB_HEADING && bl.level >= 6) {
3028 t2
3029 } else {
3030 tcol
3031 };
3032 if p.bg != 0 && !hc {
3033 self.draw_color(opaque(p.bg));
3034 self.draw_rect(x, ry, p.w, r.h);
3035 }
3036 if p.style & MS_CODE != 0 && bl.kind != MB_CODE {
3037 let (h, yy, cp) = (p.asc + self.dp(6), base - p.asc - self.dp(2), self.dp_text(CODE_PAD));
3038 if hc {
3039 self.draw_color(tcol);
3040 self.draw_frame(x - cp, yy, p.w + cp * 2, h, self.dp(3), hair);
3041 } else {
3042 self.cmd_rrect(x - cp, yy, p.w + cp * 2, h, self.dp(3), 0, tint, 0, 0);
3043 }
3044 }
3045 let s = &bl.text.as_bytes()[p.a as usize..p.b as usize];
3046 let s = if s.last() == Some(&b'\n') { &s[..s.len() - 1] } else { s };
3047 if p.shaped {
3048 let d: DrawRef = Rc::new(Shaped { s: s.to_vec(), font: p.font, size: p.size, color: col, dx: self.dp(2), base: base - ry });
3049 let ver = (text_hash(core::str::from_utf8(s).unwrap_or("")) as u32) ^ col ^ (p.size as u32).rotate_left(7) ^ p.font;
3050 self.cmd_custom(x - self.dp(2), ry, p.w + self.dp(4), r.h, Some(d), 0, ver);
3051 } else {
3052 self.cmd_text(x, base, s, p.font, p.size, col, 0);
3053 }
3054 let px = p.size >> 8;
3055 let tl = (px / 14).max(1);
3056 if link || p.style & MS_UNDER != 0 {
3057 self.draw_color(col);
3058 self.draw_rect(x, base + (px / 9).max(1), p.w, tl);
3059 }
3060 if p.style & MS_STRIKE != 0 {
3061 self.draw_color(col);
3062 self.draw_rect(x, base - p.asc * 3 / 10, p.w, tl);
3063 }
3064 }
3065 if p.link != 0 {
3067 let pr = [x, ry, p.w, r.h];
3068 match &mut prev_link {
3069 Some((lr, l)) if *l == p.link && (lr[0] + lr[2] == x || x + p.w == lr[0]) => {
3070 let x0 = lr[0].min(x);
3071 lr[2] = (lr[0] + lr[2]).max(x + p.w) - x0;
3072 lr[0] = x0;
3073 }
3074 _ => {
3075 if let Some((lr, l)) = prev_link.take() {
3076 self.md_link_rec(doc, k, l, lr, r, links);
3077 }
3078 prev_link = Some((pr, p.link));
3079 }
3080 }
3081 } else if let Some((lr, l)) = prev_link.take() {
3082 self.md_link_rec(doc, k, l, lr, r, links);
3083 }
3084 }
3085 if let Some((lr, l)) = prev_link.take() {
3086 self.md_link_rec(doc, k, l, lr, r, links);
3087 }
3088 }
3089 }
3090 if bl.marker != MK_NONE {
3092 let (base, _, mf, ms, masc) = first_base.unwrap_or((ctop + self.dp(14), 0, 0, 0, self.dp(12)));
3093 let (mf, ms, masc) = if mf == 0 {
3094 let f = self.md_faces();
3095 let fnt = f.get(false, false, false);
3096 let (a, _, _) = self.md_metrics(fnt, f.size);
3097 (fnt, f.size, a)
3098 } else {
3099 (mf, ms, masc)
3100 };
3101 let gut = self.dp_text(LIST_W);
3102 let gx = box_x(cx, cw, lay.mx - gut, gut, drtl);
3103 let mcol = if quote { t2 } else { tcol };
3104 let label = bl.text.chars().take(LABEL_MAX).collect::<String>();
3105 if bl.task != 0 {
3106 let s = ((masc as f32 * 0.9) as i32).max(self.dp(10));
3107 let bx = if drtl { gx + self.dp_text(6) } else { gx + gut - s - self.dp_text(8) };
3108 let by = base - s + self.dp(1);
3109 let done = bl.task == 2;
3110 if done {
3111 self.cmd_rrect(bx, by, s, s, self.dp(3), 0, self.color(T_ACCENT) | 0xFF00_0000, 0, 0);
3112 let c = self.color(T_ON_ACCENT) | 0xFF00_0000;
3113 self.draw_color(c);
3114 let (fx, fy) = ((bx << 8) as i64, (by << 8) as i64);
3115 let sz = (s << 8) as i64;
3116 let w = (self.dp(2).max(1) << 8) * 3 / 4;
3117 let p = |a: i64, b: i64| ((fx + sz * a / 100) as i32, (fy + sz * b / 100) as i32);
3118 let (a0, a1) = p(24, 52);
3119 let (b0, b1) = p(43, 70);
3120 let (c0, c1) = p(76, 32);
3121 self.draw_line(a0, a1, b0, b1, w);
3122 self.draw_line(b0, b1, c0, c1, w);
3123 } else {
3124 self.cmd_rrect(bx, by, s, s, self.dp(3), hair, 0, self.color(T_STRONG_STROKE) | 0xFF00_0000, self.color(T_STRONG_STROKE) | 0xFF00_0000);
3125 }
3126 self.wp.ctx.last_rect = self.ltr_rect([bx, by, s, s]);
3127 self.wp.ctx.last_state = if done { SS_CHECKED as u32 } else { 0 };
3128 self.sem(K_CHECKBOX, 0, Some(&label), done as i32);
3129 } else if bl.marker == MK_BULLET {
3130 let d = ((masc as f32 * 0.36) as i32).max(self.dp(4)) & !1;
3131 let mx = if drtl { gx + self.dp_text(12) } else { gx + gut - self.dp_text(12) };
3132 let my = base - (masc as f32 * 0.34) as i32;
3133 self.draw_color(mcol);
3134 match (bl.list.max(1) - 1) % 3 {
3135 0 => self.draw_circle(mx << 8, my << 8, (d << 8) / 2, 0),
3136 1 => self.draw_circle(mx << 8, my << 8, (d << 8) / 2 - 128, self.hair().max(1) << 8),
3137 _ => self.draw_rect(mx - d / 2, my - d / 2, d, d),
3138 }
3139 } else {
3140 let d = if bl.delim == b')' { '(' } else { '.' };
3142 let m = if drtl { format!("{}{}", d, bl.number) } else { format!("{}{}", bl.number, if bl.delim == b')' { ')' } else { '.' }) };
3143 self.text.set_font(mf, ms);
3144 let mw = self.text.width(&m);
3145 let x = if drtl { gx + self.dp_text(6) } else { gx + gut - self.dp_text(6) - mw };
3146 self.cmd_text(x, base, m.as_bytes(), mf, ms, mcol, 0);
3147 }
3148 let (lx, lw) = (lay.mx - gut, lay.x + lay.w - (lay.mx - gut));
3150 self.wp.ctx.last_rect = self.ltr_rect([box_x(cx, cw, lx, lw, drtl), ctop, lw, lay.ch]);
3151 self.wp.ctx.last_state = 0;
3152 self.sem(K_LISTITEM, 0, Some(&label), bl.number as i32);
3153 }
3154 self.wp.ctx.last_rect = self.ltr_rect([colx, ctop, lay.w, lay.ch]);
3156 self.wp.ctx.last_state = 0;
3157 match bl.kind {
3158 MB_HEADING => {
3159 let label = doc.plain_range(TextPos::new(k, 0), TextPos::new(k, bl.text.len()));
3160 self.sem(K_HEADING, 0, Some(&label), bl.level as i32);
3161 }
3162 MB_ROW => {
3163 let rows = lc.rows_of.get(&bl.table);
3167 let ri = rows.and_then(|v| v.binary_search(&k).ok()).unwrap_or(0) as u32;
3168 let texts: Vec<String> = bl.cells.iter().map(|&(a, b)| doc.plain_range(TextPos::new(k, a as usize), TextPos::new(k, b as usize))).collect();
3169 match *tab {
3170 Some((t, i)) if t == bl.table && i < self.sems.len() => {
3171 let (a, b) = (self.sems[i].rect, self.phys_rect(self.wp.ctx.last_rect));
3172 let (x0, y0) = (a[0].min(b[0]), a[1].min(b[1]));
3173 let (x1, y1) = ((a[0] + a[2]).max(b[0] + b[2]), (a[1] + a[3]).max(b[1] + b[3]));
3174 self.sems[i].rect = [x0, y0, x1 - x0, y1 - y0];
3175 }
3176 _ => {
3177 let nrows = rows.map_or(1, |v| v.len()) as u32;
3178 let ncols = doc.tables().get(bl.table as usize).map_or(0, |t| t.aligns.len()).max(bl.cells.len()) as u32;
3179 self.sem(K_TABLE, 0, Some(""), 0).grid(nrows, ncols);
3181 *tab = Some((bl.table, self.sems.len() - 1));
3182 }
3183 }
3184 self.sem(K_ROW, 0, Some(&texts.join(" ")), 0);
3185 let kind = if bl.head { K_COLHEADER } else { K_CELL };
3186 for (c, fb) in lay.boxes.iter().enumerate() {
3187 let Some(t) = texts.get(c) else { continue };
3188 let px = self.dp_text(CELL_PX);
3189 self.wp.ctx.last_rect = self.ltr_rect([box_x(cx, cw, fb.x - px, fb.w + px * 2, drtl), ctop, fb.w + px * 2, lay.ch]);
3190 self.sem(kind, 0, Some(t), 0).cell(ri, c as u32);
3191 }
3192 }
3193 MB_RULE => {}
3194 _ if bl.marker == MK_NONE || bl.kind == MB_CODE => {
3195 let t: String = if bl.kind == MB_CODE {
3196 bl.text.chars().take(LABEL_MAX).collect()
3197 } else {
3198 doc.plain_range(TextPos::new(k, 0), TextPos::new(k, bl.text.len())).chars().take(LABEL_MAX).collect()
3199 };
3200 if !t.is_empty() {
3201 self.sem(K_TEXT, 0, Some(&t), 0);
3202 }
3203 }
3204 _ => {}
3205 }
3206 }
3207
3208 fn md_link_rec(&self, doc: &MdDoc, k: usize, link: u32, lr: [i32; 4], r: &LRow, links: &mut Vec<Link>) {
3210 let Some(spot) = spot_of(doc.link_spots(), k, |s| s.link == link && r.pieces.iter().any(|p| p.link == link && p.a >= s.a && p.b <= s.b)) else {
3211 return;
3212 };
3213 let sp = doc.link_spots()[spot];
3214 let t = doc.block_text(k, sp.a as usize, sp.b as usize);
3216 links.push(Link { r: lr, logical: self.ltr_rect(lr), label: t, spot }); }
3218
3219 #[allow(clippy::too_many_arguments)]
3221 fn md_draw_image(&mut self, lc: &mut Cache, doc: &MdDoc, i: usize, x: i32, y: i32, w: i32, h: i32, tcol: u32, bgc: u32) {
3222 let img = lc.imgs.get(i).cloned().unwrap_or(Img::Failed);
3223 match img {
3224 Img::Bitmap(sf) => {
3225 let key = (i, w, h);
3226 let s = if sf.w == w && sf.h == h {
3227 sf
3228 } else if let Some(s) = lc.scaled.get(&key) {
3229 s.clone()
3230 } else {
3231 let s = img_core::ops::scale(&sf, w, h, true).map(Rc::new).unwrap_or(sf);
3232 lc.scaled.insert(key, s.clone());
3233 s
3234 };
3235 self.draw_image(&s, i as u32, x, y, w, h);
3236 }
3237 Img::Vector(svg) => {
3238 let color = tcol;
3239 let fonts = self.svg_fonts();
3240 let d = lc.svgs.entry((i, color)).or_insert_with(|| Rc::new(SvgDraw { doc: svg.clone(), color, fonts })).clone();
3241 let obj: DrawRef = d;
3242 self.cmd_custom(x, y, w, h, Some(obj), 0, (i as u32).wrapping_mul(0x9E37_79B1) ^ color);
3243 }
3244 _ => {
3245 let hair = self.hair().max(1);
3246 self.draw_color(color_mix(bgc, tcol, 90));
3247 self.draw_frame(x, y, w, h, self.dp(3), hair);
3248 let alt = doc.images().get(i).map_or("", |m| m.alt.as_str());
3249 let alt = if alt.is_empty() { tr("Bild") } else { alt };
3250 let f = self.md_faces();
3251 let font = f.get(false, false, true);
3252 let (asc, desc, _) = self.md_metrics(font, f.size);
3253 let by = y + (h - asc - desc) / 2 + asc;
3254 self.draw_clip(x, y, w, h);
3255 self.cmd_text(x + self.dp(8), by, alt.as_bytes(), font, f.size, self.color(T_TEXT2) | 0xFF00_0000, 0);
3256 self.draw_unclip();
3257 }
3258 }
3259 }
3260}
3261
3262struct Link {
3264 r: [i32; 4],
3265 logical: [i32; 4],
3266 label: String,
3267 spot: usize,
3268}
3269
3270struct Shaped {
3273 s: Vec<u8>,
3274 font: u32,
3275 size: i32,
3276 color: u32,
3277 dx: i32,
3279 base: i32,
3280}
3281
3282impl Draw for Shaped {
3283 fn draw(&self, g: &mut Gfx, r: [i32; 4]) {
3284 let old = g.text.set_shaping(true);
3285 let prev = g.text.get_font();
3286 g.dc.set_color(self.color);
3287 g.text.set_font(self.font, self.size);
3288 g.text.draw_n(g.dc, g.sf, r[0] + self.dx, r[1] + self.base, &self.s, self.s.len());
3289 g.text.set_font(prev.0, prev.1);
3290 g.text.set_shaping(old);
3291 }
3292}