render.rs
1 /* This file is part of DarkFi (https://dark.fi) 2 * 3 * Copyright (C) 2020-2025 Dyne.org foundation 4 * 5 * This program is free software: you can redistribute it and/or modify 6 * it under the terms of the GNU Affero General Public License as 7 * published by the Free Software Foundation, either version 3 of the 8 * License, or (at your option) any later version. 9 * 10 * This program is distributed in the hope that it will be useful, 11 * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 * GNU Affero General Public License for more details. 14 * 15 * You should have received a copy of the GNU Affero General Public License 16 * along with this program. If not, see <https://www.gnu.org/licenses/>. 17 */ 18 19 use crate::{ 20 gfx::{DebugTag, DrawInstruction, DrawMesh, Point, Rectangle, RenderApi}, 21 mesh::{Color, MeshBuilder, COLOR_WHITE}, 22 }; 23 24 use super::atlas::{Atlas, RenderedAtlas}; 25 26 #[derive(Copy, Clone, Debug, PartialEq, Eq)] 27 pub struct DebugRenderOptions(u32); 28 29 impl DebugRenderOptions { 30 pub const OFF: DebugRenderOptions = DebugRenderOptions(0b00); 31 pub const GLYPH: DebugRenderOptions = DebugRenderOptions(0b01); 32 pub const BASELINE: DebugRenderOptions = DebugRenderOptions(0b10); 33 34 pub fn has(self, other: Self) -> bool { 35 (self.0 & other.0) == other.0 36 } 37 } 38 39 impl std::ops::BitOr for DebugRenderOptions { 40 type Output = Self; 41 42 fn bitor(self, rhs: Self) -> Self { 43 Self(self.0 | rhs.0) 44 } 45 } 46 impl std::ops::BitOrAssign for DebugRenderOptions { 47 fn bitor_assign(&mut self, rhs: Self) { 48 self.0 |= rhs.0; 49 } 50 } 51 52 pub fn render_layout( 53 layout: &parley::Layout<Color>, 54 render_api: &RenderApi, 55 tag: DebugTag, 56 ) -> Vec<DrawInstruction> { 57 render_layout_with_opts(layout, DebugRenderOptions::OFF, render_api, tag) 58 } 59 60 pub fn render_layout_with_opts( 61 layout: &parley::Layout<Color>, 62 opts: DebugRenderOptions, 63 render_api: &RenderApi, 64 tag: DebugTag, 65 ) -> Vec<DrawInstruction> { 66 let mut scale_cx = swash::scale::ScaleContext::new(); 67 let mut run_idx = 0; 68 let mut instrs = vec![]; 69 for line in layout.lines() { 70 for item in line.items() { 71 match item { 72 parley::PositionedLayoutItem::GlyphRun(glyph_run) => { 73 let mesh = 74 render_glyph_run(&mut scale_cx, &glyph_run, run_idx, opts, render_api, tag); 75 instrs.push(DrawInstruction::Draw(mesh)); 76 run_idx += 1; 77 } 78 parley::PositionedLayoutItem::InlineBox(_) => {} 79 } 80 } 81 } 82 instrs 83 } 84 85 fn render_glyph_run( 86 scale_ctx: &mut swash::scale::ScaleContext, 87 glyph_run: &parley::GlyphRun<'_, Color>, 88 _run_idx: usize, 89 opts: DebugRenderOptions, 90 render_api: &RenderApi, 91 tag: DebugTag, 92 ) -> DrawMesh { 93 let mut run_x = glyph_run.offset(); 94 let run_y = glyph_run.baseline(); 95 let style = glyph_run.style(); 96 let color = style.brush; 97 //trace!(target: "text::render", "render_glyph_run run_idx={run_idx} baseline={run_y}"); 98 99 let atlas = create_atlas(scale_ctx, glyph_run, render_api, tag); 100 101 let mut mesh = MeshBuilder::new(tag); 102 103 if let Some(underline) = &style.underline { 104 render_underline(underline, glyph_run, &mut mesh); 105 } 106 107 for glyph in glyph_run.glyphs() { 108 let glyph_inf = atlas.fetch_uv(glyph.id as u16).expect("missing glyph UV rect"); 109 110 let glyph_x = run_x + glyph.x; 111 let glyph_y = run_y - glyph.y; 112 run_x += glyph.advance; 113 114 let glyph_rect = Rectangle::new( 115 glyph_x + glyph_inf.place.left as f32, 116 glyph_y - glyph_inf.place.top as f32, 117 glyph_inf.place.width as f32, 118 glyph_inf.place.height as f32, 119 ); 120 121 if opts.has(DebugRenderOptions::GLYPH) { 122 mesh.draw_outline(&glyph_rect, [0., 1., 0., 0.7], 1.); 123 } 124 125 let color = if glyph_inf.is_color { COLOR_WHITE } else { color }; 126 mesh.draw_box(&glyph_rect, color, &glyph_inf.uv_rect); 127 } 128 129 if opts.has(DebugRenderOptions::BASELINE) { 130 mesh.draw_filled_box( 131 &Rectangle::new(glyph_run.offset(), glyph_run.baseline(), glyph_run.advance(), 1.), 132 [0., 0., 1., 0.7], 133 ); 134 } 135 136 mesh.alloc(render_api).draw_with_texture(atlas.texture) 137 } 138 139 fn render_underline( 140 underline: &parley::layout::Decoration<Color>, 141 glyph_run: &parley::GlyphRun<'_, Color>, 142 mesh: &mut MeshBuilder, 143 ) { 144 let color = underline.brush; 145 let run_metrics = glyph_run.run().metrics(); 146 let offset = match underline.offset { 147 Some(offset) => offset, 148 None => run_metrics.underline_offset, 149 }; 150 let width = match underline.size { 151 Some(size) => size, 152 None => run_metrics.underline_size, 153 }; 154 // The `offset` is the distance from the baseline to the top of the underline 155 // so we move the line down by half the width 156 // Remember that we are using a y-down coordinate system 157 // If there's a custom width, because this is an underline, we want the custom 158 // width to go down from the default expectation 159 let y = glyph_run.baseline() - offset + width / 2.; 160 161 let start_x = glyph_run.offset(); 162 let end_x = start_x + glyph_run.advance(); 163 164 let start = Point::new(start_x, y); 165 let end = Point::new(end_x, y); 166 167 mesh.draw_line(start, end, color, width); 168 } 169 170 fn create_atlas( 171 scale_ctx: &mut swash::scale::ScaleContext, 172 glyph_run: &parley::GlyphRun<'_, Color>, 173 render_api: &RenderApi, 174 tag: DebugTag, 175 ) -> RenderedAtlas { 176 let run = glyph_run.run(); 177 let font = run.font(); 178 let font_size = run.font_size(); 179 let normalized_coords = run.normalized_coords(); 180 let font_ref = swash::FontRef::from_index(font.data.as_ref(), font.index as usize).unwrap(); 181 182 let scaler = scale_ctx 183 .builder(font_ref) 184 .size(font_size) 185 .hint(true) 186 .normalized_coords(normalized_coords) 187 .build(); 188 189 let mut atlas = Atlas::new(scaler, render_api, tag); 190 for glyph in glyph_run.glyphs() { 191 atlas.push_glyph(glyph.id as u16); 192 } 193 //atlas.dump(&format!("/tmp/atlas_{run_idx}.png")); 194 atlas.make() 195 }