Skip to main content

text/
shaping.rs

1//! Opt-in complex text layout. UI controls keep their existing metrics unless enabled.
2use alloc::{vec::Vec, string::String};
3use crate::{Text, registry::Face};
4
5#[derive(Default)]
6pub(crate) struct Cache {
7    pub enabled: bool, serial: u64, font: u32, size: i32,
8    engine: Option<text_shape::Engine>, text: String, glyphs: Vec<Placed>, carets: Vec<(usize,i32)>, width: i32,
9    fallbacks: Vec<(u32, text_shape::Engine)>,
10    segments: Vec<(usize, usize, i32, i32)>,
11}
12#[derive(Clone, Copy)]
13pub(crate) struct Placed { pub font: u32, pub id: u32, pub x: i32, pub y: i32 }
14impl Text {
15    /// Enable OpenType shaping for measure, draw, hit testing and carets. Returns
16    /// the previous state so callers can scope this to a document's text tool.
17    pub fn set_shaping(&mut self, enabled: bool) -> bool { core::mem::replace(&mut self.shaping.enabled,enabled) }
18    pub fn set_fallback_fonts(&mut self, fonts: &[u32]) {
19        if self.fb.app != fonts { self.fallback_app(fonts); }
20        let ids:Vec<_>=self.shaping.fallbacks.iter().map(|v|v.0).collect();
21        if ids==fonts { return; }
22        self.shaping.fallbacks=fonts.iter().take(16).filter_map(|&id| self.shape_engine(id).map(|e|(id,e))).collect();
23        self.shaping.size=0;
24    }
25    fn shape_face(&self, mut font: u32) -> Option<&crate::ttf::TtFont> {
26        for _ in 0..16 { match self.face(font)? { Face::Tt(tt) => return Some(tt), Face::Syn(s) => font=s.base, Face::Var(v) => font=v.base, _ => return None } }
27        None
28    }
29    pub(crate) fn shape_line(&mut self, bytes: &[u8], limit: Option<usize>) -> bool {
30        if !self.shaping.enabled { return false; }
31        let n=limit.unwrap_or(bytes.len()).min(bytes.len());
32        let n=bytes[..n].iter().position(|b| *b==0 || *b==b'\n').unwrap_or(n);
33        let Ok(text)=core::str::from_utf8(&bytes[..n]) else { return false; };
34        let render_font=if self.shape_face(self.font).is_some() { self.font } else { match self.shaping.fallbacks.first() { Some(v)=>v.0,None=>return false } };
35        if self.shape_face(render_font).is_none() { return false; }
36        let serial=self.shape_serial(render_font); // an instance of a variable font: its own engine
37        if self.shaping.serial!=serial || self.shaping.engine.is_none() {
38            let engine=self.shape_engine(render_font);
39            self.shaping.engine=engine; self.shaping.serial=serial; self.shaping.text.clear(); self.shaping.size=0;
40        }
41        if self.shaping.engine.is_none() { return false; }
42        if self.shaping.font==self.font && self.shaping.size==self.size && self.shaping.text==text { return true; }
43        if self.system_fallback() { self.shape_system_fallbacks(text, render_font); }
44        let fallback_engines:Vec<_>=self.shaping.fallbacks.iter().map(|v|&v.1).collect();
45        let Some(glyphs)=self.shaping.engine.as_ref().and_then(|e| e.shape_with_fallback(text,&fallback_engines)) else { return false; };
46        let classic=self.classic_metrics(self.font);
47        let scale=|v:i32,font:u32| { let n=(v as i64*self.size as i64/self.upem(font).max(1) as i64).clamp(i32::MIN as i64,i32::MAX as i64) as i32; if classic { n.saturating_add(128)&!255 } else { n } };
48        let mut pen=0i32; let mut placed=Vec::new(); let mut carets=Vec::new(); let mut segments=Vec::new();
49        let mut i=0;
50        while i<glyphs.len() {
51            let first=glyphs[i]; let left=pen;
52            while i<glyphs.len() && glyphs[i].cluster==first.cluster {
53                let g=glyphs[i];
54                let font=if g.font==0 { render_font } else { self.shaping.fallbacks[g.font-1].0 };
55                placed.push(Placed { font, id:g.id, x:pen.saturating_add(scale(g.x,font)), y:scale(g.y,font) });
56                pen=pen.saturating_add(scale(g.advance,font)); i+=1;
57            }
58            let right=pen; let (begin,end)=if first.rtl { (right,left) } else { (left,right) };
59            carets.push((first.cluster,begin));
60            // Expose legal UTF-8 caret boundaries even for multi-character ligatures.
61            let chars:Vec<_>=text[first.cluster..first.end].char_indices().map(|(o,_)| first.cluster+o).collect();
62            for (k,&index) in chars.iter().enumerate() {
63                let a=begin+((end-begin) as i64*k as i64/chars.len() as i64) as i32;
64                let b=begin+((end-begin) as i64*(k+1) as i64/chars.len() as i64) as i32;
65                segments.push((index,chars.get(k+1).copied().unwrap_or(first.end),a.min(b),a.max(b)));
66            }
67            for (k,&index) in chars.iter().enumerate().skip(1) {
68                carets.push((index,begin+((end-begin) as i64*k as i64/chars.len() as i64) as i32));
69            }
70            carets.push((first.end,end));
71        }
72        if carets.is_empty() { carets.push((0,0)); }
73        carets.sort_by_key(|c| c.0); carets.dedup_by_key(|c| c.0);
74        self.shaping.text=String::from(text); self.shaping.font=self.font; self.shaping.size=self.size;
75        self.shaping.glyphs=placed; self.shaping.carets=carets; self.shaping.width=pen; self.shaping.segments=segments;
76        true
77    }
78    // system fonts for the characters neither the font nor the fallback engines have
79    fn shape_system_fallbacks(&mut self, text: &str, render_font: u32) {
80        let mut need: Vec<u32> = Vec::new();
81        for ch in text.chars() {
82            let cp = ch as u32;
83            if self.has(render_font, cp) || self.shaping.fallbacks.iter().any(|f| self.has(f.0, cp)) { continue; }
84            if let Some(id) = self.fallback_for(cp) {
85                if !need.contains(&id) && !self.shaping.fallbacks.iter().any(|f| f.0 == id) { need.push(id); }
86            }
87        }
88        for id in need {
89            if self.shaping.fallbacks.len() >= 16 { break; }
90            let engine = self.shape_engine(id);
91            if let Some(e) = engine { self.shaping.fallbacks.push((id, e)); self.shaping.size = 0; }
92        }
93    }
94    pub(crate) fn shaped_draw(&mut self, pen0: i32, mut target: Option<(&mut gfx::Dc,&mut gfx::Surface)>) -> i32 {
95        let baseline=self.run_y;
96        for i in 0..self.shaping.glyphs.len() {
97            let g=self.shaping.glyphs[i];
98            if let Some((dc,sf))=&mut target {
99                self.run_y=baseline-((g.y+128)>>8);
100                if !self.color_glyph(dc,sf,g.font,g.id,pen0.saturating_add(g.x)) { self.glyph_draw(dc,sf,g.font,g.id,pen0.saturating_add(g.x),true); }
101            }
102        }
103        self.run_y=baseline; self.shaping.width
104    }
105    pub(crate) fn shaped_caret(&self, index: usize) -> i32 {
106        self.shaping.carets.iter().min_by_key(|c| c.0.abs_diff(index)).map_or(0,|c| (c.1+128)>>8)
107    }
108    pub(crate) fn shaped_hit(&self, x: i32) -> usize {
109        self.shaping.carets.iter().min_by_key(|c| ((c.1 as i64)-(x as i64*256)).abs()).map_or(0,|c| c.0)
110    }
111    /// Visual spans for a logical UTF-8 selection. Mixed bidi text may require
112    /// disjoint rectangles; the two endpoint carets alone are not sufficient.
113    pub fn selection_spans(&mut self, text: &[u8], start: usize, end: usize) -> Vec<(i32,i32)> {
114        if start>=end { return Vec::new(); }
115        if !self.shape_line(text,None) {
116            let a=self.caret_x(text,start); let b=self.caret_x(text,end);
117            return alloc::vec![(a.min(b),a.max(b))];
118        }
119        let mut spans:Vec<_>=self.shaping.segments.iter().filter(|s|s.0<end && s.1>start)
120            .map(|s|((s.2+128)>>8,(s.3+128)>>8)).collect();
121        spans.sort_unstable();
122        let mut merged:Vec<(i32,i32)>=Vec::new();
123        for (a,b) in spans {
124            if let Some(last)=merged.last_mut() { if a<=last.1 { last.1=last.1.max(b); continue; } }
125            merged.push((a,b));
126        }
127        merged
128    }
129}