├── .gitignore ├── screenshots ├── draw.png ├── clock.png ├── cutout.png └── square.png ├── nvg-gl ├── examples │ ├── lenna.png │ ├── Roboto-Bold.ttf │ ├── demo-text.rs │ ├── demo-square.rs │ ├── demo-draw.rs │ ├── demo │ │ └── mod.rs │ ├── demo-clock.rs │ └── demo-cutout.rs ├── src │ ├── shader.vert │ ├── shader.frag │ └── lib.rs └── Cargo.toml ├── src ├── lib.rs ├── renderer.rs ├── color.rs ├── math.rs ├── fonts.rs ├── cache.rs └── context.rs ├── Cargo.toml └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | **/*.rs.bk 3 | Cargo.lock 4 | .idea 5 | .DS_Store 6 | **/*.*~ 7 | -------------------------------------------------------------------------------- /screenshots/draw.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sunli829/nvg/HEAD/screenshots/draw.png -------------------------------------------------------------------------------- /screenshots/clock.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sunli829/nvg/HEAD/screenshots/clock.png -------------------------------------------------------------------------------- /screenshots/cutout.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sunli829/nvg/HEAD/screenshots/cutout.png -------------------------------------------------------------------------------- /screenshots/square.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sunli829/nvg/HEAD/screenshots/square.png -------------------------------------------------------------------------------- /nvg-gl/examples/lenna.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sunli829/nvg/HEAD/nvg-gl/examples/lenna.png -------------------------------------------------------------------------------- /nvg-gl/examples/Roboto-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sunli829/nvg/HEAD/nvg-gl/examples/Roboto-Bold.ttf -------------------------------------------------------------------------------- /nvg-gl/src/shader.vert: -------------------------------------------------------------------------------- 1 | #version 150 core 2 | 3 | uniform vec2 viewSize; 4 | in vec2 vertex; 5 | in vec2 tcoord; 6 | out vec2 ftcoord; 7 | out vec2 fpos; 8 | 9 | void main(void) { 10 | ftcoord = tcoord; 11 | fpos = vertex; 12 | gl_Position = vec4(2.0 * vertex.x / viewSize.x - 1.0, 1.0 - 2.0 * vertex.y / viewSize.y, 0, 1); 13 | } 14 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | #[macro_use] 2 | extern crate bitflags; 3 | 4 | mod cache; 5 | mod color; 6 | mod context; 7 | mod fonts; 8 | mod math; 9 | pub mod renderer; 10 | 11 | pub use color::*; 12 | pub use context::{ 13 | Align, BasicCompositeOperation, BlendFactor, CompositeOperation, Context, Gradient, ImageFlags, 14 | ImageId, ImagePattern, LineCap, LineJoin, Paint, Solidity, TextMetrics, 15 | }; 16 | pub use fonts::FontId; 17 | pub use math::*; 18 | pub use renderer::Renderer; 19 | -------------------------------------------------------------------------------- /nvg-gl/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "nvg-gl" 3 | version = "0.5.4" 4 | authors = ["Sunli "] 5 | edition = "2018" 6 | keywords = ["nanovg", "opengl"] 7 | description = "NVG Renderer for OpenGL" 8 | license = "MIT" 9 | publish = true 10 | homepage = "https://github.com/sunli829/nvg" 11 | repository = "https://github.com/sunli829/nvg" 12 | 13 | [dependencies] 14 | nvg = { version = "0.5.7", path = "../" } 15 | slab = "0.4.2" 16 | gl = "0.14.0" 17 | anyhow = "1.0.26" 18 | 19 | [dev-dependencies] 20 | glutin = "0.25.1" 21 | chrono = "0.4.10" 22 | rand = "0.7.2" 23 | lazy_static = "1.4.0" 24 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "nvg" 3 | version = "0.5.11" 4 | authors = ["Sunli "] 5 | edition = "2018" 6 | keywords = ["nanovg", "opengl"] 7 | description = "Pure-rust implementation of NanoVG" 8 | license = "MIT" 9 | publish = true 10 | homepage = "https://github.com/sunli829/nvg" 11 | repository = "https://github.com/sunli829/nvg" 12 | 13 | [dependencies] 14 | bitflags = "1.2.1" 15 | image = "0.22.3" 16 | anyhow = "1.0.26" 17 | rusttype = { version = "0.8.1", features = ["gpu_cache"] } 18 | slab = "0.4.2" 19 | num-traits = "0.2.8" 20 | clamped = "1.0.0" 21 | rawpointer = "0.2.1" 22 | 23 | [workspace] 24 | members = [ 25 | "nvg-gl" 26 | ] -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Pure-rust implementation of NanoVG 2 | 3 | ## Demos 4 | 5 | 6 | 7 | 18 | 19 | 28 | 29 | 38 | 39 | 48 |

Simple square

8 | 9 | ``` 10 | cargo run -p nvg-gl --example demo-square 11 | ``` 12 | 13 | The tiniest way to use nvg+glutin, can help beginner to start with nvg. 14 | 15 |
16 | 17 |

Clock

20 | 21 | ``` 22 | cargo run -p nvg-gl --example demo-clock 23 | ``` 24 | 25 |
26 | 27 |

Cutout

30 | 31 | ``` 32 | cargo run -p nvg-gl --example demo-cutout 33 | ``` 34 | 35 |
36 | 37 |

Draw

40 | 41 | ``` 42 | cargo run -p nvg-gl --example demo-draw 43 | ``` 44 | 45 |
46 | 47 |
49 | -------------------------------------------------------------------------------- /nvg-gl/examples/demo-text.rs: -------------------------------------------------------------------------------- 1 | use nvg::*; 2 | 3 | mod demo; 4 | 5 | struct DemoText; 6 | 7 | impl demo::Demo for DemoText { 8 | fn update(&mut self, _width: f32, _height: f32, ctx: &mut Context) -> anyhow::Result<()> { 9 | ctx.begin_path(); 10 | ctx.move_to((150, 20)); 11 | ctx.line_to((150, 170)); 12 | ctx.stroke_paint((1.0, 0.0, 0.0)); 13 | ctx.stroke()?; 14 | 15 | ctx.font_size(16.0); 16 | ctx.fill_paint((1.0, 1.0, 0.0)); 17 | 18 | // horz align 19 | ctx.text_align(nvg::Align::LEFT); 20 | ctx.text((150, 60), "left")?; 21 | 22 | ctx.text_align(nvg::Align::CENTER); 23 | ctx.text((150, 80), "center")?; 24 | 25 | ctx.text_align(nvg::Align::RIGHT); 26 | ctx.text((150, 100), "right")?; 27 | 28 | // vert align 29 | ctx.begin_path(); 30 | ctx.move_to((5, 270)); 31 | ctx.line_to((300, 270)); 32 | ctx.stroke_paint((1.0, 0.0, 0.0)); 33 | ctx.stroke()?; 34 | 35 | ctx.text_align(nvg::Align::TOP); 36 | ctx.text((5, 270), "top")?; 37 | 38 | ctx.text_align(nvg::Align::MIDDLE); 39 | ctx.text((50, 270), "middle")?; 40 | 41 | ctx.text_align(nvg::Align::BOTTOM); 42 | ctx.text((120, 270), "bottom")?; 43 | 44 | ctx.text_align(nvg::Align::BASELINE); 45 | ctx.text((200, 270), "baseline")?; 46 | 47 | // spaces 48 | ctx.text((200, 300), "a b c d")?; 49 | 50 | Ok(()) 51 | } 52 | } 53 | 54 | fn main() { 55 | demo::run(DemoText).unwrap(); 56 | } 57 | -------------------------------------------------------------------------------- /src/renderer.rs: -------------------------------------------------------------------------------- 1 | pub use crate::context::{CompositeOperationState, ImageId, Path, Vertex}; 2 | pub use crate::*; 3 | 4 | #[derive(Debug, Copy, Clone)] 5 | pub enum TextureType { 6 | RGBA, 7 | Alpha, 8 | } 9 | 10 | #[derive(Debug, Copy, Clone)] 11 | pub struct Scissor { 12 | pub xform: Transform, 13 | pub extent: Extent, 14 | } 15 | 16 | pub trait Renderer { 17 | fn edge_antialias(&self) -> bool; 18 | 19 | fn create_texture( 20 | &mut self, 21 | texture_type: TextureType, 22 | width: usize, 23 | height: usize, 24 | flags: ImageFlags, 25 | data: Option<&[u8]>, 26 | ) -> anyhow::Result; 27 | 28 | fn delete_texture(&mut self, img: ImageId) -> anyhow::Result<()>; 29 | 30 | fn update_texture( 31 | &mut self, 32 | img: ImageId, 33 | x: usize, 34 | y: usize, 35 | width: usize, 36 | height: usize, 37 | data: &[u8], 38 | ) -> anyhow::Result<()>; 39 | 40 | fn texture_size(&self, img: ImageId) -> anyhow::Result<(usize, usize)>; 41 | 42 | fn viewport(&mut self, extent: Extent, device_pixel_ratio: f32) -> anyhow::Result<()>; 43 | 44 | fn cancel(&mut self) -> anyhow::Result<()>; 45 | 46 | fn flush(&mut self) -> anyhow::Result<()>; 47 | 48 | fn fill( 49 | &mut self, 50 | paint: &Paint, 51 | composite_operation: CompositeOperationState, 52 | scissor: &Scissor, 53 | fringe: f32, 54 | bounds: Bounds, 55 | paths: &[Path], 56 | ) -> anyhow::Result<()>; 57 | 58 | fn stroke( 59 | &mut self, 60 | paint: &Paint, 61 | composite_operation: CompositeOperationState, 62 | scissor: &Scissor, 63 | fringe: f32, 64 | stroke_width: f32, 65 | paths: &[Path], 66 | ) -> anyhow::Result<()>; 67 | 68 | fn triangles( 69 | &mut self, 70 | paint: &Paint, 71 | composite_operation: CompositeOperationState, 72 | scissor: &Scissor, 73 | vertexes: &[Vertex], 74 | ) -> anyhow::Result<()>; 75 | } 76 | -------------------------------------------------------------------------------- /nvg-gl/src/shader.frag: -------------------------------------------------------------------------------- 1 | #version 150 core 2 | 3 | precision highp float; 4 | 5 | layout(std140) uniform frag { 6 | mat3 scissorMat; 7 | mat3 paintMat; 8 | vec4 innerCol; 9 | vec4 outerCol; 10 | vec2 scissorExt; 11 | vec2 scissorScale; 12 | vec2 extent; 13 | float radius; 14 | float feather; 15 | float strokeMult; 16 | float strokeThr; 17 | int texType; 18 | int type; 19 | }; 20 | 21 | uniform sampler2D tex; 22 | in vec2 ftcoord; 23 | in vec2 fpos; 24 | out vec4 outColor; 25 | 26 | float sdroundrect(vec2 pt, vec2 ext, float rad) { 27 | vec2 ext2 = ext - vec2(rad,rad); 28 | vec2 d = abs(pt) - ext2; 29 | return min(max(d.x, d.y), 0.0) + length(max(d, 0.0)) - rad; 30 | } 31 | 32 | float scissorMask(vec2 p) { 33 | vec2 sc = (abs((scissorMat * vec3(p, 1.0)).xy) - scissorExt); 34 | sc = vec2(0.5,0.5) - sc * scissorScale; 35 | return clamp(sc.x, 0.0, 1.0) * clamp(sc.y, 0.0, 1.0); 36 | } 37 | 38 | float strokeMask() { 39 | return min(1.0, (1.0 - abs(ftcoord.x * 2.0 - 1.0)) * strokeMult) * min(1.0, ftcoord.y); 40 | } 41 | 42 | void main(void) { 43 | vec4 result; 44 | float scissor = scissorMask(fpos); 45 | float strokeAlpha = strokeMask(); 46 | if (strokeAlpha < strokeThr) discard; 47 | 48 | if (type == 0) { 49 | // Gradient 50 | vec2 pt = (paintMat * vec3(fpos,1.0)).xy; 51 | float d = clamp((sdroundrect(pt, extent, radius) + feather * 0.5) / feather, 0.0, 1.0); 52 | vec4 color = mix(innerCol, outerCol, d); 53 | color *= strokeAlpha * scissor; 54 | result = color; 55 | } else if (type == 1) { 56 | // Image 57 | vec2 pt = (paintMat * vec3(fpos, 1.0)).xy / extent; 58 | vec4 color = texture(tex, pt); 59 | if (texType == 1) color = vec4(color.xyz * color.w, color.w); 60 | if (texType == 2) color = vec4(color.x); 61 | color *= innerCol; 62 | color *= strokeAlpha * scissor; 63 | result = color; 64 | } else if (type == 2) { 65 | // Stencil fill 66 | result = vec4(1, 1, 1, 1); 67 | } else if (type == 3) { 68 | // Textured tris 69 | vec4 color = texture(tex, ftcoord); 70 | if (texType == 1) color = vec4(color.xyz * color.w, color.w); 71 | if (texType == 2) color = vec4(color.x); 72 | color *= scissor; 73 | result = color * innerCol; 74 | } 75 | 76 | outColor = result; 77 | } 78 | -------------------------------------------------------------------------------- /nvg-gl/examples/demo-square.rs: -------------------------------------------------------------------------------- 1 | use gl; 2 | use glutin::ContextBuilder; 3 | use glutin::dpi::LogicalSize; 4 | use glutin::event::{Event, StartCause, WindowEvent}; 5 | use glutin::event_loop::{ControlFlow, EventLoop}; 6 | use glutin::window::WindowBuilder; 7 | use nvg; 8 | use nvg_gl; 9 | 10 | fn main() { 11 | let el = EventLoop::new(); 12 | let wb = WindowBuilder::new() 13 | .with_title("nvg - demo-square") 14 | .with_inner_size(LogicalSize::new(800.0, 600.0)); 15 | let wc = ContextBuilder::new().build_windowed(wb, &el).unwrap(); 16 | let wc = unsafe { wc.make_current().unwrap() }; 17 | gl::load_with(|p| wc.get_proc_address(p) as *const _); 18 | let renderer = nvg_gl::Renderer::create().unwrap(); 19 | let mut nvg_ctx = nvg::Context::create(renderer).unwrap(); 20 | el.run(move |evt, _, ctrl_flow| { 21 | match evt { 22 | Event::NewEvents(StartCause::Init) => 23 | *ctrl_flow = ControlFlow::Wait, 24 | Event::LoopDestroyed => return, 25 | Event::WindowEvent {event, ..} => match event { 26 | WindowEvent::CloseRequested => *ctrl_flow = ControlFlow::Exit, 27 | _ => () 28 | } 29 | Event::RedrawRequested(_) => { 30 | let size = wc.window().inner_size(); 31 | let sf = wc.window().scale_factor(); 32 | unsafe { 33 | gl::Viewport(0, 0, size.width as i32, size.height as i32); 34 | gl::ClearColor(0.0, 0.0, 0.0, 1.0); 35 | gl::Clear( 36 | gl::COLOR_BUFFER_BIT | 37 | gl::DEPTH_BUFFER_BIT | 38 | gl::STENCIL_BUFFER_BIT 39 | ); 40 | } 41 | nvg_ctx.begin_frame( 42 | nvg::Extent { 43 | width: size.width as f32, 44 | height: size.height as f32 45 | }, 46 | sf as f32 47 | ).unwrap(); 48 | nvg_ctx.save(); 49 | nvg_ctx.fill_paint(nvg::Color::rgb(1.0, 0.0, 0.0)); 50 | nvg_ctx.rect(nvg::Rect::new( 51 | nvg::Point::new(10.0, 10.0), 52 | nvg::Extent::new(40.0, 40.0) 53 | )); 54 | nvg_ctx.fill().unwrap(); 55 | nvg_ctx.restore(); 56 | nvg_ctx.end_frame().unwrap(); 57 | wc.swap_buffers().unwrap(); 58 | } 59 | _ => () 60 | } 61 | }); 62 | } 63 | -------------------------------------------------------------------------------- /src/color.rs: -------------------------------------------------------------------------------- 1 | use clamped::Clamp; 2 | use num_traits::AsPrimitive; 3 | use std::ops::Rem; 4 | 5 | #[derive(Debug, Copy, Clone, Default)] 6 | pub struct Color { 7 | pub r: f32, 8 | pub g: f32, 9 | pub b: f32, 10 | pub a: f32, 11 | } 12 | 13 | impl Color { 14 | pub fn rgba(r: f32, g: f32, b: f32, a: f32) -> Color { 15 | Color { r, g, b, a } 16 | } 17 | 18 | pub fn rgb(r: f32, g: f32, b: f32) -> Color { 19 | Color { r, g, b, a: 1.0 } 20 | } 21 | 22 | pub fn rgba_i(r: u8, g: u8, b: u8, a: u8) -> Color { 23 | Color { 24 | r: r as f32 / 255.0, 25 | g: g as f32 / 255.0, 26 | b: b as f32 / 255.0, 27 | a: a as f32 / 255.0, 28 | } 29 | } 30 | 31 | pub fn rgb_i(r: u8, g: u8, b: u8) -> Color { 32 | Self::rgba_i(r, g, b, 255) 33 | } 34 | 35 | pub fn lerp(self, c: Color, u: f32) -> Color { 36 | let u = u.clamped(0.0, 1.0); 37 | let om = 1.0 - u; 38 | Color { 39 | r: self.r * om + c.r * u, 40 | g: self.g * om + c.g * u, 41 | b: self.b * om + c.b * u, 42 | a: self.a * om + c.a * u, 43 | } 44 | } 45 | 46 | pub fn hsla(h: f32, s: f32, l: f32, a: f32) -> Color { 47 | let mut h = h.rem(1.0); 48 | if h < 0.0 { 49 | h += 1.0; 50 | } 51 | let s = s.clamped(0.0, 1.0); 52 | let l = l.clamped(0.0, 1.0); 53 | let m2 = if l <= 0.5 { 54 | l * (1.0 + s) 55 | } else { 56 | l + s - l * s 57 | }; 58 | let m1 = 2.0 * l - m2; 59 | Color { 60 | r: hue(h + 1.0 / 3.0, m1, m2).clamped(0.0, 1.0), 61 | g: hue(h, m1, m2).clamped(0.0, 1.0), 62 | b: hue(h - 1.0 / 3.0, m1, m2).clamped(0.0, 1.0), 63 | a, 64 | } 65 | } 66 | 67 | pub fn hsl(h: f32, s: f32, l: f32) -> Color { 68 | Self::hsla(h, s, l, 1.0) 69 | } 70 | } 71 | 72 | impl> From<(T, T, T)> for Color { 73 | fn from((r, g, b): (T, T, T)) -> Self { 74 | Color::rgb(r.as_(), g.as_(), b.as_()) 75 | } 76 | } 77 | 78 | impl> From<(T, T, T, T)> for Color { 79 | fn from((r, g, b, a): (T, T, T, T)) -> Self { 80 | Color::rgba(r.as_(), g.as_(), b.as_(), a.as_()) 81 | } 82 | } 83 | 84 | fn hue(mut h: f32, m1: f32, m2: f32) -> f32 { 85 | if h < 0.0 { 86 | h += 1.0; 87 | } 88 | if h > 1.0 { 89 | h -= 1.0 90 | }; 91 | if h < 1.0 / 6.0 { 92 | return m1 + (m2 - m1) * h * 6.0; 93 | } else if h < 3.0 / 6.0 { 94 | m2 95 | } else if h < 4.0 / 6.0 { 96 | m1 + (m2 - m1) * (2.0 / 3.0 - h) * 6.0 97 | } else { 98 | m1 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /nvg-gl/examples/demo-draw.rs: -------------------------------------------------------------------------------- 1 | use anyhow::Error; 2 | use nvg::*; 3 | use std::f32::consts::PI; 4 | use std::time::Instant; 5 | 6 | mod demo; 7 | 8 | struct DemoDraw { 9 | img: Option, 10 | start_time: Instant, 11 | } 12 | 13 | impl demo::Demo for DemoDraw { 14 | fn init(&mut self, ctx: &mut Context) -> Result<(), Error> { 15 | ctx.create_font_from_file("roboto", "nvg-gl/examples/Roboto-Bold.ttf")?; 16 | self.img = Some(ctx.create_image_from_file( 17 | ImageFlags::REPEATX | ImageFlags::REPEATY, 18 | "nvg-gl/examples/lenna.png", 19 | )?); 20 | Ok(()) 21 | } 22 | 23 | fn update(&mut self, _width: f32, _height: f32, ctx: &mut Context) -> anyhow::Result<()> { 24 | let elapsed = self.start_time.elapsed().as_secs_f32(); 25 | 26 | ctx.begin_path(); 27 | ctx.rect((100.0, 100.0, 300.0, 300.0)); 28 | ctx.fill_paint(Gradient::Linear { 29 | start: (100, 100).into(), 30 | end: (400, 400).into(), 31 | start_color: Color::rgb_i(0xAA, 0x6C, 0x39), 32 | end_color: Color::rgb_i(0x88, 0x2D, 0x60), 33 | }); 34 | ctx.fill()?; 35 | 36 | ctx.save(); 37 | ctx.global_composite_operation(CompositeOperation::Basic(BasicCompositeOperation::Lighter)); 38 | let origin = (150.0, 140.0); 39 | ctx.begin_path(); 40 | ctx.circle(origin, 64.0); 41 | ctx.move_to(origin); 42 | ctx.line_join(LineJoin::Round); 43 | ctx.line_to((origin.0 + 300.0, origin.1 - 50.0)); 44 | ctx.quad_to((300.0, 100.0), (origin.0 + 500.0, origin.1 + 100.0)); 45 | ctx.close_path(); 46 | ctx.fill_paint(Color::rgba(0.2, 0.0, 0.8, 1.0)); 47 | ctx.fill()?; 48 | ctx.stroke_paint(Color::rgba(1.0, 1.0, 0.0, 1.0)); 49 | ctx.stroke_width(3.0); 50 | ctx.stroke()?; 51 | ctx.restore(); 52 | 53 | ctx.begin_path(); 54 | let radius = 100.0; 55 | let distance = 500.0; // Distance to roll 56 | let rolled = ((elapsed / 5.0).sin() * 0.5 + 0.5) * distance; // Distance currently rolled 57 | let origin = (rolled + 100.0, 600.0); 58 | ctx.fill_paint({ 59 | ImagePattern { 60 | img: self.img.unwrap(), 61 | center: origin.into(), 62 | size: (100.0, 100.0).into(), 63 | angle: rolled / (2.0 * PI * radius) * 2.0 * PI, 64 | alpha: 1.0, 65 | } 66 | }); 67 | ctx.scissor((150, 600, 1000, 200)); 68 | ctx.circle(origin, radius); 69 | ctx.fill()?; 70 | 71 | ctx.reset_scissor(); 72 | 73 | ctx.begin_path(); 74 | ctx.rect((300.0, 310.0, 300.0, 300.0)); 75 | let color = Color::lerp( 76 | Color::rgb_i(0x2e, 0x50, 0x77), 77 | Color::rgb_i(0xff, 0xca, 0x77), 78 | elapsed.sin() * 0.5 + 0.5, 79 | ); 80 | ctx.fill_paint(Color::rgba(0.2, 0.2, 0.2, 0.7)); 81 | ctx.fill()?; 82 | ctx.stroke_paint(color); 83 | ctx.stroke_width(5.0); 84 | ctx.stroke()?; 85 | 86 | Ok(()) 87 | } 88 | } 89 | 90 | fn main() { 91 | demo::run(DemoDraw { 92 | img: None, 93 | start_time: Instant::now(), 94 | }, "demo-draw"); 95 | } 96 | -------------------------------------------------------------------------------- /nvg-gl/examples/demo/mod.rs: -------------------------------------------------------------------------------- 1 | use glutin::event::{Event, WindowEvent}; 2 | use glutin::event_loop::{ControlFlow, EventLoop}; 3 | use nvg::{Align, Color, Context, Renderer}; 4 | use std::time::Instant; 5 | 6 | pub trait Demo { 7 | fn init(&mut self, ctx: &mut Context) -> anyhow::Result<()> { 8 | ctx.create_font_from_file("roboto", "nvg-gl/examples/Roboto-Bold.ttf")?; 9 | Ok(()) 10 | } 11 | 12 | fn update( 13 | &mut self, 14 | _width: f32, 15 | _height: f32, 16 | _ctx: &mut Context 17 | ) -> anyhow::Result<()> { 18 | Ok(()) 19 | } 20 | 21 | fn cursor_moved(&mut self, _x: f32, _y: f32) {} 22 | } 23 | 24 | pub fn run + 'static>( 25 | mut demo: D, 26 | title: &str 27 | ) { 28 | let el = EventLoop::new(); 29 | let wb = glutin::window::WindowBuilder::new() 30 | .with_title(format!("nvg - {}", title)) 31 | .with_inner_size(glutin::dpi::LogicalSize::new(1024.0, 768.0)); 32 | let windowed_context = glutin::ContextBuilder::new() 33 | .build_windowed(wb, &el).unwrap(); 34 | let windowed_context = unsafe { windowed_context.make_current().unwrap() }; 35 | gl::load_with(|p| windowed_context.get_proc_address(p) as *const _); 36 | 37 | let mut window_size = windowed_context.window().inner_size(); 38 | let scale_factor = windowed_context.window().scale_factor(); 39 | 40 | let renderer = nvg_gl::Renderer::create().unwrap(); 41 | let mut context = nvg::Context::create(renderer).unwrap(); 42 | 43 | demo.init(&mut context).unwrap(); 44 | 45 | let mut total_frames = 0; 46 | let start_time = Instant::now(); 47 | 48 | el.run(move |evt, _, ctrl_flow| { 49 | windowed_context.window().request_redraw(); 50 | match evt { 51 | Event::LoopDestroyed => return, 52 | Event::WindowEvent {event, ..} => match event { 53 | WindowEvent::CloseRequested => *ctrl_flow = ControlFlow::Exit, 54 | WindowEvent::Resized(psize) => window_size = psize, 55 | WindowEvent::CursorMoved {position, ..} => 56 | demo.cursor_moved(position.x as f32, position.y as f32), 57 | _ => () 58 | } 59 | Event::RedrawRequested(_) => { 60 | unsafe { 61 | gl::Viewport( 62 | 0, 63 | 0, 64 | window_size.width as i32, 65 | window_size.height as i32 66 | ); 67 | gl::ClearColor(0.0, 0.0, 0.0, 1.0); 68 | gl::Clear( 69 | gl::COLOR_BUFFER_BIT | 70 | gl::DEPTH_BUFFER_BIT | 71 | gl::STENCIL_BUFFER_BIT 72 | ); 73 | } 74 | context.begin_frame( 75 | nvg::Extent { 76 | width: window_size.width as f32, 77 | height: window_size.height as f32 78 | }, 79 | scale_factor as f32 80 | ).unwrap(); 81 | 82 | context.save(); 83 | demo.update( 84 | window_size.width as f32, 85 | window_size.height as f32, 86 | &mut context 87 | ).unwrap(); 88 | context.restore(); 89 | 90 | context.save(); 91 | total_frames += 1; 92 | let fps = 93 | (total_frames as f32) / 94 | (Instant::now() - 95 | start_time).as_secs_f32(); 96 | context.fill_paint(Color::rgb(1.0, 0.0, 0.0)); 97 | context.font("roboto"); 98 | context.font_size(20.0); 99 | context.text_align(Align::TOP | Align::LEFT); 100 | context.text((10, 10), format!("FPS: {:.2}", fps)).unwrap(); 101 | context.fill().unwrap(); 102 | context.restore(); 103 | context.end_frame().unwrap(); 104 | windowed_context.swap_buffers().unwrap(); 105 | } 106 | _ => () 107 | } 108 | }); 109 | } 110 | -------------------------------------------------------------------------------- /nvg-gl/examples/demo-clock.rs: -------------------------------------------------------------------------------- 1 | use chrono::{DateTime, Datelike, Local, Timelike}; 2 | use nvg::*; 3 | use std::f32::consts::PI; 4 | 5 | mod demo; 6 | 7 | struct DemoClock; 8 | 9 | impl demo::Demo for DemoClock { 10 | fn update(&mut self, width: f32, height: f32, ctx: &mut Context) -> anyhow::Result<()> { 11 | let dt: DateTime = Local::now(); 12 | let hour = dt.hour(); 13 | let am = hour < 12; 14 | let hour: f32 = f64::from(hour % 12) as f32; 15 | let minute: f32 = f64::from(dt.minute()) as f32; 16 | let second: f32 = f64::from(dt.second()) as f32; 17 | let year: i32 = dt.year(); 18 | let month: u32 = dt.month(); 19 | let day: u32 = dt.day(); 20 | 21 | let clock_size = width.min(height) - 2.0; 22 | 23 | let font_size = 24.0; 24 | 25 | let origin = (0.0, 0.0); // upper-left corner 26 | let dial_center = ( 27 | f64::from(width) as f32 / 2.0, 28 | f64::from(height) as f32 / 2.0, 29 | ); 30 | let dial_radius: f32 = clock_size / 2.0; 31 | let second_hand_len = dial_radius * 0.9; 32 | let minute_hand_len = dial_radius * 0.8; 33 | let hour_hand_len = dial_radius * 0.6; 34 | 35 | let two_pi = 2.0 * PI; 36 | let radians_per_sec = two_pi / 60.0; 37 | let radians_per_hour = two_pi / 12.0; 38 | 39 | let white: Color = Color::rgba(1.0, 1.0, 1.0, 1.0); 40 | let silver: Color = Color::rgb_i(196, 199, 206); 41 | let darksilver: Color = Color::rgb_i(148, 152, 161); 42 | let darkgray: Color = Color::rgb_i(169, 169, 169); 43 | let dial_color = Color::rgba(0.2, 0.0, 0.8, 1.0); 44 | 45 | let sigils: Vec = (0..13).map(|n| format!("{}", n)).collect(); 46 | for h in 1..13 { 47 | let j = f64::from(h) as f32; 48 | let x = dial_center.0 + (second_hand_len * (j * radians_per_hour).sin()); 49 | let y = dial_center.1 - (second_hand_len * (j * radians_per_hour).cos()); 50 | 51 | ctx.fill_paint(silver); 52 | ctx.font_size(font_size); 53 | ctx.text_align(Align::CENTER | Align::MIDDLE); 54 | ctx.text((x, y), &sigils[h as usize])?; 55 | } 56 | 57 | for m in 1..61 { 58 | if m % 5 == 0 { 59 | continue; 60 | } 61 | let m = f64::from(m) as f32; 62 | let ticks_radius = dial_radius * 0.925; 63 | let tick_len = 3.0; 64 | let tick_width = 1.0; 65 | ctx.begin_path(); 66 | ctx.reset_transform(); 67 | ctx.transform(Transform::translate(dial_center.0, dial_center.1)); 68 | ctx.transform(Transform::rotate(m * radians_per_sec)); 69 | ctx.move_to((0.0, -ticks_radius)); 70 | ctx.line_to((0.0, -ticks_radius - tick_len)); 71 | ctx.global_composite_operation(CompositeOperation::Basic( 72 | BasicCompositeOperation::Lighter, 73 | )); 74 | ctx.stroke_paint(white); 75 | ctx.stroke_width(tick_width); 76 | ctx.stroke()?; 77 | } 78 | 79 | ctx.fill_paint(silver); 80 | 81 | ctx.text_align(Align::CENTER | Align::BASELINE); 82 | ctx.reset_transform(); 83 | ctx.text( 84 | (dial_center.0, dial_center.1 + dial_radius * 0.7 - font_size), 85 | &format!( 86 | "{}:{:02}:{:02} {}", 87 | hour, 88 | minute, 89 | second, 90 | if am { "AM" } else { "PM" } 91 | ), 92 | )?; 93 | ctx.text( 94 | (dial_center.0, dial_center.1 + dial_radius * 0.7), 95 | &format!("{:4}-{:02}-{:02}", year, month, day), 96 | )?; 97 | 98 | // draw dial 99 | ctx.begin_path(); 100 | ctx.translate(dial_center.0, dial_center.1); 101 | ctx.circle(origin, dial_radius); 102 | ctx.stroke_width(3.0); 103 | ctx.stroke_paint(silver); 104 | ctx.fill_paint(dial_color); 105 | ctx.fill()?; 106 | ctx.stroke()?; 107 | 108 | let mut draw_hand = |theta: f32, length: f32, width: f32| { 109 | ctx.stroke_width(width); 110 | ctx.begin_path(); 111 | ctx.reset_transform(); 112 | ctx.translate(dial_center.0, dial_center.1); 113 | ctx.rotate(theta); 114 | ctx.move_to(origin); 115 | ctx.line_to((0.0, -length)); 116 | ctx.stroke_paint(white); 117 | ctx.stroke() 118 | }; 119 | 120 | let hour_angle = (((hour * 60.0 + minute) / 60.0) / 12.0) * two_pi; 121 | let minute_angle = minute * radians_per_sec; 122 | let second_angle = second * radians_per_sec; 123 | 124 | draw_hand(second_angle, second_hand_len, 1.0)?; 125 | draw_hand(minute_angle, minute_hand_len, 3.0)?; 126 | draw_hand(hour_angle, hour_hand_len, 5.0)?; 127 | 128 | ctx.begin_path(); 129 | let boss_rad = 6.0; 130 | ctx.reset_transform(); 131 | ctx.translate(dial_center.0, dial_center.1); 132 | ctx.circle(origin, boss_rad); 133 | ctx.stroke_width(1.0); 134 | ctx.stroke_paint(darkgray); 135 | ctx.global_composite_operation(CompositeOperation::Basic(BasicCompositeOperation::SrcOver)); 136 | ctx.fill_paint(Gradient::Radial { 137 | center: origin.into(), 138 | in_radius: 0.0, 139 | out_radius: boss_rad, 140 | inner_color: silver, 141 | outer_color: darksilver, 142 | }); 143 | ctx.fill()?; 144 | ctx.stroke()?; 145 | 146 | Ok(()) 147 | } 148 | } 149 | 150 | fn main() { 151 | demo::run(DemoClock, "demo-clock"); 152 | } 153 | -------------------------------------------------------------------------------- /src/math.rs: -------------------------------------------------------------------------------- 1 | use num_traits::AsPrimitive; 2 | use std::ops::{Mul, MulAssign}; 3 | 4 | #[derive(Debug, Copy, Clone, Default)] 5 | pub struct Point { 6 | pub x: f32, 7 | pub y: f32, 8 | } 9 | 10 | impl Point { 11 | pub fn new(x: f32, y: f32) -> Point { 12 | Point { x, y } 13 | } 14 | 15 | pub(crate) fn equals(self, pt: Point, tol: f32) -> bool { 16 | let dx = pt.x - self.x; 17 | let dy = pt.y - self.y; 18 | dx * dx + dy * dy < tol * tol 19 | } 20 | 21 | pub(crate) fn dist_pt_seg(self, p: Point, q: Point) -> f32 { 22 | let pqx = q.x - p.x; 23 | let pqy = q.y - p.y; 24 | let dx = self.x - p.x; 25 | let dy = self.y - p.y; 26 | let d = pqx * pqx + pqy * pqy; 27 | let mut t = pqx * dx + pqy * dy; 28 | if d > 0.0 { 29 | t /= d; 30 | } 31 | if t < 0.0 { 32 | t = 0.0 33 | } else if t > 1.0 { 34 | t = 1.0 35 | }; 36 | let dx = p.x + t * pqx - self.x; 37 | let dy = p.y + t * pqy - self.y; 38 | dx * dx + dy * dy 39 | } 40 | 41 | pub(crate) fn normalize(&mut self) -> f32 { 42 | let d = ((self.x) * (self.x) + (self.y) * (self.y)).sqrt(); 43 | if d > 1e-6 { 44 | let id = 1.0 / d; 45 | self.x *= id; 46 | self.y *= id; 47 | } 48 | d 49 | } 50 | 51 | pub(crate) fn cross(pt1: Point, pt2: Point) -> f32 { 52 | pt2.x * pt1.y - pt1.x * pt2.y 53 | } 54 | 55 | pub fn offset(&self, tx: f32, ty: f32) -> Point { 56 | Point::new(self.x + tx, self.y + ty) 57 | } 58 | } 59 | 60 | impl> From<(T, T)> for Point { 61 | fn from((x, y): (T, T)) -> Self { 62 | Point::new(x.as_(), y.as_()) 63 | } 64 | } 65 | 66 | #[derive(Debug, Copy, Clone, Default)] 67 | pub struct Extent { 68 | pub width: f32, 69 | pub height: f32, 70 | } 71 | 72 | impl Extent { 73 | pub fn new(width: f32, height: f32) -> Extent { 74 | Extent { width, height } 75 | } 76 | } 77 | 78 | impl> From<(T, T)> for Extent { 79 | fn from((width, height): (T, T)) -> Self { 80 | Extent::new(width.as_(), height.as_()) 81 | } 82 | } 83 | 84 | #[derive(Debug, Copy, Clone, Default)] 85 | pub struct Rect { 86 | pub xy: Point, 87 | pub size: Extent, 88 | } 89 | 90 | impl Rect { 91 | pub fn new(xy: Point, size: Extent) -> Rect { 92 | Rect { xy, size } 93 | } 94 | 95 | pub fn intersect(self, rect: Rect) -> Rect { 96 | let Rect { 97 | xy: Point { x: ax, y: ay }, 98 | size: Extent { 99 | width: aw, 100 | height: ah, 101 | }, 102 | } = rect; 103 | 104 | let Rect { 105 | xy: Point { x: bx, y: by }, 106 | size: Extent { 107 | width: bw, 108 | height: bh, 109 | }, 110 | } = rect; 111 | 112 | let minx = ax.max(bx); 113 | let miny = ay.max(by); 114 | let maxx = (ax + aw).min(bx + bw); 115 | let maxy = (ay + ah).min(by + bh); 116 | Self::new( 117 | Point::new(minx, miny), 118 | Extent::new((maxx - minx).max(0.0), (maxy - miny).max(0.0)), 119 | ) 120 | } 121 | 122 | pub fn grow(&self, width: f32, height: f32) -> Rect { 123 | Rect::new( 124 | self.xy.offset(-width / 2.0, -height / 2.0), 125 | Extent::new(self.size.width + width, self.size.height + height), 126 | ) 127 | } 128 | } 129 | 130 | impl> From<(T, T, T, T)> for Rect { 131 | fn from((x, y, w, h): (T, T, T, T)) -> Self { 132 | Rect::new((x.as_(), y.as_()).into(), (w.as_(), h.as_()).into()) 133 | } 134 | } 135 | 136 | #[derive(Debug, Copy, Clone, Default)] 137 | pub struct Bounds { 138 | pub min: Point, 139 | pub max: Point, 140 | } 141 | 142 | impl Bounds { 143 | pub fn width(&self) -> f32 { 144 | self.max.x - self.min.x 145 | } 146 | 147 | pub fn height(&self) -> f32 { 148 | self.max.y - self.min.y 149 | } 150 | 151 | pub fn left_top(&self) -> Point { 152 | self.min 153 | } 154 | 155 | pub fn right_top(&self) -> Point { 156 | Point::new(self.max.x, self.min.y) 157 | } 158 | 159 | pub fn left_bottom(&self) -> Point { 160 | Point::new(self.min.x, self.max.y) 161 | } 162 | 163 | pub fn right_bottom(&self) -> Point { 164 | self.max 165 | } 166 | } 167 | 168 | #[derive(Debug, Copy, Clone, Default)] 169 | pub struct Transform(pub [f32; 6]); 170 | 171 | impl Transform { 172 | pub fn identity() -> Transform { 173 | Transform([1.0, 0.0, 0.0, 1.0, 0.0, 0.0]) 174 | } 175 | 176 | pub fn translate(tx: f32, ty: f32) -> Transform { 177 | Transform([1.0, 0.0, 0.0, 1.0, tx, ty]) 178 | } 179 | 180 | pub fn scale(sx: f32, sy: f32) -> Transform { 181 | Transform([sx, 0.0, 0.0, sy, 0.0, 0.0]) 182 | } 183 | 184 | pub fn rotate(a: f32) -> Transform { 185 | let cs = a.cos(); 186 | let sn = a.sin(); 187 | Transform([cs, sn, -sn, cs, 0.0, 0.0]) 188 | } 189 | 190 | pub fn skew_x(a: f32) -> Transform { 191 | Transform([1.0, 0.0, a.tan(), 1.0, 0.0, 0.0]) 192 | } 193 | 194 | pub fn skew_y(a: f32) -> Transform { 195 | Transform([1.0, a.tan(), 0.0, 1.0, 0.0, 0.0]) 196 | } 197 | 198 | pub fn pre_multiply(self, rhs: Self) -> Self { 199 | rhs * self 200 | } 201 | 202 | pub fn inverse(self) -> Transform { 203 | let t = &self.0; 204 | let det = t[0] * t[3] - t[2] * t[1]; 205 | if det > -1e-6 && det < 1e-6 { 206 | return Transform::identity(); 207 | } 208 | let invdet = 1.0 / det; 209 | let mut inv = [0f32; 6]; 210 | inv[0] = t[3] * invdet; 211 | inv[2] = -t[2] * invdet; 212 | inv[4] = (t[2] * t[5] - t[3] * t[4]) * invdet; 213 | inv[1] = -t[1] * invdet; 214 | inv[3] = t[0] * invdet; 215 | inv[5] = (t[1] * t[4] - t[0] * t[5]) * invdet; 216 | Transform(inv) 217 | } 218 | 219 | pub fn transform_point(&self, pt: Point) -> Point { 220 | let t = &self.0; 221 | Point::new( 222 | pt.x * t[0] + pt.y * t[2] + t[4], 223 | pt.x * t[1] + pt.y * t[3] + t[5], 224 | ) 225 | } 226 | 227 | pub(crate) fn average_scale(&self) -> f32 { 228 | let t = &self.0; 229 | let sx = (t[0] * t[0] + t[2] * t[2]).sqrt(); 230 | let sy = (t[1] * t[1] + t[3] * t[3]).sqrt(); 231 | (sx + sy) * 0.5 232 | } 233 | 234 | pub(crate) fn font_scale(&self) -> f32 { 235 | let a = self.average_scale(); 236 | let d = 0.01f32; 237 | (a / d).ceil() * d 238 | } 239 | } 240 | 241 | impl Mul for Transform { 242 | type Output = Transform; 243 | 244 | fn mul(mut self, rhs: Self) -> Self::Output { 245 | let t = &mut self.0; 246 | let s = &rhs.0; 247 | let t0 = t[0] * s[0] + t[1] * s[2]; 248 | let t2 = t[2] * s[0] + t[3] * s[2]; 249 | let t4 = t[4] * s[0] + t[5] * s[2] + s[4]; 250 | t[1] = t[0] * s[1] + t[1] * s[3]; 251 | t[3] = t[2] * s[1] + t[3] * s[3]; 252 | t[5] = t[4] * s[1] + t[5] * s[3] + s[5]; 253 | t[0] = t0; 254 | t[2] = t2; 255 | t[4] = t4; 256 | self 257 | } 258 | } 259 | 260 | impl MulAssign for Transform { 261 | fn mul_assign(&mut self, rhs: Self) { 262 | *self = *self * rhs; 263 | } 264 | } 265 | 266 | impl> From<(T, T, T, T, T, T)> for Transform { 267 | fn from((a1, a2, a3, a4, a5, a6): (T, T, T, T, T, T)) -> Self { 268 | Transform([a1.as_(), a2.as_(), a3.as_(), a4.as_(), a5.as_(), a6.as_()]) 269 | } 270 | } 271 | 272 | impl> From<[T; 6]> for Transform { 273 | fn from(values: [T; 6]) -> Self { 274 | let mut values2 = [0.0; 6]; 275 | for i in 0..6 { 276 | values2[i] = values[i].as_(); 277 | } 278 | Transform(values2) 279 | } 280 | } 281 | -------------------------------------------------------------------------------- /nvg-gl/examples/demo-cutout.rs: -------------------------------------------------------------------------------- 1 | #[macro_use] 2 | extern crate lazy_static; 3 | 4 | use anyhow::Error; 5 | use nvg::*; 6 | use rand::prelude::*; 7 | use std::collections::HashMap; 8 | use std::f32::consts::PI; 9 | use std::time::Instant; 10 | 11 | lazy_static! { 12 | static ref COLORS: [Color; 4] = [ 13 | Color::rgb_i(0x00, 0xBF, 0xA8), 14 | Color::rgb_i(0x99, 0x66, 0xFF), 15 | Color::rgb_i(0xFF, 0x64, 0x64), 16 | Color::rgb_i(0x00, 0xC8, 0xFF) 17 | ]; 18 | } 19 | 20 | struct ShapeCache(HashMap); 21 | 22 | impl ShapeCache { 23 | fn new() -> ShapeCache { 24 | ShapeCache(HashMap::new()) 25 | } 26 | 27 | fn get(&mut self, pair: (u16, u16), rng: &mut T) -> &mut Shape { 28 | let index = ShapeCache::elegent_pair(pair); 29 | self.0.entry(index).or_insert_with(|| Shape::new(rng)) 30 | } 31 | 32 | fn elegent_pair((x, y): (u16, u16)) -> u32 { 33 | let a = x as u32; 34 | let b = y as u32; 35 | 36 | if a >= b { 37 | a * a + a + b 38 | } else { 39 | a + b * b 40 | } 41 | } 42 | } 43 | 44 | enum ShapeKind { 45 | Polygon(u8), 46 | Squiggle(u8), 47 | } 48 | 49 | impl ShapeKind { 50 | fn rand(rng: &mut R) -> Self { 51 | match rng.gen_range(0, 2) { 52 | 0 => ShapeKind::Polygon(rng.gen_range(3, 6)), 53 | 1 => ShapeKind::Squiggle(rng.gen_range(3, 6)), 54 | _ => unreachable!(), 55 | } 56 | } 57 | } 58 | 59 | struct Shape { 60 | rotation: f32, 61 | speed: f32, 62 | color: nvg::Color, 63 | kind: ShapeKind, 64 | } 65 | 66 | impl Shape { 67 | fn new(rng: &mut T) -> Shape { 68 | let color = COLORS.choose(rng).unwrap(); 69 | let direction = [-1.0f32, 1.0f32].choose(rng).unwrap(); 70 | 71 | Shape { 72 | rotation: rng.gen_range(0.0, 2.0 * PI), 73 | speed: rng.gen_range(1.0, 4.0) * direction, 74 | color: *color, 75 | kind: ShapeKind::rand(rng), 76 | } 77 | } 78 | 79 | fn update(&mut self, dt: f32) { 80 | self.rotation = self.rotation + dt * self.speed; 81 | } 82 | 83 | fn draw(&self, ctx: &mut nvg::Context, (x, y): (f32, f32), size: f32) { 84 | let margin = size * 0.2; 85 | let x = x + margin; 86 | let y = y + margin; 87 | let size = size - margin * 2.0; 88 | let half_size = size / 2.0; 89 | let pos = (x + half_size, y + half_size); 90 | match self.kind { 91 | ShapeKind::Polygon(sides) => { 92 | Shape::render_polygon(ctx, pos, size, self.rotation, self.color, sides) 93 | } 94 | ShapeKind::Squiggle(phi) => { 95 | Shape::render_squiggle(ctx, pos, (size, size / 3.0), self.rotation, self.color, phi) 96 | } 97 | }; 98 | } 99 | 100 | fn render_polygon( 101 | ctx: &mut Context, 102 | (cx, cy): (f32, f32), 103 | diameter: f32, 104 | rotation: f32, 105 | color: nvg::Color, 106 | num_sides: u8, 107 | ) { 108 | assert!(num_sides >= 3); 109 | 110 | let radius = diameter / 2.0; 111 | let num_sides = num_sides as u32; 112 | 113 | ctx.begin_path(); 114 | ctx.reset_transform(); 115 | ctx.translate(cx, cy); 116 | ctx.rotate(rotation); 117 | ctx.move_to(Shape::get_polygon_point(0, num_sides, radius)); 118 | for i in 1..num_sides { 119 | ctx.line_to(Shape::get_polygon_point(i, num_sides, radius)); 120 | } 121 | ctx.close_path(); 122 | ctx.fill_paint(color); 123 | ctx.fill().unwrap(); 124 | } 125 | 126 | fn render_squiggle( 127 | ctx: &mut Context, 128 | (cx, cy): (f32, f32), 129 | (w, h): (f32, f32), 130 | rotation: f32, 131 | color: nvg::Color, 132 | phi: u8, 133 | ) { 134 | let phi = phi as f32; 135 | let mut points = [(0.0, 0.0); 64]; 136 | for i in 0..points.len() { 137 | let pct = i as f32 / (points.len() as f32 - 1.0); 138 | let theta = pct * PI * 2.0 * phi + PI / 2.0; 139 | let sx = w * pct - w / 2.0; 140 | let sy = h / 2.0 * theta.sin(); 141 | points[i as usize] = (sx, sy); 142 | } 143 | 144 | ctx.begin_path(); 145 | ctx.reset_transform(); 146 | ctx.translate(cx, cy); 147 | ctx.rotate(rotation); 148 | ctx.move_to(points[0]); 149 | for point in points.iter().skip(1) { 150 | ctx.line_to(*point); 151 | } 152 | ctx.stroke_width(3.0); 153 | ctx.stroke_paint(color); 154 | ctx.stroke().unwrap(); 155 | } 156 | 157 | fn get_polygon_point(index: u32, num_sides: u32, radius: f32) -> (f32, f32) { 158 | let px = radius * (2.0 * PI * index as f32 / num_sides as f32).cos(); 159 | let py = radius * (2.0 * PI * index as f32 / num_sides as f32).sin(); 160 | (px, py) 161 | } 162 | } 163 | 164 | fn lerp(from: f32, to: f32, t: f32) -> f32 { 165 | from + (to - from) * t 166 | } 167 | 168 | fn get_elapsed(instant: &Instant) -> f32 { 169 | let elapsed = instant.elapsed(); 170 | let elapsed = elapsed.as_secs() as f64 + elapsed.subsec_nanos() as f64 * 1e-9; 171 | elapsed as f32 172 | } 173 | 174 | fn render_cutout( 175 | ctx: &mut Context, 176 | (x, y): (f32, f32), 177 | (w, h): (f32, f32), 178 | (mx, my): (f32, f32), 179 | ) { 180 | let base_circle_size = 200.0; 181 | let circle_thickness = 25.0; 182 | 183 | ctx.begin_path(); 184 | ctx.rect((x, y, w, h)); 185 | ctx.circle((mx, my), base_circle_size); 186 | ctx.path_solidity(Solidity::Hole); 187 | ctx.close_path(); 188 | ctx.fill_paint(Color::rgb(1.0, 1.0, 1.0)); 189 | ctx.fill().unwrap(); 190 | 191 | ctx.begin_path(); 192 | ctx.move_to((0, 0)); 193 | ctx.circle((mx, my), base_circle_size + circle_thickness); 194 | ctx.circle((mx, my), base_circle_size); 195 | ctx.path_solidity(Solidity::Hole); 196 | ctx.close_path(); 197 | ctx.fill_paint(Color::rgba_i(90, 94, 100, 25)); 198 | ctx.fill().unwrap(); 199 | 200 | ctx.begin_path(); 201 | ctx.move_to((0, 0)); 202 | ctx.circle((mx, my), base_circle_size); 203 | ctx.circle((mx, my), base_circle_size - circle_thickness); 204 | ctx.path_solidity(Solidity::Hole); 205 | ctx.close_path(); 206 | ctx.fill_paint(Color::rgba_i(0, 0, 0, 25)); 207 | ctx.fill().unwrap(); 208 | } 209 | 210 | fn render_rectangle( 211 | ctx: &mut Context, 212 | (x, y): (f32, f32), 213 | (w, h): (f32, f32), 214 | color: Color, 215 | ) { 216 | ctx.begin_path(); 217 | ctx.rect((x, y, w, h)); 218 | ctx.fill_paint(color); 219 | ctx.fill().unwrap(); 220 | } 221 | 222 | mod demo; 223 | 224 | struct DemoCutout { 225 | start_time: Instant, 226 | prev_time: f32, 227 | shapes: ShapeCache, 228 | rng: ThreadRng, 229 | mouse: (f32, f32), 230 | smoothed_mouse: (f32, f32), 231 | } 232 | 233 | impl Default for DemoCutout { 234 | fn default() -> Self { 235 | Self { 236 | start_time: Instant::now(), 237 | prev_time: 0.0, 238 | shapes: ShapeCache::new(), 239 | rng: thread_rng(), 240 | mouse: (0.0, 0.0), 241 | smoothed_mouse: (0.0, 0.0), 242 | } 243 | } 244 | } 245 | 246 | impl demo::Demo for DemoCutout { 247 | fn update(&mut self, width: f32, height: f32, ctx: &mut Context) -> Result<(), Error> { 248 | let elapsed = get_elapsed(&self.start_time); 249 | let delta_time = elapsed - self.prev_time; 250 | self.prev_time = elapsed; 251 | 252 | self.smoothed_mouse = smooth_mouse(self.mouse, self.smoothed_mouse, delta_time, 7.0); 253 | 254 | let block_size = 75.0; 255 | let offset = block_size / 2.0; 256 | 257 | render_rectangle( 258 | ctx, 259 | (0.0, 0.0), 260 | (width, height), 261 | Color::rgb_i(0xFF, 0xFF, 0xAF), 262 | ); 263 | 264 | let max_cols = (width / block_size) as u16 + 2; 265 | let max_rows = (height / block_size) as u16 + 2; 266 | 267 | for x in 0..max_cols { 268 | for y in 0..max_rows { 269 | let shape = self.shapes.get((x, y), &mut self.rng); 270 | shape.update(delta_time); 271 | let x = x as f32 * block_size - offset; 272 | let y = y as f32 * block_size - offset; 273 | shape.draw(ctx, (x, y), block_size); 274 | } 275 | } 276 | 277 | ctx.reset_transform(); 278 | render_cutout(ctx, (0.0, 0.0), (width, height), self.smoothed_mouse); 279 | Ok(()) 280 | } 281 | 282 | fn cursor_moved(&mut self, x: f32, y: f32) { 283 | self.mouse = (x, y); 284 | } 285 | } 286 | 287 | fn smooth_mouse( 288 | mouse: (f32, f32), 289 | prev_smoothed_mouse: (f32, f32), 290 | dt: f32, 291 | speed: f32, 292 | ) -> (f32, f32) { 293 | let smx = lerp(prev_smoothed_mouse.0, mouse.0, dt * speed); 294 | let smy = lerp(prev_smoothed_mouse.1, mouse.1, dt * speed); 295 | (smx, smy) 296 | } 297 | 298 | fn main() { 299 | demo::run(DemoCutout::default(), "demo-cutout"); 300 | } 301 | -------------------------------------------------------------------------------- /src/fonts.rs: -------------------------------------------------------------------------------- 1 | use crate::context::{ImageId, TextMetrics}; 2 | use crate::renderer::TextureType; 3 | use crate::{Align, Bounds, Extent, ImageFlags, Renderer}; 4 | use bitflags::_core::borrow::Borrow; 5 | use rusttype::gpu_cache::Cache; 6 | use rusttype::{Font, Glyph, Point, PositionedGlyph, Scale}; 7 | use slab::Slab; 8 | use std::collections::HashMap; 9 | 10 | const TEX_WIDTH: usize = 1024; 11 | const TEX_HEIGHT: usize = 1024; 12 | 13 | pub type FontId = usize; 14 | 15 | #[derive(Debug)] 16 | pub struct LayoutChar { 17 | id: FontId, 18 | pub x: f32, 19 | pub next_x: f32, 20 | pub c: char, 21 | pub idx: usize, 22 | glyph: PositionedGlyph<'static>, 23 | pub uv: Bounds, 24 | pub bounds: Bounds, 25 | } 26 | 27 | struct FontData { 28 | font: Font<'static>, 29 | fallback_fonts: Vec, 30 | } 31 | 32 | pub struct Fonts { 33 | fonts: Slab, 34 | fonts_by_name: HashMap, 35 | cache: Cache<'static>, 36 | pub(crate) img: ImageId, 37 | } 38 | 39 | impl Fonts { 40 | pub fn new(renderer: &mut R) -> anyhow::Result { 41 | Ok(Fonts { 42 | fonts: Default::default(), 43 | fonts_by_name: Default::default(), 44 | img: renderer.create_texture( 45 | TextureType::Alpha, 46 | TEX_WIDTH, 47 | TEX_HEIGHT, 48 | ImageFlags::empty(), 49 | None, 50 | )?, 51 | cache: Cache::builder() 52 | .multithread(true) 53 | .dimensions(TEX_WIDTH as u32, TEX_HEIGHT as u32) 54 | .build(), 55 | }) 56 | } 57 | 58 | pub fn add_font, D: Into>>( 59 | &mut self, 60 | name: N, 61 | data: D, 62 | ) -> anyhow::Result { 63 | let font = Font::<'static>::from_bytes(data.into())?; 64 | let fd = FontData { 65 | font, 66 | fallback_fonts: Default::default(), 67 | }; 68 | let id = self.fonts.insert(fd); 69 | self.fonts_by_name.insert(name.into(), id); 70 | Ok(id) 71 | } 72 | 73 | pub fn find>(&self, name: N) -> Option { 74 | self.fonts_by_name.get(name.borrow()).map(ToOwned::to_owned) 75 | } 76 | 77 | pub fn add_fallback(&mut self, base: FontId, fallback: FontId) { 78 | if let Some(fd) = self.fonts.get_mut(base) { 79 | fd.fallback_fonts.push(fallback); 80 | } 81 | } 82 | 83 | fn glyph(&self, id: FontId, c: char) -> Option<(FontId, Glyph<'static>)> { 84 | if let Some(fd) = self.fonts.get(id) { 85 | let glyph = fd.font.glyph(c); 86 | if glyph.id().0 != 0 { 87 | Some((id, glyph)) 88 | } else { 89 | for id in &fd.fallback_fonts { 90 | if let Some(fd) = self.fonts.get(*id) { 91 | let glyph = fd.font.glyph(c); 92 | if glyph.id().0 != 0 { 93 | return Some((*id, glyph)); 94 | } 95 | } 96 | } 97 | None 98 | } 99 | } else { 100 | None 101 | } 102 | } 103 | 104 | fn render_texture(&mut self, renderer: &mut R) -> anyhow::Result<()> { 105 | let img = self.img.clone(); 106 | self.cache.cache_queued(move |rect, data| { 107 | renderer 108 | .update_texture( 109 | img.clone(), 110 | rect.min.x as usize, 111 | rect.min.y as usize, 112 | (rect.max.x - rect.min.x) as usize, 113 | (rect.max.y - rect.min.y) as usize, 114 | data, 115 | ) 116 | .unwrap(); 117 | })?; 118 | Ok(()) 119 | } 120 | 121 | pub fn text_metrics(&self, id: FontId, size: f32) -> TextMetrics { 122 | if let Some(fd) = self.fonts.get(id) { 123 | let scale = Scale::uniform(size); 124 | let v_metrics = fd.font.v_metrics(scale); 125 | TextMetrics { 126 | ascender: v_metrics.descent, 127 | descender: v_metrics.descent, 128 | line_gap: v_metrics.line_gap, 129 | } 130 | } else { 131 | TextMetrics { 132 | ascender: 0.0, 133 | descender: 0.0, 134 | line_gap: 0.0, 135 | } 136 | } 137 | } 138 | 139 | pub fn text_size(&self, text: &str, id: FontId, size: f32, spacing: f32) -> Extent { 140 | if let Some(fd) = self.fonts.get(id) { 141 | let scale = Scale::uniform(size); 142 | let v_metrics = fd.font.v_metrics(scale); 143 | let mut extent = Extent::new( 144 | 0.0, 145 | v_metrics.ascent - v_metrics.descent + v_metrics.line_gap, 146 | ); 147 | let mut last_glyph = None; 148 | let mut char_count = 0; 149 | 150 | for c in text.chars() { 151 | if let Some((_, glyph)) = self.glyph(id, c) { 152 | let glyph = glyph.scaled(scale); 153 | let h_metrics = glyph.h_metrics(); 154 | extent.width += h_metrics.advance_width; 155 | 156 | if let Some(last_glyph) = last_glyph { 157 | extent.width += fd.font.pair_kerning(scale, last_glyph, glyph.id()); 158 | } 159 | 160 | last_glyph = Some(glyph.id()); 161 | char_count += 1; 162 | } 163 | } 164 | 165 | if char_count >= 2 { 166 | extent.width += spacing * (char_count - 1) as f32; 167 | } 168 | 169 | extent 170 | } else { 171 | Default::default() 172 | } 173 | } 174 | 175 | pub fn layout_text( 176 | &mut self, 177 | renderer: &mut R, 178 | text: &str, 179 | id: FontId, 180 | position: crate::Point, 181 | size: f32, 182 | align: Align, 183 | spacing: f32, 184 | cache: bool, 185 | result: &mut Vec, 186 | ) -> anyhow::Result<()> { 187 | result.clear(); 188 | 189 | if let Some(fd) = self.fonts.get(id) { 190 | let mut offset = Point { x: 0.0, y: 0.0 }; 191 | let scale = Scale::uniform(size); 192 | let v_metrics = fd.font.v_metrics(scale); 193 | 194 | let sz = if align.contains(Align::CENTER) 195 | || align.contains(Align::RIGHT) 196 | || align.contains(Align::MIDDLE) 197 | { 198 | self.text_size(text, id, size, spacing) 199 | } else { 200 | Extent::new(0.0, 0.0) 201 | }; 202 | 203 | if align.contains(Align::CENTER) { 204 | offset.x -= sz.width / 2.0; 205 | } else if align.contains(Align::RIGHT) { 206 | offset.x -= sz.width; 207 | } 208 | 209 | if align.contains(Align::MIDDLE) { 210 | offset.y = v_metrics.descent + sz.height / 2.0; 211 | } else if align.contains(Align::BOTTOM) { 212 | offset.y = v_metrics.descent; 213 | } else if align.contains(Align::TOP) { 214 | offset.y = v_metrics.ascent; 215 | } 216 | 217 | let mut position = Point { 218 | x: position.x + offset.x, 219 | y: position.y + offset.y, 220 | }; 221 | let mut last_glyph = None; 222 | 223 | for (idx, c) in text.chars().enumerate() { 224 | if let Some((id, glyph)) = self.glyph(id, c) { 225 | let g = glyph.scaled(scale); 226 | let h_metrics = g.h_metrics(); 227 | 228 | let glyph = g.positioned(Point { 229 | x: position.x, 230 | y: position.y, 231 | }); 232 | 233 | let mut next_x = position.x + h_metrics.advance_width; 234 | if let Some(last_glyph) = last_glyph { 235 | next_x += fd.font.pair_kerning(scale, last_glyph, glyph.id()); 236 | } 237 | 238 | if let Some(bb) = glyph.pixel_bounding_box() { 239 | self.cache.queue_glyph(id, glyph.clone()); 240 | 241 | result.push(LayoutChar { 242 | id, 243 | idx, 244 | c, 245 | x: position.x, 246 | next_x, 247 | glyph: glyph.clone(), 248 | uv: Default::default(), 249 | bounds: Bounds { 250 | min: (bb.min.x, bb.min.y).into(), 251 | max: (bb.max.x, bb.max.y).into(), 252 | }, 253 | }); 254 | } 255 | 256 | position.x = next_x; 257 | last_glyph = Some(glyph.id()); 258 | } 259 | } 260 | 261 | if cache { 262 | self.render_texture(renderer)?; 263 | 264 | for lc in result { 265 | if let Ok(Some((uv, _))) = self.cache.rect_for(lc.id, &lc.glyph) { 266 | lc.uv = Bounds { 267 | min: crate::Point { 268 | x: uv.min.x, 269 | y: uv.min.y, 270 | }, 271 | max: crate::Point { 272 | x: uv.max.x, 273 | y: uv.max.y, 274 | }, 275 | }; 276 | } 277 | } 278 | } 279 | } 280 | 281 | Ok(()) 282 | } 283 | } 284 | -------------------------------------------------------------------------------- /nvg-gl/src/lib.rs: -------------------------------------------------------------------------------- 1 | #[macro_use] 2 | extern crate anyhow; 3 | 4 | use nvg::renderer::*; 5 | use slab::Slab; 6 | use std::ffi::c_void; 7 | 8 | struct Shader { 9 | prog: gl::types::GLuint, 10 | frag: gl::types::GLuint, 11 | vert: gl::types::GLuint, 12 | loc_viewsize: i32, 13 | loc_tex: i32, 14 | loc_frag: u32, 15 | } 16 | 17 | impl Drop for Shader { 18 | fn drop(&mut self) { 19 | unsafe { 20 | gl::DeleteProgram(self.prog); 21 | gl::DeleteShader(self.vert); 22 | gl::DeleteShader(self.frag); 23 | } 24 | } 25 | } 26 | 27 | impl Shader { 28 | unsafe fn load() -> anyhow::Result { 29 | let mut status: gl::types::GLint = std::mem::zeroed(); 30 | let prog = gl::CreateProgram(); 31 | let vert = gl::CreateShader(gl::VERTEX_SHADER); 32 | let frag = gl::CreateShader(gl::FRAGMENT_SHADER); 33 | let vert_source = 34 | std::ffi::CString::from_vec_unchecked(include_bytes!("shader.vert").to_vec()); 35 | let frag_source = 36 | std::ffi::CString::from_vec_unchecked(include_bytes!("shader.frag").to_vec()); 37 | 38 | gl::ShaderSource( 39 | vert, 40 | 1, 41 | [vert_source.as_ptr()].as_ptr() as *const *const i8, 42 | std::ptr::null(), 43 | ); 44 | gl::ShaderSource( 45 | frag, 46 | 1, 47 | [frag_source.as_ptr()].as_ptr() as *const *const i8, 48 | std::ptr::null(), 49 | ); 50 | 51 | gl::CompileShader(vert); 52 | gl::GetShaderiv(vert, gl::COMPILE_STATUS, &mut status); 53 | if status != gl::TRUE as i32 { 54 | return Err(shader_error(vert, "shader.vert")); 55 | } 56 | 57 | gl::CompileShader(frag); 58 | gl::GetShaderiv(frag, gl::COMPILE_STATUS, &mut status); 59 | if status != gl::TRUE as i32 { 60 | return Err(shader_error(vert, "shader.frag")); 61 | } 62 | 63 | gl::AttachShader(prog, vert); 64 | gl::AttachShader(prog, frag); 65 | 66 | let name_vertex = std::ffi::CString::new("vertex").unwrap(); 67 | let name_tcoord = std::ffi::CString::new("tcoord").unwrap(); 68 | gl::BindAttribLocation(prog, 0, name_vertex.as_ptr() as *const i8); 69 | gl::BindAttribLocation(prog, 1, name_tcoord.as_ptr() as *const i8); 70 | 71 | gl::LinkProgram(prog); 72 | gl::GetProgramiv(prog, gl::LINK_STATUS, &mut status); 73 | if status != gl::TRUE as i32 { 74 | return Err(program_error(prog)); 75 | } 76 | 77 | let name_viewsize = std::ffi::CString::new("viewSize").unwrap(); 78 | let name_tex = std::ffi::CString::new("tex").unwrap(); 79 | let name_frag = std::ffi::CString::new("frag").unwrap(); 80 | 81 | Ok(Shader { 82 | prog, 83 | frag, 84 | vert, 85 | loc_viewsize: gl::GetUniformLocation(prog, name_viewsize.as_ptr() as *const i8), 86 | loc_tex: gl::GetUniformLocation(prog, name_tex.as_ptr() as *const i8), 87 | loc_frag: gl::GetUniformBlockIndex(prog, name_frag.as_ptr() as *const i8), 88 | }) 89 | } 90 | } 91 | 92 | enum ShaderType { 93 | FillGradient, 94 | FillImage, 95 | Simple, 96 | Image, 97 | } 98 | 99 | #[derive(PartialEq, Eq)] 100 | enum CallType { 101 | Fill, 102 | ConvexFill, 103 | Stroke, 104 | Triangles, 105 | } 106 | 107 | struct Blend { 108 | src_rgb: gl::types::GLenum, 109 | dst_rgb: gl::types::GLenum, 110 | src_alpha: gl::types::GLenum, 111 | dst_alpha: gl::types::GLenum, 112 | } 113 | 114 | impl From for Blend { 115 | fn from(state: CompositeOperationState) -> Self { 116 | Blend { 117 | src_rgb: convert_blend_factor(state.src_rgb), 118 | dst_rgb: convert_blend_factor(state.dst_rgb), 119 | src_alpha: convert_blend_factor(state.src_alpha), 120 | dst_alpha: convert_blend_factor(state.dst_alpha), 121 | } 122 | } 123 | } 124 | 125 | struct Call { 126 | call_type: CallType, 127 | image: Option, 128 | path_offset: usize, 129 | path_count: usize, 130 | triangle_offset: usize, 131 | triangle_count: usize, 132 | uniform_offset: usize, 133 | blend_func: Blend, 134 | } 135 | 136 | struct Texture { 137 | tex: gl::types::GLuint, 138 | width: usize, 139 | height: usize, 140 | texture_type: TextureType, 141 | flags: ImageFlags, 142 | } 143 | 144 | impl Drop for Texture { 145 | fn drop(&mut self) { 146 | unsafe { gl::DeleteTextures(1, &self.tex) } 147 | } 148 | } 149 | 150 | struct GLPath { 151 | fill_offset: usize, 152 | fill_count: usize, 153 | stroke_offset: usize, 154 | stroke_count: usize, 155 | } 156 | 157 | #[derive(Default)] 158 | #[allow(dead_code)] 159 | struct FragUniforms { 160 | scissor_mat: [f32; 12], 161 | paint_mat: [f32; 12], 162 | inner_color: Color, 163 | outer_color: Color, 164 | scissor_ext: [f32; 2], 165 | scissor_scale: [f32; 2], 166 | extent: [f32; 2], 167 | radius: f32, 168 | feather: f32, 169 | stroke_mult: f32, 170 | stroke_thr: f32, 171 | tex_type: i32, 172 | type_: i32, 173 | } 174 | 175 | pub struct Renderer { 176 | shader: Shader, 177 | textures: Slab, 178 | view: Extent, 179 | vert_buf: gl::types::GLuint, 180 | vert_arr: gl::types::GLuint, 181 | frag_buf: gl::types::GLuint, 182 | frag_size: usize, 183 | calls: Vec, 184 | paths: Vec, 185 | vertexes: Vec, 186 | uniforms: Vec, 187 | } 188 | 189 | impl Drop for Renderer { 190 | fn drop(&mut self) { 191 | unsafe { 192 | gl::DeleteBuffers(1, &self.frag_buf); 193 | gl::DeleteBuffers(1, &self.vert_buf); 194 | gl::DeleteVertexArrays(1, &self.vert_arr); 195 | } 196 | } 197 | } 198 | 199 | impl Renderer { 200 | pub fn create() -> anyhow::Result { 201 | unsafe { 202 | let shader = Shader::load()?; 203 | 204 | let mut vert_arr: gl::types::GLuint = std::mem::zeroed(); 205 | gl::GenVertexArrays(1, &mut vert_arr); 206 | 207 | let mut vert_buf: gl::types::GLuint = std::mem::zeroed(); 208 | gl::GenBuffers(1, &mut vert_buf); 209 | 210 | gl::UniformBlockBinding(shader.prog, shader.loc_frag, 0); 211 | let mut frag_buf: gl::types::GLuint = std::mem::zeroed(); 212 | gl::GenBuffers(1, &mut frag_buf); 213 | 214 | let mut align = std::mem::zeroed(); 215 | gl::GetIntegerv(gl::UNIFORM_BUFFER_OFFSET_ALIGNMENT, &mut align); 216 | 217 | let frag_size = std::mem::size_of::() + (align as usize) 218 | - std::mem::size_of::() % (align as usize); 219 | 220 | gl::Finish(); 221 | 222 | Ok(Renderer { 223 | shader, 224 | textures: Default::default(), 225 | view: Default::default(), 226 | vert_buf, 227 | vert_arr, 228 | frag_buf, 229 | frag_size, 230 | calls: Default::default(), 231 | paths: Default::default(), 232 | vertexes: Default::default(), 233 | uniforms: Default::default(), 234 | }) 235 | } 236 | } 237 | 238 | unsafe fn set_uniforms(&self, offset: usize, img: Option) { 239 | gl::BindBufferRange( 240 | gl::UNIFORM_BUFFER, 241 | 0, 242 | self.frag_buf, 243 | (offset * self.frag_size) as isize, 244 | std::mem::size_of::() as isize, 245 | ); 246 | 247 | if let Some(img) = img { 248 | if let Some(texture) = self.textures.get(img) { 249 | gl::BindTexture(gl::TEXTURE_2D, texture.tex); 250 | } 251 | } else { 252 | gl::BindTexture(gl::TEXTURE_2D, 0); 253 | } 254 | } 255 | 256 | unsafe fn do_fill(&self, call: &Call) { 257 | let paths = &self.paths[call.path_offset..call.path_offset + call.path_count]; 258 | 259 | gl::Enable(gl::STENCIL_TEST); 260 | gl::StencilMask(0xff); 261 | gl::StencilFunc(gl::ALWAYS, 0, 0xff); 262 | gl::ColorMask(gl::FALSE, gl::FALSE, gl::FALSE, gl::FALSE); 263 | 264 | self.set_uniforms(call.uniform_offset, call.image); 265 | 266 | gl::StencilOpSeparate(gl::FRONT, gl::KEEP, gl::KEEP, gl::INCR_WRAP); 267 | gl::StencilOpSeparate(gl::BACK, gl::KEEP, gl::KEEP, gl::DECR_WRAP); 268 | gl::Disable(gl::CULL_FACE); 269 | for path in paths { 270 | gl::DrawArrays( 271 | gl::TRIANGLE_FAN, 272 | path.fill_offset as i32, 273 | path.fill_count as i32, 274 | ); 275 | } 276 | gl::Enable(gl::CULL_FACE); 277 | 278 | gl::ColorMask(gl::TRUE, gl::TRUE, gl::TRUE, gl::TRUE); 279 | 280 | self.set_uniforms(call.uniform_offset + 1, call.image); 281 | 282 | gl::StencilFunc(gl::EQUAL, 0x00, 0xff); 283 | gl::StencilOp(gl::KEEP, gl::KEEP, gl::KEEP); 284 | for path in paths { 285 | gl::DrawArrays( 286 | gl::TRIANGLE_STRIP, 287 | path.stroke_offset as i32, 288 | path.stroke_count as i32, 289 | ); 290 | } 291 | 292 | gl::StencilFunc(gl::NOTEQUAL, 0x00, 0xff); 293 | gl::StencilOp(gl::ZERO, gl::ZERO, gl::ZERO); 294 | gl::DrawArrays( 295 | gl::TRIANGLE_STRIP, 296 | call.triangle_offset as i32, 297 | call.triangle_count as i32, 298 | ); 299 | 300 | gl::Disable(gl::STENCIL_TEST); 301 | } 302 | 303 | unsafe fn do_convex_fill(&self, call: &Call) { 304 | let paths = &self.paths[call.path_offset..call.path_offset + call.path_count]; 305 | self.set_uniforms(call.uniform_offset, call.image); 306 | for path in paths { 307 | gl::DrawArrays( 308 | gl::TRIANGLE_FAN, 309 | path.fill_offset as i32, 310 | path.fill_count as i32, 311 | ); 312 | if path.stroke_count > 0 { 313 | gl::DrawArrays( 314 | gl::TRIANGLE_STRIP, 315 | path.stroke_offset as i32, 316 | path.stroke_count as i32, 317 | ); 318 | } 319 | } 320 | } 321 | 322 | unsafe fn do_stroke(&self, call: &Call) { 323 | let paths = &self.paths[call.path_offset..call.path_offset + call.path_count]; 324 | 325 | gl::Enable(gl::STENCIL_TEST); 326 | gl::StencilMask(0xff); 327 | gl::StencilFunc(gl::EQUAL, 0x0, 0xff); 328 | gl::StencilOp(gl::KEEP, gl::KEEP, gl::INCR); 329 | self.set_uniforms(call.uniform_offset + 1, call.image); 330 | for path in paths { 331 | gl::DrawArrays( 332 | gl::TRIANGLE_STRIP, 333 | path.stroke_offset as i32, 334 | path.stroke_count as i32, 335 | ); 336 | } 337 | 338 | self.set_uniforms(call.uniform_offset, call.image); 339 | gl::StencilFunc(gl::EQUAL, 0x0, 0xff); 340 | gl::StencilOp(gl::KEEP, gl::KEEP, gl::KEEP); 341 | for path in paths { 342 | gl::DrawArrays( 343 | gl::TRIANGLE_STRIP, 344 | path.stroke_offset as i32, 345 | path.stroke_count as i32, 346 | ); 347 | } 348 | 349 | gl::ColorMask(gl::FALSE, gl::FALSE, gl::FALSE, gl::FALSE); 350 | gl::StencilFunc(gl::ALWAYS, 0x0, 0xff); 351 | gl::StencilOp(gl::ZERO, gl::ZERO, gl::ZERO); 352 | for path in paths { 353 | gl::DrawArrays( 354 | gl::TRIANGLE_STRIP, 355 | path.stroke_offset as i32, 356 | path.stroke_count as i32, 357 | ); 358 | } 359 | gl::ColorMask(gl::TRUE, gl::TRUE, gl::TRUE, gl::TRUE); 360 | 361 | gl::Disable(gl::STENCIL_TEST); 362 | } 363 | 364 | unsafe fn do_triangles(&self, call: &Call) { 365 | self.set_uniforms(call.uniform_offset, call.image); 366 | gl::DrawArrays( 367 | gl::TRIANGLES, 368 | call.triangle_offset as i32, 369 | call.triangle_count as i32, 370 | ); 371 | } 372 | 373 | fn convert_paint( 374 | &self, 375 | paint: &Paint, 376 | scissor: &Scissor, 377 | width: f32, 378 | fringe: f32, 379 | stroke_thr: f32, 380 | ) -> FragUniforms { 381 | let mut frag = FragUniforms { 382 | scissor_mat: Default::default(), 383 | paint_mat: Default::default(), 384 | inner_color: premul_color(paint.inner_color), 385 | outer_color: premul_color(paint.outer_color), 386 | scissor_ext: Default::default(), 387 | scissor_scale: Default::default(), 388 | extent: Default::default(), 389 | radius: 0.0, 390 | feather: 0.0, 391 | stroke_mult: 0.0, 392 | stroke_thr, 393 | tex_type: 0, 394 | type_: 0, 395 | }; 396 | 397 | if scissor.extent.width < -0.5 || scissor.extent.height < -0.5 { 398 | frag.scissor_ext[0] = 1.0; 399 | frag.scissor_ext[1] = 1.0; 400 | frag.scissor_scale[0] = 1.0; 401 | frag.scissor_scale[1] = 1.0; 402 | } else { 403 | frag.scissor_mat = xform_to_3x4(scissor.xform.inverse()); 404 | frag.scissor_ext[0] = scissor.extent.width; 405 | frag.scissor_ext[1] = scissor.extent.height; 406 | frag.scissor_scale[0] = (scissor.xform.0[0] * scissor.xform.0[0] 407 | + scissor.xform.0[2] * scissor.xform.0[2]) 408 | .sqrt() 409 | / fringe; 410 | frag.scissor_scale[1] = (scissor.xform.0[1] * scissor.xform.0[1] 411 | + scissor.xform.0[3] * scissor.xform.0[3]) 412 | .sqrt() 413 | / fringe; 414 | } 415 | 416 | frag.extent = [paint.extent.width, paint.extent.height]; 417 | frag.stroke_mult = (width * 0.5 + fringe * 0.5) / fringe; 418 | 419 | let mut invxform = Transform::default(); 420 | 421 | if let Some(img) = paint.image { 422 | if let Some(texture) = self.textures.get(img) { 423 | if texture.flags.contains(ImageFlags::FLIPY) { 424 | let m1 = Transform::translate(0.0, frag.extent[1] * 0.5) * paint.xform; 425 | let m2 = Transform::scale(1.0, -1.0) * m1; 426 | let m1 = Transform::translate(0.0, -frag.extent[1] * 0.5) * m2; 427 | invxform = m1.inverse(); 428 | } else { 429 | invxform = paint.xform.inverse(); 430 | }; 431 | 432 | frag.type_ = ShaderType::FillImage as i32; 433 | match texture.texture_type { 434 | TextureType::RGBA => { 435 | frag.tex_type = if texture.flags.contains(ImageFlags::PREMULTIPLIED) { 436 | 0 437 | } else { 438 | 1 439 | } 440 | } 441 | TextureType::Alpha => frag.tex_type = 2, 442 | } 443 | } 444 | } else { 445 | frag.type_ = ShaderType::FillGradient as i32; 446 | frag.radius = paint.radius; 447 | frag.feather = paint.feather; 448 | invxform = paint.xform.inverse(); 449 | } 450 | 451 | frag.paint_mat = xform_to_3x4(invxform); 452 | 453 | frag 454 | } 455 | 456 | fn append_uniforms(&mut self, uniforms: FragUniforms) { 457 | self.uniforms 458 | .resize(self.uniforms.len() + self.frag_size, 0); 459 | unsafe { 460 | let idx = self.uniforms.len() - self.frag_size; 461 | let p = self.uniforms.as_mut_ptr().add(idx) as *mut FragUniforms; 462 | *p = uniforms; 463 | } 464 | } 465 | } 466 | 467 | impl renderer::Renderer for Renderer { 468 | fn edge_antialias(&self) -> bool { 469 | true 470 | } 471 | 472 | fn create_texture( 473 | &mut self, 474 | texture_type: TextureType, 475 | width: usize, 476 | height: usize, 477 | flags: ImageFlags, 478 | data: Option<&[u8]>, 479 | ) -> anyhow::Result { 480 | let tex = unsafe { 481 | let mut tex: gl::types::GLuint = std::mem::zeroed(); 482 | gl::GenTextures(1, &mut tex); 483 | gl::BindTexture(gl::TEXTURE_2D, tex); 484 | gl::PixelStorei(gl::UNPACK_ALIGNMENT, 1); 485 | 486 | match texture_type { 487 | TextureType::RGBA => { 488 | gl::TexImage2D( 489 | gl::TEXTURE_2D, 490 | 0, 491 | gl::RGBA as i32, 492 | width as i32, 493 | height as i32, 494 | 0, 495 | gl::RGBA, 496 | gl::UNSIGNED_BYTE, 497 | match data { 498 | Some(data) => data.as_ptr() as *const c_void, 499 | None => std::ptr::null(), 500 | }, 501 | ); 502 | } 503 | TextureType::Alpha => { 504 | gl::TexImage2D( 505 | gl::TEXTURE_2D, 506 | 0, 507 | gl::R8 as i32, 508 | width as i32, 509 | height as i32, 510 | 0, 511 | gl::RED, 512 | gl::UNSIGNED_BYTE, 513 | match data { 514 | Some(data) => data.as_ptr() as *const c_void, 515 | None => std::ptr::null(), 516 | }, 517 | ); 518 | } 519 | } 520 | 521 | if flags.contains(ImageFlags::GENERATE_MIPMAPS) { 522 | if flags.contains(ImageFlags::NEAREST) { 523 | gl::TexParameteri( 524 | gl::TEXTURE_2D, 525 | gl::TEXTURE_MIN_FILTER, 526 | gl::NEAREST_MIPMAP_NEAREST as i32, 527 | ); 528 | } else { 529 | gl::TexParameteri( 530 | gl::TEXTURE_2D, 531 | gl::TEXTURE_MIN_FILTER, 532 | gl::LINEAR_MIPMAP_LINEAR as i32, 533 | ); 534 | } 535 | } else { 536 | if flags.contains(ImageFlags::NEAREST) { 537 | gl::TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_MIN_FILTER, gl::NEAREST as i32); 538 | } else { 539 | gl::TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_MIN_FILTER, gl::LINEAR as i32); 540 | } 541 | } 542 | 543 | if flags.contains(ImageFlags::NEAREST) { 544 | gl::TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_MAG_FILTER, gl::NEAREST as i32); 545 | } else { 546 | gl::TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_MAG_FILTER, gl::LINEAR as i32); 547 | } 548 | 549 | if flags.contains(ImageFlags::REPEATX) { 550 | gl::TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_WRAP_S, gl::REPEAT as i32); 551 | } else { 552 | gl::TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_WRAP_S, gl::CLAMP_TO_EDGE as i32); 553 | } 554 | 555 | if flags.contains(ImageFlags::REPEATY) { 556 | gl::TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_WRAP_T, gl::REPEAT as i32); 557 | } else { 558 | gl::TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_WRAP_T, gl::CLAMP_TO_EDGE as i32); 559 | } 560 | 561 | gl::PixelStorei(gl::UNPACK_ALIGNMENT, 4); 562 | 563 | if flags.contains(ImageFlags::GENERATE_MIPMAPS) { 564 | gl::GenerateMipmap(gl::TEXTURE_2D); 565 | } 566 | 567 | gl::BindTexture(gl::TEXTURE_2D, 0); 568 | tex 569 | }; 570 | 571 | let id = self.textures.insert(Texture { 572 | tex, 573 | width, 574 | height, 575 | texture_type, 576 | flags, 577 | }); 578 | Ok(id) 579 | } 580 | 581 | fn delete_texture(&mut self, img: ImageId) -> anyhow::Result<()> { 582 | if let Some(texture) = self.textures.get(img) { 583 | unsafe { gl::DeleteTextures(1, &texture.tex) } 584 | self.textures.remove(img); 585 | Ok(()) 586 | } else { 587 | bail!("texture '{}' not found", img); 588 | } 589 | } 590 | 591 | fn update_texture( 592 | &mut self, 593 | img: ImageId, 594 | x: usize, 595 | y: usize, 596 | width: usize, 597 | height: usize, 598 | data: &[u8], 599 | ) -> anyhow::Result<()> { 600 | if let Some(texture) = self.textures.get(img) { 601 | unsafe { 602 | gl::BindTexture(gl::TEXTURE_2D, texture.tex); 603 | gl::PixelStorei(gl::UNPACK_ALIGNMENT, 1); 604 | 605 | match texture.texture_type { 606 | TextureType::RGBA => gl::TexSubImage2D( 607 | gl::TEXTURE_2D, 608 | 0, 609 | x as i32, 610 | y as i32, 611 | width as i32, 612 | height as i32, 613 | gl::RGBA, 614 | gl::UNSIGNED_BYTE, 615 | data.as_ptr() as *const c_void, 616 | ), 617 | TextureType::Alpha => gl::TexSubImage2D( 618 | gl::TEXTURE_2D, 619 | 0, 620 | x as i32, 621 | y as i32, 622 | width as i32, 623 | height as i32, 624 | gl::RED, 625 | gl::UNSIGNED_BYTE, 626 | data.as_ptr() as *const c_void, 627 | ), 628 | } 629 | 630 | gl::PixelStorei(gl::UNPACK_ALIGNMENT, 4); 631 | gl::BindTexture(gl::TEXTURE_2D, 0); 632 | } 633 | Ok(()) 634 | } else { 635 | bail!("texture '{}' not found", img); 636 | } 637 | } 638 | 639 | fn texture_size(&self, img: ImageId) -> anyhow::Result<(usize, usize)> { 640 | if let Some(texture) = self.textures.get(img) { 641 | Ok((texture.width, texture.height)) 642 | } else { 643 | bail!("texture '{}' not found", img); 644 | } 645 | } 646 | 647 | fn viewport(&mut self, extent: Extent, _device_pixel_ratio: f32) -> anyhow::Result<()> { 648 | self.view = extent; 649 | Ok(()) 650 | } 651 | 652 | fn cancel(&mut self) -> anyhow::Result<()> { 653 | self.vertexes.clear(); 654 | self.paths.clear(); 655 | self.calls.clear(); 656 | self.uniforms.clear(); 657 | Ok(()) 658 | } 659 | 660 | fn flush(&mut self) -> anyhow::Result<()> { 661 | if !self.calls.is_empty() { 662 | unsafe { 663 | gl::UseProgram(self.shader.prog); 664 | 665 | gl::Enable(gl::CULL_FACE); 666 | gl::CullFace(gl::BACK); 667 | gl::FrontFace(gl::CCW); 668 | gl::Enable(gl::BLEND); 669 | gl::Disable(gl::DEPTH_TEST); 670 | gl::Disable(gl::SCISSOR_TEST); 671 | gl::ColorMask(gl::TRUE, gl::TRUE, gl::TRUE, gl::TRUE); 672 | gl::StencilMask(0xffffffff); 673 | gl::StencilOp(gl::KEEP, gl::KEEP, gl::KEEP); 674 | gl::StencilFunc(gl::ALWAYS, 0, 0xffffffff); 675 | gl::ActiveTexture(gl::TEXTURE0); 676 | gl::BindTexture(gl::TEXTURE_2D, 0); 677 | 678 | gl::BindBuffer(gl::UNIFORM_BUFFER, self.frag_buf); 679 | gl::BufferData( 680 | gl::UNIFORM_BUFFER, 681 | self.uniforms.len() as isize, 682 | self.uniforms.as_ptr() as *const c_void, 683 | gl::STREAM_DRAW, 684 | ); 685 | 686 | gl::BindVertexArray(self.vert_arr); 687 | gl::BindBuffer(gl::ARRAY_BUFFER, self.vert_buf); 688 | gl::BufferData( 689 | gl::ARRAY_BUFFER, 690 | (self.vertexes.len() * std::mem::size_of::()) as isize, 691 | self.vertexes.as_ptr() as *const c_void, 692 | gl::STREAM_DRAW, 693 | ); 694 | gl::EnableVertexAttribArray(0); 695 | gl::EnableVertexAttribArray(1); 696 | gl::VertexAttribPointer( 697 | 0, 698 | 2, 699 | gl::FLOAT, 700 | gl::FALSE, 701 | std::mem::size_of::() as i32, 702 | std::ptr::null(), 703 | ); 704 | gl::VertexAttribPointer( 705 | 1, 706 | 2, 707 | gl::FLOAT, 708 | gl::FALSE, 709 | std::mem::size_of::() as i32, 710 | (2 * std::mem::size_of::()) as *const c_void, 711 | ); 712 | 713 | gl::Uniform1i(self.shader.loc_tex, 0); 714 | gl::Uniform2fv( 715 | self.shader.loc_viewsize, 716 | 1, 717 | &self.view as *const Extent as *const f32, 718 | ); 719 | 720 | gl::BindBuffer(gl::UNIFORM_BUFFER, self.frag_buf); 721 | 722 | for call in &self.calls { 723 | let blend = &call.blend_func; 724 | 725 | gl::BlendFuncSeparate( 726 | blend.src_rgb, 727 | blend.dst_rgb, 728 | blend.src_alpha, 729 | blend.dst_alpha, 730 | ); 731 | 732 | match call.call_type { 733 | CallType::Fill => self.do_fill(&call), 734 | CallType::ConvexFill => self.do_convex_fill(&call), 735 | CallType::Stroke => self.do_stroke(&call), 736 | CallType::Triangles => self.do_triangles(&call), 737 | } 738 | } 739 | 740 | gl::DisableVertexAttribArray(0); 741 | gl::DisableVertexAttribArray(1); 742 | gl::BindVertexArray(0); 743 | gl::Disable(gl::CULL_FACE); 744 | gl::BindBuffer(gl::ARRAY_BUFFER, 0); 745 | gl::UseProgram(0); 746 | gl::BindTexture(gl::TEXTURE_2D, 0); 747 | } 748 | } 749 | 750 | self.vertexes.clear(); 751 | self.paths.clear(); 752 | self.calls.clear(); 753 | self.uniforms.clear(); 754 | Ok(()) 755 | } 756 | 757 | fn fill( 758 | &mut self, 759 | paint: &Paint, 760 | composite_operation: CompositeOperationState, 761 | scissor: &Scissor, 762 | fringe: f32, 763 | bounds: Bounds, 764 | paths: &[Path], 765 | ) -> anyhow::Result<()> { 766 | let mut call = Call { 767 | call_type: CallType::Fill, 768 | image: paint.image, 769 | path_offset: self.paths.len(), 770 | path_count: paths.len(), 771 | triangle_offset: 0, 772 | triangle_count: 4, 773 | uniform_offset: 0, 774 | blend_func: composite_operation.into(), 775 | }; 776 | 777 | if paths.len() == 1 && paths[0].convex { 778 | call.call_type = CallType::ConvexFill; 779 | } 780 | 781 | let mut offset = self.vertexes.len(); 782 | for path in paths { 783 | let fill = path.get_fill(); 784 | let mut gl_path = GLPath { 785 | fill_offset: 0, 786 | fill_count: 0, 787 | stroke_offset: 0, 788 | stroke_count: 0, 789 | }; 790 | 791 | if !fill.is_empty() { 792 | gl_path.fill_offset = offset; 793 | gl_path.fill_count = fill.len(); 794 | self.vertexes.extend(fill); 795 | offset += fill.len(); 796 | } 797 | 798 | let stroke = path.get_stroke(); 799 | if !stroke.is_empty() { 800 | gl_path.stroke_offset = offset; 801 | gl_path.stroke_count = stroke.len(); 802 | self.vertexes.extend(stroke); 803 | offset += stroke.len(); 804 | } 805 | 806 | self.paths.push(gl_path); 807 | } 808 | 809 | if call.call_type == CallType::Fill { 810 | call.triangle_offset = offset; 811 | self.vertexes 812 | .push(Vertex::new(bounds.max.x, bounds.max.y, 0.5, 1.0)); 813 | self.vertexes 814 | .push(Vertex::new(bounds.max.x, bounds.min.y, 0.5, 1.0)); 815 | self.vertexes 816 | .push(Vertex::new(bounds.min.x, bounds.max.y, 0.5, 1.0)); 817 | self.vertexes 818 | .push(Vertex::new(bounds.min.x, bounds.min.y, 0.5, 1.0)); 819 | 820 | call.uniform_offset = self.uniforms.len() / self.frag_size; 821 | self.append_uniforms(FragUniforms { 822 | stroke_thr: -1.0, 823 | type_: ShaderType::Simple as i32, 824 | ..FragUniforms::default() 825 | }); 826 | self.append_uniforms(self.convert_paint(paint, scissor, fringe, fringe, -1.0)); 827 | } else { 828 | call.uniform_offset = self.uniforms.len() / self.frag_size; 829 | self.append_uniforms(self.convert_paint(paint, scissor, fringe, fringe, -1.0)); 830 | } 831 | 832 | self.calls.push(call); 833 | Ok(()) 834 | } 835 | 836 | fn stroke( 837 | &mut self, 838 | paint: &Paint, 839 | composite_operation: CompositeOperationState, 840 | scissor: &Scissor, 841 | fringe: f32, 842 | stroke_width: f32, 843 | paths: &[Path], 844 | ) -> anyhow::Result<()> { 845 | let mut call = Call { 846 | call_type: CallType::Stroke, 847 | image: paint.image, 848 | path_offset: self.paths.len(), 849 | path_count: paths.len(), 850 | triangle_offset: 0, 851 | triangle_count: 0, 852 | uniform_offset: 0, 853 | blend_func: composite_operation.into(), 854 | }; 855 | 856 | let mut offset = self.vertexes.len(); 857 | for path in paths { 858 | let mut gl_path = GLPath { 859 | fill_offset: 0, 860 | fill_count: 0, 861 | stroke_offset: 0, 862 | stroke_count: 0, 863 | }; 864 | 865 | let stroke = path.get_stroke(); 866 | if !stroke.is_empty() { 867 | gl_path.stroke_offset = offset; 868 | gl_path.stroke_count = stroke.len(); 869 | self.vertexes.extend(stroke); 870 | offset += stroke.len(); 871 | self.paths.push(gl_path); 872 | } 873 | } 874 | 875 | call.uniform_offset = self.uniforms.len() / self.frag_size; 876 | self.append_uniforms(self.convert_paint(paint, scissor, stroke_width, fringe, -1.0)); 877 | self.append_uniforms(self.convert_paint( 878 | paint, 879 | scissor, 880 | stroke_width, 881 | fringe, 882 | 1.0 - 0.5 / 255.0, 883 | )); 884 | 885 | self.calls.push(call); 886 | Ok(()) 887 | } 888 | 889 | fn triangles( 890 | &mut self, 891 | paint: &Paint, 892 | composite_operation: CompositeOperationState, 893 | scissor: &Scissor, 894 | vertexes: &[Vertex], 895 | ) -> anyhow::Result<()> { 896 | let call = Call { 897 | call_type: CallType::Triangles, 898 | image: paint.image, 899 | path_offset: 0, 900 | path_count: 0, 901 | triangle_offset: self.vertexes.len(), 902 | triangle_count: vertexes.len(), 903 | uniform_offset: self.uniforms.len() / self.frag_size, 904 | blend_func: composite_operation.into(), 905 | }; 906 | 907 | self.calls.push(call); 908 | self.vertexes.extend(vertexes); 909 | 910 | let mut uniforms = self.convert_paint(paint, scissor, 1.0, 1.0, -1.0); 911 | uniforms.type_ = ShaderType::Image as i32; 912 | self.append_uniforms(uniforms); 913 | Ok(()) 914 | } 915 | } 916 | 917 | fn shader_error(shader: gl::types::GLuint, filename: &str) -> anyhow::Error { 918 | unsafe { 919 | let mut data: [gl::types::GLchar; 512 + 1] = std::mem::zeroed(); 920 | let mut len: gl::types::GLsizei = std::mem::zeroed(); 921 | gl::GetShaderInfoLog(shader, 512, &mut len, data.as_mut_ptr()); 922 | if len > 512 { 923 | len = 512; 924 | } 925 | data[len as usize] = 0; 926 | let err_msg = std::ffi::CStr::from_ptr(data.as_ptr()); 927 | anyhow!( 928 | "failed to compile shader: {}: {}", 929 | filename, 930 | err_msg.to_string_lossy() 931 | ) 932 | } 933 | } 934 | 935 | fn program_error(prog: gl::types::GLuint) -> anyhow::Error { 936 | unsafe { 937 | let mut data: [gl::types::GLchar; 512 + 1] = std::mem::zeroed(); 938 | let mut len: gl::types::GLsizei = std::mem::zeroed(); 939 | gl::GetProgramInfoLog(prog, 512, &mut len, data.as_mut_ptr()); 940 | if len > 512 { 941 | len = 512; 942 | } 943 | data[len as usize] = 0; 944 | let err_msg = std::ffi::CStr::from_ptr(data.as_ptr()); 945 | anyhow!("failed to link program: {}", err_msg.to_string_lossy()) 946 | } 947 | } 948 | 949 | fn convert_blend_factor(factor: BlendFactor) -> gl::types::GLenum { 950 | match factor { 951 | BlendFactor::Zero => gl::ZERO, 952 | BlendFactor::One => gl::ONE, 953 | BlendFactor::SrcColor => gl::SRC_COLOR, 954 | BlendFactor::OneMinusSrcColor => gl::ONE_MINUS_SRC_COLOR, 955 | BlendFactor::DstColor => gl::DST_COLOR, 956 | BlendFactor::OneMinusDstColor => gl::ONE_MINUS_DST_COLOR, 957 | BlendFactor::SrcAlpha => gl::SRC_ALPHA, 958 | BlendFactor::OneMinusSrcAlpha => gl::ONE_MINUS_SRC_ALPHA, 959 | BlendFactor::DstAlpha => gl::DST_ALPHA, 960 | BlendFactor::OneMinusDstAlpha => gl::ONE_MINUS_DST_ALPHA, 961 | BlendFactor::SrcAlphaSaturate => gl::SRC_ALPHA_SATURATE, 962 | } 963 | } 964 | 965 | #[inline] 966 | fn premul_color(color: Color) -> Color { 967 | Color { 968 | r: color.r * color.a, 969 | g: color.g * color.a, 970 | b: color.b * color.a, 971 | a: color.a, 972 | } 973 | } 974 | 975 | #[inline] 976 | fn xform_to_3x4(xform: Transform) -> [f32; 12] { 977 | let mut m = [0f32; 12]; 978 | let t = &xform.0; 979 | m[0] = t[0]; 980 | m[1] = t[1]; 981 | m[2] = 0.0; 982 | m[3] = 0.0; 983 | m[4] = t[2]; 984 | m[5] = t[3]; 985 | m[6] = 0.0; 986 | m[7] = 0.0; 987 | m[8] = t[4]; 988 | m[9] = t[5]; 989 | m[10] = 1.0; 990 | m[11] = 0.0; 991 | m 992 | } 993 | -------------------------------------------------------------------------------- /src/cache.rs: -------------------------------------------------------------------------------- 1 | use crate::context::{Command, Path, Vertex}; 2 | use crate::{Bounds, LineCap, LineJoin, Point, Solidity}; 3 | use clamped::Clamp; 4 | use rawpointer::ptrdistance; 5 | use std::f32::consts::PI; 6 | 7 | bitflags! { 8 | #[derive(Default)] 9 | struct PointFlags: u32 { 10 | const PT_CORNER = 0x1; 11 | const PT_LEFT = 0x2; 12 | const PT_BEVEL = 0x4; 13 | const PR_INNERBEVEL = 0x8; 14 | } 15 | } 16 | 17 | #[derive(Debug, Default, Copy, Clone)] 18 | pub(crate) struct VPoint { 19 | xy: Point, 20 | d: Point, 21 | len: f32, 22 | dm: Point, 23 | flags: PointFlags, 24 | } 25 | 26 | #[derive(Default)] 27 | pub(crate) struct PathCache { 28 | pub(crate) points: Vec, 29 | pub(crate) paths: Vec, 30 | pub(crate) vertexes: Vec, 31 | pub(crate) bounds: Bounds, 32 | } 33 | 34 | impl PathCache { 35 | pub fn clear(&mut self) { 36 | self.points.clear(); 37 | self.paths.clear(); 38 | } 39 | 40 | fn add_path(&mut self) -> &mut Path { 41 | self.paths.push(Path { 42 | first: self.points.len(), 43 | count: 0, 44 | closed: false, 45 | num_bevel: 0, 46 | solidity: Solidity::Solid, 47 | fill: std::ptr::null_mut(), 48 | num_fill: 0, 49 | stroke: std::ptr::null_mut(), 50 | num_stroke: 0, 51 | convex: false, 52 | }); 53 | self.paths.last_mut().unwrap() 54 | } 55 | 56 | fn add_point(&mut self, pt: Point, flags: PointFlags, dist_tol: f32) { 57 | if let Some(path) = self.paths.last_mut() { 58 | if let Some(last_pt) = self.points.last_mut() { 59 | if path.count > 0 { 60 | if last_pt.xy.equals(pt, dist_tol) { 61 | last_pt.flags |= flags; 62 | return; 63 | } 64 | } 65 | } 66 | 67 | self.points.push(VPoint { 68 | xy: pt, 69 | d: Default::default(), 70 | len: 0.0, 71 | dm: Default::default(), 72 | flags, 73 | }); 74 | path.count += 1; 75 | } 76 | } 77 | 78 | fn close_path(&mut self) { 79 | if let Some(path) = self.paths.last_mut() { 80 | path.closed = true; 81 | } 82 | } 83 | 84 | fn path_solidity(&mut self, solidity: Solidity) { 85 | if let Some(path) = self.paths.last_mut() { 86 | path.solidity = solidity; 87 | } 88 | } 89 | 90 | unsafe fn alloc_temp_vertexes(&mut self, count: usize) -> *mut Vertex { 91 | self.vertexes.resize(count, Default::default()); 92 | if self.vertexes.is_empty() { 93 | return std::ptr::null_mut(); 94 | } 95 | &mut self.vertexes[0] as *mut Vertex 96 | } 97 | 98 | fn tesselate_bezier( 99 | &mut self, 100 | pt1: Point, 101 | pt2: Point, 102 | pt3: Point, 103 | pt4: Point, 104 | level: usize, 105 | flags: PointFlags, 106 | tess_tol: f32, 107 | ) { 108 | if level > 10 { 109 | return; 110 | } 111 | 112 | let Point { x: x1, y: y1 } = pt1; 113 | let Point { x: x2, y: y2 } = pt2; 114 | let Point { x: x3, y: y3 } = pt3; 115 | let Point { x: x4, y: y4 } = pt4; 116 | 117 | let x12 = (x1 + x2) * 0.5; 118 | let y12 = (y1 + y2) * 0.5; 119 | let x23 = (x2 + x3) * 0.5; 120 | let y23 = (y2 + y3) * 0.5; 121 | let x34 = (x3 + x4) * 0.5; 122 | let y34 = (y3 + y4) * 0.5; 123 | let x123 = (x12 + x23) * 0.5; 124 | let y123 = (y12 + y23) * 0.5; 125 | 126 | let dx = x4 - x1; 127 | let dy = y4 - y1; 128 | let d2 = ((x2 - x4) * dy - (y2 - y4) * dx).abs(); 129 | let d3 = ((x3 - x4) * dy - (y3 - y4) * dx).abs(); 130 | 131 | if (d2 + d3) * (d2 + d3) < tess_tol * (dx * dx + dy * dy) { 132 | self.add_point(Point::new(x4, y4), flags, tess_tol); 133 | return; 134 | } 135 | 136 | let x234 = (x23 + x34) * 0.5; 137 | let y234 = (y23 + y34) * 0.5; 138 | let x1234 = (x123 + x234) * 0.5; 139 | let y1234 = (y123 + y234) * 0.5; 140 | 141 | self.tesselate_bezier( 142 | Point::new(x1, y1), 143 | Point::new(x12, y12), 144 | Point::new(x123, y123), 145 | Point::new(x1234, y1234), 146 | level + 1, 147 | PointFlags::empty(), 148 | tess_tol, 149 | ); 150 | self.tesselate_bezier( 151 | Point::new(x1234, y1234), 152 | Point::new(x234, y234), 153 | Point::new(x34, y34), 154 | Point::new(x4, y4), 155 | level + 1, 156 | flags, 157 | tess_tol, 158 | ); 159 | } 160 | 161 | pub(crate) fn flatten_paths(&mut self, commands: &[Command], dist_tol: f32, tess_tol: f32) { 162 | for cmd in commands { 163 | match cmd { 164 | Command::MoveTo(pt) => { 165 | self.add_path(); 166 | self.add_point(*pt, PointFlags::PT_CORNER, dist_tol); 167 | } 168 | Command::LineTo(pt) => { 169 | self.add_point(*pt, PointFlags::PT_CORNER, dist_tol); 170 | } 171 | Command::BezierTo(cp1, cp2, pt) => { 172 | if let Some(last) = self.points.last().map(|pt| *pt) { 173 | self.tesselate_bezier( 174 | last.xy, 175 | *cp1, 176 | *cp2, 177 | *pt, 178 | 0, 179 | PointFlags::PT_CORNER, 180 | tess_tol, 181 | ); 182 | } 183 | } 184 | Command::Close => self.close_path(), 185 | Command::Solidity(solidity) => self.path_solidity(*solidity), 186 | } 187 | } 188 | 189 | self.bounds.min = Point::new(std::f32::MAX, std::f32::MAX); 190 | self.bounds.max = Point::new(std::f32::MIN, std::f32::MIN); 191 | 192 | unsafe { 193 | for j in 0..self.paths.len() { 194 | let path = &mut self.paths[j]; 195 | let pts = &mut self.points[path.first] as *mut VPoint; 196 | let mut p0 = pts.offset(path.count as isize - 1); 197 | let mut p1 = pts; 198 | 199 | if (*p0).xy.equals((*p1).xy, dist_tol) { 200 | if path.count > 0 { 201 | path.count -= 1; 202 | } 203 | p0 = pts.offset(path.count as isize - 1); 204 | path.closed = true; 205 | } 206 | 207 | if path.count > 2 { 208 | let area = poly_area(std::slice::from_raw_parts(pts, path.count)); 209 | if path.solidity == Solidity::Solid && area < 0.0 { 210 | poly_reverse(std::slice::from_raw_parts_mut(pts, path.count)); 211 | } 212 | if path.solidity == Solidity::Hole && area > 0.0 { 213 | poly_reverse(std::slice::from_raw_parts_mut(pts, path.count)); 214 | } 215 | } 216 | 217 | for _ in 0..path.count { 218 | (*p0).d.x = (*p1).xy.x - (*p0).xy.x; 219 | (*p0).d.y = (*p1).xy.y - (*p0).xy.y; 220 | (*p0).len = (*p0).d.normalize(); 221 | 222 | self.bounds.min.x = self.bounds.min.x.min((*p0).xy.x); 223 | self.bounds.min.y = self.bounds.min.y.min((*p0).xy.y); 224 | self.bounds.max.x = self.bounds.max.x.max((*p0).xy.x); 225 | self.bounds.max.y = self.bounds.max.y.max((*p0).xy.y); 226 | 227 | p0 = p1; 228 | p1 = p1.add(1); 229 | } 230 | } 231 | } 232 | } 233 | 234 | fn calculate_joins(&mut self, w: f32, line_join: LineJoin, miter_limit: f32) { 235 | let mut iw = 0.0; 236 | if w > 0.0 { 237 | iw = 1.0 / w; 238 | } 239 | 240 | unsafe { 241 | for i in 0..self.paths.len() { 242 | let path = &mut self.paths[i]; 243 | let pts = &mut self.points[path.first] as *mut VPoint; 244 | let mut p0 = pts.offset(path.count as isize - 1); 245 | let mut p1 = pts; 246 | let mut nleft = 0; 247 | 248 | path.num_bevel = 0; 249 | 250 | for _ in 0..path.count { 251 | let dlx0 = (*p0).d.y; 252 | let dly0 = -(*p0).d.x; 253 | let dlx1 = (*p1).d.y; 254 | let dly1 = -(*p1).d.x; 255 | 256 | (*p1).dm.x = (dlx0 + dlx1) * 0.5; 257 | (*p1).dm.y = (dly0 + dly1) * 0.5; 258 | let dmr2 = (*p1).dm.x * (*p1).dm.x + (*p1).dm.y * (*p1).dm.y; 259 | 260 | if dmr2 > 0.000001 { 261 | let mut scale = 1.0 / dmr2; 262 | if scale > 600.0 { 263 | scale = 600.0; 264 | } 265 | (*p1).dm.x *= scale; 266 | (*p1).dm.y *= scale; 267 | } 268 | 269 | (*p1).flags &= PointFlags::PT_CORNER; 270 | 271 | let cross = (*p1).d.x * (*p0).d.y - (*p0).d.x * (*p1).d.y; 272 | if cross > 0.0 { 273 | nleft += 1; 274 | (*p1).flags |= PointFlags::PT_LEFT; 275 | } 276 | 277 | let limit = (((*p0).len.min((*p1).len) as f32) * iw).max(1.01); 278 | if (dmr2 * limit * limit) < 1.0 { 279 | (*p1).flags |= PointFlags::PR_INNERBEVEL; 280 | } 281 | 282 | if (*p1).flags.contains(PointFlags::PT_CORNER) { 283 | if (dmr2 * miter_limit * miter_limit) < 1.0 284 | || line_join == LineJoin::Bevel 285 | || line_join == LineJoin::Round 286 | { 287 | (*p1).flags |= PointFlags::PT_BEVEL; 288 | } 289 | } 290 | 291 | if (*p1).flags.contains(PointFlags::PT_BEVEL) 292 | || (*p1).flags.contains(PointFlags::PR_INNERBEVEL) 293 | { 294 | path.num_bevel += 1; 295 | } 296 | 297 | p0 = p1; 298 | p1 = p1.add(1); 299 | } 300 | 301 | path.convex = nleft == path.count; 302 | } 303 | } 304 | } 305 | 306 | pub(crate) fn expand_stroke( 307 | &mut self, 308 | mut w: f32, 309 | fringe: f32, 310 | line_cap: LineCap, 311 | line_join: LineJoin, 312 | miter_limit: f32, 313 | tess_tol: f32, 314 | ) { 315 | let aa = fringe; 316 | let mut u0 = 0.0; 317 | let mut u1 = 1.0; 318 | let ncap = curve_divs(w, PI, tess_tol); 319 | 320 | w += aa * 0.5; 321 | 322 | if aa == 0.0 { 323 | u0 = 0.5; 324 | u1 = 0.5; 325 | } 326 | 327 | self.calculate_joins(w, line_join, miter_limit); 328 | 329 | let mut cverts = 0; 330 | for path in &self.paths { 331 | let loop_ = path.closed; 332 | if line_join == LineJoin::Round { 333 | cverts += (path.count + path.num_bevel * (ncap + 2) + 1) * 2; 334 | } else { 335 | cverts += (path.count + path.num_bevel * 5 + 1) * 2; 336 | if !loop_ { 337 | if line_cap == LineCap::Round { 338 | cverts += (ncap * 2 + 2) * 2; 339 | } else { 340 | cverts += (3 + 3) * 2; 341 | } 342 | } 343 | } 344 | } 345 | 346 | unsafe { 347 | let mut vertexes = self.alloc_temp_vertexes(cverts); 348 | if vertexes.is_null() { 349 | return; 350 | } 351 | 352 | for i in 0..self.paths.len() { 353 | let path = &mut self.paths[i]; 354 | let pts = &mut self.points[path.first] as *mut VPoint; 355 | 356 | path.fill = std::ptr::null_mut(); 357 | path.num_fill = 0; 358 | 359 | let loop_ = path.closed; 360 | let mut dst = vertexes; 361 | path.stroke = dst; 362 | 363 | let (mut p0, mut p1, s, e) = if loop_ { 364 | (pts.offset(path.count as isize - 1), pts, 0, path.count) 365 | } else { 366 | (pts, pts.add(1), 1, path.count - 1) 367 | }; 368 | 369 | if !loop_ { 370 | let mut d = Point::new((*p1).xy.x - (*p0).xy.x, (*p1).xy.y - (*p0).xy.y); 371 | d.normalize(); 372 | match line_cap { 373 | LineCap::Butt => { 374 | dst = butt_cap_start( 375 | dst, 376 | p0.as_mut().unwrap(), 377 | d.x, 378 | d.y, 379 | w, 380 | -aa * 0.5, 381 | aa, 382 | u0, 383 | u1, 384 | ) 385 | } 386 | LineCap::Square => { 387 | dst = butt_cap_start( 388 | dst, 389 | p0.as_mut().unwrap(), 390 | d.x, 391 | d.y, 392 | w, 393 | w - aa, 394 | aa, 395 | u0, 396 | u1, 397 | ) 398 | } 399 | LineCap::Round => { 400 | dst = round_cap_start( 401 | dst, 402 | p0.as_mut().unwrap(), 403 | d.x, 404 | d.y, 405 | w, 406 | ncap, 407 | aa, 408 | u0, 409 | u1, 410 | ) 411 | } 412 | } 413 | } 414 | 415 | for _ in s..e { 416 | if (*p1).flags.contains(PointFlags::PT_BEVEL) 417 | || (*p1).flags.contains(PointFlags::PR_INNERBEVEL) 418 | { 419 | if line_join == LineJoin::Round { 420 | dst = round_join( 421 | dst, 422 | p0.as_mut().unwrap(), 423 | p1.as_mut().unwrap(), 424 | w, 425 | w, 426 | u0, 427 | u1, 428 | ncap, 429 | aa, 430 | ); 431 | } else { 432 | dst = bevel_join( 433 | dst, 434 | p0.as_mut().unwrap(), 435 | p1.as_mut().unwrap(), 436 | w, 437 | w, 438 | u0, 439 | u1, 440 | aa, 441 | ); 442 | } 443 | } else { 444 | *dst = Vertex::new( 445 | (*p1).xy.x + ((*p1).dm.x * w), 446 | (*p1).xy.y + ((*p1).dm.y * w), 447 | u0, 448 | 1.0, 449 | ); 450 | dst = dst.add(1); 451 | 452 | *dst = Vertex::new( 453 | (*p1).xy.x - ((*p1).dm.x * w), 454 | (*p1).xy.y - ((*p1).dm.y * w), 455 | u1, 456 | 1.0, 457 | ); 458 | dst = dst.add(1); 459 | } 460 | p0 = p1; 461 | p1 = p1.add(1); 462 | } 463 | 464 | if loop_ { 465 | let v0 = vertexes; 466 | let v1 = vertexes.add(1); 467 | 468 | *dst = Vertex::new((*v0).x, (*v0).y, u0, 1.0); 469 | dst = dst.add(1); 470 | 471 | *dst = Vertex::new((*v1).x, (*v1).y, u1, 1.0); 472 | dst = dst.add(1); 473 | } else { 474 | let mut d = Point::new((*p1).xy.x - (*p0).xy.x, (*p1).xy.y - (*p0).xy.y); 475 | d.normalize(); 476 | match line_cap { 477 | LineCap::Butt => { 478 | dst = butt_cap_end( 479 | dst, 480 | p1.as_mut().unwrap(), 481 | d.x, 482 | d.y, 483 | w, 484 | -aa * 0.5, 485 | aa, 486 | u0, 487 | u1, 488 | ); 489 | } 490 | LineCap::Round => { 491 | dst = butt_cap_end( 492 | dst, 493 | p1.as_mut().unwrap(), 494 | d.x, 495 | d.y, 496 | w, 497 | w - aa, 498 | aa, 499 | u0, 500 | u1, 501 | ); 502 | } 503 | LineCap::Square => { 504 | dst = round_cap_end( 505 | dst, 506 | p1.as_mut().unwrap(), 507 | d.x, 508 | d.y, 509 | w, 510 | ncap, 511 | aa, 512 | u0, 513 | u1, 514 | ); 515 | } 516 | } 517 | } 518 | 519 | path.num_stroke = ptrdistance(vertexes, dst); 520 | vertexes = dst; 521 | } 522 | } 523 | } 524 | 525 | pub(crate) fn expand_fill( 526 | &mut self, 527 | w: f32, 528 | line_join: LineJoin, 529 | miter_limit: f32, 530 | fringe_width: f32, 531 | ) { 532 | let aa = fringe_width; 533 | let fringe = w > 0.0; 534 | 535 | self.calculate_joins(w, line_join, miter_limit); 536 | 537 | let mut cverts = 0; 538 | for path in &self.paths { 539 | cverts += path.count + path.num_bevel + 1; 540 | if fringe { 541 | cverts += (path.count + path.num_bevel * 5 + 1) * 2; 542 | } 543 | } 544 | 545 | unsafe { 546 | let mut vertexes = self.alloc_temp_vertexes(cverts); 547 | if vertexes.is_null() { 548 | return; 549 | } 550 | 551 | let convex = self.paths.len() == 1 && self.paths[0].convex; 552 | 553 | for i in 0..self.paths.len() { 554 | let path = &mut self.paths[i]; 555 | let pts = &mut self.points[path.first] as *mut VPoint; 556 | let woff = 0.5 * aa; 557 | let mut dst = vertexes; 558 | 559 | path.fill = dst; 560 | 561 | if fringe { 562 | let mut p0 = pts.offset(path.count as isize - 1); 563 | let mut p1 = pts; 564 | for _ in 0..path.count { 565 | if (*p1).flags.contains(PointFlags::PT_BEVEL) { 566 | let dlx0 = (*p0).d.y; 567 | let dly0 = -(*p0).d.x; 568 | let dlx1 = (*p1).d.y; 569 | let dly1 = -(*p1).d.x; 570 | if (*p1).flags.contains(PointFlags::PT_LEFT) { 571 | let lx = (*p1).xy.x + (*p1).dm.x * woff; 572 | let ly = (*p1).xy.y + (*p1).dm.y * woff; 573 | *dst = Vertex::new(lx, ly, 0.5, 1.0); 574 | dst = dst.add(1); 575 | } else { 576 | let lx0 = (*p1).xy.x + dlx0 * woff; 577 | let ly0 = (*p1).xy.y + dly0 * woff; 578 | let lx1 = (*p1).xy.x + dlx1 * woff; 579 | let ly1 = (*p1).xy.y + dly1 * woff; 580 | 581 | *dst = Vertex::new(lx0, ly0, 0.5, 1.0); 582 | dst = dst.add(1); 583 | 584 | *dst = Vertex::new(lx1, ly1, 0.5, 1.0); 585 | dst = dst.add(1); 586 | } 587 | } else { 588 | *dst = Vertex::new( 589 | (*p1).xy.x + ((*p1).dm.x * woff), 590 | (*p1).xy.y + ((*p1).dm.y * woff), 591 | 0.5, 592 | 1.0, 593 | ); 594 | dst = dst.add(1); 595 | } 596 | 597 | p0 = p1; 598 | p1 = p1.add(1); 599 | } 600 | } else { 601 | for j in 0..path.count { 602 | let pt = pts.add(j); 603 | *dst = Vertex::new((*pt).xy.x, (*pt).xy.y, 0.5, 1.0); 604 | dst = dst.add(1); 605 | } 606 | } 607 | 608 | path.num_fill = ptrdistance(vertexes, dst); 609 | vertexes = dst; 610 | 611 | if fringe { 612 | let mut lw = w + woff; 613 | let rw = w - woff; 614 | let mut lu = 0.0; 615 | let ru = 1.0; 616 | let mut dst = vertexes; 617 | path.stroke = dst; 618 | 619 | if convex { 620 | lw = woff; 621 | lu = 0.5; 622 | } 623 | 624 | let mut p0 = pts.offset(path.count as isize - 1); 625 | let mut p1 = pts; 626 | 627 | for _ in 0..path.count { 628 | if (*p1).flags.contains(PointFlags::PT_BEVEL) 629 | || (*p1).flags.contains(PointFlags::PR_INNERBEVEL) 630 | { 631 | dst = bevel_join( 632 | dst, 633 | p0.as_mut().unwrap(), 634 | p1.as_mut().unwrap(), 635 | lw, 636 | rw, 637 | lu, 638 | ru, 639 | fringe_width, 640 | ); 641 | } else { 642 | *dst = Vertex::new( 643 | (*p1).xy.x + ((*p1).dm.x * lw), 644 | (*p1).xy.y + ((*p1).dm.y * lw), 645 | lu, 646 | 1.0, 647 | ); 648 | dst = dst.add(1); 649 | 650 | *dst = Vertex::new( 651 | (*p1).xy.x - ((*p1).dm.x * rw), 652 | (*p1).xy.y - ((*p1).dm.y * rw), 653 | ru, 654 | 1.0, 655 | ); 656 | dst = dst.add(1); 657 | } 658 | p0 = p1; 659 | p1 = p1.add(1); 660 | } 661 | 662 | let v0 = vertexes; 663 | let v1 = vertexes.add(1); 664 | 665 | *dst = Vertex::new((*v0).x, (*v0).y, lu, 1.0); 666 | dst = dst.add(1); 667 | 668 | *dst = Vertex::new((*v1).x, (*v1).y, ru, 1.0); 669 | dst = dst.add(1); 670 | 671 | path.num_stroke = ptrdistance(vertexes, dst); 672 | vertexes = dst; 673 | } else { 674 | path.stroke = std::ptr::null_mut(); 675 | path.num_stroke = 0; 676 | } 677 | } 678 | } 679 | } 680 | } 681 | 682 | fn triangle_area(a: &VPoint, b: &VPoint, c: &VPoint) -> f32 { 683 | let a = &a.xy; 684 | let b = &b.xy; 685 | let c = &c.xy; 686 | let abx = b.x - a.x; 687 | let aby = b.y - a.y; 688 | let acx = c.x - a.x; 689 | let acy = c.y - a.y; 690 | acx * aby - abx * acy 691 | } 692 | 693 | fn poly_area(pts: &[VPoint]) -> f32 { 694 | let mut area = 0.0; 695 | for i in 2..pts.len() { 696 | let a = &pts[0]; 697 | let b = &pts[i - 1]; 698 | let c = &pts[i]; 699 | area += triangle_area(a, b, c); 700 | } 701 | area * 0.5 702 | } 703 | 704 | fn poly_reverse(pts: &mut [VPoint]) { 705 | let mut i = 0; 706 | let mut j = pts.len() as i32 - 1; 707 | while i < j { 708 | pts.swap(i as usize, j as usize); 709 | i += 1; 710 | j -= 1; 711 | } 712 | } 713 | 714 | fn curve_divs(r: f32, arc: f32, tess_tol: f32) -> usize { 715 | let da = (r / (r + tess_tol)).acos() * 2.0; 716 | ((arc / da).ceil() as i32).max(2) as usize 717 | } 718 | 719 | fn choose_bevel(bevel: bool, p0: &mut VPoint, p1: &mut VPoint, w: f32) -> (f32, f32, f32, f32) { 720 | if bevel { 721 | let x0 = p1.xy.x + p0.d.y * w; 722 | let y0 = p1.xy.y - p0.d.x * w; 723 | let x1 = p1.xy.x + p1.d.y * w; 724 | let y1 = p1.xy.y - p1.d.x * w; 725 | (x0, y0, x1, y1) 726 | } else { 727 | let x0 = p1.xy.x + p1.dm.x * w; 728 | let y0 = p1.xy.y + p1.dm.y * w; 729 | let x1 = p1.xy.x + p1.dm.x * w; 730 | let y1 = p1.xy.y + p1.dm.y * w; 731 | (x0, y0, x1, y1) 732 | } 733 | } 734 | 735 | unsafe fn round_join( 736 | mut dst: *mut Vertex, 737 | p0: &mut VPoint, 738 | p1: &mut VPoint, 739 | lw: f32, 740 | rw: f32, 741 | lu: f32, 742 | ru: f32, 743 | ncap: usize, 744 | _fringe: f32, 745 | ) -> *mut Vertex { 746 | let dlx0 = p0.d.y; 747 | let dly0 = -p0.d.x; 748 | let dlx1 = p1.d.y; 749 | let dly1 = -p1.d.x; 750 | 751 | if p1.flags.contains(PointFlags::PT_LEFT) { 752 | let (lx0, ly0, lx1, ly1) = 753 | choose_bevel(p1.flags.contains(PointFlags::PR_INNERBEVEL), p0, p1, lw); 754 | let a0 = -dly0.atan2(-dlx0); 755 | let mut a1 = -dly1.atan2(-dlx1); 756 | if a1 > a0 { 757 | a1 -= PI * 2.0; 758 | } 759 | 760 | *dst = Vertex::new(lx0, ly0, lu, 1.0); 761 | dst = dst.add(1); 762 | 763 | *dst = Vertex::new(p1.xy.x - dlx0 * rw, p1.xy.y - dly0 * rw, ru, 1.0); 764 | dst = dst.add(1); 765 | 766 | let n = ((((a0 - a1) / PI) * (ncap as f32)).ceil() as i32).clamped(2, ncap as i32); 767 | for i in 0..n { 768 | let u = (i as f32) / ((n - 1) as f32); 769 | let a = a0 + u * (a1 - a0); 770 | let rx = p1.xy.x + a.cos() * rw; 771 | let ry = p1.xy.y + a.sin() * rw; 772 | 773 | *dst = Vertex::new(p1.xy.x, p1.xy.y, 0.5, 1.0); 774 | dst = dst.add(1); 775 | 776 | *dst = Vertex::new(rx, ry, ru, 1.0); 777 | dst = dst.add(1); 778 | } 779 | 780 | *dst = Vertex::new(lx1, ly1, lu, 1.0); 781 | dst = dst.add(1); 782 | 783 | *dst = Vertex::new(p1.xy.x - dlx1 * rw, p1.xy.y - dly1 * rw, ru, 1.0); 784 | dst = dst.add(1); 785 | } else { 786 | let (rx0, ry0, rx1, ry1) = 787 | choose_bevel(p1.flags.contains(PointFlags::PR_INNERBEVEL), p0, p1, -rw); 788 | let a0 = dly0.atan2(dlx0); 789 | let mut a1 = dly1.atan2(dlx1); 790 | if a1 < a0 { 791 | a1 += PI * 2.0; 792 | } 793 | 794 | *dst = Vertex::new(p1.xy.x + dlx0 * rw, p1.xy.y + dly0 * rw, lu, 1.0); 795 | dst = dst.add(1); 796 | 797 | *dst = Vertex::new(rx0, ry0, ru, 1.0); 798 | dst = dst.add(1); 799 | 800 | let n = ((((a0 - a1) / PI) * (ncap as f32)).ceil() as i32).clamped(2, ncap as i32); 801 | for i in 0..n { 802 | let u = (i as f32) / ((n - 1) as f32); 803 | let a = a0 + u * (a1 - a0); 804 | let lx = p1.xy.x + a.cos() * lw; 805 | let ly = p1.xy.y + a.cos() * lw; 806 | 807 | *dst = Vertex::new(lx, ly, lu, 1.0); 808 | dst = dst.add(1); 809 | 810 | *dst = Vertex::new(p1.xy.x, p1.xy.y, 0.5, 1.0); 811 | dst = dst.add(1); 812 | } 813 | 814 | *dst = Vertex::new(p1.xy.x + dlx1 * rw, p1.xy.y + dly1 * rw, lu, 1.0); 815 | dst = dst.add(1); 816 | 817 | *dst = Vertex::new(rx1, ry1, ru, 1.0); 818 | dst = dst.add(1); 819 | } 820 | 821 | dst 822 | } 823 | 824 | unsafe fn bevel_join( 825 | mut dst: *mut Vertex, 826 | p0: &mut VPoint, 827 | p1: &mut VPoint, 828 | lw: f32, 829 | rw: f32, 830 | lu: f32, 831 | ru: f32, 832 | _fringe: f32, 833 | ) -> *mut Vertex { 834 | let dlx0 = p0.d.y; 835 | let dly0 = -p0.d.x; 836 | let dlx1 = p1.d.y; 837 | let dly1 = -p1.d.x; 838 | 839 | if p1.flags.contains(PointFlags::PT_LEFT) { 840 | let (lx0, ly0, lx1, ly1) = 841 | choose_bevel(p1.flags.contains(PointFlags::PR_INNERBEVEL), p0, p1, lw); 842 | 843 | *dst = Vertex::new(lx0, ly0, lu, 1.0); 844 | dst = dst.add(1); 845 | 846 | *dst = Vertex::new(p1.xy.x - dlx0 * rw, p1.xy.y - dly0 * rw, ru, 1.0); 847 | dst = dst.add(1); 848 | 849 | if p1.flags.contains(PointFlags::PT_BEVEL) { 850 | *dst = Vertex::new(lx0, ly0, lu, 1.0); 851 | dst = dst.add(1); 852 | 853 | *dst = Vertex::new(p1.xy.x - dlx0 * rw, p1.xy.y - dly0 * rw, ru, 1.0); 854 | dst = dst.add(1); 855 | 856 | *dst = Vertex::new(lx1, ly1, lu, 1.0); 857 | dst = dst.add(1); 858 | 859 | *dst = Vertex::new(p1.xy.x - dlx1 * rw, p1.xy.y - dly1 * rw, ru, 1.0); 860 | dst = dst.add(1); 861 | } else { 862 | let rx0 = p1.xy.x - p1.dm.x * rw; 863 | let ry0 = p1.xy.y - p1.dm.y * rw; 864 | 865 | *dst = Vertex::new(p1.xy.x, p1.xy.y, 0.5, 1.0); 866 | dst = dst.add(1); 867 | 868 | *dst = Vertex::new(p1.xy.x - dlx0 * rw, p1.xy.y - dly0 * rw, ru, 1.0); 869 | dst = dst.add(1); 870 | 871 | *dst = Vertex::new(rx0, ry0, ru, 1.0); 872 | dst = dst.add(1); 873 | 874 | *dst = Vertex::new(rx0, ry0, ru, 1.0); 875 | dst = dst.add(1); 876 | 877 | *dst = Vertex::new(p1.xy.x, p1.xy.y, 0.5, 1.0); 878 | dst = dst.add(1); 879 | 880 | *dst = Vertex::new(p1.xy.x - dlx1 * rw, p1.xy.y - dly1 * rw, ru, 1.0); 881 | dst = dst.add(1); 882 | } 883 | 884 | *dst = Vertex::new(lx1, ly1, lu, 1.0); 885 | dst = dst.add(1); 886 | 887 | *dst = Vertex::new(p1.xy.x - dlx1 * rw, p1.xy.y - dly1 * rw, ru, 1.0); 888 | dst = dst.add(1); 889 | } else { 890 | let (rx0, ry0, rx1, ry1) = 891 | choose_bevel(p1.flags.contains(PointFlags::PR_INNERBEVEL), p0, p1, -rw); 892 | 893 | *dst = Vertex::new(p1.xy.x + dlx0 * lw, p1.xy.y + dly0 * lw, lu, 1.0); 894 | dst = dst.add(1); 895 | 896 | *dst = Vertex::new(rx0, ry0, ru, 1.0); 897 | dst = dst.add(1); 898 | 899 | if p1.flags.contains(PointFlags::PT_BEVEL) { 900 | *dst = Vertex::new(p1.xy.x + dlx0 * lw, p1.xy.y + dly0 * lw, lu, 1.0); 901 | dst = dst.add(1); 902 | 903 | *dst = Vertex::new(rx0, ry0, ru, 1.0); 904 | dst = dst.add(1); 905 | 906 | *dst = Vertex::new(p1.xy.x + dlx1 * lw, p1.xy.y + dly1 * lw, lu, 1.0); 907 | dst = dst.add(1); 908 | 909 | *dst = Vertex::new(rx1, ry1, ru, 1.0); 910 | dst = dst.add(1); 911 | } else { 912 | let lx0 = p1.xy.x + p1.dm.x * lw; 913 | let ly0 = p1.xy.y + p1.dm.y * lw; 914 | 915 | *dst = Vertex::new(p1.xy.x + dlx0 * lw, p1.xy.y + dly0 * lw, lu, 1.0); 916 | dst = dst.add(1); 917 | 918 | *dst = Vertex::new(p1.xy.x, p1.xy.y, 0.5, 1.0); 919 | dst = dst.add(1); 920 | 921 | *dst = Vertex::new(lx0, ly0, lu, 1.0); 922 | dst = dst.add(1); 923 | 924 | *dst = Vertex::new(lx0, ly0, lu, 1.0); 925 | dst = dst.add(1); 926 | 927 | *dst = Vertex::new(p1.xy.x + dlx1 * lw, p1.xy.y + dly1 * lw, lu, 1.0); 928 | dst = dst.add(1); 929 | 930 | *dst = Vertex::new(p1.xy.x, p1.xy.y, 0.5, 1.0); 931 | dst = dst.add(1); 932 | } 933 | 934 | *dst = Vertex::new(p1.xy.x + dlx1 * lw, p1.xy.y + dly1 * lw, lu, 1.0); 935 | dst = dst.add(1); 936 | 937 | *dst = Vertex::new(rx1, ry1, ru, 1.0); 938 | dst = dst.add(1); 939 | } 940 | 941 | dst 942 | } 943 | 944 | unsafe fn butt_cap_start( 945 | mut dst: *mut Vertex, 946 | p: &mut VPoint, 947 | dx: f32, 948 | dy: f32, 949 | w: f32, 950 | d: f32, 951 | aa: f32, 952 | u0: f32, 953 | u1: f32, 954 | ) -> *mut Vertex { 955 | let px = p.xy.x - dx * d; 956 | let py = p.xy.y - dy * d; 957 | let dlx = dy; 958 | let dly = -dx; 959 | 960 | *dst = Vertex::new(px + dlx * w - dx * aa, py + dly * w - dy * aa, u0, 0.0); 961 | dst = dst.add(1); 962 | 963 | *dst = Vertex::new(px - dlx * w - dx * aa, py - dly * w - dy * aa, u1, 0.0); 964 | dst = dst.add(1); 965 | 966 | *dst = Vertex::new(px + dlx * w, py + dly * w, u0, 1.0); 967 | dst = dst.add(1); 968 | 969 | *dst = Vertex::new(px - dlx * w, py - dly * w, u1, 1.0); 970 | dst = dst.add(1); 971 | 972 | dst 973 | } 974 | 975 | unsafe fn butt_cap_end( 976 | mut dst: *mut Vertex, 977 | p: &mut VPoint, 978 | dx: f32, 979 | dy: f32, 980 | w: f32, 981 | d: f32, 982 | aa: f32, 983 | u0: f32, 984 | u1: f32, 985 | ) -> *mut Vertex { 986 | let px = p.xy.x - dx * d; 987 | let py = p.xy.y - dy * d; 988 | let dlx = dy; 989 | let dly = -dx; 990 | 991 | *dst = Vertex::new(px + dlx * w, py + dly * w, u0, 1.0); 992 | dst = dst.add(1); 993 | 994 | *dst = Vertex::new(px - dlx * w, py - dly * w, u1, 1.0); 995 | dst = dst.add(1); 996 | 997 | *dst = Vertex::new(px + dlx * w + dx * aa, py + dly * w + dy * aa, u0, 0.0); 998 | dst = dst.add(1); 999 | 1000 | *dst = Vertex::new(px - dlx * w + dx * aa, py - dly * w + dy * aa, u1, 0.0); 1001 | dst = dst.add(1); 1002 | 1003 | dst 1004 | } 1005 | 1006 | unsafe fn round_cap_start( 1007 | mut dst: *mut Vertex, 1008 | p: &mut VPoint, 1009 | dx: f32, 1010 | dy: f32, 1011 | w: f32, 1012 | ncap: usize, 1013 | _aa: f32, 1014 | u0: f32, 1015 | u1: f32, 1016 | ) -> *mut Vertex { 1017 | let px = p.xy.x; 1018 | let py = p.xy.y; 1019 | let dlx = dy; 1020 | let dly = -dx; 1021 | 1022 | for i in 0..ncap { 1023 | let a = (i as f32) / ((ncap - 1) as f32) * PI; 1024 | let ax = a.cos() * w; 1025 | let ay = a.sin() * w; 1026 | 1027 | *dst = Vertex::new(px - dlx * ax - dx * ay, py - dly * ax - dy * ay, u0, 1.0); 1028 | dst = dst.add(1); 1029 | 1030 | *dst = Vertex::new(px, py, 0.5, 1.0); 1031 | dst = dst.add(1); 1032 | } 1033 | 1034 | *dst = Vertex::new(px + dlx * w, py + dly * w, u0, 1.0); 1035 | dst = dst.add(1); 1036 | 1037 | *dst = Vertex::new(px - dlx * w, py - dly * w, u1, 1.0); 1038 | dst = dst.add(1); 1039 | 1040 | dst 1041 | } 1042 | 1043 | unsafe fn round_cap_end( 1044 | mut dst: *mut Vertex, 1045 | p: &mut VPoint, 1046 | dx: f32, 1047 | dy: f32, 1048 | w: f32, 1049 | ncap: usize, 1050 | _aa: f32, 1051 | u0: f32, 1052 | u1: f32, 1053 | ) -> *mut Vertex { 1054 | let px = p.xy.x; 1055 | let py = p.xy.y; 1056 | let dlx = dy; 1057 | let dly = -dx; 1058 | 1059 | *dst = Vertex::new(px + dlx * w, py + dly * w, u0, 1.0); 1060 | dst = dst.add(1); 1061 | 1062 | *dst = Vertex::new(px - dlx * w, py - dly * w, u1, 1.0); 1063 | dst = dst.add(1); 1064 | 1065 | for i in 0..ncap { 1066 | let a = (i as f32) / ((ncap - 1) as f32) * PI; 1067 | let ax = a.cos() * w; 1068 | let ay = a.sin() * w; 1069 | 1070 | *dst = Vertex::new(px, py, 0.5, 1.0); 1071 | dst = dst.add(1); 1072 | 1073 | *dst = Vertex::new(px - dlx * ax + dx * ay, py - dly * ax + dy * ay, u0, 1.0); 1074 | dst = dst.add(1); 1075 | } 1076 | 1077 | dst 1078 | } 1079 | -------------------------------------------------------------------------------- /src/context.rs: -------------------------------------------------------------------------------- 1 | use crate::cache::PathCache; 2 | use crate::fonts::{FontId, Fonts, LayoutChar}; 3 | use crate::renderer::{Renderer, Scissor, TextureType}; 4 | use crate::{Color, Extent, Point, Rect, Transform}; 5 | use clamped::Clamp; 6 | use std::f32::consts::PI; 7 | 8 | pub type ImageId = usize; 9 | 10 | const KAPPA90: f32 = 0.5522847493; 11 | 12 | #[derive(Debug, Copy, Clone)] 13 | pub struct Paint { 14 | pub xform: Transform, 15 | pub extent: Extent, 16 | pub radius: f32, 17 | pub feather: f32, 18 | pub inner_color: Color, 19 | pub outer_color: Color, 20 | pub image: Option, 21 | } 22 | 23 | #[derive(Debug, Copy, Clone)] 24 | pub enum Gradient { 25 | Linear { 26 | start: Point, 27 | end: Point, 28 | start_color: Color, 29 | end_color: Color, 30 | }, 31 | Radial { 32 | center: Point, 33 | in_radius: f32, 34 | out_radius: f32, 35 | inner_color: Color, 36 | outer_color: Color, 37 | }, 38 | Box { 39 | rect: Rect, 40 | radius: f32, 41 | feather: f32, 42 | inner_color: Color, 43 | outer_color: Color, 44 | }, 45 | } 46 | 47 | #[derive(Debug, Copy, Clone)] 48 | pub struct ImagePattern { 49 | pub center: Point, 50 | pub size: Extent, 51 | pub angle: f32, 52 | pub img: ImageId, 53 | pub alpha: f32, 54 | } 55 | 56 | impl From for Paint { 57 | fn from(grad: Gradient) -> Self { 58 | match grad { 59 | Gradient::Linear { 60 | start, 61 | end, 62 | start_color: inner_color, 63 | end_color: outer_color, 64 | } => { 65 | const LARGE: f32 = 1e5; 66 | 67 | let mut dx = end.x - start.x; 68 | let mut dy = end.y - start.y; 69 | let d = (dx * dx + dy * dy).sqrt(); 70 | 71 | if d > 0.0001 { 72 | dx /= d; 73 | dy /= d; 74 | } else { 75 | dx = 0.0; 76 | dy = 1.0; 77 | } 78 | 79 | Paint { 80 | xform: Transform([dy, -dx, dx, dy, start.x - dx * LARGE, start.y - dy * LARGE]), 81 | extent: Extent { 82 | width: LARGE, 83 | height: LARGE + d * 0.5, 84 | }, 85 | radius: 0.0, 86 | feather: d.max(1.0), 87 | inner_color, 88 | outer_color, 89 | image: None, 90 | } 91 | } 92 | Gradient::Radial { 93 | center, 94 | in_radius, 95 | out_radius, 96 | inner_color, 97 | outer_color, 98 | } => { 99 | let r = (in_radius + out_radius) * 0.5; 100 | let f = out_radius - in_radius; 101 | Paint { 102 | xform: Transform([1.0, 0.0, 0.0, 1.0, center.x, center.y]), 103 | extent: Extent { 104 | width: r, 105 | height: r, 106 | }, 107 | radius: r, 108 | feather: f.max(1.0), 109 | inner_color, 110 | outer_color, 111 | image: None, 112 | } 113 | } 114 | Gradient::Box { 115 | rect, 116 | radius, 117 | feather, 118 | inner_color, 119 | outer_color, 120 | } => { 121 | let Rect { xy, size } = rect; 122 | Paint { 123 | xform: Transform([ 124 | 1.0, 125 | 0.0, 126 | 0.0, 127 | 1.0, 128 | xy.x + size.width * 0.5, 129 | xy.y + size.height * 0.5, 130 | ]), 131 | extent: Extent::new(size.width * 0.5, size.height * 0.5), 132 | radius, 133 | feather: feather.max(1.0), 134 | inner_color, 135 | outer_color, 136 | image: None, 137 | } 138 | } 139 | } 140 | } 141 | } 142 | 143 | impl From for Paint { 144 | fn from(pat: ImagePattern) -> Self { 145 | let mut xform = Transform::rotate(pat.angle); 146 | xform.0[4] = pat.center.x; 147 | xform.0[5] = pat.center.y; 148 | Paint { 149 | xform, 150 | extent: pat.size, 151 | radius: 0.0, 152 | feather: 0.0, 153 | inner_color: Color::rgba(1.0, 1.0, 1.0, pat.alpha), 154 | outer_color: Color::rgba(1.0, 1.0, 1.0, pat.alpha), 155 | image: Some(pat.img), 156 | } 157 | } 158 | } 159 | 160 | impl + Clone> From for Paint { 161 | fn from(color: T) -> Self { 162 | Paint { 163 | xform: Transform::identity(), 164 | extent: Default::default(), 165 | radius: 0.0, 166 | feather: 1.0, 167 | inner_color: color.clone().into(), 168 | outer_color: color.into(), 169 | image: None, 170 | } 171 | } 172 | } 173 | 174 | #[derive(Debug, Copy, Clone, Eq, PartialEq)] 175 | pub enum Solidity { 176 | Solid, 177 | Hole, 178 | } 179 | 180 | #[derive(Debug, Copy, Clone, Eq, PartialEq)] 181 | pub enum LineJoin { 182 | Miter, 183 | Round, 184 | Bevel, 185 | } 186 | 187 | #[derive(Debug, Copy, Clone, Eq, PartialEq)] 188 | pub enum LineCap { 189 | Butt, 190 | Round, 191 | Square, 192 | } 193 | 194 | bitflags! { 195 | pub struct Align: u32 { 196 | const LEFT = 0x1; 197 | const CENTER = 0x2; 198 | const RIGHT = 0x4; 199 | const TOP = 0x8; 200 | const MIDDLE = 0x10; 201 | const BOTTOM = 0x20; 202 | const BASELINE = 0x40; 203 | } 204 | } 205 | 206 | #[derive(Debug, Copy, Clone)] 207 | pub enum BlendFactor { 208 | Zero, 209 | One, 210 | SrcColor, 211 | OneMinusSrcColor, 212 | DstColor, 213 | OneMinusDstColor, 214 | SrcAlpha, 215 | OneMinusSrcAlpha, 216 | DstAlpha, 217 | OneMinusDstAlpha, 218 | SrcAlphaSaturate, 219 | } 220 | 221 | #[derive(Debug, Copy, Clone)] 222 | pub enum BasicCompositeOperation { 223 | SrcOver, 224 | SrcIn, 225 | SrcOut, 226 | Atop, 227 | DstOver, 228 | DstIn, 229 | DstOut, 230 | DstAtop, 231 | Lighter, 232 | Copy, 233 | Xor, 234 | } 235 | 236 | #[derive(Debug, Copy, Clone)] 237 | pub enum CompositeOperation { 238 | Basic(BasicCompositeOperation), 239 | BlendFunc { 240 | src: BlendFactor, 241 | dst: BlendFactor, 242 | }, 243 | BlendFuncSeparate { 244 | src_rgb: BlendFactor, 245 | dst_rgb: BlendFactor, 246 | src_alpha: BlendFactor, 247 | dst_alpha: BlendFactor, 248 | }, 249 | } 250 | 251 | impl Into for CompositeOperation { 252 | fn into(self) -> CompositeOperationState { 253 | match self { 254 | CompositeOperation::Basic(op) => { 255 | let (src_factor, dst_factor) = match op { 256 | BasicCompositeOperation::SrcOver => { 257 | (BlendFactor::One, BlendFactor::OneMinusSrcAlpha) 258 | } 259 | BasicCompositeOperation::SrcIn => (BlendFactor::DstAlpha, BlendFactor::Zero), 260 | BasicCompositeOperation::SrcOut => { 261 | (BlendFactor::OneMinusDstAlpha, BlendFactor::Zero) 262 | } 263 | BasicCompositeOperation::Atop => { 264 | (BlendFactor::DstAlpha, BlendFactor::OneMinusSrcAlpha) 265 | } 266 | BasicCompositeOperation::DstOver => { 267 | (BlendFactor::OneMinusDstAlpha, BlendFactor::One) 268 | } 269 | BasicCompositeOperation::DstIn => (BlendFactor::Zero, BlendFactor::SrcAlpha), 270 | BasicCompositeOperation::DstOut => { 271 | (BlendFactor::Zero, BlendFactor::OneMinusSrcAlpha) 272 | } 273 | BasicCompositeOperation::DstAtop => { 274 | (BlendFactor::OneMinusDstAlpha, BlendFactor::SrcAlpha) 275 | } 276 | BasicCompositeOperation::Lighter => (BlendFactor::One, BlendFactor::One), 277 | BasicCompositeOperation::Copy => (BlendFactor::One, BlendFactor::Zero), 278 | BasicCompositeOperation::Xor => { 279 | (BlendFactor::OneMinusDstAlpha, BlendFactor::OneMinusSrcAlpha) 280 | } 281 | }; 282 | 283 | CompositeOperationState { 284 | src_rgb: src_factor, 285 | dst_rgb: dst_factor, 286 | src_alpha: src_factor, 287 | dst_alpha: dst_factor, 288 | } 289 | } 290 | CompositeOperation::BlendFunc { src, dst } => CompositeOperationState { 291 | src_rgb: src, 292 | dst_rgb: dst, 293 | src_alpha: src, 294 | dst_alpha: dst, 295 | }, 296 | CompositeOperation::BlendFuncSeparate { 297 | src_rgb, 298 | dst_rgb, 299 | src_alpha, 300 | dst_alpha, 301 | } => CompositeOperationState { 302 | src_rgb, 303 | dst_rgb, 304 | src_alpha, 305 | dst_alpha, 306 | }, 307 | } 308 | } 309 | } 310 | 311 | #[derive(Debug, Copy, Clone)] 312 | pub struct CompositeOperationState { 313 | pub src_rgb: BlendFactor, 314 | pub dst_rgb: BlendFactor, 315 | pub src_alpha: BlendFactor, 316 | pub dst_alpha: BlendFactor, 317 | } 318 | 319 | bitflags! { 320 | pub struct ImageFlags: u32 { 321 | const GENERATE_MIPMAPS = 0x1; 322 | const REPEATX = 0x2; 323 | const REPEATY = 0x4; 324 | const FLIPY = 0x8; 325 | const PREMULTIPLIED = 0x10; 326 | const NEAREST = 0x20; 327 | } 328 | } 329 | 330 | #[derive(Debug, Copy, Clone, Default)] 331 | pub struct Vertex { 332 | pub x: f32, 333 | pub y: f32, 334 | pub u: f32, 335 | pub v: f32, 336 | } 337 | 338 | impl Vertex { 339 | pub fn new(x: f32, y: f32, u: f32, v: f32) -> Vertex { 340 | Vertex { x, y, u, v } 341 | } 342 | } 343 | 344 | #[derive(Debug, Copy, Clone)] 345 | pub struct Path { 346 | pub(crate) first: usize, 347 | pub(crate) count: usize, 348 | pub(crate) closed: bool, 349 | pub(crate) num_bevel: usize, 350 | pub(crate) solidity: Solidity, 351 | pub(crate) fill: *mut Vertex, 352 | pub(crate) num_fill: usize, 353 | pub(crate) stroke: *mut Vertex, 354 | pub(crate) num_stroke: usize, 355 | pub convex: bool, 356 | } 357 | 358 | impl Path { 359 | pub fn get_fill(&self) -> &[Vertex] { 360 | if self.fill.is_null() { 361 | &[] 362 | } else { 363 | unsafe { std::slice::from_raw_parts_mut(self.fill, self.num_fill) } 364 | } 365 | } 366 | 367 | pub fn get_stroke(&self) -> &[Vertex] { 368 | if self.stroke.is_null() { 369 | &[] 370 | } else { 371 | unsafe { std::slice::from_raw_parts_mut(self.stroke, self.num_stroke) } 372 | } 373 | } 374 | } 375 | 376 | #[derive(Copy, Clone)] 377 | pub struct TextMetrics { 378 | pub ascender: f32, 379 | pub descender: f32, 380 | pub line_gap: f32, 381 | } 382 | 383 | impl TextMetrics { 384 | pub fn line_height(&self) -> f32 { 385 | self.ascender - self.descender + self.line_gap 386 | } 387 | } 388 | 389 | #[derive(Clone)] 390 | struct State { 391 | composite_operation: CompositeOperationState, 392 | shape_antialias: bool, 393 | fill: Paint, 394 | stroke: Paint, 395 | stroke_width: f32, 396 | miter_limit: f32, 397 | line_join: LineJoin, 398 | line_cap: LineCap, 399 | alpha: f32, 400 | xform: Transform, 401 | scissor: Scissor, 402 | font_size: f32, 403 | letter_spacing: f32, 404 | line_height: f32, 405 | text_align: Align, 406 | font_id: FontId, 407 | } 408 | 409 | impl Default for State { 410 | fn default() -> Self { 411 | State { 412 | composite_operation: CompositeOperation::Basic(BasicCompositeOperation::SrcOver).into(), 413 | shape_antialias: true, 414 | fill: Color::rgb(1.0, 1.0, 1.0).into(), 415 | stroke: Color::rgb(0.0, 0.0, 0.0).into(), 416 | stroke_width: 1.0, 417 | miter_limit: 10.0, 418 | line_join: LineJoin::Miter, 419 | line_cap: LineCap::Butt, 420 | alpha: 1.0, 421 | xform: Transform::identity(), 422 | scissor: Scissor { 423 | xform: Default::default(), 424 | extent: Extent { 425 | width: -1.0, 426 | height: -1.0, 427 | }, 428 | }, 429 | font_size: 16.0, 430 | letter_spacing: 0.0, 431 | line_height: 1.0, 432 | text_align: Align::LEFT | Align::BASELINE, 433 | font_id: 0, 434 | } 435 | } 436 | } 437 | 438 | #[derive(Debug)] 439 | pub(crate) enum Command { 440 | MoveTo(Point), 441 | LineTo(Point), 442 | BezierTo(Point, Point, Point), 443 | Close, 444 | Solidity(Solidity), 445 | } 446 | 447 | pub struct Context { 448 | renderer: R, 449 | commands: Vec, 450 | last_position: Point, 451 | states: Vec, 452 | cache: PathCache, 453 | tess_tol: f32, 454 | dist_tol: f32, 455 | fringe_width: f32, 456 | device_pixel_ratio: f32, 457 | fonts: Fonts, 458 | layout_chars: Vec, 459 | draw_call_count: usize, 460 | fill_triangles_count: usize, 461 | stroke_triangles_count: usize, 462 | text_triangles_count: usize, 463 | } 464 | 465 | impl Context { 466 | pub fn create(mut renderer: R) -> anyhow::Result> { 467 | let fonts = Fonts::new(&mut renderer)?; 468 | Ok(Context { 469 | renderer, 470 | commands: Default::default(), 471 | last_position: Default::default(), 472 | states: vec![Default::default()], 473 | cache: Default::default(), 474 | tess_tol: 0.0, 475 | dist_tol: 0.0, 476 | fringe_width: 0.0, 477 | device_pixel_ratio: 0.0, 478 | fonts, 479 | layout_chars: Default::default(), 480 | draw_call_count: 0, 481 | fill_triangles_count: 0, 482 | stroke_triangles_count: 0, 483 | text_triangles_count: 0, 484 | }) 485 | } 486 | 487 | fn set_device_pixel_ratio(&mut self, ratio: f32) { 488 | self.tess_tol = 0.25 / ratio; 489 | self.dist_tol = 0.01 / ratio; 490 | self.fringe_width = 1.0 / ratio; 491 | self.device_pixel_ratio = ratio; 492 | } 493 | 494 | pub fn begin_frame>( 495 | &mut self, 496 | window_extent: E, 497 | device_pixel_ratio: f32, 498 | ) -> anyhow::Result<()> { 499 | self.states.clear(); 500 | self.states.push(Default::default()); 501 | self.set_device_pixel_ratio(device_pixel_ratio); 502 | self.renderer 503 | .viewport(window_extent.into(), device_pixel_ratio)?; 504 | self.draw_call_count = 0; 505 | self.fill_triangles_count = 0; 506 | self.stroke_triangles_count = 0; 507 | self.text_triangles_count = 0; 508 | Ok(()) 509 | } 510 | 511 | pub fn cancel_frame(&mut self) -> anyhow::Result<()> { 512 | self.renderer.cancel()?; 513 | Ok(()) 514 | } 515 | 516 | pub fn end_frame(&mut self) -> anyhow::Result<()> { 517 | self.renderer.flush()?; 518 | Ok(()) 519 | } 520 | 521 | pub fn save(&mut self) { 522 | if let Some(last) = self.states.last() { 523 | let last = last.clone(); 524 | self.states.push(last); 525 | } 526 | } 527 | 528 | pub fn restore(&mut self) { 529 | if self.states.len() <= 1 { 530 | return; 531 | } 532 | self.states.pop(); 533 | } 534 | 535 | fn state(&mut self) -> &State { 536 | self.states.last().unwrap() 537 | } 538 | 539 | fn state_mut(&mut self) -> &mut State { 540 | self.states.last_mut().unwrap() 541 | } 542 | 543 | pub fn reset(&mut self) { 544 | *self.state_mut() = Default::default(); 545 | } 546 | 547 | pub fn shape_antialias(&mut self, enabled: bool) { 548 | self.state_mut().shape_antialias = enabled; 549 | } 550 | 551 | pub fn stroke_width(&mut self, width: f32) { 552 | self.state_mut().stroke_width = width * self.device_pixel_ratio; 553 | } 554 | 555 | pub fn miter_limit(&mut self, limit: f32) { 556 | self.state_mut().miter_limit = limit; 557 | } 558 | 559 | pub fn line_cap(&mut self, cap: LineCap) { 560 | self.state_mut().line_cap = cap; 561 | } 562 | 563 | pub fn line_join(&mut self, join: LineJoin) { 564 | self.state_mut().line_join = join; 565 | } 566 | 567 | pub fn global_alpha(&mut self, alpha: f32) { 568 | self.state_mut().alpha = alpha; 569 | } 570 | 571 | pub fn transform(&mut self, xform: Transform) { 572 | let state = self.state_mut(); 573 | state.xform = xform * state.xform; 574 | } 575 | 576 | pub fn reset_transform(&mut self) { 577 | self.state_mut().xform = Transform::identity(); 578 | } 579 | 580 | pub fn translate(&mut self, tx: f32, ty: f32) { 581 | self.transform(Transform::translate(tx, ty)); 582 | } 583 | 584 | pub fn rotate(&mut self, angle: f32) { 585 | self.transform(Transform::rotate(angle)); 586 | } 587 | 588 | pub fn skew_x(&mut self, angle: f32) { 589 | self.transform(Transform::skew_x(angle)); 590 | } 591 | 592 | pub fn skew_y(&mut self, angle: f32) { 593 | self.transform(Transform::skew_y(angle)); 594 | } 595 | 596 | pub fn scale(&mut self, sx: f32, sy: f32) { 597 | self.transform(Transform::scale(sx, sy)); 598 | } 599 | 600 | pub fn current_transform(&mut self) -> Transform { 601 | self.state().xform 602 | } 603 | 604 | pub fn stroke_paint>(&mut self, paint: T) { 605 | let mut paint = paint.into(); 606 | paint.xform *= self.state().xform; 607 | self.state_mut().stroke = paint; 608 | } 609 | 610 | pub fn fill_paint>(&mut self, paint: T) { 611 | let mut paint = paint.into(); 612 | paint.xform *= self.state().xform; 613 | self.state_mut().fill = paint; 614 | } 615 | 616 | pub fn create_image>( 617 | &mut self, 618 | flags: ImageFlags, 619 | data: D, 620 | ) -> anyhow::Result { 621 | let img = image::load_from_memory(data.as_ref())?; 622 | let img = img.to_rgba(); 623 | let dimensions = img.dimensions(); 624 | let img = self.renderer.create_texture( 625 | TextureType::RGBA, 626 | dimensions.0 as usize, 627 | dimensions.1 as usize, 628 | flags, 629 | Some(&img.into_raw()), 630 | )?; 631 | Ok(img) 632 | } 633 | 634 | pub fn create_image_from_file>( 635 | &mut self, 636 | flags: ImageFlags, 637 | path: P, 638 | ) -> anyhow::Result { 639 | self.create_image(flags, std::fs::read(path)?) 640 | } 641 | 642 | pub fn update_image(&mut self, img: ImageId, data: &[u8]) -> anyhow::Result<()> { 643 | let (w, h) = self.renderer.texture_size(img.clone())?; 644 | self.renderer.update_texture(img, 0, 0, w, h, data)?; 645 | Ok(()) 646 | } 647 | 648 | pub fn image_size(&self, img: ImageId) -> anyhow::Result<(usize, usize)> { 649 | let res = self.renderer.texture_size(img)?; 650 | Ok(res) 651 | } 652 | 653 | pub fn delete_image(&mut self, img: ImageId) -> anyhow::Result<()> { 654 | self.renderer.delete_texture(img)?; 655 | Ok(()) 656 | } 657 | 658 | pub fn scissor>(&mut self, rect: T) { 659 | let rect = rect.into(); 660 | let state = self.state_mut(); 661 | let x = rect.xy.x; 662 | let y = rect.xy.y; 663 | let width = rect.size.width.max(0.0); 664 | let height = rect.size.height.max(0.0); 665 | state.scissor.xform = Transform::identity(); 666 | state.scissor.xform.0[4] = x + width * 0.5; 667 | state.scissor.xform.0[5] = y + height * 0.5; 668 | state.scissor.xform *= state.xform; 669 | state.scissor.extent.width = width * 0.5; 670 | state.scissor.extent.height = height * 0.5; 671 | } 672 | 673 | pub fn intersect_scissor>(&mut self, rect: T) { 674 | let rect = rect.into(); 675 | let state = self.state_mut(); 676 | 677 | if state.scissor.extent.width < 0.0 { 678 | self.scissor(rect); 679 | return; 680 | } 681 | 682 | let Extent { 683 | width: ex, 684 | height: ey, 685 | } = state.scissor.extent; 686 | let invxorm = state.xform.inverse(); 687 | let pxform = state.scissor.xform * invxorm; 688 | let tex = ex * pxform.0[0].abs() + ey * pxform.0[2].abs(); 689 | let tey = ex * pxform.0[1].abs() + ey * pxform.0[3].abs(); 690 | self.scissor( 691 | Rect::new( 692 | Point::new(pxform.0[4] - tex, pxform.0[5] - tey), 693 | Extent::new(tex * 2.0, tey * 2.0), 694 | ) 695 | .intersect(rect), 696 | ); 697 | } 698 | 699 | pub fn reset_scissor(&mut self) { 700 | let state = self.state_mut(); 701 | state.scissor.xform = Transform::default(); 702 | state.scissor.extent.width = -1.0; 703 | state.scissor.extent.height = -1.0; 704 | } 705 | 706 | pub fn global_composite_operation(&mut self, op: CompositeOperation) { 707 | self.state_mut().composite_operation = op.into(); 708 | } 709 | 710 | fn append_command(&mut self, cmd: Command) { 711 | let state = self.states.last().unwrap(); 712 | let xform = &state.xform; 713 | match cmd { 714 | Command::MoveTo(pt) => { 715 | self.commands 716 | .push(Command::MoveTo(xform.transform_point(pt))); 717 | self.last_position = pt; 718 | } 719 | Command::LineTo(pt) => { 720 | self.commands 721 | .push(Command::LineTo(xform.transform_point(pt))); 722 | self.last_position = pt; 723 | } 724 | Command::BezierTo(pt1, pt2, pt3) => { 725 | self.last_position = pt3; 726 | self.commands.push(Command::BezierTo( 727 | xform.transform_point(pt1), 728 | xform.transform_point(pt2), 729 | xform.transform_point(pt3), 730 | )); 731 | } 732 | _ => { 733 | self.commands.push(cmd); 734 | } 735 | } 736 | } 737 | 738 | pub fn begin_path(&mut self) { 739 | self.commands.clear(); 740 | self.cache.clear(); 741 | } 742 | 743 | pub fn move_to>(&mut self, pt: P) { 744 | self.append_command(Command::MoveTo(pt.into())); 745 | } 746 | 747 | pub fn line_to>(&mut self, pt: P) { 748 | self.append_command(Command::LineTo(pt.into())); 749 | } 750 | 751 | pub fn bezier_to>(&mut self, cp1: P, cp2: P, pt: P) { 752 | self.append_command(Command::BezierTo(cp1.into(), cp2.into(), pt.into())); 753 | } 754 | 755 | pub fn quad_to>(&mut self, cp: P, pt: P) { 756 | let x0 = self.last_position.x; 757 | let y0 = self.last_position.y; 758 | let cp = cp.into(); 759 | let pt = pt.into(); 760 | self.append_command(Command::BezierTo( 761 | Point::new(x0 + 2.0 / 3.0 * (cp.x - x0), y0 + 2.0 / 3.0 * (cp.y - y0)), 762 | Point::new( 763 | pt.x + 2.0 / 3.0 * (cp.x - pt.x), 764 | pt.y + 2.0 / 3.0 * (cp.y - pt.y), 765 | ), 766 | pt, 767 | )); 768 | } 769 | 770 | pub fn arc_to>(&mut self, pt1: P, pt2: P, radius: f32) { 771 | let pt0 = self.last_position; 772 | 773 | if self.commands.is_empty() { 774 | return; 775 | } 776 | 777 | let pt1 = pt1.into(); 778 | let pt2 = pt2.into(); 779 | if pt0.equals(pt1, self.dist_tol) 780 | || pt1.equals(pt2, self.dist_tol) 781 | || pt1.dist_pt_seg(pt0, pt2) < self.dist_tol * self.dist_tol 782 | || radius < self.dist_tol 783 | { 784 | self.line_to(pt1); 785 | return; 786 | } 787 | 788 | let d0 = Point::new(pt0.x - pt1.x, pt0.y - pt1.y); 789 | let d1 = Point::new(pt2.x - pt1.x, pt2.y - pt1.y); 790 | let a = (d0.x * d1.x + d0.y * d1.y).cos(); 791 | let d = radius / (a / 2.0).tan(); 792 | 793 | if d > 10000.0 { 794 | self.line_to(pt1); 795 | return; 796 | } 797 | 798 | let (cx, cy, a0, a1, dir) = if Point::cross(d0, d1) > 0.0 { 799 | ( 800 | pt1.x + d0.x * d + d0.y * radius, 801 | pt1.y + d0.y * d + -d0.x * radius, 802 | d0.x.atan2(-d0.y), 803 | -d1.x.atan2(d1.y), 804 | Solidity::Hole, 805 | ) 806 | } else { 807 | ( 808 | pt1.x + d0.x * d + -d0.y * radius, 809 | pt1.y + d0.y * d + d0.x * radius, 810 | -d0.x.atan2(d0.y), 811 | d1.x.atan2(-d1.y), 812 | Solidity::Solid, 813 | ) 814 | }; 815 | 816 | self.arc(Point::new(cx, cy), radius, a0, a1, dir); 817 | } 818 | 819 | pub fn close_path(&mut self) { 820 | self.commands.push(Command::Close); 821 | } 822 | 823 | pub fn path_solidity(&mut self, dir: Solidity) { 824 | self.commands.push(Command::Solidity(dir)); 825 | } 826 | 827 | pub fn arc>(&mut self, cp: P, radius: f32, a0: f32, a1: f32, dir: Solidity) { 828 | let cp = cp.into(); 829 | let move_ = self.commands.is_empty(); 830 | 831 | let mut da = a1 - a0; 832 | if dir == Solidity::Hole { 833 | if da.abs() >= PI * 2.0 { 834 | da = PI * 2.0; 835 | } else { 836 | while da < 0.0 { 837 | da += PI * 2.0; 838 | } 839 | } 840 | } else { 841 | if da.abs() >= PI * 2.0 { 842 | da = -PI * 2.0; 843 | } else { 844 | while da > 0.0 { 845 | da -= PI * 2.0; 846 | } 847 | } 848 | } 849 | 850 | let ndivs = ((da.abs() / (PI * 0.5) + 0.5) as i32).min(5).max(1); 851 | let hda = (da / (ndivs as f32)) / 2.0; 852 | let mut kappa = (4.0 / 3.0 * (1.0 - hda.cos()) / hda.sin()).abs(); 853 | 854 | if dir == Solidity::Solid { 855 | kappa = -kappa; 856 | } 857 | 858 | let mut px = 0.0; 859 | let mut py = 0.0; 860 | let mut ptanx = 0.0; 861 | let mut ptany = 0.0; 862 | 863 | for i in 0..=ndivs { 864 | let a = a0 + da * ((i as f32) / (ndivs as f32)); 865 | let dx = a.cos(); 866 | let dy = a.sin(); 867 | let x = cp.x + dx * radius; 868 | let y = cp.y + dy * radius; 869 | let tanx = -dy * radius * kappa; 870 | let tany = dx * radius * kappa; 871 | 872 | if i == 0 { 873 | if move_ { 874 | self.append_command(Command::MoveTo(Point::new(x, y))); 875 | } else { 876 | self.append_command(Command::LineTo(Point::new(x, y))); 877 | } 878 | } else { 879 | self.append_command(Command::BezierTo( 880 | Point::new(px + ptanx, py + ptany), 881 | Point::new(x - tanx, y - tany), 882 | Point::new(x, y), 883 | )); 884 | } 885 | px = x; 886 | py = y; 887 | ptanx = tanx; 888 | ptany = tany; 889 | } 890 | } 891 | 892 | pub fn rect>(&mut self, rect: T) { 893 | let rect = rect.into(); 894 | self.append_command(Command::MoveTo(Point::new(rect.xy.x, rect.xy.y))); 895 | self.append_command(Command::LineTo(Point::new( 896 | rect.xy.x, 897 | rect.xy.y + rect.size.height, 898 | ))); 899 | self.append_command(Command::LineTo(Point::new( 900 | rect.xy.x + rect.size.width, 901 | rect.xy.y + rect.size.height, 902 | ))); 903 | self.append_command(Command::LineTo(Point::new( 904 | rect.xy.x + rect.size.width, 905 | rect.xy.y, 906 | ))); 907 | self.append_command(Command::Close); 908 | } 909 | 910 | pub fn rounded_rect>(&mut self, rect: T, radius: f32) { 911 | let rect = rect.into(); 912 | self.rounded_rect_varying(rect, radius, radius, radius, radius); 913 | } 914 | 915 | pub fn rounded_rect_varying>( 916 | &mut self, 917 | rect: T, 918 | lt: f32, 919 | rt: f32, 920 | rb: f32, 921 | lb: f32, 922 | ) { 923 | let rect = rect.into(); 924 | if lt < 0.1 && rt < 0.1 && lb < 0.1 && rb < 0.1 { 925 | self.rect(rect); 926 | } else { 927 | let halfw = rect.size.width.abs() * 0.5; 928 | let halfh = rect.size.height.abs() * 0.5; 929 | let rxlb = lb.min(halfw) * rect.size.width.signum(); 930 | let rylb = lb.min(halfh) * rect.size.height.signum(); 931 | let rxrb = rb.min(halfw) * rect.size.width.signum(); 932 | let ryrb = rb.min(halfh) * rect.size.height.signum(); 933 | let rxrt = rt.min(halfw) * rect.size.width.signum(); 934 | let ryrt = rt.min(halfh) * rect.size.height.signum(); 935 | let rxlt = lt.min(halfw) * rect.size.width.signum(); 936 | let rylt = lt.min(halfh) * rect.size.height.signum(); 937 | 938 | self.append_command(Command::MoveTo(Point::new(rect.xy.x, rect.xy.y + rylt))); 939 | self.append_command(Command::LineTo(Point::new( 940 | rect.xy.x, 941 | rect.xy.y + rect.size.height - rylb, 942 | ))); 943 | self.append_command(Command::BezierTo( 944 | Point::new( 945 | rect.xy.x, 946 | rect.xy.y + rect.size.height - rylb * (1.0 - KAPPA90), 947 | ), 948 | Point::new( 949 | rect.xy.x + rxlb * (1.0 - KAPPA90), 950 | rect.xy.y + rect.size.height, 951 | ), 952 | Point::new(rect.xy.x + rxlb, rect.xy.y + rect.size.height), 953 | )); 954 | self.append_command(Command::LineTo(Point::new( 955 | rect.xy.x + rect.size.width - rxrb, 956 | rect.xy.y + rect.size.height, 957 | ))); 958 | self.append_command(Command::BezierTo( 959 | Point::new( 960 | rect.xy.x + rect.size.width - rxrb * (1.0 - KAPPA90), 961 | rect.xy.y + rect.size.height, 962 | ), 963 | Point::new( 964 | rect.xy.x + rect.size.width, 965 | rect.xy.y + rect.size.height - ryrb * (1.0 - KAPPA90), 966 | ), 967 | Point::new( 968 | rect.xy.x + rect.size.width, 969 | rect.xy.y + rect.size.height - ryrb, 970 | ), 971 | )); 972 | self.append_command(Command::LineTo(Point::new( 973 | rect.xy.x + rect.size.width, 974 | rect.xy.y + ryrt, 975 | ))); 976 | self.append_command(Command::BezierTo( 977 | Point::new( 978 | rect.xy.x + rect.size.width, 979 | rect.xy.y + ryrt * (1.0 - KAPPA90), 980 | ), 981 | Point::new( 982 | rect.xy.x + rect.size.width - rxrt * (1.0 - KAPPA90), 983 | rect.xy.y, 984 | ), 985 | Point::new(rect.xy.x + rect.size.width - rxrt, rect.xy.y), 986 | )); 987 | self.append_command(Command::LineTo(Point::new(rect.xy.x + rxlt, rect.xy.y))); 988 | self.append_command(Command::BezierTo( 989 | Point::new(rect.xy.x + rxlt * (1.0 - KAPPA90), rect.xy.y), 990 | Point::new(rect.xy.x, rect.xy.y + rylt * (1.0 - KAPPA90)), 991 | Point::new(rect.xy.x, rect.xy.y + rylt), 992 | )); 993 | self.append_command(Command::Close); 994 | } 995 | } 996 | 997 | pub fn ellipse>(&mut self, center: P, radius_x: f32, radius_y: f32) { 998 | let center = center.into(); 999 | self.append_command(Command::MoveTo(Point::new(center.x - radius_x, center.y))); 1000 | self.append_command(Command::BezierTo( 1001 | Point::new(center.x - radius_x, center.y + radius_y * KAPPA90), 1002 | Point::new(center.x - radius_x * KAPPA90, center.y + radius_y), 1003 | Point::new(center.x, center.y + radius_y), 1004 | )); 1005 | self.append_command(Command::BezierTo( 1006 | Point::new(center.x + radius_x * KAPPA90, center.y + radius_y), 1007 | Point::new(center.x + radius_x, center.y + radius_y * KAPPA90), 1008 | Point::new(center.x + radius_x, center.y), 1009 | )); 1010 | self.append_command(Command::BezierTo( 1011 | Point::new(center.x + radius_x, center.y - radius_y * KAPPA90), 1012 | Point::new(center.x + radius_x * KAPPA90, center.y - radius_y), 1013 | Point::new(center.x, center.y - radius_y), 1014 | )); 1015 | self.append_command(Command::BezierTo( 1016 | Point::new(center.x - radius_x * KAPPA90, center.y - radius_y), 1017 | Point::new(center.x - radius_x, center.y - radius_y * KAPPA90), 1018 | Point::new(center.x - radius_x, center.y), 1019 | )); 1020 | self.append_command(Command::Close); 1021 | } 1022 | 1023 | pub fn circle>(&mut self, center: P, radius: f32) { 1024 | self.ellipse(center.into(), radius, radius); 1025 | } 1026 | 1027 | pub fn fill(&mut self) -> anyhow::Result<()> { 1028 | let state = self.states.last_mut().unwrap(); 1029 | let mut fill_paint = state.fill.clone(); 1030 | 1031 | self.cache 1032 | .flatten_paths(&self.commands, self.dist_tol, self.tess_tol); 1033 | if self.renderer.edge_antialias() && state.shape_antialias { 1034 | self.cache 1035 | .expand_fill(self.fringe_width, LineJoin::Miter, 2.4, self.fringe_width); 1036 | } else { 1037 | self.cache 1038 | .expand_fill(0.0, LineJoin::Miter, 2.4, self.fringe_width); 1039 | } 1040 | 1041 | fill_paint.inner_color.a *= state.alpha; 1042 | fill_paint.outer_color.a *= state.alpha; 1043 | 1044 | self.renderer.fill( 1045 | &fill_paint, 1046 | state.composite_operation, 1047 | &state.scissor, 1048 | self.fringe_width, 1049 | self.cache.bounds, 1050 | &self.cache.paths, 1051 | )?; 1052 | 1053 | for path in &self.cache.paths { 1054 | if path.num_fill > 2 { 1055 | self.fill_triangles_count += path.num_fill - 2; 1056 | } 1057 | if path.num_stroke > 2 { 1058 | self.fill_triangles_count += path.num_stroke - 2; 1059 | } 1060 | self.draw_call_count += 2; 1061 | } 1062 | 1063 | Ok(()) 1064 | } 1065 | 1066 | pub fn stroke(&mut self) -> anyhow::Result<()> { 1067 | let state = self.states.last_mut().unwrap(); 1068 | let scale = state.xform.average_scale(); 1069 | let mut stroke_width = (state.stroke_width * scale).clamped(0.0, 200.0); 1070 | let mut stroke_paint = state.stroke.clone(); 1071 | 1072 | if stroke_width < self.fringe_width { 1073 | let alpha = (stroke_width / self.fringe_width).clamped(0.0, 1.0); 1074 | stroke_paint.inner_color.a *= alpha * alpha; 1075 | stroke_paint.outer_color.a *= alpha * alpha; 1076 | stroke_width = self.fringe_width; 1077 | } 1078 | 1079 | stroke_paint.inner_color.a *= state.alpha; 1080 | stroke_paint.outer_color.a *= state.alpha; 1081 | 1082 | self.cache 1083 | .flatten_paths(&self.commands, self.dist_tol, self.tess_tol); 1084 | 1085 | if self.renderer.edge_antialias() && state.shape_antialias { 1086 | self.cache.expand_stroke( 1087 | stroke_width * 0.5, 1088 | self.fringe_width, 1089 | state.line_cap, 1090 | state.line_join, 1091 | state.miter_limit, 1092 | self.tess_tol, 1093 | ); 1094 | } else { 1095 | self.cache.expand_stroke( 1096 | stroke_width * 0.5, 1097 | 0.0, 1098 | state.line_cap, 1099 | state.line_join, 1100 | state.miter_limit, 1101 | self.tess_tol, 1102 | ); 1103 | } 1104 | 1105 | self.renderer.stroke( 1106 | &stroke_paint, 1107 | state.composite_operation, 1108 | &state.scissor, 1109 | self.fringe_width, 1110 | stroke_width, 1111 | &self.cache.paths, 1112 | )?; 1113 | 1114 | for path in &self.cache.paths { 1115 | self.fill_triangles_count += path.num_stroke - 2; 1116 | self.draw_call_count += 1; 1117 | } 1118 | 1119 | Ok(()) 1120 | } 1121 | 1122 | pub fn create_font_from_file, P: AsRef>( 1123 | &mut self, 1124 | name: N, 1125 | path: P, 1126 | ) -> anyhow::Result { 1127 | self.create_font(name, std::fs::read(path)?) 1128 | } 1129 | 1130 | pub fn create_font, D: Into>>( 1131 | &mut self, 1132 | name: N, 1133 | data: D, 1134 | ) -> anyhow::Result { 1135 | self.fonts.add_font(name, data) 1136 | } 1137 | 1138 | pub fn find_font>(&self, name: N) -> Option { 1139 | self.fonts.find(name.as_ref()) 1140 | } 1141 | 1142 | pub fn add_fallback_fontid(&mut self, base: FontId, fallback: FontId) { 1143 | self.fonts.add_fallback(base, fallback); 1144 | } 1145 | 1146 | pub fn add_fallback_font, N2: AsRef>(&mut self, base: N1, fallback: N2) { 1147 | if let (Some(base), Some(fallback)) = (self.find_font(base), self.find_font(fallback)) { 1148 | self.fonts.add_fallback(base, fallback); 1149 | } 1150 | } 1151 | 1152 | pub fn font_size(&mut self, size: f32) { 1153 | self.state_mut().font_size = size; 1154 | } 1155 | 1156 | pub fn text_letter_spacing(&mut self, spacing: f32) { 1157 | self.state_mut().letter_spacing = spacing; 1158 | } 1159 | 1160 | pub fn text_line_height(&mut self, line_height: f32) { 1161 | self.state_mut().line_height = line_height; 1162 | } 1163 | 1164 | pub fn text_align(&mut self, align: Align) { 1165 | self.state_mut().text_align = align; 1166 | } 1167 | 1168 | pub fn fontid(&mut self, id: FontId) { 1169 | self.state_mut().font_id = id; 1170 | } 1171 | 1172 | pub fn font>(&mut self, name: N) { 1173 | if let Some(id) = self.find_font(name) { 1174 | self.state_mut().font_id = id; 1175 | } 1176 | } 1177 | 1178 | pub fn text, P: Into>(&mut self, pt: P, text: S) -> anyhow::Result<()> { 1179 | let state = self.states.last().unwrap(); 1180 | let scale = state.xform.font_scale() * self.device_pixel_ratio; 1181 | let xform = &state.xform; 1182 | let invscale = 1.0 / scale; 1183 | let pt = pt.into(); 1184 | 1185 | self.fonts.layout_text( 1186 | &mut self.renderer, 1187 | text.as_ref(), 1188 | state.font_id, 1189 | (pt.x * scale, pt.y * scale).into(), 1190 | state.font_size * scale, 1191 | state.text_align, 1192 | state.letter_spacing * scale, 1193 | true, 1194 | &mut self.layout_chars, 1195 | )?; 1196 | 1197 | self.cache.vertexes.clear(); 1198 | 1199 | for lc in &self.layout_chars { 1200 | let lt = xform.transform_point(Point::new( 1201 | lc.bounds.min.x * invscale, 1202 | lc.bounds.min.y * invscale, 1203 | )); 1204 | let rt = xform.transform_point(Point::new( 1205 | lc.bounds.max.x * invscale, 1206 | lc.bounds.min.y * invscale, 1207 | )); 1208 | let lb = xform.transform_point(Point::new( 1209 | lc.bounds.min.x * invscale, 1210 | lc.bounds.max.y * invscale, 1211 | )); 1212 | let rb = xform.transform_point(Point::new( 1213 | lc.bounds.max.x * invscale, 1214 | lc.bounds.max.y * invscale, 1215 | )); 1216 | 1217 | self.cache 1218 | .vertexes 1219 | .push(Vertex::new(lt.x, lt.y, lc.uv.min.x, lc.uv.min.y)); 1220 | self.cache 1221 | .vertexes 1222 | .push(Vertex::new(rb.x, rb.y, lc.uv.max.x, lc.uv.max.y)); 1223 | self.cache 1224 | .vertexes 1225 | .push(Vertex::new(rt.x, rt.y, lc.uv.max.x, lc.uv.min.y)); 1226 | 1227 | self.cache 1228 | .vertexes 1229 | .push(Vertex::new(lt.x, lt.y, lc.uv.min.x, lc.uv.min.y)); 1230 | self.cache 1231 | .vertexes 1232 | .push(Vertex::new(lb.x, lb.y, lc.uv.min.x, lc.uv.max.y)); 1233 | self.cache 1234 | .vertexes 1235 | .push(Vertex::new(rb.x, rb.y, lc.uv.max.x, lc.uv.max.y)); 1236 | } 1237 | 1238 | let mut paint = state.fill.clone(); 1239 | paint.image = Some(self.fonts.img.clone()); 1240 | paint.inner_color.a *= state.alpha; 1241 | paint.outer_color.a *= state.alpha; 1242 | 1243 | self.renderer.triangles( 1244 | &paint, 1245 | state.composite_operation, 1246 | &state.scissor, 1247 | &self.cache.vertexes, 1248 | )?; 1249 | Ok(()) 1250 | } 1251 | 1252 | pub fn text_metrics(&self) -> TextMetrics { 1253 | let state = self.states.last().unwrap(); 1254 | let scale = state.xform.font_scale() * self.device_pixel_ratio; 1255 | self.fonts 1256 | .text_metrics(state.font_id, state.font_size * scale) 1257 | } 1258 | 1259 | pub fn text_size>(&self, text: S) -> Extent { 1260 | let state = self.states.last().unwrap(); 1261 | let scale = state.xform.font_scale() * self.device_pixel_ratio; 1262 | self.fonts.text_size( 1263 | text.as_ref(), 1264 | state.font_id, 1265 | state.font_size * scale, 1266 | state.letter_spacing * scale, 1267 | ) 1268 | } 1269 | } 1270 | --------------------------------------------------------------------------------