/* This file is part of DarkFi (https://dark.fi) * * Copyright (C) 2020-2025 Dyne.org foundation * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ use crate::{ gfx::{GfxDrawInstruction, GfxDrawMesh, Rectangle, RenderApi}, mesh::{Color, MeshBuilder, COLOR_WHITE}, }; use super::atlas::Atlas; #[derive(Copy, Clone, Debug, PartialEq, Eq)] pub struct DebugRenderOptions(u32); impl DebugRenderOptions { pub const Off: DebugRenderOptions = DebugRenderOptions(0b00); pub const Glyph: DebugRenderOptions = DebugRenderOptions(0b01); pub const Baseline: DebugRenderOptions = DebugRenderOptions(0b10); pub fn has(self, other: Self) -> bool { (self.0 & other.0) == other.0 } } impl std::ops::BitOr for DebugRenderOptions { type Output = Self; fn bitor(self, rhs: Self) -> Self { Self(self.0 | rhs.0) } } pub fn render_layout( layout: &parley::Layout, render_api: &RenderApi, ) -> Vec { render_layout_with_opts(layout, DebugRenderOptions::Off, render_api) } pub fn render_layout_with_opts( layout: &parley::Layout, opts: DebugRenderOptions, render_api: &RenderApi, ) -> Vec { let mut scale_cx = swash::scale::ScaleContext::new(); let mut run_idx = 0; let mut instrs = vec![]; for line in layout.lines() { for item in line.items() { match item { parley::PositionedLayoutItem::GlyphRun(glyph_run) => { let mesh = render_glyph_run(&mut scale_cx, &glyph_run, run_idx, opts, render_api); instrs.push(GfxDrawInstruction::Draw(mesh)); run_idx += 1; } parley::PositionedLayoutItem::InlineBox(_) => {} } } } instrs } fn render_glyph_run( scale_ctx: &mut swash::scale::ScaleContext, glyph_run: &parley::GlyphRun<'_, Color>, run_idx: usize, opts: DebugRenderOptions, render_api: &RenderApi, ) -> GfxDrawMesh { let mut run_x = glyph_run.offset(); let run_y = glyph_run.baseline(); let style = glyph_run.style(); let color = style.brush; let run = glyph_run.run(); trace!(target: "text::render", "render_glyph_run run_idx={run_idx}"); let font = run.font(); let font_size = run.font_size(); let normalized_coords = run.normalized_coords(); let font_ref = swash::FontRef::from_index(font.data.as_ref(), font.index as usize).unwrap(); let mut scaler = scale_ctx .builder(font_ref) .size(font_size) .hint(true) .normalized_coords(normalized_coords) .build(); let mut atlas = Atlas::new(scaler, render_api); for glyph in glyph_run.glyphs() { atlas.push_glyph(glyph); } //atlas.dump(&format!("/tmp/atlas_{run_idx}.png")); let atlas = atlas.make(); let mut mesh = MeshBuilder::new(); for glyph in glyph_run.glyphs() { let glyph_inf = atlas.fetch_uv(glyph.id).expect("missing glyph UV rect"); let glyph_x = run_x + glyph.x; let glyph_y = run_y - glyph.y; run_x += glyph.advance; let glyph_rect = Rectangle::new( glyph_x + glyph_inf.place.left as f32, glyph_y - glyph_inf.place.top as f32, glyph_inf.place.width as f32, glyph_inf.place.height as f32, ); if opts.has(DebugRenderOptions::Glyph) { mesh.draw_outline(&glyph_rect, [0., 1., 0., 0.7], 1.); } let color = if glyph_inf.is_color { COLOR_WHITE } else { color }; mesh.draw_box(&glyph_rect, color, &glyph_inf.uv_rect); } if opts.has(DebugRenderOptions::Baseline) { mesh.draw_filled_box( &Rectangle::new(glyph_run.offset(), glyph_run.baseline(), glyph_run.advance(), 1.), [0., 0., 1., 0.7], ); } mesh.alloc(render_api).draw_with_texture(atlas.texture) }