/ bin / app / src / text2 / render.rs
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  }