Add support for custom icons/glyphs (#102)
* add support for svg icons * remove SVG helper struct * forgot to remove default features * rework api for custom glyphs * remove unused file * expose custom glyph structs * remove `InlineBox` * use slice for TextArea::custom_glyphs * offset custom glyphs by text area position * remove svg feature * remove unused file * add scale field to CustomGlyphInput * update custom-glyphs example to winit 0.30 * fix the mess merge conflicts made * add final newline * make custom-glyphs a default feature * remove custom-glyphs feature * remove unnecessary pub(crate) * rename CustomGlyphDesc to CustomGlyph * rename CustomGlyphID to CustomGlyphId * improve custom glyph API and refactor text renderer * rename CustomGlyphInput and CustomGlyphOutput, add some docs
This commit is contained in:
		
					parent
					
						
							
								ce6ede951c
							
						
					
				
			
			
				commit
				
					
						b2129f1765
					
				
			
		
					 9 changed files with 1173 additions and 222 deletions
				
			
		| 
						 | 
					@ -17,4 +17,5 @@ rustc-hash = "2.0"
 | 
				
			||||||
[dev-dependencies]
 | 
					[dev-dependencies]
 | 
				
			||||||
winit = "0.30.3"
 | 
					winit = "0.30.3"
 | 
				
			||||||
wgpu = "22"
 | 
					wgpu = "22"
 | 
				
			||||||
 | 
					resvg = { version = "0.42", default-features = false }
 | 
				
			||||||
pollster = "0.3.0"
 | 
					pollster = "0.3.0"
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										332
									
								
								examples/custom-glyphs.rs
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										332
									
								
								examples/custom-glyphs.rs
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,332 @@
 | 
				
			||||||
 | 
					use glyphon::{
 | 
				
			||||||
 | 
					    Attrs, Buffer, Cache, Color, ContentType, CustomGlyph, RasterizationRequest, RasterizedCustomGlyph,
 | 
				
			||||||
 | 
					    Family, FontSystem, Metrics, Resolution, Shaping, SwashCache, TextArea, TextAtlas, TextBounds,
 | 
				
			||||||
 | 
					    TextRenderer, Viewport,
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					use std::sync::Arc;
 | 
				
			||||||
 | 
					use wgpu::{
 | 
				
			||||||
 | 
					    CommandEncoderDescriptor, CompositeAlphaMode, DeviceDescriptor, Instance, InstanceDescriptor,
 | 
				
			||||||
 | 
					    LoadOp, MultisampleState, Operations, PresentMode, RenderPassColorAttachment,
 | 
				
			||||||
 | 
					    RenderPassDescriptor, RequestAdapterOptions, SurfaceConfiguration, TextureFormat,
 | 
				
			||||||
 | 
					    TextureUsages, TextureViewDescriptor,
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					use winit::{dpi::LogicalSize, event::WindowEvent, event_loop::EventLoop, window::Window};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Example SVG icons are from https://publicdomainvectors.org/
 | 
				
			||||||
 | 
					static LION_SVG: &[u8] = include_bytes!("./lion.svg");
 | 
				
			||||||
 | 
					static EAGLE_SVG: &[u8] = include_bytes!("./eagle.svg");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					fn main() {
 | 
				
			||||||
 | 
					    let event_loop = EventLoop::new().unwrap();
 | 
				
			||||||
 | 
					    event_loop
 | 
				
			||||||
 | 
					        .run_app(&mut Application { window_state: None })
 | 
				
			||||||
 | 
					        .unwrap();
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					struct WindowState {
 | 
				
			||||||
 | 
					    device: wgpu::Device,
 | 
				
			||||||
 | 
					    queue: wgpu::Queue,
 | 
				
			||||||
 | 
					    surface: wgpu::Surface<'static>,
 | 
				
			||||||
 | 
					    surface_config: SurfaceConfiguration,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    font_system: FontSystem,
 | 
				
			||||||
 | 
					    swash_cache: SwashCache,
 | 
				
			||||||
 | 
					    viewport: glyphon::Viewport,
 | 
				
			||||||
 | 
					    atlas: glyphon::TextAtlas,
 | 
				
			||||||
 | 
					    text_renderer: glyphon::TextRenderer,
 | 
				
			||||||
 | 
					    text_buffer: glyphon::Buffer,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    rasterize_svg: Box<dyn Fn(RasterizationRequest) -> Option<RasterizedCustomGlyph>>,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Make sure that the winit window is last in the struct so that
 | 
				
			||||||
 | 
					    // it is dropped after the wgpu surface is dropped, otherwise the
 | 
				
			||||||
 | 
					    // program may crash when closed. This is probably a bug in wgpu.
 | 
				
			||||||
 | 
					    window: Arc<Window>,
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl WindowState {
 | 
				
			||||||
 | 
					    async fn new(window: Arc<Window>) -> Self {
 | 
				
			||||||
 | 
					        let physical_size = window.inner_size();
 | 
				
			||||||
 | 
					        let scale_factor = window.scale_factor();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // Set up surface
 | 
				
			||||||
 | 
					        let instance = Instance::new(InstanceDescriptor::default());
 | 
				
			||||||
 | 
					        let adapter = instance
 | 
				
			||||||
 | 
					            .request_adapter(&RequestAdapterOptions::default())
 | 
				
			||||||
 | 
					            .await
 | 
				
			||||||
 | 
					            .unwrap();
 | 
				
			||||||
 | 
					        let (device, queue) = adapter
 | 
				
			||||||
 | 
					            .request_device(&DeviceDescriptor::default(), None)
 | 
				
			||||||
 | 
					            .await
 | 
				
			||||||
 | 
					            .unwrap();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        let surface = instance
 | 
				
			||||||
 | 
					            .create_surface(window.clone())
 | 
				
			||||||
 | 
					            .expect("Create surface");
 | 
				
			||||||
 | 
					        let swapchain_format = TextureFormat::Bgra8UnormSrgb;
 | 
				
			||||||
 | 
					        let surface_config = SurfaceConfiguration {
 | 
				
			||||||
 | 
					            usage: TextureUsages::RENDER_ATTACHMENT,
 | 
				
			||||||
 | 
					            format: swapchain_format,
 | 
				
			||||||
 | 
					            width: physical_size.width,
 | 
				
			||||||
 | 
					            height: physical_size.height,
 | 
				
			||||||
 | 
					            present_mode: PresentMode::Fifo,
 | 
				
			||||||
 | 
					            alpha_mode: CompositeAlphaMode::Opaque,
 | 
				
			||||||
 | 
					            view_formats: vec![],
 | 
				
			||||||
 | 
					            desired_maximum_frame_latency: 2,
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					        surface.configure(&device, &surface_config);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // Set up text renderer
 | 
				
			||||||
 | 
					        let mut font_system = FontSystem::new();
 | 
				
			||||||
 | 
					        let swash_cache = SwashCache::new();
 | 
				
			||||||
 | 
					        let cache = Cache::new(&device);
 | 
				
			||||||
 | 
					        let viewport = Viewport::new(&device, &cache);
 | 
				
			||||||
 | 
					        let mut atlas = TextAtlas::new(&device, &queue, &cache, swapchain_format);
 | 
				
			||||||
 | 
					        let text_renderer =
 | 
				
			||||||
 | 
					            TextRenderer::new(&mut atlas, &device, MultisampleState::default(), None);
 | 
				
			||||||
 | 
					        let mut text_buffer = Buffer::new(&mut font_system, Metrics::new(30.0, 42.0));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        let physical_width = (physical_size.width as f64 * scale_factor) as f32;
 | 
				
			||||||
 | 
					        let physical_height = (physical_size.height as f64 * scale_factor) as f32;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        text_buffer.set_size(
 | 
				
			||||||
 | 
					            &mut font_system,
 | 
				
			||||||
 | 
					            Some(physical_width),
 | 
				
			||||||
 | 
					            Some(physical_height),
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
 | 
					        text_buffer.set_text(
 | 
				
			||||||
 | 
					            &mut font_system,
 | 
				
			||||||
 | 
					            "SVG icons!     --->\n\nThe icons below should be partially clipped.",
 | 
				
			||||||
 | 
					            Attrs::new().family(Family::SansSerif),
 | 
				
			||||||
 | 
					            Shaping::Advanced,
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
 | 
					        text_buffer.shape_until_scroll(&mut font_system, false);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // Set up custom svg renderer
 | 
				
			||||||
 | 
					        let svg_0 = resvg::usvg::Tree::from_data(LION_SVG, &Default::default()).unwrap();
 | 
				
			||||||
 | 
					        let svg_1 = resvg::usvg::Tree::from_data(EAGLE_SVG, &Default::default()).unwrap();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        let rasterize_svg = move |input: RasterizationRequest| -> Option<RasterizedCustomGlyph> {
 | 
				
			||||||
 | 
					            // Select the svg data based on the custom glyph ID.
 | 
				
			||||||
 | 
					            let (svg, content_type) = match input.id {
 | 
				
			||||||
 | 
					                0 => (&svg_0, ContentType::Mask),
 | 
				
			||||||
 | 
					                1 => (&svg_1, ContentType::Color),
 | 
				
			||||||
 | 
					                _ => return None,
 | 
				
			||||||
 | 
					            };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // Calculate the scale based on the "glyph size".
 | 
				
			||||||
 | 
					            let svg_size = svg.size();
 | 
				
			||||||
 | 
					            let scale_x = input.width as f32 / svg_size.width();
 | 
				
			||||||
 | 
					            let scale_y = input.height as f32 / svg_size.height();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            let Some(mut pixmap) =
 | 
				
			||||||
 | 
					                resvg::tiny_skia::Pixmap::new(input.width as u32, input.height as u32)
 | 
				
			||||||
 | 
					            else {
 | 
				
			||||||
 | 
					                return None;
 | 
				
			||||||
 | 
					            };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            let mut transform = resvg::usvg::Transform::from_scale(scale_x, scale_y);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // Offset the glyph by the subpixel amount.
 | 
				
			||||||
 | 
					            let offset_x = input.x_bin.as_float();
 | 
				
			||||||
 | 
					            let offset_y = input.y_bin.as_float();
 | 
				
			||||||
 | 
					            if offset_x != 0.0 || offset_y != 0.0 {
 | 
				
			||||||
 | 
					                transform = transform.post_translate(offset_x, offset_y);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            resvg::render(svg, transform, &mut pixmap.as_mut());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            let data: Vec<u8> = if let ContentType::Mask = content_type {
 | 
				
			||||||
 | 
					                // Only use the alpha channel for symbolic icons.
 | 
				
			||||||
 | 
					                pixmap.data().iter().skip(3).step_by(4).copied().collect()
 | 
				
			||||||
 | 
					            } else {
 | 
				
			||||||
 | 
					                pixmap.data().to_vec()
 | 
				
			||||||
 | 
					            };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            Some(RasterizedCustomGlyph { data, content_type })
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        Self {
 | 
				
			||||||
 | 
					            device,
 | 
				
			||||||
 | 
					            queue,
 | 
				
			||||||
 | 
					            surface,
 | 
				
			||||||
 | 
					            surface_config,
 | 
				
			||||||
 | 
					            font_system,
 | 
				
			||||||
 | 
					            swash_cache,
 | 
				
			||||||
 | 
					            viewport,
 | 
				
			||||||
 | 
					            atlas,
 | 
				
			||||||
 | 
					            text_renderer,
 | 
				
			||||||
 | 
					            text_buffer,
 | 
				
			||||||
 | 
					            rasterize_svg: Box::new(rasterize_svg),
 | 
				
			||||||
 | 
					            window,
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					struct Application {
 | 
				
			||||||
 | 
					    window_state: Option<WindowState>,
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl winit::application::ApplicationHandler for Application {
 | 
				
			||||||
 | 
					    fn resumed(&mut self, event_loop: &winit::event_loop::ActiveEventLoop) {
 | 
				
			||||||
 | 
					        if self.window_state.is_some() {
 | 
				
			||||||
 | 
					            return;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // Set up window
 | 
				
			||||||
 | 
					        let (width, height) = (800, 600);
 | 
				
			||||||
 | 
					        let window_attributes = Window::default_attributes()
 | 
				
			||||||
 | 
					            .with_inner_size(LogicalSize::new(width as f64, height as f64))
 | 
				
			||||||
 | 
					            .with_title("glyphon hello world");
 | 
				
			||||||
 | 
					        let window = Arc::new(event_loop.create_window(window_attributes).unwrap());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        self.window_state = Some(pollster::block_on(WindowState::new(window)));
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fn window_event(
 | 
				
			||||||
 | 
					        &mut self,
 | 
				
			||||||
 | 
					        event_loop: &winit::event_loop::ActiveEventLoop,
 | 
				
			||||||
 | 
					        _window_id: winit::window::WindowId,
 | 
				
			||||||
 | 
					        event: WindowEvent,
 | 
				
			||||||
 | 
					    ) {
 | 
				
			||||||
 | 
					        let Some(state) = &mut self.window_state else {
 | 
				
			||||||
 | 
					            return;
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        let WindowState {
 | 
				
			||||||
 | 
					            window,
 | 
				
			||||||
 | 
					            device,
 | 
				
			||||||
 | 
					            queue,
 | 
				
			||||||
 | 
					            surface,
 | 
				
			||||||
 | 
					            surface_config,
 | 
				
			||||||
 | 
					            font_system,
 | 
				
			||||||
 | 
					            swash_cache,
 | 
				
			||||||
 | 
					            viewport,
 | 
				
			||||||
 | 
					            atlas,
 | 
				
			||||||
 | 
					            text_renderer,
 | 
				
			||||||
 | 
					            text_buffer,
 | 
				
			||||||
 | 
					            rasterize_svg,
 | 
				
			||||||
 | 
					            ..
 | 
				
			||||||
 | 
					        } = state;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        match event {
 | 
				
			||||||
 | 
					            WindowEvent::Resized(size) => {
 | 
				
			||||||
 | 
					                surface_config.width = size.width;
 | 
				
			||||||
 | 
					                surface_config.height = size.height;
 | 
				
			||||||
 | 
					                surface.configure(&device, &surface_config);
 | 
				
			||||||
 | 
					                window.request_redraw();
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            WindowEvent::RedrawRequested => {
 | 
				
			||||||
 | 
					                viewport.update(
 | 
				
			||||||
 | 
					                    &queue,
 | 
				
			||||||
 | 
					                    Resolution {
 | 
				
			||||||
 | 
					                        width: surface_config.width,
 | 
				
			||||||
 | 
					                        height: surface_config.height,
 | 
				
			||||||
 | 
					                    },
 | 
				
			||||||
 | 
					                );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                text_renderer
 | 
				
			||||||
 | 
					                    .prepare_with_custom(
 | 
				
			||||||
 | 
					                        device,
 | 
				
			||||||
 | 
					                        queue,
 | 
				
			||||||
 | 
					                        font_system,
 | 
				
			||||||
 | 
					                        atlas,
 | 
				
			||||||
 | 
					                        viewport,
 | 
				
			||||||
 | 
					                        [TextArea {
 | 
				
			||||||
 | 
					                            buffer: &text_buffer,
 | 
				
			||||||
 | 
					                            left: 10.0,
 | 
				
			||||||
 | 
					                            top: 10.0,
 | 
				
			||||||
 | 
					                            scale: 1.0,
 | 
				
			||||||
 | 
					                            bounds: TextBounds {
 | 
				
			||||||
 | 
					                                left: 0,
 | 
				
			||||||
 | 
					                                top: 0,
 | 
				
			||||||
 | 
					                                right: 650,
 | 
				
			||||||
 | 
					                                bottom: 180,
 | 
				
			||||||
 | 
					                            },
 | 
				
			||||||
 | 
					                            default_color: Color::rgb(255, 255, 255),
 | 
				
			||||||
 | 
					                            custom_glyphs: &[
 | 
				
			||||||
 | 
					                                CustomGlyph {
 | 
				
			||||||
 | 
					                                    id: 0,
 | 
				
			||||||
 | 
					                                    left: 300.0,
 | 
				
			||||||
 | 
					                                    top: 5.0,
 | 
				
			||||||
 | 
					                                    width: 64.0,
 | 
				
			||||||
 | 
					                                    height: 64.0,
 | 
				
			||||||
 | 
					                                    color: Some(Color::rgb(200, 200, 255)),
 | 
				
			||||||
 | 
					                                    snap_to_physical_pixel: true,
 | 
				
			||||||
 | 
					                                    metadata: 0,
 | 
				
			||||||
 | 
					                                },
 | 
				
			||||||
 | 
					                                CustomGlyph {
 | 
				
			||||||
 | 
					                                    id: 1,
 | 
				
			||||||
 | 
					                                    left: 400.0,
 | 
				
			||||||
 | 
					                                    top: 5.0,
 | 
				
			||||||
 | 
					                                    width: 64.0,
 | 
				
			||||||
 | 
					                                    height: 64.0,
 | 
				
			||||||
 | 
					                                    color: None,
 | 
				
			||||||
 | 
					                                    snap_to_physical_pixel: true,
 | 
				
			||||||
 | 
					                                    metadata: 0,
 | 
				
			||||||
 | 
					                                },
 | 
				
			||||||
 | 
					                                CustomGlyph {
 | 
				
			||||||
 | 
					                                    id: 0,
 | 
				
			||||||
 | 
					                                    left: 300.0,
 | 
				
			||||||
 | 
					                                    top: 130.0,
 | 
				
			||||||
 | 
					                                    width: 64.0,
 | 
				
			||||||
 | 
					                                    height: 64.0,
 | 
				
			||||||
 | 
					                                    color: Some(Color::rgb(200, 255, 200)),
 | 
				
			||||||
 | 
					                                    snap_to_physical_pixel: true,
 | 
				
			||||||
 | 
					                                    metadata: 0,
 | 
				
			||||||
 | 
					                                },
 | 
				
			||||||
 | 
					                                CustomGlyph {
 | 
				
			||||||
 | 
					                                    id: 1,
 | 
				
			||||||
 | 
					                                    left: 400.0,
 | 
				
			||||||
 | 
					                                    top: 130.0,
 | 
				
			||||||
 | 
					                                    width: 64.0,
 | 
				
			||||||
 | 
					                                    height: 64.0,
 | 
				
			||||||
 | 
					                                    color: None,
 | 
				
			||||||
 | 
					                                    snap_to_physical_pixel: true,
 | 
				
			||||||
 | 
					                                    metadata: 0,
 | 
				
			||||||
 | 
					                                },
 | 
				
			||||||
 | 
					                            ],
 | 
				
			||||||
 | 
					                        }],
 | 
				
			||||||
 | 
					                        swash_cache,
 | 
				
			||||||
 | 
					                        rasterize_svg,
 | 
				
			||||||
 | 
					                    )
 | 
				
			||||||
 | 
					                    .unwrap();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                let frame = surface.get_current_texture().unwrap();
 | 
				
			||||||
 | 
					                let view = frame.texture.create_view(&TextureViewDescriptor::default());
 | 
				
			||||||
 | 
					                let mut encoder =
 | 
				
			||||||
 | 
					                    device.create_command_encoder(&CommandEncoderDescriptor { label: None });
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    let mut pass = encoder.begin_render_pass(&RenderPassDescriptor {
 | 
				
			||||||
 | 
					                        label: None,
 | 
				
			||||||
 | 
					                        color_attachments: &[Some(RenderPassColorAttachment {
 | 
				
			||||||
 | 
					                            view: &view,
 | 
				
			||||||
 | 
					                            resolve_target: None,
 | 
				
			||||||
 | 
					                            ops: Operations {
 | 
				
			||||||
 | 
					                                load: LoadOp::Clear(wgpu::Color {
 | 
				
			||||||
 | 
					                                    r: 0.02,
 | 
				
			||||||
 | 
					                                    g: 0.02,
 | 
				
			||||||
 | 
					                                    b: 0.02,
 | 
				
			||||||
 | 
					                                    a: 1.0,
 | 
				
			||||||
 | 
					                                }),
 | 
				
			||||||
 | 
					                                store: wgpu::StoreOp::Store,
 | 
				
			||||||
 | 
					                            },
 | 
				
			||||||
 | 
					                        })],
 | 
				
			||||||
 | 
					                        depth_stencil_attachment: None,
 | 
				
			||||||
 | 
					                        timestamp_writes: None,
 | 
				
			||||||
 | 
					                        occlusion_query_set: None,
 | 
				
			||||||
 | 
					                    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    text_renderer.render(&atlas, &viewport, &mut pass).unwrap();
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                queue.submit(Some(encoder.finish()));
 | 
				
			||||||
 | 
					                frame.present();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                atlas.trim();
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            WindowEvent::CloseRequested => event_loop.exit(),
 | 
				
			||||||
 | 
					            _ => {}
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										72
									
								
								examples/eagle.svg
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										72
									
								
								examples/eagle.svg
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,72 @@
 | 
				
			||||||
 | 
					<?xml version="1.0"?>
 | 
				
			||||||
 | 
					<svg xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns="http://www.w3.org/2000/svg" xmlns:cc="http://web.resource.org/cc/" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:svg="http://www.w3.org/2000/svg" id="svg2" viewBox="0 0 141.78 179.81" version="1.0">
 | 
				
			||||||
 | 
					  <defs id="defs4">
 | 
				
			||||||
 | 
					    <linearGradient id="linearGradient9890" x1="347" gradientUnits="userSpaceOnUse" y1="417.11" gradientTransform="matrix(.96509 0 0 .96509 -293.49 -301.38)" x2="376.5" y2="375.11">
 | 
				
			||||||
 | 
					      <stop id="stop8114" stop-color="#5a5a5a" offset="0"/>
 | 
				
			||||||
 | 
					      <stop id="stop8116" stop-color="#c6c6c6" stop-opacity="0" offset="1"/>
 | 
				
			||||||
 | 
					    </linearGradient>
 | 
				
			||||||
 | 
					    <radialGradient id="radialGradient15226" gradientUnits="userSpaceOnUse" cy="330.67" cx="452.81" gradientTransform="matrix(-.63914 -.42728 .71829 -1.0744 74.046 594.02)" r="7.1607">
 | 
				
			||||||
 | 
					      <stop id="stop14335" stop-color="#eebe9e" offset="0"/>
 | 
				
			||||||
 | 
					      <stop id="stop14337" stop-color="#eebe9e" stop-opacity="0" offset="1"/>
 | 
				
			||||||
 | 
					    </radialGradient>
 | 
				
			||||||
 | 
					    <linearGradient id="linearGradient4561" y2="25.336" gradientUnits="userSpaceOnUse" y1="125.39" x2="108.14" x1="-43.815">
 | 
				
			||||||
 | 
					      <stop id="stop16117" stop-color="#b3b3b3" offset="0"/>
 | 
				
			||||||
 | 
					      <stop id="stop16119" stop-color="#f7f7f7" offset="1"/>
 | 
				
			||||||
 | 
					    </linearGradient>
 | 
				
			||||||
 | 
					  </defs>
 | 
				
			||||||
 | 
					  <g id="layer1" transform="translate(-7.3576 -5.5473)">
 | 
				
			||||||
 | 
					    <path id="path1894" fill-rule="evenodd" d="m58.516 106.37l-2.642 11.93c-28.766 2.73-22.398 47.57-24.71 67.17l117.9 0.25 0.07-98.485-14.49-13.058c-28.7 8.017-56.132 11.116-76.124 32.193z"/>
 | 
				
			||||||
 | 
					    <path id="path1886" fill-rule="evenodd" fill="url(#linearGradient4561)" d="m27.249 31.823c1.452-14.893 18.492-18.294 36.559-19.986 23.107 0.077 35.052 6.85 45.332 14.624 15.34 15.16 15.31 28.398 21.45 42.407l16.08 16.086c-38.59-9.253-64.004 6.637-88.712 23.886l-0.487-36.072c-3.625-1.037-11.68-12.167-20.96-17.061l-9.262-23.884z"/>
 | 
				
			||||||
 | 
					    <path id="path1884" fill-rule="evenodd" fill="#d38d5f" d="m65.782 44.837c-1.036 3.412-7.139 6.824-14.624 10.237-10.561-0.65-21.804 2.464-32.366 6.97 3.921-6.85 14.622-12.551 46.99-17.207z"/>
 | 
				
			||||||
 | 
					    <path id="path2775" fill-rule="evenodd" fill="#a05a2c" d="m43.335 32.31l20.473-4.874c0.978 3.513 2.129 7.038-1.95 10.236-2.856-0.224-5.613 1.324-8.774-4.387l-9.749-0.975z"/>
 | 
				
			||||||
 | 
					    <path id="path1882" fill-rule="evenodd" fill="#cf7b00" d="m16.526 68.868c-5.801-12.64-8.6819-25.194 12.324-36.558l5.224 0.487 17.06 10.237 15.111 1.949c-16.305 4.237-34.523 3.371-47.282 17.061l-2.437 6.824z"/>
 | 
				
			||||||
 | 
					    <path id="path2777" fill-rule="evenodd" fill="#d0d0d0" d="m57.593 29.629c2.992 0.09 7.856-2.564 6.336 4.143-1.204 4.683-4.186 2.702-6.58 2.925-3.515-2.356-2.724-4.712 0.244-7.068z"/>
 | 
				
			||||||
 | 
					    <path id="path2779" opacity=".69512" transform="matrix(.96509 0 0 .96509 -293.49 -301.38)" fill="none" d="m367.19 344.37a2.5885 0.25254 0 1 1 -5.18 0 2.5885 0.25254 0 1 1 5.18 0z"/>
 | 
				
			||||||
 | 
					    <path id="path8102" fill-rule="evenodd" fill="url(#linearGradient9890)" d="m51.048 55.088l28.47-1.448 12.547 35.226c-13.29 5.876-22.533 11.784-34.232 20.204l-0.029-36.611c-5.487-2.133-16.434-14.231-20.749-16.889l13.993-0.482z"/>
 | 
				
			||||||
 | 
					    <path id="path10777" opacity=".69512" transform="matrix(.96509 0 0 .96509 -414.61 -272.91)" fill="#333" d="m492.85 316.79a1.591 1.591 0 1 1 -3.18 0 1.591 1.591 0 1 1 3.18 0z"/>
 | 
				
			||||||
 | 
					    <path id="path12553" opacity=".43089" fill-rule="evenodd" fill="url(#radialGradient15226)" d="m28.384 34.419c-9.284 3.339-14.14 10.143-12.966 21.667l13.648-8.36-0.682-13.307z"/>
 | 
				
			||||||
 | 
					  </g>
 | 
				
			||||||
 | 
					  <metadata>
 | 
				
			||||||
 | 
					    <rdf:RDF>
 | 
				
			||||||
 | 
					      <cc:Work>
 | 
				
			||||||
 | 
					        <dc:format>image/svg+xml</dc:format>
 | 
				
			||||||
 | 
					        <dc:type rdf:resource="http://purl.org/dc/dcmitype/StillImage"/>
 | 
				
			||||||
 | 
					        <cc:license rdf:resource="http://creativecommons.org/licenses/publicdomain/"/>
 | 
				
			||||||
 | 
					        <dc:publisher>
 | 
				
			||||||
 | 
					          <cc:Agent rdf:about="http://openclipart.org/">
 | 
				
			||||||
 | 
					            <dc:title>Openclipart</dc:title>
 | 
				
			||||||
 | 
					          </cc:Agent>
 | 
				
			||||||
 | 
					        </dc:publisher>
 | 
				
			||||||
 | 
					        <dc:title>Eagle</dc:title>
 | 
				
			||||||
 | 
					        <dc:date>2007-01-24T06:25:54</dc:date>
 | 
				
			||||||
 | 
					        <dc:description>animal, animal, bird, bird, clip art, clipart, eagle, eagle, head, head, image, media, nature, nature, public domain, svg, </dc:description>
 | 
				
			||||||
 | 
					        <dc:source>http://openclipart.org/detail/2962/eagle-by-nfroidure</dc:source>
 | 
				
			||||||
 | 
					        <dc:creator>
 | 
				
			||||||
 | 
					          <cc:Agent>
 | 
				
			||||||
 | 
					            <dc:title>nfroidure</dc:title>
 | 
				
			||||||
 | 
					          </cc:Agent>
 | 
				
			||||||
 | 
					        </dc:creator>
 | 
				
			||||||
 | 
					        <dc:subject>
 | 
				
			||||||
 | 
					          <rdf:Bag>
 | 
				
			||||||
 | 
					            <rdf:li>animal</rdf:li>
 | 
				
			||||||
 | 
					            <rdf:li>bird</rdf:li>
 | 
				
			||||||
 | 
					            <rdf:li>clip art</rdf:li>
 | 
				
			||||||
 | 
					            <rdf:li>clipart</rdf:li>
 | 
				
			||||||
 | 
					            <rdf:li>eagle</rdf:li>
 | 
				
			||||||
 | 
					            <rdf:li>head</rdf:li>
 | 
				
			||||||
 | 
					            <rdf:li>image</rdf:li>
 | 
				
			||||||
 | 
					            <rdf:li>media</rdf:li>
 | 
				
			||||||
 | 
					            <rdf:li>nature</rdf:li>
 | 
				
			||||||
 | 
					            <rdf:li>public domain</rdf:li>
 | 
				
			||||||
 | 
					            <rdf:li>svg</rdf:li>
 | 
				
			||||||
 | 
					          </rdf:Bag>
 | 
				
			||||||
 | 
					        </dc:subject>
 | 
				
			||||||
 | 
					      </cc:Work>
 | 
				
			||||||
 | 
					      <cc:License rdf:about="http://creativecommons.org/licenses/publicdomain/">
 | 
				
			||||||
 | 
					        <cc:permits rdf:resource="http://creativecommons.org/ns#Reproduction"/>
 | 
				
			||||||
 | 
					        <cc:permits rdf:resource="http://creativecommons.org/ns#Distribution"/>
 | 
				
			||||||
 | 
					        <cc:permits rdf:resource="http://creativecommons.org/ns#DerivativeWorks"/>
 | 
				
			||||||
 | 
					      </cc:License>
 | 
				
			||||||
 | 
					    </rdf:RDF>
 | 
				
			||||||
 | 
					  </metadata>
 | 
				
			||||||
 | 
					</svg>
 | 
				
			||||||
| 
		 After Width: | Height: | Size: 5.1 KiB  | 
| 
						 | 
					@ -186,6 +186,7 @@ impl winit::application::ApplicationHandler for Application {
 | 
				
			||||||
                                bottom: 160,
 | 
					                                bottom: 160,
 | 
				
			||||||
                            },
 | 
					                            },
 | 
				
			||||||
                            default_color: Color::rgb(255, 255, 255),
 | 
					                            default_color: Color::rgb(255, 255, 255),
 | 
				
			||||||
 | 
					                            custom_glyphs: &[],
 | 
				
			||||||
                        }],
 | 
					                        }],
 | 
				
			||||||
                        swash_cache,
 | 
					                        swash_cache,
 | 
				
			||||||
                    )
 | 
					                    )
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										169
									
								
								examples/lion.svg
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										169
									
								
								examples/lion.svg
									
										
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							| 
		 After Width: | Height: | Size: 23 KiB  | 
							
								
								
									
										113
									
								
								src/custom_glyph.rs
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										113
									
								
								src/custom_glyph.rs
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,113 @@
 | 
				
			||||||
 | 
					use crate::Color;
 | 
				
			||||||
 | 
					use cosmic_text::SubpixelBin;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub type CustomGlyphId = u16;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// A custom glyph to render
 | 
				
			||||||
 | 
					#[derive(Default, Debug, Clone, Copy, PartialEq)]
 | 
				
			||||||
 | 
					pub struct CustomGlyph {
 | 
				
			||||||
 | 
					    /// The unique identifier for this glyph
 | 
				
			||||||
 | 
					    pub id: CustomGlyphId,
 | 
				
			||||||
 | 
					    /// The position of the left edge of the glyph
 | 
				
			||||||
 | 
					    pub left: f32,
 | 
				
			||||||
 | 
					    /// The position of the top edge of the glyph
 | 
				
			||||||
 | 
					    pub top: f32,
 | 
				
			||||||
 | 
					    /// The width of the glyph
 | 
				
			||||||
 | 
					    pub width: f32,
 | 
				
			||||||
 | 
					    /// The height of the glyph
 | 
				
			||||||
 | 
					    pub height: f32,
 | 
				
			||||||
 | 
					    /// The color of this glyph (only relevant if the glyph is rendered with the
 | 
				
			||||||
 | 
					    /// type [`ContentType::Mask`])
 | 
				
			||||||
 | 
					    ///
 | 
				
			||||||
 | 
					    /// Set to `None` to use [`TextArea::default_color`].
 | 
				
			||||||
 | 
					    pub color: Option<Color>,
 | 
				
			||||||
 | 
					    /// If `true`, then this glyph will be snapped to the nearest whole physical
 | 
				
			||||||
 | 
					    /// pixel and the resulting `SubpixelBin`'s in `RasterizationRequest` will always
 | 
				
			||||||
 | 
					    /// be `Zero` (useful for images and other large glyphs).
 | 
				
			||||||
 | 
					    pub snap_to_physical_pixel: bool,
 | 
				
			||||||
 | 
					    /// Additional metadata about the glyph
 | 
				
			||||||
 | 
					    pub metadata: usize,
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// A request to rasterize a custom glyph
 | 
				
			||||||
 | 
					#[derive(Debug, Clone, Copy, PartialEq)]
 | 
				
			||||||
 | 
					pub struct RasterizationRequest {
 | 
				
			||||||
 | 
					    /// The unique identifier of the glyph
 | 
				
			||||||
 | 
					    pub id: CustomGlyphId,
 | 
				
			||||||
 | 
					    /// The width of the glyph in physical pixels
 | 
				
			||||||
 | 
					    pub width: u16,
 | 
				
			||||||
 | 
					    /// The height of the glyph in physical pixels
 | 
				
			||||||
 | 
					    pub height: u16,
 | 
				
			||||||
 | 
					    /// Binning of fractional X offset
 | 
				
			||||||
 | 
					    ///
 | 
				
			||||||
 | 
					    /// If `CustomGlyph::snap_to_physical_pixel` was set to `true`, then this
 | 
				
			||||||
 | 
					    /// will always be `Zero`.
 | 
				
			||||||
 | 
					    pub x_bin: SubpixelBin,
 | 
				
			||||||
 | 
					    /// Binning of fractional Y offset
 | 
				
			||||||
 | 
					    ///
 | 
				
			||||||
 | 
					    /// If `CustomGlyph::snap_to_physical_pixel` was set to `true`, then this
 | 
				
			||||||
 | 
					    /// will always be `Zero`.
 | 
				
			||||||
 | 
					    pub y_bin: SubpixelBin,
 | 
				
			||||||
 | 
					    /// The scaling factor applied to the text area (Note that `width` and
 | 
				
			||||||
 | 
					    /// `height` are already scaled by this factor.)
 | 
				
			||||||
 | 
					    pub scale: f32,
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// A rasterized custom glyph
 | 
				
			||||||
 | 
					#[derive(Debug, Clone)]
 | 
				
			||||||
 | 
					pub struct RasterizedCustomGlyph {
 | 
				
			||||||
 | 
					    /// The raw image data
 | 
				
			||||||
 | 
					    pub data: Vec<u8>,
 | 
				
			||||||
 | 
					    /// The type of image data contained in `data`
 | 
				
			||||||
 | 
					    pub content_type: ContentType,
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl RasterizedCustomGlyph {
 | 
				
			||||||
 | 
					    pub(crate) fn validate(&self, input: &RasterizationRequest, expected_type: Option<ContentType>) {
 | 
				
			||||||
 | 
					        if let Some(expected_type) = expected_type {
 | 
				
			||||||
 | 
					            assert_eq!(self.content_type, expected_type, "Custom glyph rasterizer must always produce the same content type for a given input. Expected {:?}, got {:?}. Input: {:?}", expected_type, self.content_type, input);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        assert_eq!(
 | 
				
			||||||
 | 
					            self.data.len(),
 | 
				
			||||||
 | 
					            input.width as usize * input.height as usize * self.content_type.bytes_per_pixel(),
 | 
				
			||||||
 | 
					            "Invalid custom glyph rasterizer output. Expected data of length {}, got length {}. Input: {:?}",
 | 
				
			||||||
 | 
					            input.width as usize * input.height as usize * self.content_type.bytes_per_pixel(),
 | 
				
			||||||
 | 
					            self.data.len(),
 | 
				
			||||||
 | 
					            input,
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
 | 
				
			||||||
 | 
					pub struct CustomGlyphCacheKey {
 | 
				
			||||||
 | 
					    /// Font ID
 | 
				
			||||||
 | 
					    pub glyph_id: CustomGlyphId,
 | 
				
			||||||
 | 
					    /// Glyph width
 | 
				
			||||||
 | 
					    pub width: u16,
 | 
				
			||||||
 | 
					    /// Glyph height
 | 
				
			||||||
 | 
					    pub height: u16,
 | 
				
			||||||
 | 
					    /// Binning of fractional X offset
 | 
				
			||||||
 | 
					    pub x_bin: SubpixelBin,
 | 
				
			||||||
 | 
					    /// Binning of fractional Y offset
 | 
				
			||||||
 | 
					    pub y_bin: SubpixelBin,
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// The type of image data contained in a rasterized glyph
 | 
				
			||||||
 | 
					#[derive(Debug, Clone, Copy, Eq, PartialEq)]
 | 
				
			||||||
 | 
					pub enum ContentType {
 | 
				
			||||||
 | 
					    /// Each pixel contains 32 bits of rgba data
 | 
				
			||||||
 | 
					    Color,
 | 
				
			||||||
 | 
					    /// Each pixel contains a single 8 bit channel
 | 
				
			||||||
 | 
					    Mask,
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl ContentType {
 | 
				
			||||||
 | 
					    /// The number of bytes per pixel for this content type
 | 
				
			||||||
 | 
					    pub fn bytes_per_pixel(&self) -> usize {
 | 
				
			||||||
 | 
					        match self {
 | 
				
			||||||
 | 
					            Self::Color => 4,
 | 
				
			||||||
 | 
					            Self::Mask => 1,
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -5,19 +5,21 @@
 | 
				
			||||||
//! [etagere]: https://github.com/nical/etagere
 | 
					//! [etagere]: https://github.com/nical/etagere
 | 
				
			||||||
 | 
					
 | 
				
			||||||
mod cache;
 | 
					mod cache;
 | 
				
			||||||
 | 
					mod custom_glyph;
 | 
				
			||||||
mod error;
 | 
					mod error;
 | 
				
			||||||
mod text_atlas;
 | 
					mod text_atlas;
 | 
				
			||||||
mod text_render;
 | 
					mod text_render;
 | 
				
			||||||
mod viewport;
 | 
					mod viewport;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
pub use cache::Cache;
 | 
					pub use cache::Cache;
 | 
				
			||||||
 | 
					pub use custom_glyph::{
 | 
				
			||||||
 | 
					    ContentType, CustomGlyph, CustomGlyphId, RasterizationRequest, RasterizedCustomGlyph,
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
pub use error::{PrepareError, RenderError};
 | 
					pub use error::{PrepareError, RenderError};
 | 
				
			||||||
pub use text_atlas::{ColorMode, TextAtlas};
 | 
					pub use text_atlas::{ColorMode, TextAtlas};
 | 
				
			||||||
pub use text_render::TextRenderer;
 | 
					pub use text_render::TextRenderer;
 | 
				
			||||||
pub use viewport::Viewport;
 | 
					pub use viewport::Viewport;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
use text_render::ContentType;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// Re-export all top-level types from `cosmic-text` for convenience.
 | 
					// Re-export all top-level types from `cosmic-text` for convenience.
 | 
				
			||||||
#[doc(no_inline)]
 | 
					#[doc(no_inline)]
 | 
				
			||||||
pub use cosmic_text::{
 | 
					pub use cosmic_text::{
 | 
				
			||||||
| 
						 | 
					@ -117,4 +119,7 @@ pub struct TextArea<'a> {
 | 
				
			||||||
    pub bounds: TextBounds,
 | 
					    pub bounds: TextBounds,
 | 
				
			||||||
    // The default color of the text area.
 | 
					    // The default color of the text area.
 | 
				
			||||||
    pub default_color: Color,
 | 
					    pub default_color: Color,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /// Additional custom glyphs to render
 | 
				
			||||||
 | 
					    pub custom_glyphs: &'a [CustomGlyph],
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,5 +1,6 @@
 | 
				
			||||||
use crate::{
 | 
					use crate::{
 | 
				
			||||||
    text_render::ContentType, Cache, CacheKey, FontSystem, GlyphDetails, GpuCacheStatus, SwashCache,
 | 
					    text_render::GlyphonCacheKey, Cache, ContentType, RasterizationRequest, RasterizedCustomGlyph,
 | 
				
			||||||
 | 
					    FontSystem, GlyphDetails, GpuCacheStatus, SwashCache,
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
use etagere::{size2, Allocation, BucketedAtlasAllocator};
 | 
					use etagere::{size2, Allocation, BucketedAtlasAllocator};
 | 
				
			||||||
use lru::LruCache;
 | 
					use lru::LruCache;
 | 
				
			||||||
| 
						 | 
					@ -20,8 +21,8 @@ pub(crate) struct InnerAtlas {
 | 
				
			||||||
    pub texture_view: TextureView,
 | 
					    pub texture_view: TextureView,
 | 
				
			||||||
    pub packer: BucketedAtlasAllocator,
 | 
					    pub packer: BucketedAtlasAllocator,
 | 
				
			||||||
    pub size: u32,
 | 
					    pub size: u32,
 | 
				
			||||||
    pub glyph_cache: LruCache<CacheKey, GlyphDetails, Hasher>,
 | 
					    pub glyph_cache: LruCache<GlyphonCacheKey, GlyphDetails, Hasher>,
 | 
				
			||||||
    pub glyphs_in_use: HashSet<CacheKey, Hasher>,
 | 
					    pub glyphs_in_use: HashSet<GlyphonCacheKey, Hasher>,
 | 
				
			||||||
    pub max_texture_dimension_2d: u32,
 | 
					    pub max_texture_dimension_2d: u32,
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -106,12 +107,12 @@ impl InnerAtlas {
 | 
				
			||||||
        self.kind.num_channels()
 | 
					        self.kind.num_channels()
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    pub(crate) fn promote(&mut self, glyph: CacheKey) {
 | 
					    pub(crate) fn promote(&mut self, glyph: GlyphonCacheKey) {
 | 
				
			||||||
        self.glyph_cache.promote(&glyph);
 | 
					        self.glyph_cache.promote(&glyph);
 | 
				
			||||||
        self.glyphs_in_use.insert(glyph);
 | 
					        self.glyphs_in_use.insert(glyph);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    pub(crate) fn put(&mut self, glyph: CacheKey, details: GlyphDetails) {
 | 
					    pub(crate) fn put(&mut self, glyph: GlyphonCacheKey, details: GlyphDetails) {
 | 
				
			||||||
        self.glyph_cache.put(glyph, details);
 | 
					        self.glyph_cache.put(glyph, details);
 | 
				
			||||||
        self.glyphs_in_use.insert(glyph);
 | 
					        self.glyphs_in_use.insert(glyph);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
| 
						 | 
					@ -122,6 +123,8 @@ impl InnerAtlas {
 | 
				
			||||||
        queue: &wgpu::Queue,
 | 
					        queue: &wgpu::Queue,
 | 
				
			||||||
        font_system: &mut FontSystem,
 | 
					        font_system: &mut FontSystem,
 | 
				
			||||||
        cache: &mut SwashCache,
 | 
					        cache: &mut SwashCache,
 | 
				
			||||||
 | 
					        scale_factor: f32,
 | 
				
			||||||
 | 
					        mut rasterize_custom_glyph: impl FnMut(RasterizationRequest) -> Option<RasterizedCustomGlyph>,
 | 
				
			||||||
    ) -> bool {
 | 
					    ) -> bool {
 | 
				
			||||||
        if self.size >= self.max_texture_dimension_2d {
 | 
					        if self.size >= self.max_texture_dimension_2d {
 | 
				
			||||||
            return false;
 | 
					            return false;
 | 
				
			||||||
| 
						 | 
					@ -157,11 +160,39 @@ impl InnerAtlas {
 | 
				
			||||||
                GpuCacheStatus::SkipRasterization => continue,
 | 
					                GpuCacheStatus::SkipRasterization => continue,
 | 
				
			||||||
            };
 | 
					            };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            let (image_data, width, height) = match cache_key {
 | 
				
			||||||
 | 
					                GlyphonCacheKey::Text(cache_key) => {
 | 
				
			||||||
                    let image = cache.get_image_uncached(font_system, cache_key).unwrap();
 | 
					                    let image = cache.get_image_uncached(font_system, cache_key).unwrap();
 | 
				
			||||||
 | 
					 | 
				
			||||||
                    let width = image.placement.width as usize;
 | 
					                    let width = image.placement.width as usize;
 | 
				
			||||||
                    let height = image.placement.height as usize;
 | 
					                    let height = image.placement.height as usize;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    (image.data, width, height)
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                GlyphonCacheKey::Custom(cache_key) => {
 | 
				
			||||||
 | 
					                    let input = RasterizationRequest {
 | 
				
			||||||
 | 
					                        id: cache_key.glyph_id,
 | 
				
			||||||
 | 
					                        width: cache_key.width,
 | 
				
			||||||
 | 
					                        height: cache_key.height,
 | 
				
			||||||
 | 
					                        x_bin: cache_key.x_bin,
 | 
				
			||||||
 | 
					                        y_bin: cache_key.y_bin,
 | 
				
			||||||
 | 
					                        scale: scale_factor,
 | 
				
			||||||
 | 
					                    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    let Some(rasterized_glyph) = (rasterize_custom_glyph)(input) else {
 | 
				
			||||||
 | 
					                        panic!("Custom glyph rasterizer returned `None` when it previously returned `Some` for the same input {:?}", &input);
 | 
				
			||||||
 | 
					                    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    // Sanity checks on the rasterizer output
 | 
				
			||||||
 | 
					                    rasterized_glyph.validate(&input, Some(self.kind.as_content_type()));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    (
 | 
				
			||||||
 | 
					                        rasterized_glyph.data,
 | 
				
			||||||
 | 
					                        cache_key.width as usize,
 | 
				
			||||||
 | 
					                        cache_key.height as usize,
 | 
				
			||||||
 | 
					                    )
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            queue.write_texture(
 | 
					            queue.write_texture(
 | 
				
			||||||
                ImageCopyTexture {
 | 
					                ImageCopyTexture {
 | 
				
			||||||
                    texture: &self.texture,
 | 
					                    texture: &self.texture,
 | 
				
			||||||
| 
						 | 
					@ -173,7 +204,7 @@ impl InnerAtlas {
 | 
				
			||||||
                    },
 | 
					                    },
 | 
				
			||||||
                    aspect: TextureAspect::All,
 | 
					                    aspect: TextureAspect::All,
 | 
				
			||||||
                },
 | 
					                },
 | 
				
			||||||
                &image.data,
 | 
					                &image_data,
 | 
				
			||||||
                ImageDataLayout {
 | 
					                ImageDataLayout {
 | 
				
			||||||
                    offset: 0,
 | 
					                    offset: 0,
 | 
				
			||||||
                    bytes_per_row: Some(width as u32 * self.kind.num_channels() as u32),
 | 
					                    bytes_per_row: Some(width as u32 * self.kind.num_channels() as u32),
 | 
				
			||||||
| 
						 | 
					@ -224,6 +255,13 @@ impl Kind {
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fn as_content_type(&self) -> ContentType {
 | 
				
			||||||
 | 
					        match self {
 | 
				
			||||||
 | 
					            Self::Mask => ContentType::Mask,
 | 
				
			||||||
 | 
					            Self::Color { .. } => ContentType::Color,
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/// The color mode of an [`Atlas`].
 | 
					/// The color mode of an [`Atlas`].
 | 
				
			||||||
| 
						 | 
					@ -313,10 +351,26 @@ impl TextAtlas {
 | 
				
			||||||
        font_system: &mut FontSystem,
 | 
					        font_system: &mut FontSystem,
 | 
				
			||||||
        cache: &mut SwashCache,
 | 
					        cache: &mut SwashCache,
 | 
				
			||||||
        content_type: ContentType,
 | 
					        content_type: ContentType,
 | 
				
			||||||
 | 
					        scale_factor: f32,
 | 
				
			||||||
 | 
					        rasterize_custom_glyph: impl FnMut(RasterizationRequest) -> Option<RasterizedCustomGlyph>,
 | 
				
			||||||
    ) -> bool {
 | 
					    ) -> bool {
 | 
				
			||||||
        let did_grow = match content_type {
 | 
					        let did_grow = match content_type {
 | 
				
			||||||
            ContentType::Mask => self.mask_atlas.grow(device, queue, font_system, cache),
 | 
					            ContentType::Mask => self.mask_atlas.grow(
 | 
				
			||||||
            ContentType::Color => self.color_atlas.grow(device, queue, font_system, cache),
 | 
					                device,
 | 
				
			||||||
 | 
					                queue,
 | 
				
			||||||
 | 
					                font_system,
 | 
				
			||||||
 | 
					                cache,
 | 
				
			||||||
 | 
					                scale_factor,
 | 
				
			||||||
 | 
					                rasterize_custom_glyph,
 | 
				
			||||||
 | 
					            ),
 | 
				
			||||||
 | 
					            ContentType::Color => self.color_atlas.grow(
 | 
				
			||||||
 | 
					                device,
 | 
				
			||||||
 | 
					                queue,
 | 
				
			||||||
 | 
					                font_system,
 | 
				
			||||||
 | 
					                cache,
 | 
				
			||||||
 | 
					                scale_factor,
 | 
				
			||||||
 | 
					                rasterize_custom_glyph,
 | 
				
			||||||
 | 
					            ),
 | 
				
			||||||
        };
 | 
					        };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if did_grow {
 | 
					        if did_grow {
 | 
				
			||||||
| 
						 | 
					@ -326,7 +380,7 @@ impl TextAtlas {
 | 
				
			||||||
        did_grow
 | 
					        did_grow
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    pub(crate) fn glyph(&self, glyph: &CacheKey) -> Option<&GlyphDetails> {
 | 
					    pub(crate) fn glyph(&self, glyph: &GlyphonCacheKey) -> Option<&GlyphDetails> {
 | 
				
			||||||
        self.mask_atlas
 | 
					        self.mask_atlas
 | 
				
			||||||
            .glyph_cache
 | 
					            .glyph_cache
 | 
				
			||||||
            .peek(glyph)
 | 
					            .peek(glyph)
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,7 +1,9 @@
 | 
				
			||||||
use crate::{
 | 
					use crate::{
 | 
				
			||||||
    ColorMode, FontSystem, GlyphDetails, GlyphToRender, GpuCacheStatus, PrepareError, RenderError,
 | 
					    custom_glyph::CustomGlyphCacheKey, ColorMode, ContentType, RasterizationRequest, RasterizedCustomGlyph,
 | 
				
			||||||
    SwashCache, SwashContent, TextArea, TextAtlas, Viewport,
 | 
					    FontSystem, GlyphDetails, GlyphToRender, GpuCacheStatus, PrepareError, RenderError, SwashCache,
 | 
				
			||||||
 | 
					    SwashContent, TextArea, TextAtlas, Viewport,
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					use cosmic_text::{Color, SubpixelBin};
 | 
				
			||||||
use std::{slice, sync::Arc};
 | 
					use std::{slice, sync::Arc};
 | 
				
			||||||
use wgpu::{
 | 
					use wgpu::{
 | 
				
			||||||
    Buffer, BufferDescriptor, BufferUsages, DepthStencilState, Device, Extent3d, ImageCopyTexture,
 | 
					    Buffer, BufferDescriptor, BufferUsages, DepthStencilState, Device, Extent3d, ImageCopyTexture,
 | 
				
			||||||
| 
						 | 
					@ -43,8 +45,82 @@ impl TextRenderer {
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /// Prepares all of the provided text areas for rendering.
 | 
				
			||||||
 | 
					    pub fn prepare<'a>(
 | 
				
			||||||
 | 
					        &mut self,
 | 
				
			||||||
 | 
					        device: &Device,
 | 
				
			||||||
 | 
					        queue: &Queue,
 | 
				
			||||||
 | 
					        font_system: &mut FontSystem,
 | 
				
			||||||
 | 
					        atlas: &mut TextAtlas,
 | 
				
			||||||
 | 
					        viewport: &Viewport,
 | 
				
			||||||
 | 
					        text_areas: impl IntoIterator<Item = TextArea<'a>>,
 | 
				
			||||||
 | 
					        cache: &mut SwashCache,
 | 
				
			||||||
 | 
					    ) -> Result<(), PrepareError> {
 | 
				
			||||||
 | 
					        self.prepare_with_depth_and_custom(
 | 
				
			||||||
 | 
					            device,
 | 
				
			||||||
 | 
					            queue,
 | 
				
			||||||
 | 
					            font_system,
 | 
				
			||||||
 | 
					            atlas,
 | 
				
			||||||
 | 
					            viewport,
 | 
				
			||||||
 | 
					            text_areas,
 | 
				
			||||||
 | 
					            cache,
 | 
				
			||||||
 | 
					            zero_depth,
 | 
				
			||||||
 | 
					            |_| None,
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /// Prepares all of the provided text areas for rendering.
 | 
					    /// Prepares all of the provided text areas for rendering.
 | 
				
			||||||
    pub fn prepare_with_depth<'a>(
 | 
					    pub fn prepare_with_depth<'a>(
 | 
				
			||||||
 | 
					        &mut self,
 | 
				
			||||||
 | 
					        device: &Device,
 | 
				
			||||||
 | 
					        queue: &Queue,
 | 
				
			||||||
 | 
					        font_system: &mut FontSystem,
 | 
				
			||||||
 | 
					        atlas: &mut TextAtlas,
 | 
				
			||||||
 | 
					        viewport: &Viewport,
 | 
				
			||||||
 | 
					        text_areas: impl IntoIterator<Item = TextArea<'a>>,
 | 
				
			||||||
 | 
					        cache: &mut SwashCache,
 | 
				
			||||||
 | 
					        metadata_to_depth: impl FnMut(usize) -> f32,
 | 
				
			||||||
 | 
					    ) -> Result<(), PrepareError> {
 | 
				
			||||||
 | 
					        self.prepare_with_depth_and_custom(
 | 
				
			||||||
 | 
					            device,
 | 
				
			||||||
 | 
					            queue,
 | 
				
			||||||
 | 
					            font_system,
 | 
				
			||||||
 | 
					            atlas,
 | 
				
			||||||
 | 
					            viewport,
 | 
				
			||||||
 | 
					            text_areas,
 | 
				
			||||||
 | 
					            cache,
 | 
				
			||||||
 | 
					            metadata_to_depth,
 | 
				
			||||||
 | 
					            |_| None,
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /// Prepares all of the provided text areas for rendering.
 | 
				
			||||||
 | 
					    pub fn prepare_with_custom<'a>(
 | 
				
			||||||
 | 
					        &mut self,
 | 
				
			||||||
 | 
					        device: &Device,
 | 
				
			||||||
 | 
					        queue: &Queue,
 | 
				
			||||||
 | 
					        font_system: &mut FontSystem,
 | 
				
			||||||
 | 
					        atlas: &mut TextAtlas,
 | 
				
			||||||
 | 
					        viewport: &Viewport,
 | 
				
			||||||
 | 
					        text_areas: impl IntoIterator<Item = TextArea<'a>>,
 | 
				
			||||||
 | 
					        cache: &mut SwashCache,
 | 
				
			||||||
 | 
					        rasterize_custom_glyph: impl FnMut(RasterizationRequest) -> Option<RasterizedCustomGlyph>,
 | 
				
			||||||
 | 
					    ) -> Result<(), PrepareError> {
 | 
				
			||||||
 | 
					        self.prepare_with_depth_and_custom(
 | 
				
			||||||
 | 
					            device,
 | 
				
			||||||
 | 
					            queue,
 | 
				
			||||||
 | 
					            font_system,
 | 
				
			||||||
 | 
					            atlas,
 | 
				
			||||||
 | 
					            viewport,
 | 
				
			||||||
 | 
					            text_areas,
 | 
				
			||||||
 | 
					            cache,
 | 
				
			||||||
 | 
					            zero_depth,
 | 
				
			||||||
 | 
					            rasterize_custom_glyph,
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /// Prepares all of the provided text areas for rendering.
 | 
				
			||||||
 | 
					    pub fn prepare_with_depth_and_custom<'a>(
 | 
				
			||||||
        &mut self,
 | 
					        &mut self,
 | 
				
			||||||
        device: &Device,
 | 
					        device: &Device,
 | 
				
			||||||
        queue: &Queue,
 | 
					        queue: &Queue,
 | 
				
			||||||
| 
						 | 
					@ -54,6 +130,7 @@ impl TextRenderer {
 | 
				
			||||||
        text_areas: impl IntoIterator<Item = TextArea<'a>>,
 | 
					        text_areas: impl IntoIterator<Item = TextArea<'a>>,
 | 
				
			||||||
        cache: &mut SwashCache,
 | 
					        cache: &mut SwashCache,
 | 
				
			||||||
        mut metadata_to_depth: impl FnMut(usize) -> f32,
 | 
					        mut metadata_to_depth: impl FnMut(usize) -> f32,
 | 
				
			||||||
 | 
					        mut rasterize_custom_glyph: impl FnMut(RasterizationRequest) -> Option<RasterizedCustomGlyph>,
 | 
				
			||||||
    ) -> Result<(), PrepareError> {
 | 
					    ) -> Result<(), PrepareError> {
 | 
				
			||||||
        self.glyph_vertices.clear();
 | 
					        self.glyph_vertices.clear();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -65,6 +142,88 @@ impl TextRenderer {
 | 
				
			||||||
            let bounds_max_x = text_area.bounds.right.min(resolution.width as i32);
 | 
					            let bounds_max_x = text_area.bounds.right.min(resolution.width as i32);
 | 
				
			||||||
            let bounds_max_y = text_area.bounds.bottom.min(resolution.height as i32);
 | 
					            let bounds_max_y = text_area.bounds.bottom.min(resolution.height as i32);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            for glyph in text_area.custom_glyphs.iter() {
 | 
				
			||||||
 | 
					                let x = text_area.left + (glyph.left * text_area.scale);
 | 
				
			||||||
 | 
					                let y = text_area.top + (glyph.top * text_area.scale);
 | 
				
			||||||
 | 
					                let width = (glyph.width * text_area.scale).round() as u16;
 | 
				
			||||||
 | 
					                let height = (glyph.height * text_area.scale).round() as u16;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                let (x, y, x_bin, y_bin) = if glyph.snap_to_physical_pixel {
 | 
				
			||||||
 | 
					                    (
 | 
				
			||||||
 | 
					                        x.round() as i32,
 | 
				
			||||||
 | 
					                        y.round() as i32,
 | 
				
			||||||
 | 
					                        SubpixelBin::Zero,
 | 
				
			||||||
 | 
					                        SubpixelBin::Zero,
 | 
				
			||||||
 | 
					                    )
 | 
				
			||||||
 | 
					                } else {
 | 
				
			||||||
 | 
					                    let (x, x_bin) = SubpixelBin::new(x);
 | 
				
			||||||
 | 
					                    let (y, y_bin) = SubpixelBin::new(y);
 | 
				
			||||||
 | 
					                    (x, y, x_bin, y_bin)
 | 
				
			||||||
 | 
					                };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                let cache_key = GlyphonCacheKey::Custom(CustomGlyphCacheKey {
 | 
				
			||||||
 | 
					                    glyph_id: glyph.id,
 | 
				
			||||||
 | 
					                    width,
 | 
				
			||||||
 | 
					                    height,
 | 
				
			||||||
 | 
					                    x_bin,
 | 
				
			||||||
 | 
					                    y_bin,
 | 
				
			||||||
 | 
					                });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                let color = glyph.color.unwrap_or(text_area.default_color);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                if let Some(glyph_to_render) = prepare_glyph(
 | 
				
			||||||
 | 
					                    x,
 | 
				
			||||||
 | 
					                    y,
 | 
				
			||||||
 | 
					                    0.0,
 | 
				
			||||||
 | 
					                    color,
 | 
				
			||||||
 | 
					                    glyph.metadata,
 | 
				
			||||||
 | 
					                    cache_key,
 | 
				
			||||||
 | 
					                    atlas,
 | 
				
			||||||
 | 
					                    device,
 | 
				
			||||||
 | 
					                    queue,
 | 
				
			||||||
 | 
					                    cache,
 | 
				
			||||||
 | 
					                    font_system,
 | 
				
			||||||
 | 
					                    text_area.scale,
 | 
				
			||||||
 | 
					                    bounds_min_x,
 | 
				
			||||||
 | 
					                    bounds_min_y,
 | 
				
			||||||
 | 
					                    bounds_max_x,
 | 
				
			||||||
 | 
					                    bounds_max_y,
 | 
				
			||||||
 | 
					                    |_cache, _font_system, rasterize_custom_glyph| -> Option<GetGlyphImageResult> {
 | 
				
			||||||
 | 
					                        if width == 0 || height == 0 {
 | 
				
			||||||
 | 
					                            return None;
 | 
				
			||||||
 | 
					                        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                        let input = RasterizationRequest {
 | 
				
			||||||
 | 
					                            id: glyph.id,
 | 
				
			||||||
 | 
					                            width,
 | 
				
			||||||
 | 
					                            height,
 | 
				
			||||||
 | 
					                            x_bin,
 | 
				
			||||||
 | 
					                            y_bin,
 | 
				
			||||||
 | 
					                            scale: text_area.scale,
 | 
				
			||||||
 | 
					                        };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                        let Some(output) = (rasterize_custom_glyph)(input) else {
 | 
				
			||||||
 | 
					                            return None;
 | 
				
			||||||
 | 
					                        };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                        output.validate(&input, None);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                        Some(GetGlyphImageResult {
 | 
				
			||||||
 | 
					                            content_type: output.content_type,
 | 
				
			||||||
 | 
					                            top: 0,
 | 
				
			||||||
 | 
					                            left: 0,
 | 
				
			||||||
 | 
					                            width,
 | 
				
			||||||
 | 
					                            height,
 | 
				
			||||||
 | 
					                            data: output.data,
 | 
				
			||||||
 | 
					                        })
 | 
				
			||||||
 | 
					                    },
 | 
				
			||||||
 | 
					                    &mut metadata_to_depth,
 | 
				
			||||||
 | 
					                    &mut rasterize_custom_glyph,
 | 
				
			||||||
 | 
					                )? {
 | 
				
			||||||
 | 
					                    self.glyph_vertices.push(glyph_to_render);
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            let is_run_visible = |run: &cosmic_text::LayoutRun| {
 | 
					            let is_run_visible = |run: &cosmic_text::LayoutRun| {
 | 
				
			||||||
                let start_y = (text_area.top + run.line_top) as i32;
 | 
					                let start_y = (text_area.top + run.line_top) as i32;
 | 
				
			||||||
                let end_y = (text_area.top + run.line_top + run.line_height) as i32;
 | 
					                let end_y = (text_area.top + run.line_top + run.line_height) as i32;
 | 
				
			||||||
| 
						 | 
					@ -83,23 +242,36 @@ impl TextRenderer {
 | 
				
			||||||
                    let physical_glyph =
 | 
					                    let physical_glyph =
 | 
				
			||||||
                        glyph.physical((text_area.left, text_area.top), text_area.scale);
 | 
					                        glyph.physical((text_area.left, text_area.top), text_area.scale);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                    if atlas
 | 
					                    let color = match glyph.color_opt {
 | 
				
			||||||
                        .mask_atlas
 | 
					                        Some(some) => some,
 | 
				
			||||||
                        .glyph_cache
 | 
					                        None => text_area.default_color,
 | 
				
			||||||
                        .contains(&physical_glyph.cache_key)
 | 
					                    };
 | 
				
			||||||
                    {
 | 
					
 | 
				
			||||||
                        atlas.mask_atlas.promote(physical_glyph.cache_key);
 | 
					                    if let Some(glyph_to_render) = prepare_glyph(
 | 
				
			||||||
                    } else if atlas
 | 
					                        physical_glyph.x,
 | 
				
			||||||
                        .color_atlas
 | 
					                        physical_glyph.y,
 | 
				
			||||||
                        .glyph_cache
 | 
					                        run.line_y,
 | 
				
			||||||
                        .contains(&physical_glyph.cache_key)
 | 
					                        color,
 | 
				
			||||||
                    {
 | 
					                        glyph.metadata,
 | 
				
			||||||
                        atlas.color_atlas.promote(physical_glyph.cache_key);
 | 
					                        GlyphonCacheKey::Text(physical_glyph.cache_key),
 | 
				
			||||||
                    } else {
 | 
					                        atlas,
 | 
				
			||||||
 | 
					                        device,
 | 
				
			||||||
 | 
					                        queue,
 | 
				
			||||||
 | 
					                        cache,
 | 
				
			||||||
 | 
					                        font_system,
 | 
				
			||||||
 | 
					                        text_area.scale,
 | 
				
			||||||
 | 
					                        bounds_min_x,
 | 
				
			||||||
 | 
					                        bounds_min_y,
 | 
				
			||||||
 | 
					                        bounds_max_x,
 | 
				
			||||||
 | 
					                        bounds_max_y,
 | 
				
			||||||
 | 
					                        |cache,
 | 
				
			||||||
 | 
					                         font_system,
 | 
				
			||||||
 | 
					                         _rasterize_custom_glyph|
 | 
				
			||||||
 | 
					                         -> Option<GetGlyphImageResult> {
 | 
				
			||||||
                            let Some(image) =
 | 
					                            let Some(image) =
 | 
				
			||||||
                                cache.get_image_uncached(font_system, physical_glyph.cache_key)
 | 
					                                cache.get_image_uncached(font_system, physical_glyph.cache_key)
 | 
				
			||||||
                            else {
 | 
					                            else {
 | 
				
			||||||
                            continue;
 | 
					                                return None;
 | 
				
			||||||
                            };
 | 
					                            };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                            let content_type = match image.content {
 | 
					                            let content_type = match image.content {
 | 
				
			||||||
| 
						 | 
					@ -111,161 +283,20 @@ impl TextRenderer {
 | 
				
			||||||
                                }
 | 
					                                }
 | 
				
			||||||
                            };
 | 
					                            };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                        let width = image.placement.width as usize;
 | 
					                            Some(GetGlyphImageResult {
 | 
				
			||||||
                        let height = image.placement.height as usize;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                        let should_rasterize = width > 0 && height > 0;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                        let (gpu_cache, atlas_id, inner) = if should_rasterize {
 | 
					 | 
				
			||||||
                            let mut inner = atlas.inner_for_content_mut(content_type);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                            // Find a position in the packer
 | 
					 | 
				
			||||||
                            let allocation = loop {
 | 
					 | 
				
			||||||
                                match inner.try_allocate(width, height) {
 | 
					 | 
				
			||||||
                                    Some(a) => break a,
 | 
					 | 
				
			||||||
                                    None => {
 | 
					 | 
				
			||||||
                                        if !atlas.grow(
 | 
					 | 
				
			||||||
                                            device,
 | 
					 | 
				
			||||||
                                            queue,
 | 
					 | 
				
			||||||
                                            font_system,
 | 
					 | 
				
			||||||
                                            cache,
 | 
					 | 
				
			||||||
                                content_type,
 | 
					                                content_type,
 | 
				
			||||||
                                        ) {
 | 
					 | 
				
			||||||
                                            return Err(PrepareError::AtlasFull);
 | 
					 | 
				
			||||||
                                        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                                        inner = atlas.inner_for_content_mut(content_type);
 | 
					 | 
				
			||||||
                                    }
 | 
					 | 
				
			||||||
                                }
 | 
					 | 
				
			||||||
                            };
 | 
					 | 
				
			||||||
                            let atlas_min = allocation.rectangle.min;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                            queue.write_texture(
 | 
					 | 
				
			||||||
                                ImageCopyTexture {
 | 
					 | 
				
			||||||
                                    texture: &inner.texture,
 | 
					 | 
				
			||||||
                                    mip_level: 0,
 | 
					 | 
				
			||||||
                                    origin: Origin3d {
 | 
					 | 
				
			||||||
                                        x: atlas_min.x as u32,
 | 
					 | 
				
			||||||
                                        y: atlas_min.y as u32,
 | 
					 | 
				
			||||||
                                        z: 0,
 | 
					 | 
				
			||||||
                                    },
 | 
					 | 
				
			||||||
                                    aspect: TextureAspect::All,
 | 
					 | 
				
			||||||
                                },
 | 
					 | 
				
			||||||
                                &image.data,
 | 
					 | 
				
			||||||
                                ImageDataLayout {
 | 
					 | 
				
			||||||
                                    offset: 0,
 | 
					 | 
				
			||||||
                                    bytes_per_row: Some(width as u32 * inner.num_channels() as u32),
 | 
					 | 
				
			||||||
                                    rows_per_image: None,
 | 
					 | 
				
			||||||
                                },
 | 
					 | 
				
			||||||
                                Extent3d {
 | 
					 | 
				
			||||||
                                    width: width as u32,
 | 
					 | 
				
			||||||
                                    height: height as u32,
 | 
					 | 
				
			||||||
                                    depth_or_array_layers: 1,
 | 
					 | 
				
			||||||
                                },
 | 
					 | 
				
			||||||
                            );
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                            (
 | 
					 | 
				
			||||||
                                GpuCacheStatus::InAtlas {
 | 
					 | 
				
			||||||
                                    x: atlas_min.x as u16,
 | 
					 | 
				
			||||||
                                    y: atlas_min.y as u16,
 | 
					 | 
				
			||||||
                                    content_type,
 | 
					 | 
				
			||||||
                                },
 | 
					 | 
				
			||||||
                                Some(allocation.id),
 | 
					 | 
				
			||||||
                                inner,
 | 
					 | 
				
			||||||
                            )
 | 
					 | 
				
			||||||
                        } else {
 | 
					 | 
				
			||||||
                            let inner = &mut atlas.color_atlas;
 | 
					 | 
				
			||||||
                            (GpuCacheStatus::SkipRasterization, None, inner)
 | 
					 | 
				
			||||||
                        };
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                        inner.put(
 | 
					 | 
				
			||||||
                            physical_glyph.cache_key,
 | 
					 | 
				
			||||||
                            GlyphDetails {
 | 
					 | 
				
			||||||
                                width: width as u16,
 | 
					 | 
				
			||||||
                                height: height as u16,
 | 
					 | 
				
			||||||
                                gpu_cache,
 | 
					 | 
				
			||||||
                                atlas_id,
 | 
					 | 
				
			||||||
                                top: image.placement.top as i16,
 | 
					                                top: image.placement.top as i16,
 | 
				
			||||||
                                left: image.placement.left as i16,
 | 
					                                left: image.placement.left as i16,
 | 
				
			||||||
 | 
					                                width: image.placement.width as u16,
 | 
				
			||||||
 | 
					                                height: image.placement.height as u16,
 | 
				
			||||||
 | 
					                                data: image.data,
 | 
				
			||||||
 | 
					                            })
 | 
				
			||||||
                        },
 | 
					                        },
 | 
				
			||||||
                        );
 | 
					                        &mut metadata_to_depth,
 | 
				
			||||||
 | 
					                        &mut rasterize_custom_glyph,
 | 
				
			||||||
 | 
					                    )? {
 | 
				
			||||||
 | 
					                        self.glyph_vertices.push(glyph_to_render);
 | 
				
			||||||
                    }
 | 
					                    }
 | 
				
			||||||
 | 
					 | 
				
			||||||
                    let details = atlas.glyph(&physical_glyph.cache_key).unwrap();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                    let mut x = physical_glyph.x + details.left as i32;
 | 
					 | 
				
			||||||
                    let mut y = (run.line_y * text_area.scale).round() as i32 + physical_glyph.y
 | 
					 | 
				
			||||||
                        - details.top as i32;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                    let (mut atlas_x, mut atlas_y, content_type) = match details.gpu_cache {
 | 
					 | 
				
			||||||
                        GpuCacheStatus::InAtlas { x, y, content_type } => (x, y, content_type),
 | 
					 | 
				
			||||||
                        GpuCacheStatus::SkipRasterization => continue,
 | 
					 | 
				
			||||||
                    };
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                    let mut width = details.width as i32;
 | 
					 | 
				
			||||||
                    let mut height = details.height as i32;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                    // Starts beyond right edge or ends beyond left edge
 | 
					 | 
				
			||||||
                    let max_x = x + width;
 | 
					 | 
				
			||||||
                    if x > bounds_max_x || max_x < bounds_min_x {
 | 
					 | 
				
			||||||
                        continue;
 | 
					 | 
				
			||||||
                    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                    // Starts beyond bottom edge or ends beyond top edge
 | 
					 | 
				
			||||||
                    let max_y = y + height;
 | 
					 | 
				
			||||||
                    if y > bounds_max_y || max_y < bounds_min_y {
 | 
					 | 
				
			||||||
                        continue;
 | 
					 | 
				
			||||||
                    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                    // Clip left ege
 | 
					 | 
				
			||||||
                    if x < bounds_min_x {
 | 
					 | 
				
			||||||
                        let right_shift = bounds_min_x - x;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                        x = bounds_min_x;
 | 
					 | 
				
			||||||
                        width = max_x - bounds_min_x;
 | 
					 | 
				
			||||||
                        atlas_x += right_shift as u16;
 | 
					 | 
				
			||||||
                    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                    // Clip right edge
 | 
					 | 
				
			||||||
                    if x + width > bounds_max_x {
 | 
					 | 
				
			||||||
                        width = bounds_max_x - x;
 | 
					 | 
				
			||||||
                    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                    // Clip top edge
 | 
					 | 
				
			||||||
                    if y < bounds_min_y {
 | 
					 | 
				
			||||||
                        let bottom_shift = bounds_min_y - y;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                        y = bounds_min_y;
 | 
					 | 
				
			||||||
                        height = max_y - bounds_min_y;
 | 
					 | 
				
			||||||
                        atlas_y += bottom_shift as u16;
 | 
					 | 
				
			||||||
                    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                    // Clip bottom edge
 | 
					 | 
				
			||||||
                    if y + height > bounds_max_y {
 | 
					 | 
				
			||||||
                        height = bounds_max_y - y;
 | 
					 | 
				
			||||||
                    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                    let color = match glyph.color_opt {
 | 
					 | 
				
			||||||
                        Some(some) => some,
 | 
					 | 
				
			||||||
                        None => text_area.default_color,
 | 
					 | 
				
			||||||
                    };
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                    let depth = metadata_to_depth(glyph.metadata);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                    self.glyph_vertices.push(GlyphToRender {
 | 
					 | 
				
			||||||
                        pos: [x, y],
 | 
					 | 
				
			||||||
                        dim: [width as u16, height as u16],
 | 
					 | 
				
			||||||
                        uv: [atlas_x, atlas_y],
 | 
					 | 
				
			||||||
                        color: color.0,
 | 
					 | 
				
			||||||
                        content_type_with_srgb: [
 | 
					 | 
				
			||||||
                            content_type as u16,
 | 
					 | 
				
			||||||
                            match atlas.color_mode {
 | 
					 | 
				
			||||||
                                ColorMode::Accurate => TextColorConversion::ConvertToLinear,
 | 
					 | 
				
			||||||
                                ColorMode::Web => TextColorConversion::None,
 | 
					 | 
				
			||||||
                            } as u16,
 | 
					 | 
				
			||||||
                        ],
 | 
					 | 
				
			||||||
                        depth,
 | 
					 | 
				
			||||||
                    });
 | 
					 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
| 
						 | 
					@ -302,28 +333,6 @@ impl TextRenderer {
 | 
				
			||||||
        Ok(())
 | 
					        Ok(())
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    pub fn prepare<'a>(
 | 
					 | 
				
			||||||
        &mut self,
 | 
					 | 
				
			||||||
        device: &Device,
 | 
					 | 
				
			||||||
        queue: &Queue,
 | 
					 | 
				
			||||||
        font_system: &mut FontSystem,
 | 
					 | 
				
			||||||
        atlas: &mut TextAtlas,
 | 
					 | 
				
			||||||
        viewport: &Viewport,
 | 
					 | 
				
			||||||
        text_areas: impl IntoIterator<Item = TextArea<'a>>,
 | 
					 | 
				
			||||||
        cache: &mut SwashCache,
 | 
					 | 
				
			||||||
    ) -> Result<(), PrepareError> {
 | 
					 | 
				
			||||||
        self.prepare_with_depth(
 | 
					 | 
				
			||||||
            device,
 | 
					 | 
				
			||||||
            queue,
 | 
					 | 
				
			||||||
            font_system,
 | 
					 | 
				
			||||||
            atlas,
 | 
					 | 
				
			||||||
            viewport,
 | 
					 | 
				
			||||||
            text_areas,
 | 
					 | 
				
			||||||
            cache,
 | 
					 | 
				
			||||||
            zero_depth,
 | 
					 | 
				
			||||||
        )
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /// Renders all layouts that were previously provided to `prepare`.
 | 
					    /// Renders all layouts that were previously provided to `prepare`.
 | 
				
			||||||
    pub fn render<'pass>(
 | 
					    pub fn render<'pass>(
 | 
				
			||||||
        &'pass self,
 | 
					        &'pass self,
 | 
				
			||||||
| 
						 | 
					@ -345,13 +354,6 @@ impl TextRenderer {
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#[repr(u16)]
 | 
					 | 
				
			||||||
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
 | 
					 | 
				
			||||||
pub enum ContentType {
 | 
					 | 
				
			||||||
    Color = 0,
 | 
					 | 
				
			||||||
    Mask = 1,
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#[repr(u16)]
 | 
					#[repr(u16)]
 | 
				
			||||||
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
 | 
					#[derive(Debug, Clone, Copy, Eq, PartialEq)]
 | 
				
			||||||
enum TextColorConversion {
 | 
					enum TextColorConversion {
 | 
				
			||||||
| 
						 | 
					@ -359,6 +361,12 @@ enum TextColorConversion {
 | 
				
			||||||
    ConvertToLinear = 1,
 | 
					    ConvertToLinear = 1,
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
 | 
				
			||||||
 | 
					pub(crate) enum GlyphonCacheKey {
 | 
				
			||||||
 | 
					    Text(cosmic_text::CacheKey),
 | 
				
			||||||
 | 
					    Custom(CustomGlyphCacheKey),
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
fn next_copy_buffer_size(size: u64) -> u64 {
 | 
					fn next_copy_buffer_size(size: u64) -> u64 {
 | 
				
			||||||
    let align_mask = COPY_BUFFER_ALIGNMENT - 1;
 | 
					    let align_mask = COPY_BUFFER_ALIGNMENT - 1;
 | 
				
			||||||
    ((size.next_power_of_two() + align_mask) & !align_mask).max(COPY_BUFFER_ALIGNMENT)
 | 
					    ((size.next_power_of_two() + align_mask) & !align_mask).max(COPY_BUFFER_ALIGNMENT)
 | 
				
			||||||
| 
						 | 
					@ -385,3 +393,199 @@ fn create_oversized_buffer(
 | 
				
			||||||
fn zero_depth(_: usize) -> f32 {
 | 
					fn zero_depth(_: usize) -> f32 {
 | 
				
			||||||
    0f32
 | 
					    0f32
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					struct GetGlyphImageResult {
 | 
				
			||||||
 | 
					    content_type: ContentType,
 | 
				
			||||||
 | 
					    top: i16,
 | 
				
			||||||
 | 
					    left: i16,
 | 
				
			||||||
 | 
					    width: u16,
 | 
				
			||||||
 | 
					    height: u16,
 | 
				
			||||||
 | 
					    data: Vec<u8>,
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					fn prepare_glyph<R>(
 | 
				
			||||||
 | 
					    x: i32,
 | 
				
			||||||
 | 
					    y: i32,
 | 
				
			||||||
 | 
					    line_y: f32,
 | 
				
			||||||
 | 
					    color: Color,
 | 
				
			||||||
 | 
					    metadata: usize,
 | 
				
			||||||
 | 
					    cache_key: GlyphonCacheKey,
 | 
				
			||||||
 | 
					    atlas: &mut TextAtlas,
 | 
				
			||||||
 | 
					    device: &Device,
 | 
				
			||||||
 | 
					    queue: &Queue,
 | 
				
			||||||
 | 
					    cache: &mut SwashCache,
 | 
				
			||||||
 | 
					    font_system: &mut FontSystem,
 | 
				
			||||||
 | 
					    scale_factor: f32,
 | 
				
			||||||
 | 
					    bounds_min_x: i32,
 | 
				
			||||||
 | 
					    bounds_min_y: i32,
 | 
				
			||||||
 | 
					    bounds_max_x: i32,
 | 
				
			||||||
 | 
					    bounds_max_y: i32,
 | 
				
			||||||
 | 
					    get_glyph_image: impl FnOnce(
 | 
				
			||||||
 | 
					        &mut SwashCache,
 | 
				
			||||||
 | 
					        &mut FontSystem,
 | 
				
			||||||
 | 
					        &mut R,
 | 
				
			||||||
 | 
					    ) -> Option<GetGlyphImageResult>,
 | 
				
			||||||
 | 
					    mut metadata_to_depth: impl FnMut(usize) -> f32,
 | 
				
			||||||
 | 
					    mut rasterize_custom_glyph: R,
 | 
				
			||||||
 | 
					) -> Result<Option<GlyphToRender>, PrepareError>
 | 
				
			||||||
 | 
					where
 | 
				
			||||||
 | 
					    R: FnMut(RasterizationRequest) -> Option<RasterizedCustomGlyph>,
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    if atlas.mask_atlas.glyph_cache.contains(&cache_key) {
 | 
				
			||||||
 | 
					        atlas.mask_atlas.promote(cache_key);
 | 
				
			||||||
 | 
					    } else if atlas.color_atlas.glyph_cache.contains(&cache_key) {
 | 
				
			||||||
 | 
					        atlas.color_atlas.promote(cache_key);
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					        let Some(image) = (get_glyph_image)(cache, font_system, &mut rasterize_custom_glyph) else {
 | 
				
			||||||
 | 
					            return Ok(None);
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        let should_rasterize = image.width > 0 && image.height > 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        let (gpu_cache, atlas_id, inner) = if should_rasterize {
 | 
				
			||||||
 | 
					            let mut inner = atlas.inner_for_content_mut(image.content_type);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // Find a position in the packer
 | 
				
			||||||
 | 
					            let allocation = loop {
 | 
				
			||||||
 | 
					                match inner.try_allocate(image.width as usize, image.height as usize) {
 | 
				
			||||||
 | 
					                    Some(a) => break a,
 | 
				
			||||||
 | 
					                    None => {
 | 
				
			||||||
 | 
					                        if !atlas.grow(
 | 
				
			||||||
 | 
					                            device,
 | 
				
			||||||
 | 
					                            queue,
 | 
				
			||||||
 | 
					                            font_system,
 | 
				
			||||||
 | 
					                            cache,
 | 
				
			||||||
 | 
					                            image.content_type,
 | 
				
			||||||
 | 
					                            scale_factor,
 | 
				
			||||||
 | 
					                            &mut rasterize_custom_glyph,
 | 
				
			||||||
 | 
					                        ) {
 | 
				
			||||||
 | 
					                            return Err(PrepareError::AtlasFull);
 | 
				
			||||||
 | 
					                        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                        inner = atlas.inner_for_content_mut(image.content_type);
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            };
 | 
				
			||||||
 | 
					            let atlas_min = allocation.rectangle.min;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            queue.write_texture(
 | 
				
			||||||
 | 
					                ImageCopyTexture {
 | 
				
			||||||
 | 
					                    texture: &inner.texture,
 | 
				
			||||||
 | 
					                    mip_level: 0,
 | 
				
			||||||
 | 
					                    origin: Origin3d {
 | 
				
			||||||
 | 
					                        x: atlas_min.x as u32,
 | 
				
			||||||
 | 
					                        y: atlas_min.y as u32,
 | 
				
			||||||
 | 
					                        z: 0,
 | 
				
			||||||
 | 
					                    },
 | 
				
			||||||
 | 
					                    aspect: TextureAspect::All,
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
 | 
					                &image.data,
 | 
				
			||||||
 | 
					                ImageDataLayout {
 | 
				
			||||||
 | 
					                    offset: 0,
 | 
				
			||||||
 | 
					                    bytes_per_row: Some(image.width as u32 * inner.num_channels() as u32),
 | 
				
			||||||
 | 
					                    rows_per_image: None,
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
 | 
					                Extent3d {
 | 
				
			||||||
 | 
					                    width: image.width as u32,
 | 
				
			||||||
 | 
					                    height: image.height as u32,
 | 
				
			||||||
 | 
					                    depth_or_array_layers: 1,
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
 | 
					            );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            (
 | 
				
			||||||
 | 
					                GpuCacheStatus::InAtlas {
 | 
				
			||||||
 | 
					                    x: atlas_min.x as u16,
 | 
				
			||||||
 | 
					                    y: atlas_min.y as u16,
 | 
				
			||||||
 | 
					                    content_type: image.content_type,
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
 | 
					                Some(allocation.id),
 | 
				
			||||||
 | 
					                inner,
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					            let inner = &mut atlas.color_atlas;
 | 
				
			||||||
 | 
					            (GpuCacheStatus::SkipRasterization, None, inner)
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        inner.put(
 | 
				
			||||||
 | 
					            cache_key,
 | 
				
			||||||
 | 
					            GlyphDetails {
 | 
				
			||||||
 | 
					                width: image.width,
 | 
				
			||||||
 | 
					                height: image.height,
 | 
				
			||||||
 | 
					                gpu_cache,
 | 
				
			||||||
 | 
					                atlas_id,
 | 
				
			||||||
 | 
					                top: image.top,
 | 
				
			||||||
 | 
					                left: image.left,
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    let details = atlas.glyph(&cache_key).unwrap();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    let mut x = x + details.left as i32;
 | 
				
			||||||
 | 
					    let mut y = (line_y * scale_factor).round() as i32 + y - details.top as i32;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    let (mut atlas_x, mut atlas_y, content_type) = match details.gpu_cache {
 | 
				
			||||||
 | 
					        GpuCacheStatus::InAtlas { x, y, content_type } => (x, y, content_type),
 | 
				
			||||||
 | 
					        GpuCacheStatus::SkipRasterization => return Ok(None),
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    let mut width = details.width as i32;
 | 
				
			||||||
 | 
					    let mut height = details.height as i32;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Starts beyond right edge or ends beyond left edge
 | 
				
			||||||
 | 
					    let max_x = x + width;
 | 
				
			||||||
 | 
					    if x > bounds_max_x || max_x < bounds_min_x {
 | 
				
			||||||
 | 
					        return Ok(None);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Starts beyond bottom edge or ends beyond top edge
 | 
				
			||||||
 | 
					    let max_y = y + height;
 | 
				
			||||||
 | 
					    if y > bounds_max_y || max_y < bounds_min_y {
 | 
				
			||||||
 | 
					        return Ok(None);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Clip left ege
 | 
				
			||||||
 | 
					    if x < bounds_min_x {
 | 
				
			||||||
 | 
					        let right_shift = bounds_min_x - x;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        x = bounds_min_x;
 | 
				
			||||||
 | 
					        width = max_x - bounds_min_x;
 | 
				
			||||||
 | 
					        atlas_x += right_shift as u16;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Clip right edge
 | 
				
			||||||
 | 
					    if x + width > bounds_max_x {
 | 
				
			||||||
 | 
					        width = bounds_max_x - x;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Clip top edge
 | 
				
			||||||
 | 
					    if y < bounds_min_y {
 | 
				
			||||||
 | 
					        let bottom_shift = bounds_min_y - y;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        y = bounds_min_y;
 | 
				
			||||||
 | 
					        height = max_y - bounds_min_y;
 | 
				
			||||||
 | 
					        atlas_y += bottom_shift as u16;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Clip bottom edge
 | 
				
			||||||
 | 
					    if y + height > bounds_max_y {
 | 
				
			||||||
 | 
					        height = bounds_max_y - y;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    let depth = metadata_to_depth(metadata);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    Ok(Some(GlyphToRender {
 | 
				
			||||||
 | 
					        pos: [x, y],
 | 
				
			||||||
 | 
					        dim: [width as u16, height as u16],
 | 
				
			||||||
 | 
					        uv: [atlas_x, atlas_y],
 | 
				
			||||||
 | 
					        color: color.0,
 | 
				
			||||||
 | 
					        content_type_with_srgb: [
 | 
				
			||||||
 | 
					            content_type as u16,
 | 
				
			||||||
 | 
					            match atlas.color_mode {
 | 
				
			||||||
 | 
					                ColorMode::Accurate => TextColorConversion::ConvertToLinear,
 | 
				
			||||||
 | 
					                ColorMode::Web => TextColorConversion::None,
 | 
				
			||||||
 | 
					            } as u16,
 | 
				
			||||||
 | 
					        ],
 | 
				
			||||||
 | 
					        depth,
 | 
				
			||||||
 | 
					    }))
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue