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