diff --git a/Cargo.lock b/Cargo.lock index 0dc8327e..c6227b9f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -540,17 +540,6 @@ dependencies = [ "unicode-segmentation", ] -[[package]] -name = "cosmic_text" -version = "0.0.0" -dependencies = [ - "anyhow", - "cosmic-text", - "pollster", - "vello", - "winit", -] - [[package]] name = "crc32fast" version = "1.4.2" @@ -2082,10 +2071,12 @@ version = "0.0.0" dependencies = [ "anyhow", "clap", + "cosmic-text", "getrandom", "image", "rand", "roxmltree", + "unicode-segmentation", "vello", "web-time", ] diff --git a/Cargo.toml b/Cargo.toml index e63ec338..ddfa873e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,7 +11,6 @@ members = [ "examples/run_wasm", "examples/scenes", "examples/simple", - "examples/cosmic_text", "examples/simple_sdl2", ] diff --git a/examples/cosmic_text/Cargo.toml b/examples/cosmic_text/Cargo.toml deleted file mode 100644 index 5cd367fa..00000000 --- a/examples/cosmic_text/Cargo.toml +++ /dev/null @@ -1,19 +0,0 @@ -[package] -name = "cosmic_text" -edition.workspace = true -license.workspace = true -repository.workspace = true -publish = false - -[lints] -workspace = true - -# The dependencies here are independent from the workspace versions -[dependencies] -# When using this example outside of the original Vello workspace, -# remove the path property of the following Vello dependency requirement. -vello = { version = "0.3.0", path = "../../vello" } -anyhow = "1.0.89" -pollster = "0.3.0" -winit = "0.30.5" -cosmic-text = "0.12.1" \ No newline at end of file diff --git a/examples/cosmic_text/src/main.rs b/examples/cosmic_text/src/main.rs deleted file mode 100644 index 7f69efb4..00000000 --- a/examples/cosmic_text/src/main.rs +++ /dev/null @@ -1,371 +0,0 @@ -// Copyright 2024 the Vello Authors -// SPDX-License-Identifier: Apache-2.0 OR MIT - -use anyhow::Result; -use std::collections::HashMap; -use std::num::NonZeroUsize; -use std::sync::Arc; - -use cosmic_text::fontdb::ID; -use cosmic_text::{Attrs, Buffer, FontSystem, Metrics, Shaping}; - -use vello::kurbo::{Affine, Circle, Ellipse, Line, RoundedRect, Stroke, Vec2}; -use vello::peniko::{Blob, Color, Font}; -use vello::util::{RenderContext, RenderSurface}; -use vello::wgpu; -use vello::{AaConfig, Glyph, Renderer, RendererOptions, Scene}; -use winit::application::ApplicationHandler; -use winit::dpi::LogicalSize; -use winit::event::*; -use winit::event_loop::{ActiveEventLoop, EventLoop}; -use winit::window::Window; - -// Simple struct to hold the state of the renderer -pub struct ActiveRenderState<'s> { - // The fields MUST be in this order, so that the surface is dropped before the window - surface: RenderSurface<'s>, - window: Arc, -} - -enum RenderState<'s> { - Active(ActiveRenderState<'s>), - // Cache a window so that it can be reused when the app is resumed after being suspended - Suspended(Option>), -} - -struct BufferGlyphRun { - font: ID, - glyphs: Vec, - line_y: f64, -} - -struct BufferGlyphs { - font_size: f32, - glyphs: Vec, -} - -struct SimpleVelloApp<'s> { - // The vello RenderContext which is a global context that lasts for the - // lifetime of the application - context: RenderContext, - - // An array of renderers, one per wgpu device - renderers: Vec>, - - // State for our example where we store the winit Window and the wgpu Surface - state: RenderState<'s>, - - // A vello Scene which is a data structure which allows one to build up a - // description a scene to be drawn (with paths, fills, images, text, etc) - // which is then passed to a renderer for rendering - scene: Scene, - - // Copy fonts from cosmic_text so that vello can use them - vello_fonts: HashMap, - - // The glyphs to draw. - glyphs: BufferGlyphs, -} - -impl<'s> ApplicationHandler for SimpleVelloApp<'s> { - fn resumed(&mut self, event_loop: &ActiveEventLoop) { - let RenderState::Suspended(cached_window) = &mut self.state else { - return; - }; - - // Get the winit window cached in a previous Suspended event or else create a new window - let window = cached_window - .take() - .unwrap_or_else(|| create_winit_window(event_loop)); - - // Create a vello Surface - let size = window.inner_size(); - let surface_future = self.context.create_surface( - window.clone(), - size.width, - size.height, - wgpu::PresentMode::AutoVsync, - ); - let surface = pollster::block_on(surface_future).expect("Error creating surface"); - - // Create a vello Renderer for the surface (using its device id) - self.renderers - .resize_with(self.context.devices.len(), || None); - self.renderers[surface.dev_id] - .get_or_insert_with(|| create_vello_renderer(&self.context, &surface)); - - // Save the Window and Surface to a state variable - self.state = RenderState::Active(ActiveRenderState { window, surface }); - } - - fn suspended(&mut self, _event_loop: &ActiveEventLoop) { - if let RenderState::Active(state) = &self.state { - self.state = RenderState::Suspended(Some(state.window.clone())); - } - } - - fn window_event( - &mut self, - event_loop: &ActiveEventLoop, - window_id: winit::window::WindowId, - event: WindowEvent, - ) { - // Ignore the event (return from the function) if - // - we have no render_state - // - OR the window id of the event doesn't match the window id of our render_state - // - // Else extract a mutable reference to the render state from its containing option for use below - let render_state = match &mut self.state { - RenderState::Active(state) if state.window.id() == window_id => state, - _ => return, - }; - - match event { - // Exit the event loop when a close is requested (e.g. window's close button is pressed) - WindowEvent::CloseRequested => event_loop.exit(), - - // Resize the surface when the window is resized - WindowEvent::Resized(size) => { - self.context - .resize_surface(&mut render_state.surface, size.width, size.height); - } - - // This is where all the rendering happens - WindowEvent::RedrawRequested => { - // Empty the scene of objects to draw. You could create a new Scene each time, but in this case - // the same Scene is reused so that the underlying memory allocation can also be reused. - self.scene.reset(); - - // Re-add the objects to draw to the scene. - add_shapes_to_scene(&mut self.scene, &self.glyphs, &self.vello_fonts); - - // Get the RenderSurface (surface + config) - let surface = &render_state.surface; - - // Get the window size - let width = surface.config.width; - let height = surface.config.height; - - // Get a handle to the device - let device_handle = &self.context.devices[surface.dev_id]; - - // Get the surface's texture - let surface_texture = surface - .surface - .get_current_texture() - .expect("failed to get surface texture"); - - // Render to the surface's texture - self.renderers[surface.dev_id] - .as_mut() - .unwrap() - .render_to_surface( - &device_handle.device, - &device_handle.queue, - &self.scene, - &surface_texture, - &vello::RenderParams { - base_color: Color::BLACK, // Background color - width, - height, - antialiasing_method: AaConfig::Msaa16, - }, - ) - .expect("failed to render to surface"); - - // Queue the texture to be presented on the surface - surface_texture.present(); - - device_handle.device.poll(wgpu::Maintain::Poll); - } - _ => {} - } - } -} - -fn main() -> Result<()> { - let mut font_system = FontSystem::new(); - let mut vello_fonts = HashMap::new(); - - // Copy fonts from cosmic_text, so vello can use them - let font_faces: Vec<(ID, u32)> = font_system - .db() - .faces() - .map(|face| (face.id, face.index)) - .collect(); - - for (font_id, index) in font_faces { - let font = font_system.get_font(font_id).unwrap(); - let resource = Arc::new(font.data().to_vec()); - let font_blob = Blob::new(resource); - let vello_font = Font::new(font_blob, index); - vello_fonts.insert(font_id, vello_font); - } - - let text = "おはよう (ja) (ohayō) 🌅✨ (morning), こんにちは (ja) (konnichi wa) ☀️😊 (daytime), こんばんは (ja) (konban wa) 🌙🌟 (evening)"; - - // Text metrics indicate the font size and line height of a buffer - const FONT_SIZE: f32 = 24.0; - const LINE_HEIGHT: f32 = FONT_SIZE * 1.2; - let metrics = Metrics::new(FONT_SIZE, LINE_HEIGHT); - - // A Buffer provides shaping and layout for a UTF-8 string, create one per text widget - let mut buffer = Buffer::new(&mut font_system, metrics); - - // Set a size for the text buffer, in pixels - let width = 200.0; - // The height is unbounded - buffer.set_size(&mut font_system, Some(width), None); - - // Attributes indicate what font to choose - let attrs = Attrs::new(); - - // Ensure advanced shaping is enabled for complex scripts - buffer.set_text(&mut font_system, text, attrs, Shaping::Advanced); - - // Perform shaping as desired - buffer.shape_until_scroll(&mut font_system, true); - - // Setup a bunch of state: - let mut app = SimpleVelloApp { - context: RenderContext::new(), - renderers: vec![], - state: RenderState::Suspended(None), - scene: Scene::new(), - vello_fonts, - glyphs: create_glyphs(&buffer), - }; - - // Create and run a winit event loop - let event_loop = EventLoop::new()?; - event_loop - .run_app(&mut app) - .expect("Couldn't run event loop"); - Ok(()) -} - -/// Helper function that creates a Winit window and returns it (wrapped in an Arc for sharing between threads) -fn create_winit_window(event_loop: &ActiveEventLoop) -> Arc { - let attr = Window::default_attributes() - .with_inner_size(LogicalSize::new(1044, 800)) - .with_resizable(true) - .with_title("Vello Shapes"); - Arc::new(event_loop.create_window(attr).unwrap()) -} - -/// Helper function that creates a vello `Renderer` for a given `RenderContext` and `RenderSurface` -fn create_vello_renderer(render_cx: &RenderContext, surface: &RenderSurface) -> Renderer { - Renderer::new( - &render_cx.devices[surface.dev_id].device, - RendererOptions { - surface_format: Some(surface.format), - use_cpu: false, - antialiasing_support: vello::AaSupport::all(), - num_init_threads: NonZeroUsize::new(1), - }, - ) - .expect("Couldn't create renderer") -} - -/// Add shapes to a vello scene. This does not actually render the shapes, but adds them -/// to the Scene data structure which represents a set of objects to draw. -fn add_shapes_to_scene( - scene: &mut Scene, - all_glyphs: &BufferGlyphs, - vello_fonts: &HashMap, -) { - // Draw an outlined rectangle - let stroke = Stroke::new(6.0); - let rect = RoundedRect::new(10.0, 10.0, 240.0, 240.0, 20.0); - let rect_stroke_color = Color::rgb(0.9804, 0.702, 0.5294); - scene.stroke(&stroke, Affine::IDENTITY, rect_stroke_color, None, &rect); - - // Draw a filled circle - let circle = Circle::new((420.0, 200.0), 120.0); - let circle_fill_color = Color::rgb(0.9529, 0.5451, 0.6588); - scene.fill( - vello::peniko::Fill::NonZero, - Affine::IDENTITY, - circle_fill_color, - None, - &circle, - ); - - // Draw a filled ellipse - let ellipse = Ellipse::new((250.0, 420.0), (100.0, 160.0), -90.0); - let ellipse_fill_color = Color::rgb(0.7961, 0.651, 0.9686); - scene.fill( - vello::peniko::Fill::NonZero, - Affine::IDENTITY, - ellipse_fill_color, - None, - &ellipse, - ); - - // Draw a straight line - let line = Line::new((260.0, 20.0), (620.0, 100.0)); - let line_stroke_color = Color::rgb(0.5373, 0.7059, 0.9804); - scene.stroke(&stroke, Affine::IDENTITY, line_stroke_color, None, &line); - - const TEXT_COLOR: Color = Color::WHITE; - let text_transform = Affine::translate((500.0, 300.0)); - - // Draw the Glyphs - for glyph_run in all_glyphs.glyphs.iter() { - let font = vello_fonts.get(&glyph_run.font).unwrap(); - let glyphs = glyph_run.glyphs.clone(); - scene - .draw_glyphs(font) - .font_size(all_glyphs.font_size) - .brush(TEXT_COLOR) - .transform(text_transform.then_translate(Vec2::new(0.0, glyph_run.line_y))) - .draw(vello::peniko::Fill::NonZero, glyphs.into_iter()); - } -} - -fn create_glyphs(buffer: &Buffer) -> BufferGlyphs { - // Get the laid out glyphs and convert them to Glyphs for vello - - let mut last_font = None; - - let mut buffer_glyphs = BufferGlyphs { - font_size: buffer.metrics().font_size, - glyphs: vec![], - }; - - let mut current_glyphs: Vec = vec![]; - - for layout_run in buffer.layout_runs() { - let line_y = layout_run.line_y as f64; - - for glyph in layout_run.glyphs { - if let Some(last_font) = last_font { - if last_font != glyph.font_id { - buffer_glyphs.glyphs.push(BufferGlyphRun { - font: last_font, - glyphs: current_glyphs, - line_y, - }); - current_glyphs = vec![]; - } - } - - last_font = Some(glyph.font_id); - current_glyphs.push(Glyph { - x: glyph.x, - y: glyph.y, - id: glyph.glyph_id as u32, - }); - } - if !current_glyphs.is_empty() { - buffer_glyphs.glyphs.push(BufferGlyphRun { - font: last_font.unwrap(), - glyphs: current_glyphs, - line_y, - }); - current_glyphs = vec![]; - } - } - - buffer_glyphs -} diff --git a/examples/scenes/Cargo.toml b/examples/scenes/Cargo.toml index 28ca944a..ea27fe1f 100644 --- a/examples/scenes/Cargo.toml +++ b/examples/scenes/Cargo.toml @@ -15,9 +15,11 @@ anyhow = { workspace = true } clap = { workspace = true, features = ["derive"] } image = { workspace = true, features = ["jpeg"] } rand = "0.8.5" +cosmic-text = { version = "0.12.1", optional = true } # for pico_svg roxmltree = "0.20.0" +unicode-segmentation = { version = "1.12.0", optional = true } [target.'cfg(target_arch = "wasm32")'.dependencies] web-time = { workspace = true } @@ -26,3 +28,6 @@ web-time = { workspace = true } # We have a transitive dependency on getrandom and it does not automatically # support wasm32-unknown-unknown. We need to enable the js feature. getrandom = { version = "0.2.15", features = ["js"] } + +[features] +cosmic_text = ["dep:cosmic-text", "dep:unicode-segmentation"] \ No newline at end of file diff --git a/examples/scenes/src/lib.rs b/examples/scenes/src/lib.rs index c6c95166..7cc349e3 100644 --- a/examples/scenes/src/lib.rs +++ b/examples/scenes/src/lib.rs @@ -11,6 +11,9 @@ mod simple_text; mod svg; pub mod test_scenes; +#[cfg(feature = "cosmic_text")] +pub mod cosmic_text_scene; + use anyhow::{anyhow, Result}; use clap::Args; pub use images::ImageCache; @@ -23,6 +26,9 @@ use vello::kurbo::Vec2; use vello::peniko::Color; use vello::Scene; +#[cfg(feature = "cosmic_text")] +use crate::cosmic_text_scene::CosmicTextSceneState; + pub struct SceneParams<'a> { pub time: f64, /// Whether blocking should be limited @@ -34,6 +40,8 @@ pub struct SceneParams<'a> { pub resolution: Option, pub base_color: Option, pub complexity: usize, + #[cfg(feature = "cosmic_text")] + pub cosmic_text_scene_state: &'a CosmicTextSceneState, } pub struct SceneConfig { diff --git a/examples/scenes/src/test_scenes.rs b/examples/scenes/src/test_scenes.rs index 89c131d6..bd790303 100644 --- a/examples/scenes/src/test_scenes.rs +++ b/examples/scenes/src/test_scenes.rs @@ -13,19 +13,27 @@ pub fn test_scenes() -> SceneSet { /// /// This is used to avoid having to repeatedly define a macro_rules! export_scenes { - ($($scene_name: ident($($scene: tt)+)),*$(,)?) => { + ($( + $(#[cfg($feature:meta)])? // Optional feature gate + $scene_name:ident($($scene:tt)+) + ),*$(,)?) => { pub fn test_scenes_inner() -> SceneSet { - let scenes = vec![ - $($scene_name()),+ - ]; + let mut scenes = Vec::new(); + $( + $(#[cfg($feature)])? + { + scenes.push($scene_name()); + } + )* SceneSet { scenes } } $( + $(#[cfg($feature)])? pub fn $scene_name() -> ExampleScene { scene!($($scene)+) } - )+ + )* }; } @@ -53,6 +61,7 @@ macro_rules! scene { } export_scenes!( + #[cfg(feature = "cosmic_text")] cosmic_text_scene(cosmic_text), splash_with_tiger(impls::splash_with_tiger(), "splash_with_tiger", false), funky_paths(funky_paths), stroke_styles(impls::stroke_styles(Affine::IDENTITY), "stroke_styles", false), @@ -101,6 +110,9 @@ mod impls { const FLOWER_IMAGE: &[u8] = include_bytes!("../../assets/splash-flower.jpg"); + #[cfg(feature = "cosmic_text")] + pub(super) use crate::cosmic_text_scene::cosmic_text; + pub(super) fn emoji(scene: &mut Scene, params: &mut SceneParams) { let text_size = 120. + 20. * (params.time * 2.).sin() as f32; let s = "🎉🤠✅"; diff --git a/examples/with_winit/Cargo.toml b/examples/with_winit/Cargo.toml index 34d4c886..61ed7449 100644 --- a/examples/with_winit/Cargo.toml +++ b/examples/with_winit/Cargo.toml @@ -18,6 +18,7 @@ default = ["wgpu-profiler"] wgpu-profiler = ["dep:wgpu-profiler", "vello/wgpu-profiler"] # Test for dependencies which implement std traits in ways that cause type inference issues. _ci_dep_features_to_test = ["dep:kurbo", "kurbo/schemars"] +cosmic_text = ["scenes/cosmic_text"] [lints] workspace = true diff --git a/examples/with_winit/src/lib.rs b/examples/with_winit/src/lib.rs index f13bbd65..0f08c573 100644 --- a/examples/with_winit/src/lib.rs +++ b/examples/with_winit/src/lib.rs @@ -29,6 +29,9 @@ use vello::peniko::Color; use vello::util::{RenderContext, RenderSurface}; use vello::{low_level::BumpAllocators, AaConfig, Renderer, RendererOptions, Scene}; +#[cfg(feature = "cosmic_text")] +use scenes::cosmic_text_scene::CosmicTextSceneState; + use winit::dpi::LogicalSize; use winit::event_loop::EventLoop; use winit::window::{Window, WindowAttributes}; @@ -164,6 +167,9 @@ struct VelloApp<'s> { modifiers: ModifiersState, debug: DebugLayers, + + #[cfg(feature = "cosmic_text")] + cosmic_text_scene_state: CosmicTextSceneState, } impl<'s> ApplicationHandler for VelloApp<'s> { @@ -470,6 +476,8 @@ impl<'s> ApplicationHandler for VelloApp<'s> { base_color: None, interactive: true, complexity: self.complexity, + #[cfg(feature = "cosmic_text")] + cosmic_text_scene_state: &self.cosmic_text_scene_state, }; example_scene .function @@ -746,6 +754,8 @@ fn run( prev_scene_ix: 0, modifiers: ModifiersState::default(), debug, + #[cfg(feature = "cosmic_text")] + cosmic_text_scene_state: CosmicTextSceneState::new(), }; event_loop.run_app(&mut app).expect("run to completion");