render.rs 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303
  1. /* This file is part of DarkFi (https://dark.fi)
  2. *
  3. * Copyright (C) 2020-2026 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. use crate::{
  19. gfx::{DebugTag, DrawInstruction, DrawMesh, Point, Rectangle, RectangleUnion, Renderer},
  20. mesh::{Color, MeshBuilder, COLOR_WHITE},
  21. };
  22. use super::{
  23. atlas::{Atlas, RenderedAtlas, RunIdx},
  24. TextLayout,
  25. };
  26. #[derive(Copy, Clone, Debug, PartialEq, Eq)]
  27. pub struct DebugRenderOptions(u32);
  28. impl DebugRenderOptions {
  29. pub const OFF: DebugRenderOptions = DebugRenderOptions(0b00);
  30. pub const GLYPH: DebugRenderOptions = DebugRenderOptions(0b01);
  31. pub const BASELINE: DebugRenderOptions = DebugRenderOptions(0b10);
  32. pub fn has(self, other: Self) -> bool {
  33. (self.0 & other.0) == other.0
  34. }
  35. }
  36. impl std::ops::BitOr for DebugRenderOptions {
  37. type Output = Self;
  38. fn bitor(self, rhs: Self) -> Self {
  39. Self(self.0 | rhs.0)
  40. }
  41. }
  42. impl std::ops::BitOrAssign for DebugRenderOptions {
  43. fn bitor_assign(&mut self, rhs: Self) {
  44. self.0 |= rhs.0;
  45. }
  46. }
  47. pub fn render_layout(
  48. layout: &TextLayout,
  49. renderer: &Renderer,
  50. tag: DebugTag,
  51. ) -> Vec<DrawInstruction> {
  52. render_layout_with_opts(layout, DebugRenderOptions::OFF, renderer, tag)
  53. }
  54. /// Render a raw parley layout that was built with the given scale. Used
  55. /// by editors that own their layout internally (e.g. `PlainEditor`).
  56. #[cfg(not(target_os = "android"))]
  57. pub fn render_raw_layout(
  58. layout: &parley::Layout<Color>,
  59. scale: f32,
  60. renderer: &Renderer,
  61. tag: DebugTag,
  62. ) -> Vec<DrawInstruction> {
  63. render_raw_layout_impl(layout, scale, DebugRenderOptions::OFF, renderer, tag).0
  64. }
  65. /// Draw a filled (and optionally outlined) background box behind every glyph run
  66. /// whose style brush equals `match_brush`. The box tracks the run's horizontal
  67. /// advance and the font-metric ascent/descent vertically, so a run that wraps
  68. /// across lines gets one box per wrapped line.
  69. ///
  70. /// Matching by brush (rather than by byte range) is required because a parley
  71. /// `GlyphRun` does not expose its own byte range — only its parent font run does,
  72. /// and a font run is coarser than the per-color segment (e.g. the nick, body, and
  73. /// URL of one line all share a font run). Matching `style().brush` pins the box to
  74. /// exactly the color segment, so only the intended runs (here: the URL runs) are
  75. /// highlighted. The fill is skipped when `bg_color` alpha is ~0; the outline is
  76. /// skipped when `border_size` is ~0 or `border_color` alpha is ~0.
  77. pub fn render_backgrounds(
  78. layout: &TextLayout,
  79. match_brush: Color,
  80. bg_color: Color,
  81. border_color: Color,
  82. border_size: f32,
  83. renderer: &Renderer,
  84. tag: DebugTag,
  85. ) -> Vec<DrawInstruction> {
  86. let mut instrs = vec![];
  87. let has_fill = bg_color[3] > 0.;
  88. let has_border = border_size > 0. && border_color[3] > 0.;
  89. if !has_fill && !has_border {
  90. return instrs
  91. }
  92. let scale = layout.scale();
  93. for line in layout.lines() {
  94. for item in line.items() {
  95. let parley::PositionedLayoutItem::GlyphRun(glyph_run) = item else { continue };
  96. if glyph_run.style().brush != match_brush {
  97. continue
  98. }
  99. let metrics = glyph_run.run().metrics();
  100. let x = glyph_run.offset();
  101. let y = glyph_run.baseline() - metrics.ascent;
  102. let w = glyph_run.advance();
  103. let h = metrics.ascent + metrics.descent;
  104. let rect = Rectangle::new(x, y, w, h) / scale;
  105. let mut mesh = MeshBuilder::new(tag);
  106. if has_fill {
  107. mesh.draw_filled_box(&rect, bg_color);
  108. }
  109. if has_border {
  110. mesh.draw_outline(&rect, border_color, border_size);
  111. }
  112. instrs.push(DrawInstruction::Draw(mesh.alloc(renderer).draw_untextured()));
  113. }
  114. }
  115. instrs
  116. }
  117. pub fn render_layout_with_opts(
  118. layout: &TextLayout,
  119. opts: DebugRenderOptions,
  120. renderer: &Renderer,
  121. tag: DebugTag,
  122. ) -> Vec<DrawInstruction> {
  123. render_raw_layout_impl(layout, layout.scale(), opts, renderer, tag).0
  124. }
  125. /// Layout coordinates are physical (scale is baked in by parley) while
  126. /// meshes are consumed in virtual units and scaled up again by the
  127. /// renderer's `SetScale`. So every emitted coordinate is divided by
  128. /// `scale` here. Glyphs are still rasterized at physical resolution so
  129. /// the final on-screen texel mapping stays crisp.
  130. fn render_raw_layout_impl(
  131. layout: &parley::Layout<Color>,
  132. scale: f32,
  133. opts: DebugRenderOptions,
  134. renderer: &Renderer,
  135. tag: DebugTag,
  136. ) -> (Vec<DrawInstruction>, Rectangle) {
  137. // First pass to create atlas
  138. let mut scale_ctx = swash::scale::ScaleContext::new();
  139. let mut atlas = Atlas::new(renderer, tag);
  140. let mut run_idx = 0;
  141. for line in layout.lines() {
  142. for item in line.items() {
  143. match item {
  144. parley::PositionedLayoutItem::GlyphRun(glyph_run) => {
  145. push_glyphs(&mut atlas, &glyph_run, run_idx, &mut scale_ctx);
  146. run_idx += 1;
  147. }
  148. parley::PositionedLayoutItem::InlineBox(_) => {}
  149. }
  150. }
  151. }
  152. // Render the atlas
  153. let atlas = atlas.make();
  154. // Second pass to draw glyphs
  155. let mut run_idx = 0;
  156. let mut instrs = vec![];
  157. let mut bounds = RectangleUnion::new();
  158. for line in layout.lines() {
  159. for item in line.items() {
  160. match item {
  161. parley::PositionedLayoutItem::GlyphRun(glyph_run) => {
  162. let (mesh, run_bounds) =
  163. render_glyph_run(&glyph_run, run_idx, opts, &atlas, scale, renderer, tag);
  164. bounds.join(run_bounds);
  165. instrs.push(DrawInstruction::Draw(mesh));
  166. run_idx += 1;
  167. }
  168. parley::PositionedLayoutItem::InlineBox(_) => {}
  169. }
  170. }
  171. }
  172. (instrs, bounds.get().unwrap_or(Rectangle::zero()))
  173. }
  174. pub(super) fn push_glyphs(
  175. atlas: &mut Atlas,
  176. glyph_run: &parley::GlyphRun<'_, Color>,
  177. run_idx: RunIdx,
  178. scale_ctx: &mut swash::scale::ScaleContext,
  179. ) {
  180. let run = glyph_run.run();
  181. let font = run.font();
  182. let font_size = run.font_size();
  183. let normalized_coords = run.normalized_coords();
  184. let font_ref = swash::FontRef::from_index(font.data.as_ref(), font.index as usize).unwrap();
  185. let mut scaler = scale_ctx
  186. .builder(font_ref)
  187. .size(font_size)
  188. .hint(true)
  189. .normalized_coords(normalized_coords)
  190. .build();
  191. for glyph in glyph_run.glyphs() {
  192. atlas.push_glyph(glyph.id as u16, run_idx, &mut scaler);
  193. }
  194. }
  195. fn render_glyph_run(
  196. glyph_run: &parley::GlyphRun<'_, Color>,
  197. run_idx: usize,
  198. opts: DebugRenderOptions,
  199. atlas: &RenderedAtlas,
  200. scale: f32,
  201. renderer: &Renderer,
  202. tag: DebugTag,
  203. ) -> (DrawMesh, RectangleUnion) {
  204. let mut run_x = glyph_run.offset();
  205. let run_y = glyph_run.baseline();
  206. let style = glyph_run.style();
  207. let color = style.brush;
  208. //trace!(target: "text::render", "render_glyph_run run_idx={run_idx} baseline={run_y}");
  209. let mut mesh = MeshBuilder::new(tag);
  210. let mut bounds = RectangleUnion::new();
  211. if let Some(underline) = &style.underline {
  212. render_underline(underline, glyph_run, scale, &mut mesh);
  213. }
  214. for glyph in glyph_run.glyphs() {
  215. let glyph_inf = atlas.fetch_uv(glyph.id as u16, run_idx).expect("missing glyph UV rect");
  216. let glyph_x = run_x + glyph.x;
  217. let glyph_y = run_y - glyph.y;
  218. run_x += glyph.advance;
  219. let glyph_rect = Rectangle::new(
  220. (glyph_x + glyph_inf.place.left as f32) / scale,
  221. (glyph_y - glyph_inf.place.top as f32) / scale,
  222. glyph_inf.place.width as f32 / scale,
  223. glyph_inf.place.height as f32 / scale,
  224. );
  225. bounds.add(glyph_rect);
  226. if opts.has(DebugRenderOptions::GLYPH) {
  227. mesh.draw_outline(&glyph_rect, [0., 1., 0., 0.7], 1.);
  228. }
  229. let color = if glyph_inf.is_color { COLOR_WHITE } else { color };
  230. mesh.draw_box(&glyph_rect, color, &glyph_inf.uv_rect);
  231. }
  232. if opts.has(DebugRenderOptions::BASELINE) {
  233. let rect =
  234. Rectangle::new(glyph_run.offset(), glyph_run.baseline(), glyph_run.advance(), 1.) /
  235. scale;
  236. mesh.draw_filled_box(&rect, [0., 0., 1., 0.7]);
  237. }
  238. (mesh.alloc(renderer).draw_with_textures(vec![atlas.texture.clone()]), bounds)
  239. }
  240. fn render_underline(
  241. underline: &parley::layout::Decoration<Color>,
  242. glyph_run: &parley::GlyphRun<'_, Color>,
  243. scale: f32,
  244. mesh: &mut MeshBuilder,
  245. ) {
  246. let color = underline.brush;
  247. let run_metrics = glyph_run.run().metrics();
  248. let offset = match underline.offset {
  249. Some(offset) => offset,
  250. None => run_metrics.underline_offset,
  251. };
  252. let width = match underline.size {
  253. Some(size) => size,
  254. None => run_metrics.underline_size,
  255. };
  256. // The `offset` is the distance from the baseline to the top of the underline
  257. // so we move the line down by half the width
  258. // Remember that we are using a y-down coordinate system
  259. // If there's a custom width, because this is an underline, we want the custom
  260. // width to go down from the default expectation
  261. let y = (glyph_run.baseline() - offset + width / 2.) / scale;
  262. let start_x = glyph_run.offset() / scale;
  263. let end_x = start_x + glyph_run.advance() / scale;
  264. let start = Point::new(start_x, y);
  265. let end = Point::new(end_x, y);
  266. mesh.draw_line(start, end, color, width / scale);
  267. }