diff --git a/crates/text/src/font/raster.rs b/crates/text/src/font/raster.rs deleted file mode 100644 index c276ae1..0000000 --- a/crates/text/src/font/raster.rs +++ /dev/null @@ -1,235 +0,0 @@ -use super::tables::glyf::{Contour, GlyphOutline, Point}; - -/// A rasterized grayscale bitmap of a single glyph. -#[derive(Debug, Clone)] -pub struct GlyphBitmap { - pub width: u32, - pub height: u32, - pub bearing_x: i32, - pub bearing_y: i32, - /// 8-bit coverage values (0 = transparent, 255 = fully opaque). - /// Row-major layout, top-to-bottom. - pub data: Vec, -} - -/// Convert a physical glyph outline into an anti-aliased bitmap. -pub fn rasterize_glyph( - outline: &GlyphOutline, - scale: f32, -) -> Option { - // 1. Flatten the contour into line segments (scaled to pixels). - let mut segments: Vec<(f32, f32, f32, f32)> = Vec::new(); - - for contour in &outline.contours { - if contour.points.is_empty() { - continue; - } - - // TrueType points sequence parsing - let pts = &contour.points; - let mut curr = pts[0]; - - // Find a starting on-curve point - let mut start_idx = 0; - if !curr.on_curve { - let next = pts[1]; - if next.on_curve { - start_idx = 1; - curr = next; - } else { - // If both are off-curve, the implicit point is midway - curr = Point { - x: (curr.x + next.x) / 2, - y: (curr.y + next.y) / 2, - on_curve: true, - }; - } - } - - let first = curr; - let mut i = start_idx + 1; - let n = pts.len(); - - while i <= n { - // Read next point (wrap around up to start_idx) - let next = if i < n { pts[i] } else { pts[i % n] }; - let p1; - - if next.on_curve { - p1 = next; - add_line(&mut segments, curr, p1, scale); - curr = p1; - i += 1; - if i > n { break; } - } else { - // Next is off-curve - let p_ctrl = next; - let mut p2; - i += 1; - - let next2 = if i < n { pts[i] } else { pts[i % n] }; - if next2.on_curve { - p2 = next2; - i += 1; - } else { - // Implicit on-curve point midway between two off-curve - p2 = Point { - x: (p_ctrl.x + next2.x) / 2, - y: (p_ctrl.y + next2.y) / 2, - on_curve: true, - }; - } - - add_quadratic(&mut segments, curr, p_ctrl, p2, scale); - curr = p2; - - if i > n { break; } - } - } - - // Close the contour - add_line(&mut segments, curr, first, scale); - } - - if segments.is_empty() { - return None; - } - - // 2. Determine bounding box - let mut min_x = f32::MAX; - let mut min_y = f32::MAX; - let mut max_x = f32::MIN; - let mut max_y = f32::MIN; - - for &(x0, y0, x1, y1) in &segments { - min_x = min_x.min(x0).min(x1); - min_y = min_y.min(y0).min(y1); - max_x = max_x.max(x0).max(x1); - max_y = max_y.max(y0).max(y1); - } - - if min_x > max_x || min_y > max_y { - return None; - } - - let px_min_x = min_x.floor() as i32; - // Note: TrueType Y goes up. So y_max is the top of the glyph. - // We invert Y so that 0 is at the top of the bitmap. - let px_min_y = (-max_y).floor() as i32; - let px_max_x = max_x.ceil() as i32; - let px_max_y = (-min_y).ceil() as i32; - - let width = (px_max_x - px_min_x).max(1) as u32; - let height = (px_max_y - px_min_y).max(1) as u32; - - let bearing_x = px_min_x; - let bearing_y = px_min_y; // actually -max_y - - // 3. Rasterize using a simple 16x16 oversampling for each pixel - const SUBPIXELS: i32 = 16; - let sub_w = width as usize * SUBPIXELS as usize; - let sub_h = height as usize * SUBPIXELS as usize; - - // An active edge table could be used, but for simplicity - // we use a buffer of wind counts per subpixel row. - let mut coverages = vec![0i32; width as usize * height as usize]; - - // We'll rasterize each segment by drawing lines at subpixel resolution - for &(mut x0, mut y0, mut x1, mut y1) in &segments { - // shift relative to bounding box - x0 -= px_min_x as f32; - x1 -= px_min_x as f32; - // invert Y - y0 = -y0 - px_min_y as f32; - y1 = -y1 - px_min_y as f32; - - plot_line_analytic(&mut coverages, width as usize, height as usize, x0, y0, x1, y1); - } - - // Accumulate coverages and produce final bitmap - let mut data = Vec::with_capacity(width as usize * height as usize); - for row in 0..height as usize { - let mut accum = 0.0; - for col in 0..width as usize { - accum += coverages[row * width as usize + col] as f32 / 256.0; - // non-zero winding rule - let mut alpha = accum.abs(); - if alpha > 1.0 { alpha = 1.0; } - let intensity = (alpha * 255.0).round() as u8; - data.push(intensity); - } - } - - Some(GlyphBitmap { - width, - height, - bearing_x, - bearing_y, - data, - }) -} - -// Analytic anti-aliased line plotting into a coverage buffer. -// Inspired by font-rs and stb_truetype. -fn plot_line_analytic( - coverages: &mut [i32], - w: usize, - h: usize, - mut x0: f32, - mut y0: f32, - mut x1: f32, - mut y1: f32, -) { - let dx = x1 - x0; - let dy = y1 - y0; - let dir_y = dy.signum() as i32; - - if dir_y == 0 { return; } // horizontal lines cover no vertical area - - let mut ex = x0.floor() as i32; - let mut ey = y0.floor() as i32; - let ext_x = x1.floor() as i32; - let ext_y = y1.floor() as i32; - - // To handle negative directions, step is 1 or -1 - let step_x = dx.signum() as i32; - let step_y = dy.signum() as i32; - - // compute intersections with pixel boundaries - // ... - // A much simpler and robust fallback: 16x supersampling - let samples = 16; -} - -fn add_line(segments: &mut Vec<(f32, f32, f32, f32)>, p0: Point, p1: Point, scale: f32) { - segments.push(( - p0.x as f32 * scale, - p0.y as f32 * scale, - p1.x as f32 * scale, - p1.y as f32 * scale, - )); -} - -fn add_quadratic(segments: &mut Vec<(f32, f32, f32, f32)>, p0: Point, p1: Point, p2: Point, scale: f32) { - let mut last_x = p0.x as f32 * scale; - let mut last_y = p0.y as f32 * scale; - - let cx = p1.x as f32 * scale; - let cy = p1.y as f32 * scale; - - let px2 = p2.x as f32 * scale; - let py2 = p2.y as f32 * scale; - - let steps = 8; - for i in 1..=steps { - let t = i as f32 / steps as f32; - let mt = 1.0 - t; - - let x = mt * mt * last_x + 2.0 * mt * t * cx + t * t * px2; - let y = mt * mt * last_y + 2.0 * mt * t * cy + t * t * py2; - - segments.push((last_x, last_y, x, y)); - last_x = x; - last_y = y; - } -}