Another project
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

feat(render): dimension text via bone-text

Lewis: May this revision serve well! <lu5a@proton.me>

+66 -147
+1
crates/bone-render/Cargo.toml
··· 8 8 [dependencies] 9 9 bone-document = { workspace = true } 10 10 bone-kernel = { workspace = true } 11 + bone-text = { workspace = true } 11 12 bone-types = { workspace = true } 12 13 bytemuck = { workspace = true } 13 14 lyon_tessellation = { workspace = true }
+65 -147
crates/bone-render/src/pipelines/text.rs
··· 1 1 use std::collections::HashMap; 2 2 3 + use bone_text::{ 4 + FontFace, FontWeight, ShapeRequest, ShapedText, Shaper, TessellatedOutline, append_outline, 5 + load_font, tessellate_path, 6 + }; 3 7 use bone_types::SketchDimensionId; 4 - use lyon_tessellation::{ 5 - BuffersBuilder, FillOptions, FillTessellator, FillVertex, VertexBuffers, 6 - path::{Path as LyonPath, builder::WithSvg, math::Point as LyonPoint, path::BuilderImpl}, 7 - }; 8 + use lyon_tessellation::{FillTessellator, path::Path as LyonPath}; 8 9 use swash::{ 9 10 FontRef, 10 11 scale::{ScaleContext, outline::Outline}, 11 - zeno::{PathBuilder as ZenoPathBuilder, PathData as _, Point as ZenoPoint}, 12 + zeno::Point as ZenoPoint, 12 13 }; 13 14 use wgpu::util::DeviceExt; 14 15 ··· 16 17 use crate::gpu::{Gpu, PICK_FORMAT}; 17 18 use crate::scene::SketchScene; 18 19 use crate::snapshot::{Style, TextStyle}; 19 - 20 - const FONT_DATA: &[u8] = include_bytes!("../../assets/DejaVuSansMono.ttf"); 21 20 22 21 #[repr(C, align(16))] 23 22 #[derive(Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)] ··· 43 42 44 43 const VERTEX_STRIDE: u64 = core::mem::size_of::<TextVertex>() as u64; 45 44 46 - #[derive(Clone)] 47 - struct Tessellated { 48 - vertices_px: Vec<[f32; 2]>, 49 - indices: Vec<u32>, 50 - } 51 - 52 45 struct Cached { 53 46 text: String, 54 47 size_px: f32, 55 - tess: Tessellated, 48 + tess: TessellatedOutline, 56 49 } 57 50 58 51 impl Cached { ··· 68 61 uniform_buffer: wgpu::Buffer, 69 62 bind_group: wgpu::BindGroup, 70 63 font: FontRef<'static>, 64 + shaper: Shaper, 71 65 scale_context: ScaleContext, 72 66 tessellator: FillTessellator, 73 67 cache: HashMap<SketchDimensionId, Cached>, ··· 94 88 resource: uniform_buffer.as_entire_binding(), 95 89 }], 96 90 }); 97 - let Some(font) = FontRef::from_index(FONT_DATA, 0) else { 98 - panic!("bundled DejaVuSansMono.ttf failed to parse; asset is broken"); 99 - }; 100 91 Self { 101 92 device, 102 93 queue, 103 94 pipeline, 104 95 uniform_buffer, 105 96 bind_group, 106 - font, 97 + font: load_font(FontFace::Mono), 98 + shaper: Shaper::new(), 107 99 scale_context: ScaleContext::new(), 108 100 tessellator: FillTessellator::new(), 109 101 cache: HashMap::new(), ··· 127 119 text, 128 120 size_px, 129 121 &self.font, 122 + &mut self.shaper, 130 123 &mut self.scale_context, 131 124 &mut self.tessellator, 132 125 ), ··· 347 340 text: &str, 348 341 size_px: f32, 349 342 font: &FontRef<'_>, 343 + shaper: &mut Shaper, 350 344 scale_ctx: &mut ScaleContext, 351 - tessellator: &mut FillTessellator, 352 - ) -> Tessellated { 353 - let mut scaler = scale_ctx.builder(*font).size(size_px).build(); 354 - let charmap = font.charmap(); 355 - let glyph_metrics = font.glyph_metrics(&[]).scale(size_px); 345 + fill: &mut FillTessellator, 346 + ) -> TessellatedOutline { 347 + let layout = shaper.shape( 348 + text, 349 + ShapeRequest { 350 + face: FontFace::Mono, 351 + size_px, 352 + weight: FontWeight::Regular, 353 + line_height_px: 0.0, 354 + letter_spacing_px: 0.0, 355 + max_width: None, 356 + }, 357 + ); 356 358 let metrics = font.metrics(&[]).scale(size_px); 359 + let center = label_center(&layout, metrics.cap_height); 360 + let path = build_centered_path(&layout, scale_ctx, font, center); 361 + tessellate_path(&path, fill).unwrap_or_else(|e| { 362 + tracing::warn!(error = %e, text, size_px, "text tessellation failed"); 363 + TessellatedOutline::default() 364 + }) 365 + } 357 366 358 - let placements = text 359 - .chars() 360 - .map(|c| charmap.map(u32::from(c))) 361 - .scan(0.0_f32, |cursor, glyph_id| { 362 - let advance = glyph_metrics.advance_width(glyph_id); 363 - let origin = *cursor; 364 - *cursor += advance; 365 - Some((glyph_id, origin, *cursor)) 366 - }) 367 - .collect::<Vec<_>>(); 368 - 369 - let total_width = placements.last().map_or(0.0, |(_, _, end)| *end); 370 - let center_x = -total_width * 0.5; 371 - let center_y = -metrics.cap_height * 0.5; 372 - 373 - let path = build_path(&placements, &mut scaler, center_x, center_y); 374 - let mut buffers: VertexBuffers<[f32; 2], u32> = VertexBuffers::new(); 375 - if let Err(e) = tessellator.tessellate_path( 376 - &path, 377 - &FillOptions::default().with_tolerance(0.2), 378 - &mut BuffersBuilder::new(&mut buffers, |vertex: FillVertex| { 379 - [vertex.position().x, vertex.position().y] 380 - }), 381 - ) { 382 - tracing::warn!(error = %e, text, size_px, "text tessellation failed"); 383 - } 384 - Tessellated { 385 - vertices_px: buffers.vertices, 386 - indices: buffers.indices, 387 - } 367 + fn label_center(layout: &ShapedText, cap_height: f32) -> ZenoPoint { 368 + let visible_advance = layout 369 + .lines 370 + .first() 371 + .map_or(0.0, bone_text::ShapedLine::visible_advance_px); 372 + ZenoPoint::new(-visible_advance * 0.5, -cap_height * 0.5) 388 373 } 389 374 390 - fn build_path( 391 - placements: &[(u16, f32, f32)], 392 - scaler: &mut swash::scale::Scaler<'_>, 393 - offset_x: f32, 394 - offset_y: f32, 375 + fn build_centered_path( 376 + layout: &ShapedText, 377 + scale_ctx: &mut ScaleContext, 378 + font: &FontRef<'_>, 379 + center: ZenoPoint, 395 380 ) -> LyonPath { 396 - placements 381 + let mut scaler = scale_ctx.builder(*font).size(layout.font_size_px).build(); 382 + layout 383 + .lines 397 384 .iter() 398 - .fold( 399 - LyonPath::svg_builder(), 400 - |mut builder, (glyph_id, origin, _)| { 401 - let mut outline = Outline::new(); 402 - if scaler.scale_outline_into(*glyph_id, &mut outline) { 403 - let mut adapter = LyonAdapter::new(&mut builder, offset_x + origin, offset_y); 404 - outline.path().copy_to(&mut adapter); 405 - } 406 - builder 407 - }, 408 - ) 385 + .flat_map(|line| line.runs.iter()) 386 + .flat_map(|run| { 387 + let origin = run.origin_x_px; 388 + run.glyphs.iter().map(move |g| (origin + g.x_px, g.id)) 389 + }) 390 + .fold(LyonPath::svg_builder(), |mut builder, (x, id)| { 391 + let Ok(glyph_id_u16) = u16::try_from(id.raw()) else { 392 + return builder; 393 + }; 394 + let mut outline = Outline::new(); 395 + if scaler.scale_outline_into(glyph_id_u16, &mut outline) { 396 + append_outline( 397 + &mut builder, 398 + &outline, 399 + ZenoPoint::new(center.x + x, center.y), 400 + ); 401 + } 402 + builder 403 + }) 409 404 .build() 410 405 } 411 - 412 - struct LyonAdapter<'a> { 413 - builder: &'a mut WithSvg<BuilderImpl>, 414 - offset_x: f32, 415 - offset_y: f32, 416 - current: ZenoPoint, 417 - open: bool, 418 - } 419 - 420 - impl<'a> LyonAdapter<'a> { 421 - fn new(builder: &'a mut WithSvg<BuilderImpl>, offset_x: f32, offset_y: f32) -> Self { 422 - Self { 423 - builder, 424 - offset_x, 425 - offset_y, 426 - current: ZenoPoint::new(0.0, 0.0), 427 - open: false, 428 - } 429 - } 430 - 431 - fn transform(&self, p: ZenoPoint) -> LyonPoint { 432 - LyonPoint::new(p.x + self.offset_x, p.y + self.offset_y) 433 - } 434 - } 435 - 436 - impl ZenoPathBuilder for LyonAdapter<'_> { 437 - fn current_point(&self) -> ZenoPoint { 438 - self.current 439 - } 440 - 441 - fn move_to(&mut self, to: impl Into<ZenoPoint>) -> &mut Self { 442 - let p = to.into(); 443 - self.current = p; 444 - self.builder.move_to(self.transform(p)); 445 - self.open = true; 446 - self 447 - } 448 - 449 - fn line_to(&mut self, to: impl Into<ZenoPoint>) -> &mut Self { 450 - let p = to.into(); 451 - self.current = p; 452 - self.builder.line_to(self.transform(p)); 453 - self 454 - } 455 - 456 - fn quad_to(&mut self, control: impl Into<ZenoPoint>, to: impl Into<ZenoPoint>) -> &mut Self { 457 - let c = control.into(); 458 - let p = to.into(); 459 - self.current = p; 460 - self.builder 461 - .quadratic_bezier_to(self.transform(c), self.transform(p)); 462 - self 463 - } 464 - 465 - fn curve_to( 466 - &mut self, 467 - control1: impl Into<ZenoPoint>, 468 - control2: impl Into<ZenoPoint>, 469 - to: impl Into<ZenoPoint>, 470 - ) -> &mut Self { 471 - let c1 = control1.into(); 472 - let c2 = control2.into(); 473 - let p = to.into(); 474 - self.current = p; 475 - self.builder 476 - .cubic_bezier_to(self.transform(c1), self.transform(c2), self.transform(p)); 477 - self 478 - } 479 - 480 - fn close(&mut self) -> &mut Self { 481 - if self.open { 482 - self.builder.close(); 483 - self.open = false; 484 - } 485 - self 486 - } 487 - }