Skip to main content

text/
lib.rs

1// text -- fonts (embedded SGF1 / TrueType, runtime OpenType / collections, synthetic styles,
2// colour glyphs), the glyph cache and text runs                        [src/text/*.s]
3//
4//   Text::new(fonts, names)          register the style's four embedded fonts
5//   set_font(font, size_fx)          select a font id and the pixel size (24.8)
6//   draw(dc, sf, x, y, s) -> px      y = baseline (origin-relative); stops at NUL / newline
7//   draw_n(dc, sf, x, y, s, len)     at most len bytes (a UTF-8 sequence cut by len ends it)
8//   measure(s) -> 24.8   width(s) -> px (rounded up)   ascent() / descent() / line_h() -> px
9//   hit(s, len, x) -> byte index     caret_x(s, index) -> px
10//   decor(dc, sf, x, y, w, TD_*)     underline / strikeout rectangles
11//   icon_draw(dc, sf, cp, x, y, size_fx)   FONT_ICON with its em box at x, y
12//   gc_get / gc_get_bitmap / gc_box / gc_flush / gc_scale / gc_usage   the glyph cache
13//                              (glyphcache.rs)
14//   png_decode / img_scale     CBDT hooks (default: img_png::decode, img_core::ops::scale)
15//   font registry: registry.rs; system font database: fontdb.rs; generic families,
16//   substitutes and the languages' fonts (a light fontconfig): fontconf.rs
17//   segment: grapheme clusters, words (UAX #29) and line break opportunities (UAX #14)
18//   set_antialias(on) / antialias()   smoothed (default) or black-and-white glyph masks
19//                                  (the system's "font smoothing off"; glyphs too large for
20//                                  the cache stay smoothed)
21//   set_subpixel(SUBPIXEL_*) / subpixel()   subpixel (LCD) smoothing in the display's order
22//                                  on opaque targets (lcd.rs); NONE (default): greyscale
23//   set_fallback_fonts / set_system_fallback / fallback_for   characters the font lacks come
24//                                  from other fonts (fallback.rs; also when shaping)
25//   record_n / record_icon / glyphs   glyphs placed now, blended later from the glyph cache by
26//                                  any thread (record.rs: GlyphRec, GlyphView)
27//   var_axes / var_instances / var_values / vary / vary_named   variable fonts: their axes,
28//                                  named instances and instances at axis values (registry.rs,
29//                                  var.rs: gvar, avar, HVAR, MVAR; shaping at the same
30//                                  coordinates)
31//
32// Pen positions are 24.8 fixed point; glyphs are placed on quarter pixels and their
33// coverage masks are cached per (font, glyph, size, phase, greyscale / subpixel).  Colour
34// fonts: COLR glyphs are drawn layer by layer (palette 0; the foreground entry uses the
35// current colour, palette alphas are multiplied by the current colour's alpha), CBDT glyphs
36// as PARGB bitmaps (not drawn into A8 targets).  Glyphs too large for the glyph cache are
37// filled directly.  U+00AD SOFT HYPHEN is not drawn and has no width (line wrapping shows a
38// hyphen where it breaks: text::segment::wrap_end_hyph).
39
40#![no_std]
41#![forbid(unsafe_code)]
42
43extern crate alloc;
44
45pub mod cff;
46pub mod colr;
47mod fallback;
48pub mod font;
49pub mod fontconf;
50pub mod fontdb;
51pub mod glyphcache;
52pub mod lcd;
53pub mod outline;
54mod record;
55pub mod rendering;
56pub mod registry;
57pub mod segment;
58mod segment_data;
59pub mod ttf;
60mod shaping;
61pub mod var;
62
63use alloc::vec::Vec;
64
65use gfx::path::Path;
66use gfx::raster::Raster;
67use gfx::{blend, Dc, Surface, FMT_A8};
68use rt::fx::{cvt, div255};
69
70pub use font::*;
71pub use glyphcache::{GcEntry, GcRef};
72pub use lcd::{SUBPIXEL_BGR, SUBPIXEL_NONE, SUBPIXEL_RGB, SUBPIXEL_VBGR, SUBPIXEL_VRGB};
73pub use record::{GlyphRec, GlyphView};
74pub use registry::{FONT_EMBEDDED, FONT_MAX_IDS};
75pub use rendering::FontRaster;
76pub use var::{VarAxis, VarInstance};
77
78use colr::LAYER_FG;
79use outline::Outline;
80use registry::Slot;
81
82pub const FONT_REG: u32 = 0;
83pub const FONT_SEMI: u32 = 1;
84pub const FONT_BOLD: u32 = 2;
85pub const FONT_ICON: u32 = 3;
86const LAYER_CAP: usize = 256; // colour layers drawn per glyph
87
88pub struct Text {
89    shaping: shaping::Cache,
90    pub(crate) fb: fallback::Fallback,
91    pub(crate) slots: Vec<Slot>,
92    pub(crate) serial: u64,
93    pub(crate) names: [[&'static str; 5]; 4],
94    /// the outline buffer of runtime / derived fonts
95    pub ol: Outline,
96    /// the glyph transform used by glyph_path
97    pub gp: Gp,
98    pub path: Path,
99    pub ras: Raster,
100    pub(crate) gc: glyphcache::Gc,
101    pub(crate) cb_metrics: [u8; 5],
102    /// PNG decoder and bilinear scaler for CBDT colour bitmaps (set by the app / ui)
103    pub png_decode: Option<colr::PngDecode>,
104    pub img_scale: Option<colr::ImgScale>,
105    font: u32,
106    size: i32,
107    sx: f32,
108    run_y: i32,
109    layers: Vec<(u32, u32)>,
110    /// SUBPIXEL_* (lcd.rs) and its blend tables
111    lcd: u8,
112    lcd_lut: Option<alloc::boxed::Box<lcd::Lut>>,
113    /// record_n / record_icon: glyph_draw appends here instead of blending (record.rs)
114    rec: Option<record::GlyphRec>,
115}
116
117pub(crate) fn utf8_put(out: &mut Vec<u8>, cp: u32) {
118    if cp < 0x80 {
119        out.push(cp as u8);
120    } else if cp < 0x800 {
121        out.push(0xC0 | (cp >> 6) as u8);
122        out.push(0x80 | (cp & 0x3F) as u8);
123    } else if cp < 0x10000 {
124        out.push(0xE0 | (cp >> 12) as u8);
125        out.push(0x80 | ((cp >> 6) & 0x3F) as u8);
126        out.push(0x80 | (cp & 0x3F) as u8);
127    } else {
128        out.push(0xF0 | (cp >> 18) as u8);
129        out.push(0x80 | ((cp >> 12) & 0x3F) as u8);
130        out.push(0x80 | ((cp >> 6) & 0x3F) as u8);
131        out.push(0x80 | (cp & 0x3F) as u8);
132    }
133}
134
135impl Text {
136    /// font_init: register FONT_REG, FONT_SEMI, FONT_BOLD, FONT_ICON (the style's embedded
137    /// fonts, SGF1 or TrueType/OpenType) with their names (family, style, full,
138    /// typographic family / style). Each face starts with the legacy raster policy.
139    pub fn new(fonts: [&'static [u8]; 4], names: [[&'static str; 5]; 4]) -> Text {
140        let mut t = Text {
141            shaping: Default::default(),
142            fb: Default::default(),
143            slots: Vec::new(),
144            serial: 0,
145            names,
146            ol: Outline::default(),
147            gp: Gp::default(),
148            path: Path::default(),
149            ras: Raster::default(),
150            gc: glyphcache::Gc::default(),
151            cb_metrics: [0; 5],
152            png_decode: Some(png_decode),
153            img_scale: Some(img_scale),
154            font: 0,
155            size: 0,
156            sx: 0.0,
157            run_y: 0,
158            layers: Vec::new(),
159            lcd: SUBPIXEL_NONE,
160            lcd_lut: None,
161            rec: None,
162        };
163        t.ol.reset();
164        for f in fonts {
165            if f.starts_with(b"SGF1") { t.register(f); } else { t.load_mem(f, 0); }
166        }
167        t.set_font(FONT_REG, 14 << 8);
168        t
169    }
170
171    /// text_font(font, size_fx)
172    pub fn set_font(&mut self, font: u32, size_fx: i32) {
173        self.font = font;
174        self.size = size_fx;
175        self.sx = self.gc_scale(font, size_fx).0;
176    }
177
178    /// text_get_font() -> (font, size_fx)
179    pub fn get_font(&self) -> (u32, i32) {
180        (self.font, self.size)
181    }
182
183    // round(units * sx * 256)
184    fn units_fx(&self, u: i32) -> i32 {
185        cvt(u as f32 * self.sx * 256.0)
186    }
187
188    // units_fx for another font of the run (a fallback font) at the run's size
189    fn units_fx_of(&self, font: u32, u: i32) -> i32 {
190        if font == self.font { self.units_fx(u) } else { cvt(u as f32 * self.gc_scale(font, self.size).0 * 256.0) }
191    }
192
193    /// ceil(ascender px)
194    pub fn ascent(&self) -> i32 {
195        (self.units_fx(self.hdr(self.font).asc) + 255) >> 8
196    }
197
198    /// ceil(descender px), positive
199    pub fn descent(&self) -> i32 {
200        (self.units_fx(self.hdr(self.font).desc.wrapping_neg()) + 255) >> 8
201    }
202
203    /// round((asc - desc + gap) px)
204    pub fn line_h(&self) -> i32 {
205        let h = self.hdr(self.font);
206        (self.units_fx(h.asc - h.desc + h.gap) + 128) >> 8
207    }
208
209    /// text_measure: advance of the run (24.8), up to NUL / newline
210    pub fn measure(&mut self, s: &str) -> i32 {
211        self.run(s.as_bytes(), 0, None, None)
212    }
213
214    /// text_measure_n(s, len)
215    pub fn measure_n(&mut self, s: &[u8], len: usize) -> i32 {
216        self.run(s, 0, Some(len), None)
217    }
218
219    /// text_width: px, rounded up
220    pub fn width(&mut self, s: &str) -> i32 {
221        (self.measure(s) + 255) >> 8
222    }
223
224    pub fn width_n(&mut self, s: &[u8], len: usize) -> i32 {
225        (self.measure_n(s, len) + 255) >> 8
226    }
227
228    /// text_draw(x, y = baseline, s) -> width px
229    pub fn draw(&mut self, dc: &mut Dc, sf: &mut Surface, x: i32, y: i32, s: &[u8]) -> i32 {
230        self.draw_impl(dc, sf, x, y, s, None)
231    }
232
233    /// text_draw_n(x, y, s, len) -> width px
234    pub fn draw_n(&mut self, dc: &mut Dc, sf: &mut Surface, x: i32, y: i32, s: &[u8], len: usize) -> i32 {
235        self.draw_impl(dc, sf, x, y, s, Some(len))
236    }
237
238    fn draw_impl(&mut self, dc: &mut Dc, sf: &mut Surface, x: i32, y: i32, s: &[u8], len: Option<usize>) -> i32 {
239        let pen = x.wrapping_add(dc.ox) << 8;
240        self.run_y = y.wrapping_add(dc.oy);
241        let w = self.run(s, pen, len, Some((dc, sf)));
242        (w + 255) >> 8
243    }
244
245    // text_run: pen positions with kerning; draws when a target is given.  Stops at NUL,
246    // newline, the end of s or after len bytes.
247    fn run(&mut self, s: &[u8], pen0: i32, len: Option<usize>, mut target: Option<(&mut Dc, &mut Surface)>) -> i32 {
248        if self.shape_line(s,len) { return self.shaped_draw(pen0,target); }
249        let font = self.font;
250        let classic = self.classic_metrics(font);
251        let spacing = |v: i32| if classic { v.saturating_add(128) & !255 } else { v };
252        let fallback = self.fallback_active();
253        let mut pen = pen0;
254        let mut prev: i32 = -1;
255        let mut prev_font = font;
256        let mut left = len.unwrap_or(usize::MAX);
257        let mut i = 0;
258        while left > 0 && i < s.len() {
259            let b = s[i];
260            if b == 0 || b == b'\n' {
261                break;
262            }
263            if len.is_some() {
264                // with a length: a UTF-8 sequence cut by it ends the run
265                let n = if b < 0xC0 {
266                    1
267                } else if b < 0xE0 {
268                    2
269                } else if b < 0xF0 {
270                    3
271                } else {
272                    4
273                };
274                if left < n {
275                    break;
276                }
277            }
278            let (cp, next) = base::utf8_decode(s, i);
279            if len.is_some() {
280                left = left.saturating_sub(next - i);
281            }
282            i = next;
283            if cp == 0xAD {
284                continue; // SOFT HYPHEN: invisible (wrapping draws a hyphen where it breaks)
285            }
286            // no-break, narrow and other spaces of number formats: a plain space when missing
287            let cp = if matches!(cp, 0xA0 | 0x2000..=0x200A | 0x202F | 0x205F) && !self.has(font, cp) { 0x20 } else { cp };
288            // a character the font lacks: the first font of the fallback chain that has it
289            let mut f = font;
290            if fallback && !self.has(font, cp) {
291                if let Some(alt) = self.fallback_for(cp) {
292                    f = alt;
293                }
294            }
295            let g = self.glyph(f, cp);
296            if prev >= 0 && f == prev_font {
297                let k = self.kern(f, prev as u32, g);
298                if k != 0 {
299                    pen = pen.wrapping_add(spacing(self.units_fx_of(f, k)));
300                }
301            }
302            if let Some((dc, sf)) = target.as_mut() {
303                let flags = if self.slots.is_empty() { 0 } else { self.hdr(f).flags };
304                let mut drawn = false;
305                if flags & (SGH_COLR | SGH_CBDT) != 0 {
306                    drawn = self.color_glyph(dc, sf, f, g, pen);
307                }
308                if !drawn {
309                    self.glyph_draw(dc, sf, f, g, pen, true);
310                }
311            }
312            let adv = self.adv(f, g);
313            pen = pen.wrapping_add(spacing(self.units_fx_of(f, adv)));
314            prev = g as i32;
315            prev_font = f;
316        }
317        pen.wrapping_sub(pen0)
318    }
319
320    // draw one glyph with the current colour at pen (absolute 24.8) on the baseline run_y:
321    // cached mask, or a direct fill when too large; lcd: subpixel smoothing allowed (not
322    // for colour layers); while recording (record.rs) the glyph is appended to the record
323    // instead  [glyph_draw]
324    pub(crate) fn glyph_draw(&mut self, dc: &Dc, sf: &mut Surface, font: u32, g: u32, pen: i32, lcd: bool) {
325        let size = self.size;
326        let pen = self.font_raster(font).position(pen);
327        let phase = (pen >> 6) & 3;
328        let base = self.run_y;
329        let rec = self.rec.is_some();
330        if lcd && self.lcd_for(sf, font) {
331            self.lcd_prepare(dc.color);
332            let order = self.lcd;
333            let vertical = order >= SUBPIXEL_VRGB;
334            let bgr = order == SUBPIXEL_BGR || order == SUBPIXEL_VBGR;
335            match self.gc_get_lcd(font, g, size, phase, vertical) {
336                GcRef::None => return,
337                GcRef::Entry(_) => {
338                    let key = glyphcache::lcd_key(font, g, size, phase, vertical);
339                    if let (Some(e), Some(lut)) = (self.gc.map.get(&key), self.lcd_lut.as_deref()) {
340                        match self.rec.as_mut() {
341                            Some(r) => r.lcd(key, e, pen >> 8, base, dc.color, bgr, lut.adj()),
342                            None => lcd::blit(dc, sf, pen >> 8, base, e, bgr, lut, lut.adj()),
343                        }
344                    }
345                    return;
346                }
347                GcRef::Direct => {} // too large for the cache: greyscale below
348            }
349        }
350        match self.gc_get(font, g, size, phase) {
351            GcRef::None => {}
352            GcRef::Entry(e) if !rec => mask_blit(dc, sf, pen >> 8, base, e),
353            GcRef::Entry(_) => {
354                let key = glyphcache::gc_key(font, g, size, phase);
355                if let (Some(e), Some(r)) = (self.gc.map.get(&key), self.rec.as_mut()) {
356                    r.mask(key, e, pen >> 8, base, dc.color);
357                }
358            }
359            GcRef::Direct if rec => {
360                if let Some(r) = self.rec.as_mut() {
361                    r.fail(); // a filled path: not in the cache
362                }
363            }
364            GcRef::Direct => {
365                // same placement as a cache entry: integer x + quarter-pixel phase
366                let (sx, sy) = self.gc_scale(font, size);
367                self.gp = Gp { x: (pen >> 6) as f32 * 0.25, y: base as f32, sx, sy, dx: 0, dy: 0 };
368                self.path.begin(dc);
369                self.glyph_path(font, g);
370                self.path.fill(&mut self.ras, dc, sf, 0);
371            }
372        }
373    }
374
375    // a colour glyph: true drawn / false (not a colour glyph: draw it normally)  [color_glyph]
376    fn color_glyph(&mut self, dc: &mut Dc, sf: &mut Surface, font: u32, g: u32, pen: i32) -> bool {
377        if self.hdr(font).flags & SGH_CBDT != 0 {
378            let size = self.size;
379            let base = self.run_y;
380            let rec = self.rec.is_some();
381            match self.gc_get_bitmap(font, g, size) {
382                Some(e) if !rec => {
383                    bitmap_blit(dc, sf, pen >> 8, base, e);
384                    return true;
385                }
386                Some(_) => {
387                    // a bitmap above the cache's limit is not in the table: not recordable
388                    let key = glyphcache::bitmap_key(font, g, size);
389                    match (self.gc.map.get(&key), self.rec.as_mut()) {
390                        (Some(e), Some(r)) => r.bitmap(key, e, pen >> 8, base, dc.color),
391                        (None, Some(r)) => r.fail(),
392                        _ => {}
393                    }
394                    return true;
395                }
396                None => {}
397            }
398        }
399        let mut layers = core::mem::take(&mut self.layers);
400        self.layers(font, g, LAYER_CAP, &mut layers);
401        if layers.is_empty() {
402            self.layers = layers;
403            return false;
404        }
405        let saved = dc.get_color();
406        for &(lg, argb) in &layers {
407            let c = if lg & LAYER_FG != 0 {
408                saved
409            } else {
410                // palette colour, alpha x the text colour's alpha
411                let a = div255((argb >> 24) * (saved >> 24));
412                (a << 24) | (argb & 0xFF_FFFF)
413            };
414            dc.set_color(c);
415            self.glyph_draw(dc, sf, font, lg & 0xFFFF, pen, false);
416        }
417        dc.set_color(saved);
418        self.layers = layers;
419        true
420    }
421
422    /// text_caret_x(s, index) -> x px (rounded) of the caret before byte index
423    pub fn caret_x(&mut self, s: &[u8], index: usize) -> i32 {
424        if self.shape_line(s,None) { return self.shaped_caret(index); }
425        (self.measure_n(s, index) + 128) >> 8
426    }
427
428    /// text_hit(s, len, x) -> byte index whose caret position is closest to x (len None:
429    /// up to NUL)
430    pub fn hit(&mut self, s: &[u8], len: Option<usize>, x: i32) -> usize {
431        if self.shape_line(s,len) { return self.shaped_hit(x); }
432        let xf = x << 8;
433        let mut i = 0usize;
434        let mut pen = 0i32;
435        loop {
436            if let Some(l) = len
437                && i >= l
438            {
439                break;
440            }
441            if i >= s.len() || s[i] == 0 || s[i] == b'\n' {
442                break;
443            }
444            let next = utf8_next(s, i);
445            let p = self.measure_n(s, next);
446            // past the midpoint between pen(index) and pen(next)?
447            if xf < (pen.wrapping_add(p)) >> 1 {
448                break;
449            }
450            pen = p;
451            i = next;
452        }
453        i
454    }
455
456    /// text_decor(x, y, w, flags): underline / strikeout rectangles of the current font and
457    /// size (filled from x, w px long; y = baseline)
458    pub fn decor(&self, dc: &Dc, sf: &mut Surface, x: i32, y: i32, w: i32, flags: u32) {
459        let m = self.metrics(self.font).unwrap_or_default();
460        if flags & TD_UNDERLINE != 0 {
461            self.td_rect(dc, sf, x, y, w, m.ul_pos, m.ul_thick);
462        }
463        if flags & TD_STRIKEOUT != 0 {
464            self.td_rect(dc, sf, x, y, w, m.st_pos, m.st_thick);
465        }
466    }
467
468    // the rectangle whose top is pos font units above the baseline
469    fn td_rect(&self, dc: &Dc, sf: &mut Surface, x: i32, y: i32, w: i32, pos: i32, thick: i32) {
470        let top = y.wrapping_sub(cvt(pos as f32 * self.sx));
471        let th = cvt(thick as f32 * self.sx).max(1);
472        dc.fill(sf, x, top, w, th);
473    }
474
475    /// icon_draw(cp, x, y, size_fx): the icon's em box has its top-left corner at x, y
476    pub fn icon_draw(&mut self, dc: &mut Dc, sf: &mut Surface, cp: u32, x: i32, y: i32, size_fx: i32) {
477        let saved = self.get_font();
478        self.set_font(FONT_ICON, size_fx);
479        let mut buf = Vec::new();
480        utf8_put(&mut buf, cp);
481        let asc = self.ascent(); // the icon font's ascender is its em height
482        self.draw(dc, sf, x, y + asc, &buf);
483        self.set_font(saved.0, saved.1);
484    }
485}
486
487// the CBDT hooks' defaults: img_png's decoder (PARGB) and img_scale's bilinear copy
488fn png_decode(d: &[u8]) -> Option<Surface> {
489    let mut sf = Surface::new(0, 0, gfx::FMT_PARGB);
490    if img_png::dec::decode(d, &mut sf) == 0 { Some(sf) } else { None }
491}
492
493fn img_scale(src: &Surface, w: i32, h: i32) -> Option<Surface> {
494    img_core::ops::scale(src, w, h, true).ok()
495}
496
497// the index after the UTF-8 sequence at i (utf8_next)
498fn utf8_next(s: &[u8], i: usize) -> usize {
499    base::utf8_decode(s, i).1
500}
501
502// blend a glyph mask with the colour of dc at pen x / baseline (absolute target
503// coordinates), clipped  [mask_blit]
504pub(crate) fn mask_blit(dc: &Dc, sf: &mut Surface, pen_x: i32, base: i32, gl: &GcEntry) {
505    let mut x0 = pen_x.wrapping_add(gl.left);
506    let mut y0 = base.wrapping_add(gl.top);
507    let mut sx = 0i32;
508    let mut sy = 0i32;
509    let x1 = x0.wrapping_add(gl.w).min(dc.clip[2]);
510    let y1 = y0.wrapping_add(gl.h).min(dc.clip[3]);
511    if dc.clip[0] > x0 {
512        sx = dc.clip[0] - x0;
513        x0 = dc.clip[0];
514    }
515    if dc.clip[1] > y0 {
516        sy = dc.clip[1] - y0;
517        y0 = dc.clip[1];
518    }
519    let w = x1 - x0;
520    let h = y1 - y0;
521    if w <= 0 || h <= 0 {
522        return;
523    }
524    let stride = sf.stride as usize;
525    for r in 0..h as usize {
526        let mo = (sy as usize + r) * gl.w as usize + sx as usize;
527        let mask = &gl.px[mo..mo + w as usize];
528        let o = ((y0 - dc.row0) as usize + r) * stride;
529        if sf.fmt == FMT_A8 {
530            blend::a8_over(&mut sf.px[o + x0 as usize..o + x1 as usize], mask, dc.pcol32 >> 24);
531        } else {
532            blend::mask_over(&mut sf.px[o + x0 as usize * 4..o + x1 as usize * 4], mask, &dc.pcol);
533        }
534    }
535}
536
537// draw a PARGB glyph bitmap (absolute target coordinates), clipped, multiplied by the
538// colour's alpha and the global opacity  [bitmap_blit]
539pub(crate) fn bitmap_blit(dc: &Dc, sf: &mut Surface, pen_x: i32, base: i32, e: &GcEntry) {
540    if sf.fmt == FMT_A8 {
541        return;
542    }
543    let mut x0 = pen_x.wrapping_add(e.left);
544    let mut y0 = base.wrapping_add(e.top);
545    let mut sx = 0i32;
546    let mut sy = 0i32;
547    let x1 = x0.wrapping_add(e.w).min(dc.clip[2]);
548    let y1 = y0.wrapping_add(e.h).min(dc.clip[3]);
549    if dc.clip[0] > x0 {
550        sx = dc.clip[0] - x0;
551        x0 = dc.clip[0];
552    }
553    if dc.clip[1] > y0 {
554        sy = dc.clip[1] - y0;
555        y0 = dc.clip[1];
556    }
557    let w = x1 - x0;
558    let h = y1 - y0;
559    if w <= 0 || h <= 0 {
560        return;
561    }
562    let op = div255((dc.color >> 24) * dc.opac);
563    let stride = sf.stride as usize;
564    let sstride = e.w as usize * 4;
565    for r in 0..h as usize {
566        let so = (sy as usize + r) * sstride + sx as usize * 4;
567        let o = ((y0 - dc.row0) as usize + r) * stride + x0 as usize * 4;
568        blend::blit_over(&mut sf.px[o..o + w as usize * 4], &e.px[so..so + w as usize * 4], op);
569    }
570}