From ff5e932c370eb153d56b6e4578ab76bb26d71ab6 Mon Sep 17 00:00:00 2001 From: Shark Date: Sat, 18 May 2024 16:19:14 +0200 Subject: [PATCH 1/9] add RenderBackend trait --- Cargo.lock | 21 + Cargo.toml | 1 + crates/gosub_render_backend/Cargo.toml | 9 + crates/gosub_render_backend/src/lib.rs | 346 +++++++++ crates/gosub_render_utils/Cargo.toml | 1 + crates/gosub_render_utils/src/layout.rs | 17 +- crates/gosub_render_utils/src/style.rs | 9 +- crates/gosub_render_utils/src/style/parse.rs | 82 +- .../src/style/parse_properties.rs | 129 +++- crates/gosub_renderer/Cargo.toml | 1 + crates/gosub_renderer/src/draw.rs | 717 +++++++++--------- crates/gosub_renderer/src/render_tree.rs | 13 +- crates/gosub_renderer/src/renderer.rs | 9 +- crates/gosub_renderer/src/window.rs | 15 +- crates/gosub_styling/Cargo.toml | 2 + crates/gosub_styling/src/lib.rs | 2 +- crates/gosub_styling/src/prerender_text.rs | 250 +----- crates/gosub_styling/src/render_tree.rs | 227 +++--- crates/gosub_typeface/Cargo.toml | 8 + crates/gosub_typeface/src/lib.rs | 251 ++++++ src/bin/renderer.rs | 7 +- src/bin/style-parser.rs | 26 +- 22 files changed, 1346 insertions(+), 797 deletions(-) create mode 100644 crates/gosub_render_backend/Cargo.toml create mode 100644 crates/gosub_render_backend/src/lib.rs create mode 100644 crates/gosub_typeface/Cargo.toml create mode 100644 crates/gosub_typeface/src/lib.rs diff --git a/Cargo.lock b/Cargo.lock index ade8e81a1..51f29c853 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1425,6 +1425,7 @@ dependencies = [ "gosub_html5", "gosub_jsapi", "gosub_net", + "gosub_render_backend", "gosub_renderer", "gosub_rendering", "gosub_shared", @@ -1493,6 +1494,14 @@ dependencies = [ "url", ] +[[package]] +name = "gosub_render_backend" +version = "0.1.0" +dependencies = [ + "image", + "smallvec", +] + [[package]] name = "gosub_renderer" version = "0.1.0" @@ -1501,6 +1510,7 @@ dependencies = [ "futures", "gosub_html5", "gosub_net", + "gosub_render_backend", "gosub_rendering", "gosub_shared", "gosub_styling", @@ -1522,6 +1532,7 @@ version = "0.1.0" dependencies = [ "anyhow", "gosub_html5", + "gosub_render_backend", "gosub_styling", "regex", "rstar", @@ -1553,7 +1564,9 @@ dependencies = [ "colors-transform", "gosub_css3", "gosub_html5", + "gosub_render_backend", "gosub_shared", + "gosub_typeface", "lazy_static", "regex", "rust-fontconfig", @@ -1575,6 +1588,14 @@ dependencies = [ "serde_json", ] +[[package]] +name = "gosub_typeface" +version = "0.1.0" +dependencies = [ + "lazy_static", + "rust-fontconfig", +] + [[package]] name = "gosub_v8" version = "0.1.0" diff --git a/Cargo.toml b/Cargo.toml index 4d5c31356..7d18a7535 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -32,6 +32,7 @@ gosub_jsapi = { path = "./crates/gosub_jsapi", features = [] } gosub_testing = { path = "./crates/gosub_testing", features = [] } gosub_rendering = { path = "crates/gosub_render_utils", features = [] } gosub_renderer = { path = "./crates/gosub_renderer", features = [] } +gosub_render_backend = { path = "./crates/gosub_render_backend", features = [] } serde = { version = "1.0", features = ["derive"] } serde_derive = "1.0" derive_more = "0.99" diff --git a/crates/gosub_render_backend/Cargo.toml b/crates/gosub_render_backend/Cargo.toml new file mode 100644 index 000000000..75c52742d --- /dev/null +++ b/crates/gosub_render_backend/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "gosub_render_backend" +version = "0.1.0" +edition = "2021" + +[dependencies] +smallvec = "1.13.2" +image = "0.25.1" + diff --git a/crates/gosub_render_backend/src/lib.rs b/crates/gosub_render_backend/src/lib.rs new file mode 100644 index 000000000..1ad2155b0 --- /dev/null +++ b/crates/gosub_render_backend/src/lib.rs @@ -0,0 +1,346 @@ +use smallvec::SmallVec; +use std::fmt::Debug; +use std::ops::{Mul, MulAssign}; + +pub trait RenderBackend: Sized + Debug { + type Rect: Rect; + type Border: Border; + type BorderSide: BorderSide; + type BorderRadius: BorderRadius; + type Transform: Transform; + type PreRenderText: PreRenderText; + type Text: Text; + type Gradient: Gradient; + type Color: Color; + type Image: Image; + type Brush: Brush; + + fn draw_rect(&mut self, rect: &RenderRect); + fn draw_text(&mut self, text: &RenderText); + fn reset(&mut self); +} + +pub type FP = f32; + +pub struct Point { + pub x: FP, + pub y: FP, +} + +#[derive(Debug)] +pub struct Size { + pub width: FP, + pub height: FP, +} + +pub struct RenderRect { + pub rect: B::Rect, + pub transform: Option, + pub radius: Option, + pub brush: B::Brush, + pub brush_transform: Option, + pub border: Option>, +} + +impl RenderRect { + pub fn new(rect: B::Rect, brush: B::Brush) -> Self { + Self { + rect, + transform: None, + radius: None, + brush, + brush_transform: None, + border: None, + } + } + + pub fn with_border(rect: B::Rect, brush: B::Brush, border: RenderBorder) -> Self { + Self { + rect, + transform: None, + radius: None, + brush, + brush_transform: None, + border: Some(border), + } + } + + pub fn border(&mut self, border: RenderBorder) { + self.border = Some(border); + } + + pub fn transform(&mut self, transform: B::Transform) { + self.transform = Some(transform); + } + + pub fn radius(&mut self, radius: B::BorderRadius) { + self.radius = Some(radius); + } + + pub fn brush_transform(&mut self, brush_transform: B::Transform) { + self.brush_transform = Some(brush_transform); + } +} + +pub struct RenderText { + pub text: B::Text, + pub rect: B::Rect, + pub transform: Option, + pub brush: B::Brush, + pub brush_transform: Option, +} + +impl RenderText { + pub fn new(text: B::Text, rect: B::Rect, brush: B::Brush) -> Self { + Self { + text, + rect, + transform: None, + brush, + brush_transform: None, + } + } + + pub fn transform(&mut self, transform: B::Transform) { + self.transform = Some(transform); + } + + pub fn brush_transform(&mut self, brush_transform: B::Transform) { + self.brush_transform = Some(brush_transform); + } +} + +pub struct RenderBorder { + pub border: B::Border, + pub transform: Option, +} + +impl RenderBorder { + pub fn new(border: B::Border) -> Self { + Self { + border, + transform: None, + } + } + + pub fn transform(&mut self, transform: B::Transform) { + self.transform = Some(transform); + } +} + +pub trait Rect { + fn new(x: FP, y: FP, width: FP, height: FP) -> Self; + + fn from_point(point: Point, size: Size) -> Self; +} + +pub trait Border { + fn new(all: B::BorderSide) -> Self; + + fn all( + left: B::BorderSide, + right: B::BorderSide, + top: B::BorderSide, + bottom: B::BorderSide, + ) -> Self; + + fn left(&mut self, side: B::BorderSide); + + fn right(&mut self, side: B::BorderSide); + + fn top(&mut self, side: B::BorderSide); + + fn bottom(&mut self, side: B::BorderSide); +} + +pub trait BorderSide { + fn new(width: FP, style: BorderStyle, brush: B::Brush) -> Self; +} + +pub enum BorderStyle { + Solid, + Dashed, + Dotted, + Double, + Groove, + Ridge, + Inset, + Outset, + None, + Hidden, +} + +pub trait BorderRadius: + Sized + + From<[FP; 4]> + + From<[FP; 8]> + + From<(FP, FP, FP, FP)> + + From<(FP, FP, FP, FP, FP, FP, FP, FP)> +{ + fn empty() -> Self; + fn uniform(radius: FP) -> Self; + fn uniform_elliptical(radius_x: FP, radius_y: FP) -> Self; + + fn top_left(&mut self, radius: FP); + fn top_left_elliptical(&mut self, radius_x: FP, radius_y: FP); + + fn top_right(&mut self, radius: FP); + fn top_right_elliptical(&mut self, radius_x: FP, radius_y: FP); + + fn bottom_left(&mut self, radius: FP); + fn bottom_left_elliptical(&mut self, radius_x: FP, radius_y: FP); + + fn bottom_right(&mut self, radius: FP); + fn bottom_right_elliptical(&mut self, radius_x: FP, radius_y: FP); + + //Can be used if the border was initially created with the empty method + fn build(self) -> Option; +} + +pub trait Transform: Sized + Mul + MulAssign { + const IDENTITY: Self; + const FLIP_X: Self; + const FLIP_Y: Self; + + fn scale(s: FP) -> Self; + fn scale_xy(sx: FP, sy: FP) -> Self; + + fn translate(x: FP, y: FP) -> Self; + + fn rotate(angle: FP) -> Self; + + fn rotate_around(angle: FP, center: Point) -> Self; + + fn skew_x(angle: FP) -> Self; + + fn skew_y(angle: FP) -> Self; + + fn skew_xy(angle_x: FP, angle_y: FP) -> Self; + + fn pre_scale(self, s: FP) -> Self; + + fn pre_scale_xy(self, sx: FP, sy: FP) -> Self; + + fn pre_translate(self, x: FP, y: FP) -> Self; + + fn pre_rotate(self, angle: FP) -> Self; + + fn pre_rotate_around(self, angle: FP, center: Point) -> Self; + + fn pre_skew_x(self, angle: FP) -> Self; + + fn pre_skew_y(self, angle: FP) -> Self; + + fn pre_skew_xy(self, angle_x: FP, angle_y: FP) -> Self; + + fn then_scale(self, s: FP) -> Self; + + fn then_scale_xy(self, sx: FP, sy: FP) -> Self; + + fn then_translate(self, x: FP, y: FP) -> Self; + + fn then_rotate(self, angle: FP) -> Self; + + fn then_rotate_around(self, angle: FP, center: Point) -> Self; + + fn then_skew_x(self, angle: FP) -> Self; + + fn then_skew_y(self, angle: FP) -> Self; + + fn then_skew_xy(self, angle_x: FP, angle_y: FP) -> Self; + + fn as_matrix(&self) -> [FP; 6]; + + fn from_matrix(matrix: [FP; 6]) -> Self; + + fn determinant(&self) -> FP; + + fn inverse(self) -> Self; + + fn with_translation(&self, translation: (FP, FP)) -> Self; +} + +pub trait PreRenderText { + fn new(text: String, font: Option>, size: FP) -> Self; + + fn prerender(&mut self, backend: &B) -> Size; + fn value(&self) -> &str; + fn font(&self) -> Option<&[String]>; + fn fs(&self) -> FP; + + //TODO: Who should be responsible for line breaking if the text is too long? +} + +pub trait Text { + fn new(pre: &B::PreRenderText) -> Self; +} + +pub struct ColorStop { + pub offset: FP, + pub color: B::Color, +} + +type ColorStops = SmallVec<[ColorStop; 4]>; + +pub trait Gradient { + fn new_linear(start: Point, end: Point, stops: ColorStops) -> Self; + + fn new_radial( + start_center: Point, + start_radius: FP, + end_center: Point, + end_radius: FP, + stops: ColorStops, + ) -> Self; + + fn new_sweep(center: Point, start_angle: FP, end_angle: FP, stops: ColorStops) -> Self; +} + +pub trait Color { + fn new(r: u8, g: u8, b: u8) -> Self + where + Self: Sized, + { + Self::with_alpha(r, g, b, 255) + } + + fn with_alpha(r: u8, g: u8, b: u8, a: u8) -> Self; + + fn rgb(r: u8, g: u8, b: u8) -> Self + where + Self: Sized, + { + Self::new(r, g, b) + } + + fn rgba(r: u8, g: u8, b: u8, a: u8) -> Self + where + Self: Sized, + { + Self::with_alpha(r, g, b, a) + } + + const WHITE: Self; + const BLACK: Self; + const RED: Self; + const GREEN: Self; + const BLUE: Self; + const YELLOW: Self; + const CYAN: Self; + const MAGENTA: Self; + const TRANSPARENT: Self; +} + +pub trait Image { + fn new(size: (FP, FP), data: Vec) -> Self; + + fn from_img(img: &image::DynamicImage) -> Self; +} + +pub trait Brush { + fn gradient(gradient: B::Gradient) -> Self; + + fn color(color: B::Color) -> Self; + + fn image(image: B::Image) -> Self; +} diff --git a/crates/gosub_render_utils/Cargo.toml b/crates/gosub_render_utils/Cargo.toml index ded0fda96..e6d0ffb34 100644 --- a/crates/gosub_render_utils/Cargo.toml +++ b/crates/gosub_render_utils/Cargo.toml @@ -8,6 +8,7 @@ license = "MIT" [dependencies] gosub_html5 = { path = "../gosub_html5" } gosub_styling = { path = "../gosub_styling" } +gosub_render_backend = { path = "../gosub_render_backend" } taffy = "0.5.1" anyhow = "1.0.86" regex = "1.10.4" diff --git a/crates/gosub_render_utils/src/layout.rs b/crates/gosub_render_utils/src/layout.rs index b8a40283c..6c51f10c2 100644 --- a/crates/gosub_render_utils/src/layout.rs +++ b/crates/gosub_render_utils/src/layout.rs @@ -1,24 +1,29 @@ use taffy::prelude::*; use gosub_html5::node::NodeId as GosubID; +use gosub_render_backend::RenderBackend; use gosub_styling::render_tree::RenderTree; use crate::style::get_style_from_node; -pub fn generate_taffy_tree(rt: &mut RenderTree) -> anyhow::Result<(TaffyTree, NodeId)> { +pub fn generate_taffy_tree( + rt: &mut RenderTree, + backend: &B, +) -> anyhow::Result<(TaffyTree, NodeId)> { let mut tree: TaffyTree = TaffyTree::with_capacity(rt.nodes.len()); rt.get_root(); - let root = add_children_to_tree(rt, &mut tree, rt.root)?; + let root = add_children_to_tree(rt, &mut tree, rt.root, backend)?; Ok((tree, root)) } -fn add_children_to_tree( - rt: &mut RenderTree, +fn add_children_to_tree( + rt: &mut RenderTree, tree: &mut TaffyTree, node_id: GosubID, + backend: &B, ) -> anyhow::Result { let Some(node_children) = rt.get_children(node_id) else { return Err(anyhow::anyhow!("Node not found {:?}", node_id)); @@ -28,7 +33,7 @@ fn add_children_to_tree( //clone, so we can drop the borrow of RT, we would be copying the NodeID anyway, so it's not a big deal (only a few bytes) for child in node_children.clone() { - match add_children_to_tree(rt, tree, child) { + match add_children_to_tree(rt, tree, child, backend) { Ok(node) => children.push(node), Err(e) => eprintln!("Error adding child to tree: {:?}", e), } @@ -38,7 +43,7 @@ fn add_children_to_tree( return Err(anyhow::anyhow!("Node not found")); }; - let style = get_style_from_node(node); + let style = get_style_from_node(node, backend); let node = tree .new_with_children(style, &children) diff --git a/crates/gosub_render_utils/src/style.rs b/crates/gosub_render_utils/src/style.rs index 70e002d35..d8c2d50ad 100644 --- a/crates/gosub_render_utils/src/style.rs +++ b/crates/gosub_render_utils/src/style.rs @@ -1,19 +1,20 @@ mod parse; mod parse_properties; +use gosub_render_backend::RenderBackend; use gosub_styling::render_tree::RenderTreeNode; use taffy::Style; const SCROLLBAR_WIDTH: f32 = 16.0; -pub fn get_style_from_node(node: &mut RenderTreeNode) -> Style { +pub fn get_style_from_node(node: &mut RenderTreeNode, backend: &B) -> Style { let display = parse_properties::parse_display(node); let overflow = parse_properties::parse_overflow(node); let position = parse_properties::parse_position(node); let inset = parse_properties::parse_inset(node); - let size = parse_properties::parse_size(node); - let min_size = parse_properties::parse_min_size(node); - let max_size = parse_properties::parse_max_size(node); + let size = parse_properties::parse_size(node, backend); + let min_size = parse_properties::parse_min_size(node, backend); + let max_size = parse_properties::parse_max_size(node, backend); let aspect_ratio = parse_properties::parse_aspect_ratio(node); let margin = parse_properties::parse_margin(node); let padding = parse_properties::parse_padding(node); diff --git a/crates/gosub_render_utils/src/style/parse.rs b/crates/gosub_render_utils/src/style/parse.rs index ab5a2b141..9d9f57527 100644 --- a/crates/gosub_render_utils/src/style/parse.rs +++ b/crates/gosub_render_utils/src/style/parse.rs @@ -4,10 +4,14 @@ use taffy::{ TrackSizingFunction, }; +use gosub_render_backend::{PreRenderText, RenderBackend}; use gosub_styling::css_values::CssValue; -use gosub_styling::render_tree::{RenderNodeData, RenderTreeNode}; +use gosub_styling::render_tree::{RenderTreeNode, TextData}; -pub(crate) fn parse_len(node: &mut RenderTreeNode, name: &str) -> LengthPercentage { +pub(crate) fn parse_len( + node: &mut RenderTreeNode, + name: &str, +) -> LengthPercentage { let Some(property) = node.get_property(name) else { return LengthPercentage::Length(0.0); }; @@ -22,7 +26,10 @@ pub(crate) fn parse_len(node: &mut RenderTreeNode, name: &str) -> LengthPercenta } } -pub(crate) fn parse_len_auto(node: &mut RenderTreeNode, name: &str) -> LengthPercentageAuto { +pub(crate) fn parse_len_auto( + node: &mut RenderTreeNode, + name: &str, +) -> LengthPercentageAuto { let Some(property) = node.get_property(name) else { return LengthPercentageAuto::Auto; }; @@ -40,29 +47,19 @@ pub(crate) fn parse_len_auto(node: &mut RenderTreeNode, name: &str) -> LengthPer } } -pub(crate) fn parse_dimension(node: &mut RenderTreeNode, name: &str) -> Dimension { - let mut auto = Dimension::Auto; - if let RenderNodeData::Text(text) = &node.data { - if name == "width" { - auto = Dimension::Length(text.width); - } else if name == "height" { - auto = Dimension::Length(text.height); - } - } - +pub(crate) fn parse_dimension( + node: &mut RenderTreeNode, + name: &str, +) -> Dimension { let Some(property) = node.get_property(name) else { - return auto; + return Dimension::Auto; }; property.compute_value(); - if name == "width" { - println!("Width: {:?}", property.actual); - } - match &property.actual { CssValue::String(value) => match value.as_str() { - "auto" => auto, + "auto" => Dimension::Auto, s if s.ends_with('%') => { let value = s.trim_end_matches('%').parse::().unwrap_or(0.0); Dimension::Percent(value) @@ -71,11 +68,30 @@ pub(crate) fn parse_dimension(node: &mut RenderTreeNode, name: &str) -> Dimensio }, CssValue::Percentage(value) => Dimension::Percent(*value), CssValue::Unit(..) => Dimension::Length(property.actual.unit_to_px()), - _ => auto, + _ => Dimension::Auto, } } -pub(crate) fn parse_align_i(node: &mut RenderTreeNode, name: &str) -> Option { +pub(crate) fn parse_text_dim( + text: &mut TextData, + name: &str, + backend: &B, +) -> Dimension { + let size = text.prerender.prerender(backend); + + if name == "width" || name == "max-width" || name == "min-width" { + Dimension::Length(size.width) + } else if name == "height" || name == "max-height" || name == "min-height" { + Dimension::Length(size.height) + } else { + Dimension::Auto + } +} + +pub(crate) fn parse_align_i( + node: &mut RenderTreeNode, + name: &str, +) -> Option { let display = node.get_property(name)?; display.compute_value(); @@ -95,7 +111,10 @@ pub(crate) fn parse_align_i(node: &mut RenderTreeNode, name: &str) -> Option Option { +pub(crate) fn parse_align_c( + node: &mut RenderTreeNode, + name: &str, +) -> Option { let display = node.get_property(name)?; display.compute_value(); @@ -117,8 +136,8 @@ pub(crate) fn parse_align_c(node: &mut RenderTreeNode, name: &str) -> Option( + node: &mut RenderTreeNode, name: &str, ) -> Vec { let Some(display) = node.get_property(name) else { @@ -135,15 +154,15 @@ pub(crate) fn parse_tracking_sizing_function( } #[allow(dead_code)] -pub(crate) fn parse_non_repeated_tracking_sizing_function( - _node: &mut RenderTreeNode, +pub(crate) fn parse_non_repeated_tracking_sizing_function( + _node: &mut RenderTreeNode, _name: &str, ) -> NonRepeatedTrackSizingFunction { todo!("implement parse_non_repeated_tracking_sizing_function") } -pub(crate) fn parse_grid_auto( - node: &mut RenderTreeNode, +pub(crate) fn parse_grid_auto( + node: &mut RenderTreeNode, name: &str, ) -> Vec { let Some(display) = node.get_property(name) else { @@ -159,7 +178,10 @@ pub(crate) fn parse_grid_auto( Vec::new() //TODO: Implement this } -pub(crate) fn parse_grid_placement(node: &mut RenderTreeNode, name: &str) -> GridPlacement { +pub(crate) fn parse_grid_placement( + node: &mut RenderTreeNode, + name: &str, +) -> GridPlacement { let Some(display) = node.get_property(name) else { return GridPlacement::Auto; }; @@ -182,7 +204,7 @@ pub(crate) fn parse_grid_placement(node: &mut RenderTreeNode, name: &str) -> Gri GridPlacement::Auto } } - CssValue::Number(value) => GridPlacement::from_line_index(*value as i16), + CssValue::Number(value) => GridPlacement::from_line_index((*value) as i16), _ => GridPlacement::Auto, } } diff --git a/crates/gosub_render_utils/src/style/parse_properties.rs b/crates/gosub_render_utils/src/style/parse_properties.rs index 26b8f1bed..03edae3ad 100644 --- a/crates/gosub_render_utils/src/style/parse_properties.rs +++ b/crates/gosub_render_utils/src/style/parse_properties.rs @@ -1,16 +1,17 @@ +use gosub_render_backend::RenderBackend; use regex::Regex; use taffy::prelude::*; use taffy::{Overflow, Point}; use gosub_styling::css_values::CssValue; -use gosub_styling::render_tree::RenderTreeNode; +use gosub_styling::render_tree::{RenderNodeData, RenderTreeNode}; use crate::style::parse::{ parse_align_c, parse_align_i, parse_dimension, parse_grid_auto, parse_grid_placement, - parse_len, parse_len_auto, parse_tracking_sizing_function, + parse_len, parse_len_auto, parse_text_dim, parse_tracking_sizing_function, }; -pub(crate) fn parse_display(node: &mut RenderTreeNode) -> Display { +pub(crate) fn parse_display(node: &mut RenderTreeNode) -> Display { let Some(display) = node.get_property("display") else { return Display::Block; }; @@ -30,7 +31,7 @@ pub(crate) fn parse_display(node: &mut RenderTreeNode) -> Display { } } -pub(crate) fn parse_overflow(node: &mut RenderTreeNode) -> Point { +pub(crate) fn parse_overflow(node: &mut RenderTreeNode) -> Point { fn parse(str: &str) -> Overflow { match str { "visible" => Overflow::Visible, @@ -66,7 +67,7 @@ pub(crate) fn parse_overflow(node: &mut RenderTreeNode) -> Point { overflow } -pub(crate) fn parse_position(node: &mut RenderTreeNode) -> Position { +pub(crate) fn parse_position(node: &mut RenderTreeNode) -> Position { let Some(position) = node.get_property("position") else { return Position::Relative; }; @@ -84,7 +85,9 @@ pub(crate) fn parse_position(node: &mut RenderTreeNode) -> Position { } } -pub(crate) fn parse_inset(node: &mut RenderTreeNode) -> Rect { +pub(crate) fn parse_inset( + node: &mut RenderTreeNode, +) -> Rect { Rect { top: parse_len_auto(node, "inset-top"), right: parse_len_auto(node, "inset-right"), @@ -93,28 +96,58 @@ pub(crate) fn parse_inset(node: &mut RenderTreeNode) -> Rect Size { +pub(crate) fn parse_size( + node: &mut RenderTreeNode, + backend: &B, +) -> Size { + if let RenderNodeData::Text(t) = &mut node.data { + return Size { + width: parse_text_dim(t, "width", backend), + height: parse_text_dim(t, "height", backend), + }; + } + Size { width: parse_dimension(node, "width"), height: parse_dimension(node, "height"), } } -pub(crate) fn parse_min_size(node: &mut RenderTreeNode) -> Size { +pub(crate) fn parse_min_size( + node: &mut RenderTreeNode, + backend: &B, +) -> Size { + if let RenderNodeData::Text(t) = &mut node.data { + return Size { + width: parse_text_dim(t, "min-width", backend), + height: parse_text_dim(t, "min-height", backend), + }; + } + Size { width: parse_dimension(node, "min-width"), height: parse_dimension(node, "min-height"), } } -pub(crate) fn parse_max_size(node: &mut RenderTreeNode) -> Size { +pub(crate) fn parse_max_size( + node: &mut RenderTreeNode, + backend: &B, +) -> Size { + if let RenderNodeData::Text(t) = &mut node.data { + return Size { + width: parse_text_dim(t, "max-width", backend), + height: parse_text_dim(t, "max-height", backend), + }; + } + Size { width: parse_dimension(node, "max-width"), height: parse_dimension(node, "max-height"), } } -pub(crate) fn parse_aspect_ratio(node: &mut RenderTreeNode) -> Option { +pub(crate) fn parse_aspect_ratio(node: &mut RenderTreeNode) -> Option { let aspect_ratio = node.get_property("aspect-ratio")?; aspect_ratio.compute_value(); @@ -150,7 +183,9 @@ pub(crate) fn parse_aspect_ratio(node: &mut RenderTreeNode) -> Option { } } -pub(crate) fn parse_margin(node: &mut RenderTreeNode) -> Rect { +pub(crate) fn parse_margin( + node: &mut RenderTreeNode, +) -> Rect { Rect { top: parse_len_auto(node, "margin-top"), right: parse_len_auto(node, "margin-right"), @@ -159,7 +194,9 @@ pub(crate) fn parse_margin(node: &mut RenderTreeNode) -> Rect Rect { +pub(crate) fn parse_padding( + node: &mut RenderTreeNode, +) -> Rect { Rect { top: parse_len(node, "padding-top"), right: parse_len(node, "padding-right"), @@ -168,7 +205,9 @@ pub(crate) fn parse_padding(node: &mut RenderTreeNode) -> Rect } } -pub(crate) fn parse_border(node: &mut RenderTreeNode) -> Rect { +pub(crate) fn parse_border( + node: &mut RenderTreeNode, +) -> Rect { Rect { top: parse_len(node, "border-top-width"), right: parse_len(node, "border-right-width"), @@ -177,7 +216,9 @@ pub(crate) fn parse_border(node: &mut RenderTreeNode) -> Rect } } -pub(crate) fn parse_align_items(node: &mut RenderTreeNode) -> Option { +pub(crate) fn parse_align_items( + node: &mut RenderTreeNode, +) -> Option { let display = node.get_property("align-items")?; display.compute_value(); @@ -198,34 +239,46 @@ pub(crate) fn parse_align_items(node: &mut RenderTreeNode) -> Option } } -pub(crate) fn parse_align_self(node: &mut RenderTreeNode) -> Option { +pub(crate) fn parse_align_self( + node: &mut RenderTreeNode, +) -> Option { parse_align_i(node, "align-self") } -pub(crate) fn parse_justify_items(node: &mut RenderTreeNode) -> Option { +pub(crate) fn parse_justify_items( + node: &mut RenderTreeNode, +) -> Option { parse_align_i(node, "justify-items") } -pub(crate) fn parse_justify_self(node: &mut RenderTreeNode) -> Option { +pub(crate) fn parse_justify_self( + node: &mut RenderTreeNode, +) -> Option { parse_align_i(node, "justify-self") } -pub(crate) fn parse_align_content(node: &mut RenderTreeNode) -> Option { +pub(crate) fn parse_align_content( + node: &mut RenderTreeNode, +) -> Option { parse_align_c(node, "align-content") } -pub(crate) fn parse_justify_content(node: &mut RenderTreeNode) -> Option { +pub(crate) fn parse_justify_content( + node: &mut RenderTreeNode, +) -> Option { parse_align_c(node, "justify-content") } -pub(crate) fn parse_gap(node: &mut RenderTreeNode) -> Size { +pub(crate) fn parse_gap(node: &mut RenderTreeNode) -> Size { Size { width: parse_len(node, "column-gap"), height: parse_len(node, "row-gap"), } } -pub(crate) fn parse_flex_direction(node: &mut RenderTreeNode) -> FlexDirection { +pub(crate) fn parse_flex_direction( + node: &mut RenderTreeNode, +) -> FlexDirection { let Some(property) = node.get_property("flex-direction") else { return FlexDirection::Row; }; @@ -244,7 +297,7 @@ pub(crate) fn parse_flex_direction(node: &mut RenderTreeNode) -> FlexDirection { } } -pub(crate) fn parse_flex_wrap(node: &mut RenderTreeNode) -> FlexWrap { +pub(crate) fn parse_flex_wrap(node: &mut RenderTreeNode) -> FlexWrap { let Some(property) = node.get_property("flex-wrap") else { return FlexWrap::NoWrap; }; @@ -262,11 +315,11 @@ pub(crate) fn parse_flex_wrap(node: &mut RenderTreeNode) -> FlexWrap { } } -pub(crate) fn parse_flex_basis(node: &mut RenderTreeNode) -> Dimension { +pub(crate) fn parse_flex_basis(node: &mut RenderTreeNode) -> Dimension { parse_dimension(node, "flex-basis") } -pub(crate) fn parse_flex_grow(node: &mut RenderTreeNode) -> f32 { +pub(crate) fn parse_flex_grow(node: &mut RenderTreeNode) -> f32 { let Some(property) = node.get_property("flex-grow") else { return 0.0; }; @@ -279,7 +332,7 @@ pub(crate) fn parse_flex_grow(node: &mut RenderTreeNode) -> f32 { } } -pub(crate) fn parse_flex_shrink(node: &mut RenderTreeNode) -> f32 { +pub(crate) fn parse_flex_shrink(node: &mut RenderTreeNode) -> f32 { let Some(property) = node.get_property("flex-shrink") else { return 1.0; }; @@ -292,27 +345,31 @@ pub(crate) fn parse_flex_shrink(node: &mut RenderTreeNode) -> f32 { } } -pub(crate) fn parse_grid_template_rows(node: &mut RenderTreeNode) -> Vec { +pub(crate) fn parse_grid_template_rows( + node: &mut RenderTreeNode, +) -> Vec { parse_tracking_sizing_function(node, "grid-template-rows") } -pub(crate) fn parse_grid_template_columns(node: &mut RenderTreeNode) -> Vec { +pub(crate) fn parse_grid_template_columns( + node: &mut RenderTreeNode, +) -> Vec { parse_tracking_sizing_function(node, "grid-template-columns") } -pub(crate) fn parse_grid_auto_rows( - node: &mut RenderTreeNode, +pub(crate) fn parse_grid_auto_rows( + node: &mut RenderTreeNode, ) -> Vec { parse_grid_auto(node, "grid-auto-rows") } -pub(crate) fn parse_grid_auto_columns( - node: &mut RenderTreeNode, +pub(crate) fn parse_grid_auto_columns( + node: &mut RenderTreeNode, ) -> Vec { parse_grid_auto(node, "grid-auto-columns") } -pub(crate) fn parse_grid_auto_flow(node: &mut RenderTreeNode) -> GridAutoFlow { +pub(crate) fn parse_grid_auto_flow(node: &mut RenderTreeNode) -> GridAutoFlow { let Some(property) = node.get_property("grid-auto-flow") else { return GridAutoFlow::Row; }; @@ -331,14 +388,18 @@ pub(crate) fn parse_grid_auto_flow(node: &mut RenderTreeNode) -> GridAutoFlow { } } -pub(crate) fn parse_grid_row(node: &mut RenderTreeNode) -> Line { +pub(crate) fn parse_grid_row( + node: &mut RenderTreeNode, +) -> Line { Line { start: parse_grid_placement(node, "grid-row-start"), end: parse_grid_placement(node, "grid-row-end"), } } -pub(crate) fn parse_grid_column(node: &mut RenderTreeNode) -> Line { +pub(crate) fn parse_grid_column( + node: &mut RenderTreeNode, +) -> Line { Line { start: parse_grid_placement(node, "grid-column-start"), end: parse_grid_placement(node, "grid-column-end"), diff --git a/crates/gosub_renderer/Cargo.toml b/crates/gosub_renderer/Cargo.toml index 1a621b238..a12b656db 100644 --- a/crates/gosub_renderer/Cargo.toml +++ b/crates/gosub_renderer/Cargo.toml @@ -13,6 +13,7 @@ gosub_html5 = { path = "../gosub_html5" } gosub_shared = { path = "../gosub_shared" } gosub_styling = { path = "../gosub_styling" } gosub_net = { path = "../gosub_net" } +gosub_render_backend = { path = "../gosub_render_backend" } taffy = "0.5.1" vello = "0.1.0" winit = "0.29.15" diff --git a/crates/gosub_renderer/src/draw.rs b/crates/gosub_renderer/src/draw.rs index d8fa9d4a0..d4e8e9626 100644 --- a/crates/gosub_renderer/src/draw.rs +++ b/crates/gosub_renderer/src/draw.rs @@ -1,16 +1,16 @@ use std::io::Read; -use std::ops::Div; use anyhow::anyhow; -use smallvec::SmallVec; +use image::DynamicImage; use taffy::{AvailableSpace, Layout, NodeId, PrintTree, Size, TaffyTree, TraversePartialTree}; use url::Url; -use vello::kurbo::{Affine, Arc, BezPath, Cap, Join, Rect, RoundedRect, Stroke}; -use vello::peniko::{Color, Fill, Format, Image}; -use vello::Scene; use winit::dpi::PhysicalSize; use gosub_html5::node::NodeId as GosubId; +use gosub_render_backend::{ + Brush, Color, Image, PreRenderText, Rect, RenderBackend, RenderRect, RenderText, Text, + Transform, FP, +}; use gosub_rendering::position::PositionTree; use gosub_styling::css_colors::RgbColor; use gosub_styling::css_values::CssValue; @@ -18,14 +18,13 @@ use gosub_styling::render_tree::{RenderNodeData, RenderTree, RenderTreeNode}; use crate::render_tree::{NodeID, TreeDrawer}; -pub trait SceneDrawer { - /// Returns true if the texture needs to be redrawn - fn draw(&mut self, scene: &mut Scene, size: PhysicalSize); - fn mouse_move(&mut self, scene: &mut Scene, x: f64, y: f64); +pub trait SceneDrawer { + fn draw(&mut self, scene: &mut B, size: PhysicalSize); + fn mouse_move(&mut self, scene: &mut B, x: f64, y: f64); } -impl SceneDrawer for TreeDrawer { - fn draw(&mut self, scene: &mut Scene, size: PhysicalSize) { +impl SceneDrawer for TreeDrawer { + fn draw(&mut self, scene: &mut B, size: PhysicalSize) { if self.size == Some(size) { //This check needs to be updated in the future, when the tree is mutable return; @@ -37,7 +36,7 @@ impl SceneDrawer for TreeDrawer { self.render(scene, size); } - fn mouse_move(&mut self, _scene: &mut Scene, x: f64, y: f64) { + fn mouse_move(&mut self, _scene: &mut B, x: f64, y: f64) { if let Some(e) = self.position.find(x as f32, y as f32) { if self.last_hover != Some(e) { self.last_hover = Some(e); @@ -49,14 +48,14 @@ impl SceneDrawer for TreeDrawer { return; }; - println!("Hovering over: {:?} ({:?})@({x},{y})", node.data, e); + println!("Hovering over: {:?} ({e:?})@({x},{y})", node.data); } }; } } -impl TreeDrawer { - pub(crate) fn render(&mut self, scene: &mut Scene, size: PhysicalSize) { +impl TreeDrawer { + pub(crate) fn render(&mut self, scene: &mut B, size: PhysicalSize) { let space = Size { width: AvailableSpace::Definite(size.width as f32), height: AvailableSpace::Definite(size.height as f32), @@ -71,13 +70,23 @@ impl TreeDrawer { self.position = PositionTree::from_taffy(&self.taffy, self.root); - let bg = Rect::new(0.0, 0.0, size.width as f64, size.height as f64); - scene.fill(Fill::NonZero, Affine::IDENTITY, Color::BLACK, None, &bg); + let bg = B::Rect::new(0.0, 0.0, size.width as FP, size.height as FP); + + let rect = RenderRect { + rect: bg, + transform: None, + radius: None, + brush: B::Brush::color(B::Color::WHITE), + brush_transform: None, + border: None, + }; + + scene.draw_rect(&rect); self.render_node_with_children(self.root, scene, (0.0, 0.0)); } - fn render_node_with_children(&mut self, id: NodeID, scene: &mut Scene, mut pos: (f64, f64)) { + fn render_node_with_children(&mut self, id: NodeID, scene: &mut B, mut pos: (FP, FP)) { let err = self.render_node(id, scene, &mut pos); if let Err(e) = err { eprintln!("Error rendering node: {:?}", e); @@ -96,12 +105,7 @@ impl TreeDrawer { } } - fn render_node( - &mut self, - id: NodeID, - scene: &mut Scene, - pos: &mut (f64, f64), - ) -> anyhow::Result<()> { + fn render_node(&mut self, id: NodeID, scene: &mut B, pos: &mut (FP, FP)) -> anyhow::Result<()> { let gosub_id = *self .taffy .get_node_context(id) @@ -114,8 +118,8 @@ impl TreeDrawer { .get_node_mut(gosub_id) .ok_or(anyhow!("Node not found"))?; - pos.0 += layout.location.x as f64; - pos.1 += layout.location.y as f64; + pos.0 += layout.location.x as FP; + pos.1 += layout.location.y as FP; let border_radius = render_bg(node, scene, layout, pos, &self.url); @@ -140,35 +144,27 @@ impl TreeDrawer { let img = image::load_from_memory(&img)?; - let width = img.width(); - let height = img.height(); - - let img = Image::new( - img.into_rgba8().into_raw().into(), - Format::Rgba8, - width, - height, - ); - let fit = element .attributes .get("object-fit") .map(|prop| prop.as_str()) .unwrap_or("contain"); - render_image(&img, scene, *pos, layout.size, border_radius, fit)?; + render_image(img, scene, *pos, layout.size, border_radius, fit)?; } } render_text(node, scene, pos, layout); - - render_border(node, scene, layout, pos, border_radius); - Ok(()) } } -fn render_text(node: &mut RenderTreeNode, scene: &mut Scene, pos: &(f64, f64), layout: &Layout) { +fn render_text( + node: &mut RenderTreeNode, + scene: &mut B, + pos: &(FP, FP), + layout: &Layout, +) { if let RenderNodeData::Text(text) = &node.data { let color = node .properties @@ -182,22 +178,39 @@ fn render_text(node: &mut RenderTreeNode, scene: &mut Scene, pos: &(f64, f64), l _ => None, } }) - .map(|color| Color::rgba8(color.r as u8, color.g as u8, color.b as u8, color.a as u8)) + .map(|color| Color::rgba(color.r as u8, color.g as u8, color.b as u8, color.a as u8)) .unwrap_or(Color::BLACK); - let affine = Affine::translate((pos.0, pos.1 + layout.size.height as f64)); + let translate = Transform::translate(pos.0 as FP, pos.1 + layout.size.height as FP); + + let text = Text::new(&text.prerender); + + let rect = Rect::new( + pos.0 as FP, + pos.1 as FP, + pos.0 + layout.size.width as FP, + pos.1 + layout.size.height as FP, + ); + + let render_text = RenderText { + text, + rect, + transform: Some(translate), + brush: Brush::color(color), + brush_transform: None, + }; - text.show(scene, color, affine, Fill::NonZero, None); + scene.draw_text(&render_text); } } -fn render_bg( - node: &mut RenderTreeNode, - scene: &mut Scene, +fn render_bg( + node: &mut RenderTreeNode, + scene: &mut B, layout: &Layout, - pos: &(f64, f64), + pos: &(FP, FP), root_url: &Url, -) -> (f64, f64, f64, f64) { +) -> (FP, FP, FP, FP) { let bg_color = node .properties .get("background-color") @@ -210,7 +223,7 @@ fn render_bg( _ => None, } }) - .map(|color| Color::rgba8(color.r as u8, color.g as u8, color.b as u8, color.a as u8)); + .map(|color| Color::rgba(color.r as u8, color.g as u8, color.b as u8, color.a as u8)); let border_radius_left = node .properties @@ -249,22 +262,30 @@ fn render_bg( .unwrap_or(0.0); let border_radius = ( - border_radius_top, - border_radius_right, - border_radius_bottom, - border_radius_left, - ); - - let rect = RoundedRect::new( - pos.0, - pos.1, - pos.0 + layout.size.width as f64, - pos.1 + layout.size.height as f64, - border_radius, + border_radius_top as FP, + border_radius_right as FP, + border_radius_bottom as FP, + border_radius_left as FP, ); if let Some(bg_color) = bg_color { - scene.fill(Fill::NonZero, Affine::IDENTITY, bg_color, None, &rect); + let rect = Rect::new( + pos.0 as FP, + pos.1 as FP, + pos.0 + layout.size.width as FP, + pos.1 + layout.size.height as FP, + ); + + let rect = RenderRect { + rect, + transform: None, + radius: Some(B::BorderRadius::from(border_radius)), + brush: Brush::color(bg_color), + brush_transform: None, + border: None, + }; + + scene.draw_rect(&rect); } let background_image = node.properties.get("background-image").and_then(|prop| { @@ -295,17 +316,7 @@ fn render_bg( let img = image::load_from_memory(&img).unwrap(); - let height = img.height(); - let width = img.width(); - - let img = Image::new( - img.into_rgba8().into_raw().into(), - Format::Rgba8, - width, - height, - ); - - let _ = render_image(&img, scene, *pos, layout.size, border_radius, "fill").map_err(|e| { + let _ = render_image(img, scene, *pos, layout.size, border_radius, "fill").map_err(|e| { eprintln!("Error rendering image: {:?}", e); }); } @@ -335,304 +346,310 @@ impl Side { } } -fn render_border( - node: &mut RenderTreeNode, - scene: &mut Scene, - layout: &Layout, - pos: &(f64, f64), - border_radius: (f64, f64, f64, f64), -) { - for side in Side::all() { - let radi = match side { - Side::Top => border_radius.0, - Side::Right => border_radius.1, - Side::Bottom => border_radius.2, - Side::Left => border_radius.3, - }; - render_border_side(node, scene, layout, pos, radi, side); - } -} - -fn render_border_side( - node: &mut RenderTreeNode, - scene: &mut Scene, - layout: &Layout, - pos: &(f64, f64), - border_radius: f64, - side: Side, -) { - let border_width = match side { - Side::Top => layout.border.top, - Side::Right => layout.border.right, - Side::Bottom => layout.border.bottom, - Side::Left => layout.border.left, - } as f64; - - let border_color = node - .properties - .get(&format!("border-{}-color", side.to_str())) - .and_then(|prop| { - prop.compute_value(); - - match &prop.actual { - CssValue::Color(color) => Some(*color), - CssValue::String(color) => Some(RgbColor::from(color.as_str())), - _ => None, - } - }) - .map(|color| Color::rgba8(color.r as u8, color.g as u8, color.b as u8, color.a as u8)); - - // let border_radius = 16f64; - - let width = layout.size.width as f64; - let height = layout.size.height as f64; - - if let Some(border_color) = border_color { - let mut path = BezPath::new(); - - //draw the border segment with rounded corners - - match side { - Side::Top => { - let offset = border_radius.powi(2).div(2.0).sqrt() - border_radius; - - path.move_to((pos.0 - offset, pos.1 - offset)); - - let arc = Arc::new( - (pos.0 + border_radius, pos.1 + border_radius), - (border_radius, border_radius), - -std::f64::consts::PI * 3.0 / 4.0, - std::f64::consts::PI / 4.0, - 0.0, - ); - - arc.to_cubic_beziers(0.1, |p1, p2, p3| { - path.curve_to(p1, p2, p3); - }); - - path.line_to((pos.0 + width - border_radius, pos.1)); - - let arc = Arc::new( - (pos.0 + width - border_radius, pos.1 + border_radius), - (border_radius, border_radius), - -std::f64::consts::PI / 2.0, - std::f64::consts::PI / 4.0, - 0.0, - ); - - arc.to_cubic_beziers(0.1, |p1, p2, p3| { - path.curve_to(p1, p2, p3); - }); - } - Side::Right => { - let offset = border_radius.powi(2).div(2.0).sqrt() - border_radius; - path.move_to((pos.0 + width + offset, pos.1 - offset)); - - let arc = Arc::new( - (pos.0 + width - border_radius, pos.1 + border_radius), - (border_radius, border_radius), - -std::f64::consts::PI / 4.0, - std::f64::consts::PI / 4.0, - 0.0, - ); - - arc.to_cubic_beziers(0.1, |p1, p2, p3| { - path.curve_to(p1, p2, p3); - }); - - path.line_to((pos.0 + width, pos.1 + height - border_radius)); - - let arc = Arc::new( - ( - pos.0 + width - border_radius, - pos.1 + height - border_radius, - ), - (border_radius, border_radius), - 0.0, - std::f64::consts::PI / 4.0, - 0.0, - ); - - arc.to_cubic_beziers(0.1, |p1, p2, p3| { - path.curve_to(p1, p2, p3); - }); - } - Side::Bottom => { - let offset = border_radius.powi(2).div(2.0).sqrt() - border_radius; - - path.move_to((pos.0 + width + offset, pos.1 + height + offset)); - - let arc = Arc::new( - ( - pos.0 + width - border_radius, - pos.1 + height - border_radius, - ), - (border_radius, border_radius), - -std::f64::consts::PI * 7.0 / 4.0, - std::f64::consts::PI / 4.0, - 0.0, - ); - - arc.to_cubic_beziers(0.1, |p1, p2, p3| { - path.curve_to(p1, p2, p3); - }); - - path.line_to((pos.0 + border_radius, pos.1 + height)); - - let arc = Arc::new( - (pos.0 + border_radius, pos.1 + height - border_radius), - (border_radius, border_radius), - -std::f64::consts::PI * 3.0 / 2.0, - std::f64::consts::PI / 4.0, - 0.0, - ); - - arc.to_cubic_beziers(0.1, |p1, p2, p3| { - path.curve_to(p1, p2, p3); - }); - } - Side::Left => { - let offset = border_radius.powi(2).div(2.0).sqrt() - border_radius; - - path.move_to((pos.0 - offset, pos.1 + height + offset)); - - let arc = Arc::new( - (pos.0 + border_radius, pos.1 + height - border_radius), - (border_radius, border_radius), - -std::f64::consts::PI * 5.0 / 4.0, - std::f64::consts::PI / 4.0, - 0.0, - ); - - arc.to_cubic_beziers(0.1, |p1, p2, p3| { - path.curve_to(p1, p2, p3); - }); - - path.line_to((pos.0, pos.1 + border_radius)); - - let arc = Arc::new( - (pos.0 + border_radius, pos.1 + border_radius), - (border_radius, border_radius), - -std::f64::consts::PI, - std::f64::consts::PI / 4.0, - 0.0, - ); - - arc.to_cubic_beziers(0.1, |p1, p2, p3| { - path.curve_to(p1, p2, p3); - }); - } - } - - let Some(border_style) = node - .properties - .get(&format!("border-{}-style", side.to_str())) - .and_then(|prop| { - prop.compute_value(); - - match &prop.actual { - CssValue::String(style) => Some(style.as_str()), - _ => None, - } - }) - else { - return; - }; - - let border_style = BorderStyle::from_str(border_style); - - let cap = match border_style { - BorderStyle::Dashed => Cap::Square, - BorderStyle::Dotted => Cap::Round, - _ => Cap::Butt, - }; - - let dash_pattern = match border_style { - BorderStyle::Dashed => SmallVec::from([ - border_width * 3.0, - border_width * 3.0, - border_width * 3.0, - border_width * 3.0, - ]), - BorderStyle::Dotted => { - SmallVec::from([border_width, border_width, border_width, border_width]) - //TODO: somehow this doesn't result in circles. It is more like a rounded rectangle - } - _ => SmallVec::default(), - }; - - let stroke = Stroke { - width: border_width, - join: Join::Bevel, - miter_limit: 0.0, - start_cap: cap, - end_cap: cap, - dash_pattern, - dash_offset: 0.0, - }; - - scene.stroke(&stroke, Affine::IDENTITY, border_color, None, &path); - } -} - -fn render_image( - img: &Image, - scene: &mut Scene, - pos: (f64, f64), +// ---- This will be needed for the vello backend ---- +// fn render_border( +// node: &mut RenderTreeNode, +// scene: &mut Scene, +// layout: &Layout, +// pos: &(f64, f64), +// border_radius: (f64, f64, f64, f64), +// ) { +// for side in Side::all() { +// let radi = match side { +// Side::Top => border_radius.0, +// Side::Right => border_radius.1, +// Side::Bottom => border_radius.2, +// Side::Left => border_radius.3, +// }; +// render_border_side(node, scene, layout, pos, radi, side); +// } +// } +// +// fn render_border_side( +// node: &mut RenderTreeNode, +// scene: &mut B , +// layout: &Layout, +// pos: &(FP, FP), +// border_radius: FP, +// side: Side, +// ) { +// let border_width = match side { +// Side::Top => layout.border.top, +// Side::Right => layout.border.right, +// Side::Bottom => layout.border.bottom, +// Side::Left => layout.border.left, +// } as f64; +// +// let border_color = node +// .properties +// .get(&format!("border-{}-color", side.to_str())) +// .and_then(|prop| { +// prop.compute_value(); +// +// match &prop.actual { +// CssValue::Color(color) => Some(*color), +// CssValue::String(color) => Some(RgbColor::from(color.as_str())), +// _ => None, +// } +// }) +// .map(|color| Color::rgba8(color.r as u8, color.g as u8, color.b as u8, color.a as u8)); +// +// // let border_radius = 16f64; +// +// let width = layout.size.width as f64; +// let height = layout.size.height as f64; +// +// if let Some(border_color) = border_color { +// let mut path = BezPath::new(); +// +// //draw the border segment with rounded corners +// +// match side { +// Side::Top => { +// let offset = border_radius.powi(2).div(2.0).sqrt() - border_radius; +// +// path.move_to((pos.0 - offset, pos.1 - offset)); +// +// let arc = Arc::new( +// (pos.0 + border_radius, pos.1 + border_radius), +// (border_radius, border_radius), +// -std::f64::consts::PI * 3.0 / 4.0, +// std::f64::consts::PI / 4.0, +// 0.0, +// ); +// +// arc.to_cubic_beziers(0.1, |p1, p2, p3| { +// path.curve_to(p1, p2, p3); +// }); +// +// path.line_to((pos.0 + width - border_radius, pos.1)); +// +// let arc = Arc::new( +// (pos.0 + width - border_radius, pos.1 + border_radius), +// (border_radius, border_radius), +// -std::f64::consts::PI / 2.0, +// std::f64::consts::PI / 4.0, +// 0.0, +// ); +// +// arc.to_cubic_beziers(0.1, |p1, p2, p3| { +// path.curve_to(p1, p2, p3); +// }); +// } +// Side::Right => { +// let offset = border_radius.powi(2).div(2.0).sqrt() - border_radius; +// path.move_to((pos.0 + width + offset, pos.1 - offset)); +// +// let arc = Arc::new( +// (pos.0 + width - border_radius, pos.1 + border_radius), +// (border_radius, border_radius), +// -std::f64::consts::PI / 4.0, +// std::f64::consts::PI / 4.0, +// 0.0, +// ); +// +// arc.to_cubic_beziers(0.1, |p1, p2, p3| { +// path.curve_to(p1, p2, p3); +// }); +// +// path.line_to((pos.0 + width, pos.1 + height - border_radius)); +// +// let arc = Arc::new( +// ( +// pos.0 + width - border_radius, +// pos.1 + height - border_radius, +// ), +// (border_radius, border_radius), +// 0.0, +// std::f64::consts::PI / 4.0, +// 0.0, +// ); +// +// arc.to_cubic_beziers(0.1, |p1, p2, p3| { +// path.curve_to(p1, p2, p3); +// }); +// } +// Side::Bottom => { +// let offset = border_radius.powi(2).div(2.0).sqrt() - border_radius; +// +// path.move_to((pos.0 + width + offset, pos.1 + height + offset)); +// +// let arc = Arc::new( +// ( +// pos.0 + width - border_radius, +// pos.1 + height - border_radius, +// ), +// (border_radius, border_radius), +// -std::f64::consts::PI * 7.0 / 4.0, +// std::f64::consts::PI / 4.0, +// 0.0, +// ); +// +// arc.to_cubic_beziers(0.1, |p1, p2, p3| { +// path.curve_to(p1, p2, p3); +// }); +// +// path.line_to((pos.0 + border_radius, pos.1 + height)); +// +// let arc = Arc::new( +// (pos.0 + border_radius, pos.1 + height - border_radius), +// (border_radius, border_radius), +// -std::f64::consts::PI * 3.0 / 2.0, +// std::f64::consts::PI / 4.0, +// 0.0, +// ); +// +// arc.to_cubic_beziers(0.1, |p1, p2, p3| { +// path.curve_to(p1, p2, p3); +// }); +// } +// Side::Left => { +// let offset = border_radius.powi(2).div(2.0).sqrt() - border_radius; +// +// path.move_to((pos.0 - offset, pos.1 + height + offset)); +// +// let arc = Arc::new( +// (pos.0 + border_radius, pos.1 + height - border_radius), +// (border_radius, border_radius), +// -std::f64::consts::PI * 5.0 / 4.0, +// std::f64::consts::PI / 4.0, +// 0.0, +// ); +// +// arc.to_cubic_beziers(0.1, |p1, p2, p3| { +// path.curve_to(p1, p2, p3); +// }); +// +// path.line_to((pos.0, pos.1 + border_radius)); +// +// let arc = Arc::new( +// (pos.0 + border_radius, pos.1 + border_radius), +// (border_radius, border_radius), +// -std::f64::consts::PI, +// std::f64::consts::PI / 4.0, +// 0.0, +// ); +// +// arc.to_cubic_beziers(0.1, |p1, p2, p3| { +// path.curve_to(p1, p2, p3); +// }); +// } +// } +// +// let Some(border_style) = node +// .properties +// .get(&format!("border-{}-style", side.to_str())) +// .and_then(|prop| { +// prop.compute_value(); +// +// match &prop.actual { +// CssValue::String(style) => Some(style.as_str()), +// _ => None, +// } +// }) +// else { +// return; +// }; +// +// let border_style = BorderStyle::from_str(border_style); +// +// let cap = match border_style { +// BorderStyle::Dashed => Cap::Square, +// BorderStyle::Dotted => Cap::Round, +// _ => Cap::Butt, +// }; +// +// let dash_pattern = match border_style { +// BorderStyle::Dashed => SmallVec::from([ +// border_width * 3.0, +// border_width * 3.0, +// border_width * 3.0, +// border_width * 3.0, +// ]), +// BorderStyle::Dotted => { +// SmallVec::from([border_width, border_width, border_width, border_width]) +// //TODO: somehow this doesn't result in circles. It is more like a rounded rectangle +// } +// _ => SmallVec::default(), +// }; +// +// let stroke = Stroke { +// width: border_width, +// join: Join::Bevel, +// miter_limit: 0.0, +// start_cap: cap, +// end_cap: cap, +// dash_pattern, +// dash_offset: 0.0, +// }; +// +// scene.stroke(&stroke, Affine::IDENTITY, border_color, None, &path); +// } +// } +// ---- This will be needed for the vello backend ---- + +fn render_image( + img: DynamicImage, + scene: &mut B, + pos: (FP, FP), size: Size, - radii: (f64, f64, f64, f64), + radii: (FP, FP, FP, FP), fit: &str, ) -> anyhow::Result<()> { - let width = size.width as f64; - let height = size.height as f64; + let width = size.width as FP; + let height = size.height as FP; - let rect = RoundedRect::new(pos.0, pos.1, pos.0 + width, pos.1 + height, radii); + let rect = Rect::new(pos.0, pos.1, pos.0 + width, pos.1 + height); - let affine = match fit { + let img_size = (img.width() as FP, img.height() as FP); + + let transform = match fit { "fill" => { - let scale_x = width / img.width as f64; - let scale_y = height / img.height as f64; + let scale_x = width / img_size.0; + let scale_y = height / img_size.1; - Affine::scale_non_uniform(scale_x, scale_y) + B::Transform::scale_xy(scale_x, scale_y) } "contain" => { - let scale_x = width / img.width as f64; - let scale_y = height / img.height as f64; + let scale_x = width / img_size.0; + let scale_y = height / img_size.1; let scale = scale_x.min(scale_y); - Affine::scale_non_uniform(scale, scale) + Transform::scale_xy(scale, scale) } "cover" => { - let scale_x = width / img.width as f64; - let scale_y = height / img.height as f64; + let scale_x = width / img_size.0; + let scale_y = height / img_size.1; let scale = scale_x.max(scale_y); - Affine::scale_non_uniform(scale, scale) + Transform::scale_xy(scale, scale) } "scale-down" => { - let scale_x = width / img.width as f64; - let scale_y = height / img.height as f64; + let scale_x = width / img_size.0; + let scale_y = height / img_size.1; let scale = scale_x.min(scale_y); let scale = scale.min(1.0); - Affine::scale_non_uniform(scale, scale) + Transform::scale_xy(scale, scale) } - _ => Affine::IDENTITY, + _ => Transform::IDENTITY, }; - let affine = affine.with_translation(pos.into()); + let transform = transform.with_translation(pos); - println!("affine: {:?}", affine); - println!("fit: {:?}", fit); - println!("width: {:?}", width); - println!("height: {:?}", height); - println!("img size: {}x{}", img.width, img.height); - println!("rect: {:?}", rect); + let rect = RenderRect { + rect, + transform: Some(transform), + radius: Some(B::BorderRadius::from(radii)), + brush: Brush::image(Image::new(img_size, img.into_rgba8().into_raw())), + brush_transform: None, + border: None, + }; - scene.fill(Fill::NonZero, Affine::IDENTITY, img, Some(affine), &rect); + scene.draw_rect(&rect); Ok(()) } @@ -672,17 +689,21 @@ impl BorderStyle { } //just for debugging -pub fn print_tree(tree: &TaffyTree, root: NodeId, gosub_tree: &RenderTree) { +pub fn print_tree( + tree: &TaffyTree, + root: NodeId, + gosub_tree: &RenderTree, +) { println!("TREE"); print_node(tree, root, false, String::new(), gosub_tree); /// Recursive function that prints each node in the tree - fn print_node( + fn print_node( tree: &TaffyTree, node_id: NodeId, has_sibling: bool, lines_string: String, - gosub_tree: &RenderTree, + gosub_tree: &RenderTree, ) { let layout = &tree.get_final_layout(node_id); let display = tree.get_debug_label(node_id); @@ -708,7 +729,7 @@ pub fn print_tree(tree: &TaffyTree, root: NodeId, gosub_tree: &RenderTr node_render.push('>'); } RenderNodeData::Text(text) => { - let text = text.text.replace('\n', " "); + let text = text.prerender.value().replace('\n', " "); node_render.push_str(text.trim()); } diff --git a/crates/gosub_renderer/src/render_tree.rs b/crates/gosub_renderer/src/render_tree.rs index 9f3060cd5..11ae90db1 100644 --- a/crates/gosub_renderer/src/render_tree.rs +++ b/crates/gosub_renderer/src/render_tree.rs @@ -4,14 +4,15 @@ use url::Url; use winit::dpi::PhysicalSize; use gosub_html5::node::NodeId as GosubID; +use gosub_render_backend::RenderBackend; use gosub_rendering::position::PositionTree; use gosub_styling::css_values::CssProperties; use gosub_styling::render_tree::{RenderNodeData, RenderTree as StyleTree}; pub type NodeID = TaffyID; -pub struct TreeDrawer { - pub(crate) style: StyleTree, +pub struct TreeDrawer { + pub(crate) style: StyleTree, pub(crate) root: NodeID, pub(crate) taffy: TaffyTree, pub(crate) size: Option>, @@ -20,8 +21,8 @@ pub struct TreeDrawer { pub(crate) last_hover: Option, } -impl TreeDrawer { - pub fn new(style: StyleTree, taffy: TaffyTree, root: TaffyID, url: Url) -> Self { +impl TreeDrawer { + pub fn new(style: StyleTree, taffy: TaffyTree, root: TaffyID, url: Url) -> Self { let position = PositionTree::from_taffy(&taffy, root); Self { style, @@ -35,12 +36,12 @@ impl TreeDrawer { } } -pub struct RenderTreeNode { +pub struct RenderTreeNode { pub parent: Option, pub children: Vec, pub layout: Layout, pub name: String, pub properties: CssProperties, pub namespace: Option, - pub data: RenderNodeData, + pub data: RenderNodeData, } diff --git a/crates/gosub_renderer/src/renderer.rs b/crates/gosub_renderer/src/renderer.rs index 56a8637c4..2bd617a7f 100644 --- a/crates/gosub_renderer/src/renderer.rs +++ b/crates/gosub_renderer/src/renderer.rs @@ -4,6 +4,7 @@ use std::sync::Arc; use std::thread::JoinHandle; use anyhow::anyhow; +use gosub_render_backend::RenderBackend; use vello::{AaSupport, Renderer as VelloRenderer, RendererOptions as VelloRendererOptions}; use wgpu::util::{ backend_bits_from_env, dx12_shader_compiler_from_env, gles_minor_version_from_env, @@ -208,7 +209,7 @@ impl Renderer { } #[cfg(not(target_arch = "wasm32"))] - pub fn start_in_thread( + pub fn start_in_thread + Send + 'static, B: RenderBackend>( &self, drawers: D, #[cfg(target_arch = "wasm32")] id: Option, @@ -216,20 +217,20 @@ impl Renderer { let adapter = Arc::clone(&self.instance_adapter); std::thread::spawn(move || { - let mut window = Window::new(adapter, drawers)?; + let mut window: Window = Window::new(adapter, drawers)?; window.start()?; Ok(()) }) } - pub fn start( + pub fn start + 'static, B: RenderBackend>( &self, drawers: D, #[cfg(target_arch = "wasm32")] id: Option, ) -> Result<()> { #[cfg(not(target_arch = "wasm32"))] - let mut window = Window::new(Arc::clone(&self.instance_adapter), drawers)?; + let mut window: Window = Window::new(Arc::clone(&self.instance_adapter), drawers)?; #[cfg(target_arch = "wasm32")] let mut window = Window::new(Arc::clone(&self.instance_adapter), drawers, id)?; window.start()?; diff --git a/crates/gosub_renderer/src/window.rs b/crates/gosub_renderer/src/window.rs index a8f0028e1..68558f1f1 100644 --- a/crates/gosub_renderer/src/window.rs +++ b/crates/gosub_renderer/src/window.rs @@ -2,13 +2,14 @@ use std::sync::Arc; use anyhow::anyhow; use vello::peniko::Color; -use vello::{AaConfig, RenderParams, Renderer, Scene}; +use vello::{AaConfig, RenderParams, Renderer}; use winit::dpi::LogicalSize; use winit::event::{Event, WindowEvent}; use winit::event_loop; use winit::event_loop::EventLoopWindowTarget; use winit::window::{Window as WinitWindow, WindowBuilder, WindowId}; +use gosub_render_backend::RenderBackend; use gosub_shared::types::Result; use crate::draw::SceneDrawer; @@ -26,16 +27,16 @@ type CustomEvent = (); type EventLoop = event_loop::EventLoop; type WindowEventLoop = EventLoopWindowTarget; -pub struct Window<'a, D: SceneDrawer> { +pub struct Window<'a, D: SceneDrawer, B: RenderBackend> { event_loop: Option, state: WindowState<'a>, - scene: Scene, + scene: B, adapter: Arc, renderer: Renderer, scene_drawer: D, } -impl<'a, D: SceneDrawer> Window<'a, D> { +impl<'a, D: SceneDrawer, B: RenderBackend> Window<'a, D, B> { /// Creates a new window AND opens it pub fn new( adapter: Arc, @@ -73,7 +74,7 @@ impl<'a, D: SceneDrawer> Window<'a, D> { Ok(Self { event_loop: Some(event_loop), state, - scene: Scene::new(), + scene: todo!(), adapter, renderer, scene_drawer, @@ -187,7 +188,7 @@ impl<'a, D: SceneDrawer> Window<'a, D> { .resize_surface(surface, size.width, size.height); window.request_redraw(); } - + #[allow(unused, clippy::diverging_sub_expression, unreachable_code)] //shut up clippy WindowEvent::RedrawRequested => { let size = window.inner_size(); self.scene_drawer.draw(&mut self.scene, size); @@ -200,7 +201,7 @@ impl<'a, D: SceneDrawer> Window<'a, D> { .render_to_surface( &self.adapter.device, &self.adapter.queue, - &self.scene, + todo!(), &surface_texture, &RenderParams { base_color: Color::BLACK, diff --git a/crates/gosub_styling/Cargo.toml b/crates/gosub_styling/Cargo.toml index dc1192c71..dd7aaa9b2 100644 --- a/crates/gosub_styling/Cargo.toml +++ b/crates/gosub_styling/Cargo.toml @@ -9,6 +9,8 @@ license = "MIT" gosub_shared = { path = "../gosub_shared" } gosub_css3 = { path = "../gosub_css3" } gosub_html5 = { path = "../gosub_html5" } +gosub_render_backend = { path = "../gosub_render_backend" } +gosub_typeface = { path = "../gosub_typeface" } lazy_static = "1.4" anyhow = "1.0.86" regex = "1.10.4" diff --git a/crates/gosub_styling/src/lib.rs b/crates/gosub_styling/src/lib.rs index 1ee6dd510..49085931f 100644 --- a/crates/gosub_styling/src/lib.rs +++ b/crates/gosub_styling/src/lib.rs @@ -12,7 +12,7 @@ use gosub_css3::Css3; pub mod css_colors; pub mod css_values; -pub mod prerender_text; +// pub mod prerender_text; mod property_list; pub mod render_tree; diff --git a/crates/gosub_styling/src/prerender_text.rs b/crates/gosub_styling/src/prerender_text.rs index 340c1796a..aaca4bbe7 100644 --- a/crates/gosub_styling/src/prerender_text.rs +++ b/crates/gosub_styling/src/prerender_text.rs @@ -2,7 +2,6 @@ use std::sync::{Arc, Mutex}; use lazy_static::lazy_static; #[cfg(not(target_arch = "wasm32"))] -use rust_fontconfig::{FcFontCache, FcPattern}; use vello::glyph::Glyph; use vello::kurbo::Affine; use vello::peniko::{Blob, BrushRef, Font, StyleRef}; @@ -11,222 +10,8 @@ use vello::skrifa::{FontRef, MetadataProvider}; use vello::Scene; use gosub_html5::node::data::text::TextData; - -#[cfg(target_arch = "wasm32")] -#[derive(Default, Clone, Debug, PartialEq, Eq)] -pub struct FcPattern { - name: Option, -} - -pub const DEFAULT_FS: f32 = 12.0; - -#[cfg(not(target_arch = "wasm32"))] -lazy_static! { - pub static ref FONT_PATH_CACHE: FcFontCache = FcFontCache::build(); -} - -lazy_static! { - pub static ref FONT_RENDERER_CACHE: Mutex = { - let font = Font::new( - Blob::new(Arc::new(include_bytes!( - "../../../resources/fonts/Roboto-Regular.ttf" - ))), - 0, - ); - - let backup = TextRenderer { - pattern: FcPattern { - name: Some("Roboto".to_string()), - ..Default::default() - }, - font, - sizing: Vec::new(), - }; - - Mutex::new(FontRendererCache::new(backup)) - }; -} - -pub struct FontRendererCache { - renderers: Vec, - pub backup: TextRenderer, -} - -enum Index { - Some(usize), - Backup, -} - -impl Index { - fn is_backup(&self) -> bool { - matches!(self, Self::Backup) - } -} - -impl From> for Index { - fn from(index: Option) -> Self { - match index { - Some(index) => Self::Some(index), - None => Self::Backup, - } - } -} - -#[allow(dead_code)] -enum IndexNoBackup { - None, - Some(usize), - Insert(String), -} - -impl IndexNoBackup { - fn is_none(&self) -> bool { - matches!(self, Self::None) - } -} - -impl From> for IndexNoBackup { - fn from(index: Option) -> Self { - match index { - Some(index) => Self::Some(index), - None => Self::None, - } - } -} - -impl FontRendererCache { - fn new(backup: TextRenderer) -> Self { - Self { - renderers: Vec::new(), - backup, - } - } - - fn query_no_backup(&mut self, pattern: FcPattern) -> IndexNoBackup { - let index: IndexNoBackup = self - .renderers - .iter() - .position(|r| r.pattern == pattern) - .into(); - - if index.is_none() { - #[cfg(not(target_arch = "wasm32"))] - { - let Some(font_path) = FONT_PATH_CACHE.query(&pattern) else { - return IndexNoBackup::None; - }; - - return IndexNoBackup::Insert(font_path.path.clone()); - } - #[cfg(target_arch = "wasm32")] - return IndexNoBackup::None; - } - - index - } - - pub fn query(&mut self, pattern: FcPattern) -> &mut TextRenderer { - if self.backup.pattern == pattern { - return &mut self.backup; - } - - // we need to do this with an index value because of https://github.com/rust-lang/rust/issues/21906 - #[allow(unused_mut)] - let mut index: Index = self - .renderers - .iter() - .position(|r| r.pattern == pattern) - .into(); - - if index.is_backup() { - #[cfg(not(target_arch = "wasm32"))] - { - let Some(font_path) = FONT_PATH_CACHE.query(&pattern) else { - return &mut self.backup; - }; - - let Ok(font_bytes) = std::fs::read(&font_path.path) else { - return &mut self.backup; - }; - - let font = Font::new(Blob::new(Arc::new(font_bytes)), 0); - - let r = TextRenderer { - pattern, - font, - sizing: Vec::new(), - }; - - self.renderers.push(r); - index = Index::Some(self.renderers.len() - 1); - } - #[cfg(target_arch = "wasm32")] - return &mut self.backup; - } - - match index { - Index::Some(index) => &mut self.renderers[index], - Index::Backup => &mut self.backup, - } - } - - pub fn query_ff(&mut self, font_family: Vec) -> &mut TextRenderer { - let mut renderer = IndexNoBackup::None; - for f in font_family { - let pattern = FcPattern { - name: Some(f), - ..Default::default() - }; - - let rend = self.query_no_backup(pattern); - - match rend { - IndexNoBackup::Some(index) => { - return &mut self.renderers[index]; - } - IndexNoBackup::Insert(path) => { - renderer = IndexNoBackup::Insert(path); - } - IndexNoBackup::None => {} - } - } - - match renderer { - IndexNoBackup::Some(index) => &mut self.renderers[index], //unreachable, but we handle it just in case - IndexNoBackup::Insert(path) => { - let font_bytes = std::fs::read(&path).expect("Failed to read font file"); - let font = Font::new(Blob::new(Arc::new(font_bytes)), 0); - - let r = TextRenderer { - pattern: FcPattern { - name: Some(path), - ..Default::default() - }, - font, - sizing: Vec::new(), - }; - - let idx = self.renderers.len(); - self.renderers.push(r); - &mut self.renderers[idx] - } - IndexNoBackup::None => &mut self.backup, - } - } -} - -#[derive(Clone)] -pub struct TextRenderer { - pattern: FcPattern, - pub font: Font, - sizing: Vec, -} - -#[derive(Clone)] -pub struct FontSizing { - pub font_size: f32, - pub line_height: f32, -} +use gosub_render_backend::RenderBackend; +use gosub_typeface::{FontSizing, TextRenderer, FONT_RENDERER_CACHE}; #[derive(Debug)] pub struct PrerenderText { @@ -270,8 +55,9 @@ impl PrerenderText { font_size: f32, renderer: &mut TextRenderer, ) -> anyhow::Result { + let font = Font::new(Blob::new(renderer.font.data), 0); let font_ref = - to_font_ref(&renderer.font).ok_or_else(|| anyhow::anyhow!("Failed to get font ref"))?; + to_font_ref(&font).ok_or_else(|| anyhow::anyhow!("Failed to get font ref"))?; let axes = font_ref.axes(); let char_map = font_ref.charmap(); @@ -321,28 +107,32 @@ impl PrerenderText { line_height, font_size, glyphs, - font: renderer.font.clone(), + font, }) } - pub fn show<'a>( + pub fn show<'a, B: RenderBackend>( &self, - scene: &mut Scene, + scene: &mut B, brush: impl Into>, - transform: Affine, + transform: B::Transform, style: impl Into>, - glyph_transform: Option, + glyph_transform: Option, ) { let brush = brush.into(); let style = style.into(); - scene - .draw_glyphs(&self.font) - .font_size(self.font_size) - .transform(transform) - .glyph_transform(glyph_transform) - .brush(brush) - .draw(style, self.glyphs.iter().copied()); + let _ = (scene, transform, glyph_transform, brush, style); + + todo!() + + // scene + // .draw_glyphs(&self.font) + // .font_size(self.font_size) + // .transform(transform) + // .glyph_transform(glyph_transform) + // .brush(brush) + // .draw(style, self.glyphs.iter().copied()); } } diff --git a/crates/gosub_styling/src/render_tree.rs b/crates/gosub_styling/src/render_tree.rs index 42980d04d..f84e662b1 100644 --- a/crates/gosub_styling/src/render_tree.rs +++ b/crates/gosub_styling/src/render_tree.rs @@ -1,30 +1,26 @@ use std::collections::HashMap; +use std::fmt::Debug; -use anyhow::anyhow; - -use gosub_html5::node::data::comment::CommentData; -use gosub_html5::node::data::doctype::DocTypeData; -use gosub_html5::node::data::document::DocumentData; use gosub_html5::node::data::element::ElementData; -use gosub_html5::node::data::text::TextData; use gosub_html5::node::{NodeData, NodeId}; use gosub_html5::parser::document::{DocumentHandle, TreeIterator}; +use gosub_render_backend::{PreRenderText, RenderBackend, FP}; use gosub_shared::types::Result; +use gosub_typeface::DEFAULT_FS; use crate::css_values::{ match_selector, CssProperties, CssProperty, CssValue, DeclarationProperty, }; -use crate::prerender_text::{PrerenderText, DEFAULT_FS, FONT_RENDERER_CACHE}; /// Map of all declared values for all nodes in the document #[derive(Default, Debug)] -pub struct RenderTree { - pub nodes: HashMap, +pub struct RenderTree { + pub nodes: HashMap>, pub root: NodeId, pub dirty: bool, } -impl RenderTree { +impl RenderTree { // Generates a new render tree with a root node pub fn with_capacity(capacity: usize) -> Self { let mut tree = Self { @@ -42,7 +38,7 @@ impl RenderTree { parent: None, name: String::from("root"), namespace: None, - data: RenderNodeData::Document(DocumentData::default()), + data: RenderNodeData::Document, }, ); @@ -50,17 +46,17 @@ impl RenderTree { } /// Returns the root node of the render tree - pub fn get_root(&self) -> &RenderTreeNode { + pub fn get_root(&self) -> &RenderTreeNode { self.nodes.get(&self.root).expect("root node") } /// Returns the node with the given id - pub fn get_node(&self, id: NodeId) -> Option<&RenderTreeNode> { + pub fn get_node(&self, id: NodeId) -> Option<&RenderTreeNode> { self.nodes.get(&id) } /// Returns a mutable reference to the node with the given id - pub fn get_node_mut(&mut self, id: NodeId) -> Option<&mut RenderTreeNode> { + pub fn get_node_mut(&mut self, id: NodeId) -> Option<&mut RenderTreeNode> { self.nodes.get_mut(&id) } @@ -71,12 +67,12 @@ impl RenderTree { /// Inserts a new node into the render tree, note that you are responsible for the node id /// and the children of the node - pub fn insert_node(&mut self, id: NodeId, node: RenderTreeNode) { + pub fn insert_node(&mut self, id: NodeId, node: RenderTreeNode) { self.nodes.insert(id, node); } /// Deletes the node with the given id from the render tree - pub fn delete_node(&mut self, id: &NodeId) -> Option<(NodeId, RenderTreeNode)> { + pub fn delete_node(&mut self, id: &NodeId) -> Option<(NodeId, RenderTreeNode)> { if self.nodes.contains_key(id) { self.nodes.remove_entry(id) } else { @@ -213,9 +209,13 @@ impl RenderTree { RenderNodeData::from_node_data(current_node.data.clone(), None) }; - let Ok(data) = data() else { - eprintln!("Failed to create node data for node: {:?}", current_node_id); - continue; + let data = match data() { + ControlFlow::Ok(data) => data, + ControlFlow::Drop => continue, + ControlFlow::Error(e) => { + eprintln!("Failed to create node data for node: {current_node_id:?} ({e}"); + continue; + } }; let render_tree_node = RenderTreeNode { @@ -265,52 +265,61 @@ impl RenderTree { } #[derive(Debug)] -pub enum RenderNodeData { - Document(DocumentData), +pub enum RenderNodeData { + Document, Element(Box), - Text(PrerenderText), - Comment(CommentData), - //are these really needed in the render tree? - DocType(DocTypeData), + Text(Box>), +} + +pub struct TextData { + pub prerender: B::PreRenderText, } -impl RenderNodeData { - pub fn from_node_data(node: NodeData, props: Option<&mut CssProperties>) -> Result { - Ok(match node { - NodeData::Document(data) => RenderNodeData::Document(data), +impl Debug for TextData { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("TextData") + .field("text", &self.prerender.value()) + .field("font", &self.prerender.font()) + .field("fs", &self.prerender.fs()) + .finish() + } +} + +pub enum ControlFlow { + Ok(T), + Drop, + Error(anyhow::Error), +} + +impl RenderNodeData { + pub fn from_node_data(node: NodeData, props: Option<&mut CssProperties>) -> ControlFlow { + ControlFlow::Ok(match node { NodeData::Element(data) => RenderNodeData::Element(data), NodeData::Text(data) => { let text = data.value.trim(); let text = text.replace('\n', ""); let text = text.replace('\r', ""); - let props = props.ok_or(anyhow::anyhow!("No properties found"))?; - - let font_cache = &mut *FONT_RENDERER_CACHE - .lock() - .map_err(|e| anyhow!(e.to_string()))?; + let Some(props) = props else { + return ControlFlow::Error(anyhow::anyhow!("No properties found")); + }; let font = props.get("font-family").and_then(|prop| { prop.compute_value(); if let CssValue::String(font_family) = &prop.actual { - let ff = font_family - .trim() - .split(',') - .map(|ff| ff.to_string()) - .collect::>(); + return Some( + font_family + .trim() + .split(',') + .map(|ff| ff.to_string()) + .collect::>(), + ); + } - return Some(font_cache.query_ff(ff)); - }; None }); - let font = if let Some(font) = font { - font - } else { - &mut font_cache.backup - }; - let fs = props .get("font-size") .and_then(|prop| { @@ -322,29 +331,31 @@ impl RenderNodeData { } None }) - .unwrap_or(DEFAULT_FS); + .unwrap_or(DEFAULT_FS) as FP; - let text = PrerenderText::with_renderer(text, fs, font)?; - RenderNodeData::Text(text) + let prerender = PreRenderText::new(text, font, fs); + + let text = TextData { prerender }; + + RenderNodeData::Text(Box::new(text)) } - NodeData::Comment(data) => RenderNodeData::Comment(data), - NodeData::DocType(data) => RenderNodeData::DocType(data), + _ => return ControlFlow::Drop, }) } } #[derive(Debug)] -pub struct RenderTreeNode { +pub struct RenderTreeNode { pub id: NodeId, pub properties: CssProperties, pub children: Vec, pub parent: Option, pub name: String, pub namespace: Option, - pub data: RenderNodeData, + pub data: RenderNodeData, } -impl RenderTreeNode { +impl RenderTreeNode { /// Returns true if the node is an element node pub fn is_element(&self) -> bool { matches!(self.data, RenderNodeData::Element(_)) @@ -370,61 +381,61 @@ impl RenderTreeNode { } /// Generates a render tree for the given document based on its loaded stylesheets -pub fn generate_render_tree(document: DocumentHandle) -> Result { +pub fn generate_render_tree(document: DocumentHandle) -> Result> { let mut render_tree = RenderTree::from_document(document); render_tree.remove_unrenderable_nodes(); Ok(render_tree) } -pub fn walk_render_tree(tree: &RenderTree, visitor: &mut Box>) { - let root = tree.get_root(); - internal_walk_render_tree(tree, root, visitor); -} - -fn internal_walk_render_tree( - tree: &RenderTree, - node: &RenderTreeNode, - visitor: &mut Box>, -) { - // Enter node - match &node.data { - RenderNodeData::Document(document) => visitor.document_enter(tree, node, document), - RenderNodeData::DocType(doctype) => visitor.doctype_enter(tree, node, doctype), - RenderNodeData::Text(text) => visitor.text_enter(tree, node, &text.into()), - RenderNodeData::Comment(comment) => visitor.comment_enter(tree, node, comment), - RenderNodeData::Element(element) => visitor.element_enter(tree, node, element), - } - - for child_id in &node.children { - if tree.nodes.contains_key(child_id) { - let child_node = tree.nodes.get(child_id).expect("node"); - internal_walk_render_tree(tree, child_node, visitor); - } - } - - // Leave node - match &node.data { - RenderNodeData::Document(document) => visitor.document_leave(tree, node, document), - RenderNodeData::DocType(doctype) => visitor.doctype_leave(tree, node, doctype), - RenderNodeData::Text(text) => visitor.text_leave(tree, node, &text.into()), - RenderNodeData::Comment(comment) => visitor.comment_leave(tree, node, comment), - RenderNodeData::Element(element) => visitor.element_leave(tree, node, element), - } -} - -pub trait TreeVisitor { - fn document_enter(&mut self, tree: &RenderTree, node: &RenderTreeNode, data: &DocumentData); - fn document_leave(&mut self, tree: &RenderTree, node: &RenderTreeNode, data: &DocumentData); - - fn doctype_enter(&mut self, tree: &RenderTree, node: &RenderTreeNode, data: &DocTypeData); - fn doctype_leave(&mut self, tree: &RenderTree, node: &RenderTreeNode, data: &DocTypeData); - - fn text_enter(&mut self, tree: &RenderTree, node: &RenderTreeNode, data: &TextData); - fn text_leave(&mut self, tree: &RenderTree, node: &RenderTreeNode, data: &TextData); - - fn comment_enter(&mut self, tree: &RenderTree, node: &RenderTreeNode, data: &CommentData); - fn comment_leave(&mut self, tree: &RenderTree, node: &RenderTreeNode, data: &CommentData); - - fn element_enter(&mut self, tree: &RenderTree, node: &RenderTreeNode, data: &ElementData); - fn element_leave(&mut self, tree: &RenderTree, node: &RenderTreeNode, data: &ElementData); -} +// pub fn walk_render_tree(tree: &RenderTree, visitor: &mut Box>) { +// let root = tree.get_root(); +// internal_walk_render_tree(tree, root, visitor); +// } +// +// fn internal_walk_render_tree( +// tree: &RenderTree, +// node: &RenderTreeNode, +// visitor: &mut Box>, +// ) { +// // Enter node +// match &node.data { +// RenderNodeData::Document(document) => visitor.document_enter(tree, node, document), +// RenderNodeData::DocType(doctype) => visitor.doctype_enter(tree, node, doctype), +// RenderNodeData::Text(text) => visitor.text_enter(tree, node, &text.into()), +// RenderNodeData::Comment(comment) => visitor.comment_enter(tree, node, comment), +// RenderNodeData::Element(element) => visitor.element_enter(tree, node, element), +// } +// +// for child_id in &node.children { +// if tree.nodes.contains_key(child_id) { +// let child_node = tree.nodes.get(child_id).expect("node"); +// internal_walk_render_tree(tree, child_node, visitor); +// } +// } +// +// // Leave node +// match &node.data { +// RenderNodeData::Document(document) => visitor.document_leave(tree, node, document), +// RenderNodeData::DocType(doctype) => visitor.doctype_leave(tree, node, doctype), +// RenderNodeData::Text(text) => visitor.text_leave(tree, node, &text.into()), +// RenderNodeData::Comment(comment) => visitor.comment_leave(tree, node, comment), +// RenderNodeData::Element(element) => visitor.element_leave(tree, node, element), +// } +// } +// +// pub trait TreeVisitor { +// fn document_enter(&mut self, tree: &RenderTree, node: &RenderTreeNode, data: &DocumentData); +// fn document_leave(&mut self, tree: &RenderTree, node: &RenderTreeNode, data: &DocumentData); +// +// fn doctype_enter(&mut self, tree: &RenderTree, node: &RenderTreeNode, data: &DocTypeData); +// fn doctype_leave(&mut self, tree: &RenderTree, node: &RenderTreeNode, data: &DocTypeData); +// +// fn text_enter(&mut self, tree: &RenderTree, node: &RenderTreeNode, data: &TextData); +// fn text_leave(&mut self, tree: &RenderTree, node: &RenderTreeNode, data: &TextData); +// +// fn comment_enter(&mut self, tree: &RenderTree, node: &RenderTreeNode, data: &CommentData); +// fn comment_leave(&mut self, tree: &RenderTree, node: &RenderTreeNode, data: &CommentData); +// +// fn element_enter(&mut self, tree: &RenderTree, node: &RenderTreeNode, data: &ElementData); +// fn element_leave(&mut self, tree: &RenderTree, node: &RenderTreeNode, data: &ElementData); +// } diff --git a/crates/gosub_typeface/Cargo.toml b/crates/gosub_typeface/Cargo.toml new file mode 100644 index 000000000..8f4d1c9b4 --- /dev/null +++ b/crates/gosub_typeface/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "gosub_typeface" +version = "0.1.0" +edition = "2021" + +[dependencies] +lazy_static = "1.4.0" +rust-fontconfig = "0.1.7" diff --git a/crates/gosub_typeface/src/lib.rs b/crates/gosub_typeface/src/lib.rs new file mode 100644 index 000000000..fd854513b --- /dev/null +++ b/crates/gosub_typeface/src/lib.rs @@ -0,0 +1,251 @@ +use std::sync::{Arc, Mutex}; + +use lazy_static::lazy_static; +use rust_fontconfig::{FcFontCache, FcPattern}; + +#[derive(Clone, PartialEq, Debug)] +pub struct Font { + pub data: Arc>, + pub ty: FontType, +} + +impl Font { + pub fn new(data: Arc>) -> Self { + Self { + data, + ty: FontType::Unknown, + } + } + + pub fn unknown(data: Arc>) -> Self { + Self { + data, + ty: FontType::Unknown, + } + } +} + +#[derive(Clone, PartialEq, Debug)] +pub enum FontType { + TrueType, + OpenType, + Woff, + Woff2, + Svg, + Unknown, + //TODO: add others (maybe) +} + +#[cfg(target_arch = "wasm32")] +#[derive(Default, Clone, Debug, PartialEq, Eq)] +pub struct FcPattern { + name: Option, +} + +pub const DEFAULT_FS: f32 = 12.0; //TODO: this needs to be moved to somewhere and made configurable + +#[cfg(not(target_arch = "wasm32"))] +lazy_static! { + pub static ref FONT_PATH_CACHE: FcFontCache = FcFontCache::build(); +} + +lazy_static! { + pub static ref FONT_RENDERER_CACHE: Mutex = { + let font = Font { + data: Arc::new(include_bytes!("../../../resources/fonts/Roboto-Regular.ttf").to_vec()), + ty: FontType::TrueType, + }; + + let backup = TextRenderer { + pattern: FcPattern { + name: Some("Roboto".to_string()), + ..Default::default() + }, + font, + sizing: Vec::new(), + }; + + Mutex::new(FontRendererCache::new(backup)) + }; +} + +pub struct FontRendererCache { + renderers: Vec, + pub backup: TextRenderer, +} + +enum Index { + Some(usize), + Backup, +} + +impl Index { + fn is_backup(&self) -> bool { + matches!(self, Self::Backup) + } +} + +impl From> for Index { + fn from(index: Option) -> Self { + match index { + Some(index) => Self::Some(index), + None => Self::Backup, + } + } +} + +#[allow(dead_code)] +enum IndexNoBackup { + None, + Some(usize), + Insert(String), +} + +impl IndexNoBackup { + fn is_none(&self) -> bool { + matches!(self, Self::None) + } +} + +impl From> for IndexNoBackup { + fn from(index: Option) -> Self { + match index { + Some(index) => Self::Some(index), + None => Self::None, + } + } +} + +impl FontRendererCache { + fn new(backup: TextRenderer) -> Self { + Self { + renderers: Vec::new(), + backup, + } + } + + fn query_no_backup(&mut self, pattern: FcPattern) -> IndexNoBackup { + let index: IndexNoBackup = self + .renderers + .iter() + .position(|r| r.pattern == pattern) + .into(); + + if index.is_none() { + #[cfg(not(target_arch = "wasm32"))] + { + let Some(font_path) = FONT_PATH_CACHE.query(&pattern) else { + return IndexNoBackup::None; + }; + + return IndexNoBackup::Insert(font_path.path.clone()); + } + #[cfg(target_arch = "wasm32")] + return IndexNoBackup::None; + } + + index + } + + pub fn query(&mut self, pattern: FcPattern) -> &mut TextRenderer { + if self.backup.pattern == pattern { + return &mut self.backup; + } + + // we need to do this with an index value because of https://github.com/rust-lang/rust/issues/21906 + #[allow(unused_mut)] + let mut index: Index = self + .renderers + .iter() + .position(|r| r.pattern == pattern) + .into(); + + if index.is_backup() { + #[cfg(not(target_arch = "wasm32"))] + { + let Some(font_path) = FONT_PATH_CACHE.query(&pattern) else { + return &mut self.backup; + }; + + let Ok(font_bytes) = std::fs::read(&font_path.path) else { + return &mut self.backup; + }; + + let font = Font::new(Arc::new(font_bytes)); + + let r = TextRenderer { + pattern, + font, + sizing: Vec::new(), + }; + + self.renderers.push(r); + index = Index::Some(self.renderers.len() - 1); + } + #[cfg(target_arch = "wasm32")] + return &mut self.backup; + } + + match index { + Index::Some(index) => &mut self.renderers[index], + Index::Backup => &mut self.backup, + } + } + + pub fn query_ff(&mut self, font_family: Vec) -> &mut TextRenderer { + let mut renderer = IndexNoBackup::None; + for f in font_family { + let pattern = FcPattern { + name: Some(f), + ..Default::default() + }; + + let rend = self.query_no_backup(pattern); + + match rend { + IndexNoBackup::Some(index) => { + return &mut self.renderers[index]; + } + IndexNoBackup::Insert(path) => { + renderer = IndexNoBackup::Insert(path); + } + IndexNoBackup::None => {} + } + } + + match renderer { + IndexNoBackup::Some(index) => &mut self.renderers[index], //unreachable, but we handle it just in case + IndexNoBackup::Insert(path) => { + let font_bytes = std::fs::read(&path).expect("Failed to read font file"); + let font = Font::unknown(Arc::new(font_bytes)); + + let r = TextRenderer { + pattern: FcPattern { + name: Some(path), + ..Default::default() + }, + font, + sizing: Vec::new(), + }; + + let idx = self.renderers.len(); + self.renderers.push(r); + &mut self.renderers[idx] + } + IndexNoBackup::None => &mut self.backup, + } + } +} + +#[derive(Clone)] +pub struct TextRenderer { + pub pattern: FcPattern, + pub font: Font, + pub sizing: Vec, +} + +#[derive(Clone)] +pub struct FontSizing { + pub font_size: f32, + pub line_height: f32, +} diff --git a/src/bin/renderer.rs b/src/bin/renderer.rs index 23bec6b6f..37038530d 100644 --- a/src/bin/renderer.rs +++ b/src/bin/renderer.rs @@ -5,13 +5,14 @@ use url::Url; use gosub_html5::parser::document::{Document, DocumentBuilder}; use gosub_html5::parser::Html5Parser; +use gosub_render_backend::RenderBackend; use gosub_renderer::render_tree::TreeDrawer; use gosub_renderer::renderer::{Renderer, RendererOptions}; use gosub_rendering::layout::generate_taffy_tree; use gosub_shared::bytes::CharIterator; use gosub_shared::bytes::{Confidence, Encoding}; use gosub_shared::types::Result; -use gosub_styling::render_tree::{generate_render_tree, RenderTree as StyleTree}; +use gosub_styling::render_tree::{generate_render_tree, RenderTree as StyleTree, RenderTree}; fn main() -> Result<()> { let matches = clap::Command::new("Gosub Renderer") @@ -27,7 +28,7 @@ fn main() -> Result<()> { let mut rt = load_html_rendertree(&url)?; - let (taffy_tree, root) = generate_taffy_tree(&mut rt)?; + let (taffy_tree, root) = generate_taffy_tree(&mut rt, todo!())?; let render_tree = TreeDrawer::new(rt, taffy_tree, root, Url::parse("https://gosub.io/")?); @@ -39,7 +40,7 @@ fn main() -> Result<()> { Ok(()) } -fn load_html_rendertree(str_url: &str) -> Result { +fn load_html_rendertree(str_url: &str) -> Result> { let url = Url::parse(str_url)?; let html = if url.scheme() == "http" || url.scheme() == "https" { // Fetch the html from the url diff --git a/src/bin/style-parser.rs b/src/bin/style-parser.rs index 554740b97..6b32e14ea 100644 --- a/src/bin/style-parser.rs +++ b/src/bin/style-parser.rs @@ -3,18 +3,11 @@ use std::fs; use anyhow::{bail, Result}; use url::Url; -use gosub_html5::node::data::comment::CommentData; -use gosub_html5::node::data::doctype::DocTypeData; -use gosub_html5::node::data::document::DocumentData; -use gosub_html5::node::data::element::ElementData; -use gosub_html5::node::data::text::TextData; use gosub_html5::parser::document::Document; use gosub_html5::parser::document::DocumentBuilder; use gosub_html5::parser::Html5Parser; use gosub_shared::bytes::{CharIterator, Confidence, Encoding}; -use gosub_styling::render_tree::{ - generate_render_tree, walk_render_tree, RenderTree, RenderTreeNode, TreeVisitor, -}; +use gosub_styling::render_tree::generate_render_tree; struct TextVisitor { color: String, @@ -27,13 +20,11 @@ impl TextVisitor { } } } - +/* impl TreeVisitor for TextVisitor { - fn document_enter(&mut self, _tree: &RenderTree, _node: &RenderTreeNode, _data: &DocumentData) { - } + fn document_enter(&mut self, _tree: &RenderTree, _node: &RenderTreeNode, _data: &DocumentData) {} - fn document_leave(&mut self, _tree: &RenderTree, _node: &RenderTreeNode, _data: &DocumentData) { - } + fn document_leave(&mut self, _tree: &RenderTree, _node: &RenderTreeNode, _data: &DocumentData) {} fn doctype_enter(&mut self, _tree: &RenderTree, _node: &RenderTreeNode, _data: &DocTypeData) {} @@ -96,6 +87,7 @@ impl TreeVisitor for TextVisitor { print!("\x1b[39;49m"); // default terminal color reset } } + */ fn main() -> Result<()> { let matches = clap::Command::new("Gosub Style parser") @@ -135,10 +127,12 @@ fn main() -> Result<()> { let _parse_errors = Html5Parser::parse_document(&mut chars, Document::clone(&doc_handle), None)?; - let render_tree = generate_render_tree(Document::clone(&doc_handle))?; + let _render_tree = generate_render_tree(Document::clone(&doc_handle))?; + + //TODO: what do we do with the TreeVisitor? - let mut visitor = Box::new(TextVisitor::new()) as Box>; - walk_render_tree(&render_tree, &mut visitor); + // let mut visitor = Box::new(TextVisitor::new()) as Box>; + // walk_render_tree(&render_tree, &mut visitor); Ok(()) } From ac42d158e9c0a4f027e70e3fdd61677c8eb13d7d Mon Sep 17 00:00:00 2001 From: Shark Date: Sun, 19 May 2024 04:34:19 +0200 Subject: [PATCH 2/9] impement vello backend --- Cargo.lock | 13 + Cargo.toml | 1 + crates/gosub_render_backend/src/lib.rs | 227 ++++++++-- crates/gosub_renderer/src/draw.rs | 8 +- crates/gosub_renderer/src/window.rs | 4 - crates/gosub_typeface/src/lib.rs | 81 +++- crates/gosub_vello/Cargo.toml | 12 + crates/gosub_vello/src/border.rs | 557 +++++++++++++++++++++++++ crates/gosub_vello/src/brush.rs | 26 ++ crates/gosub_vello/src/color.rs | 32 ++ crates/gosub_vello/src/gradient.rs | 84 ++++ crates/gosub_vello/src/image.rs | 33 ++ crates/gosub_vello/src/lib.rs | 101 +++++ crates/gosub_vello/src/rect.rs | 20 + crates/gosub_vello/src/text.rs | 166 ++++++++ crates/gosub_vello/src/transform.rs | 147 +++++++ src/bin/renderer.rs | 7 +- 17 files changed, 1465 insertions(+), 54 deletions(-) create mode 100644 crates/gosub_vello/Cargo.toml create mode 100644 crates/gosub_vello/src/border.rs create mode 100644 crates/gosub_vello/src/brush.rs create mode 100644 crates/gosub_vello/src/color.rs create mode 100644 crates/gosub_vello/src/gradient.rs create mode 100644 crates/gosub_vello/src/image.rs create mode 100644 crates/gosub_vello/src/lib.rs create mode 100644 crates/gosub_vello/src/rect.rs create mode 100644 crates/gosub_vello/src/text.rs create mode 100644 crates/gosub_vello/src/transform.rs diff --git a/Cargo.lock b/Cargo.lock index 51f29c853..8c3fd0da4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1432,6 +1432,7 @@ dependencies = [ "gosub_styling", "gosub_testing", "gosub_v8", + "gosub_vello", "gosub_webexecutor", "js-sys", "lazy_static", @@ -1608,6 +1609,18 @@ dependencies = [ "v8", ] +[[package]] +name = "gosub_vello" +version = "0.1.0" +dependencies = [ + "gosub_render_backend", + "gosub_shared", + "gosub_typeface", + "image", + "smallvec", + "vello", +] + [[package]] name = "gosub_webexecutor" version = "0.1.0" diff --git a/Cargo.toml b/Cargo.toml index 7d18a7535..d957ca619 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -33,6 +33,7 @@ gosub_testing = { path = "./crates/gosub_testing", features = [] } gosub_rendering = { path = "crates/gosub_render_utils", features = [] } gosub_renderer = { path = "./crates/gosub_renderer", features = [] } gosub_render_backend = { path = "./crates/gosub_render_backend", features = [] } +gosub_vello = { path = "./crates/gosub_vello", features = [] } serde = { version = "1.0", features = ["derive"] } serde_derive = "1.0" derive_more = "0.99" diff --git a/crates/gosub_render_backend/src/lib.rs b/crates/gosub_render_backend/src/lib.rs index 1ad2155b0..c9956a1ba 100644 --- a/crates/gosub_render_backend/src/lib.rs +++ b/crates/gosub_render_backend/src/lib.rs @@ -1,6 +1,7 @@ -use smallvec::SmallVec; use std::fmt::Debug; -use std::ops::{Mul, MulAssign}; +use std::ops::{Div, Mul, MulAssign}; + +use smallvec::SmallVec; pub trait RenderBackend: Sized + Debug { type Rect: Rect; @@ -22,6 +23,7 @@ pub trait RenderBackend: Sized + Debug { pub type FP = f32; +#[derive(Clone, Copy)] pub struct Point { pub x: FP, pub y: FP, @@ -33,6 +35,19 @@ pub struct Size { pub height: FP, } +impl Size { + pub fn new(width: FP, height: FP) -> Self { + Self { width, height } + } + + pub fn uniform(size: FP) -> Self { + Self { + width: size, + height: size, + } + } +} + pub struct RenderRect { pub rect: B::Rect, pub transform: Option, @@ -157,6 +172,7 @@ pub trait BorderSide { fn new(width: FP, style: BorderStyle, brush: B::Brush) -> Self; } +#[derive(Clone, Copy)] pub enum BorderStyle { Solid, Dashed, @@ -170,31 +186,184 @@ pub enum BorderStyle { Hidden, } +#[derive(Clone, Copy)] +pub enum Radius { + Uniform(FP), + Elliptical(FP, FP), +} + +impl Radius { + pub fn offset(&self) -> Size { + match self { + Radius::Uniform(value) => Size::uniform(value.powi(2).div(2.0).sqrt() - *value), + Radius::Elliptical(x, y) => { + //TODO: is this correct? + + let theta = (std::f64::consts::PI / 4.0) as FP; + let ox = x * theta.cos(); + let oy = y * theta.sin(); + + Size::new(ox - *x, oy - *y) + } + } + } + + pub fn radi_x(&self) -> FP { + match self { + Radius::Uniform(value) => *value, + Radius::Elliptical(x, _) => *x, + } + } + + pub fn radi_y(&self) -> FP { + match self { + Radius::Uniform(value) => *value, + Radius::Elliptical(_, y) => *y, + } + } + + pub fn radii(&self) -> [FP; 2] { + match self { + Radius::Uniform(value) => [*value, *value], + Radius::Elliptical(x, y) => [*x, *y], + } + } + + pub fn radii_f64(&self) -> (f64, f64) { + match self { + Radius::Uniform(value) => (*value as f64, *value as f64), + Radius::Elliptical(x, y) => (*x as f64, *y as f64), + } + } +} + +impl From for Radius { + fn from(value: FP) -> Self { + Radius::Uniform(value) + } +} + +impl From<[FP; 2]> for Radius { + fn from(value: [FP; 2]) -> Self { + Radius::Elliptical(value[0], value[1]) + } +} + +impl From<(FP, FP)> for Radius { + fn from(value: (FP, FP)) -> Self { + Radius::Elliptical(value.0, value.1) + } +} + +impl From for (f64, f64) { + fn from(value: Radius) -> Self { + match value { + Radius::Uniform(value) => (value as f64, value as f64), + Radius::Elliptical(x, y) => (x as f64, y as f64), + } + } +} + +impl From for f64 { + fn from(value: Radius) -> Self { + match value { + Radius::Uniform(value) => value as f64, + Radius::Elliptical(x, y) => (x * y).sqrt() as f64, + } + } +} + +impl From for FP { + fn from(value: Radius) -> Self { + match value { + Radius::Uniform(value) => value, + Radius::Elliptical(x, y) => (x * y).sqrt(), + } + } +} + +impl From for [FP; 2] { + fn from(value: Radius) -> Self { + match value { + Radius::Uniform(value) => [value, value], + Radius::Elliptical(x, y) => [x, y], + } + } +} + +impl From for (FP, FP) { + fn from(value: Radius) -> Self { + match value { + Radius::Uniform(value) => (value, value), + Radius::Elliptical(x, y) => (x, y), + } + } +} + pub trait BorderRadius: Sized + + From + + From + From<[FP; 4]> + + From<[Radius; 4]> + From<[FP; 8]> + From<(FP, FP, FP, FP)> + + From<(Radius, Radius, Radius, Radius)> + From<(FP, FP, FP, FP, FP, FP, FP, FP)> { - fn empty() -> Self; - fn uniform(radius: FP) -> Self; - fn uniform_elliptical(radius_x: FP, radius_y: FP) -> Self; + fn empty() -> Self { + Self::uniform(0.0) + } + fn uniform(radius: FP) -> Self { + Self::from(radius) + } + fn uniform_radius(radius: Radius) -> Self; + fn uniform_elliptical(radius_x: FP, radius_y: FP) -> Self { + Self::from([radius_x, radius_y, radius_x, radius_y]) + } + + fn all(radius: FP) -> Self { + let radius = radius.into(); + Self::all_radius(radius, radius, radius, radius) + } + fn all_elliptical(&self, radius_x: FP, radius_y: FP) -> Self { + let radius = Radius::Elliptical(radius_x, radius_y); - fn top_left(&mut self, radius: FP); - fn top_left_elliptical(&mut self, radius_x: FP, radius_y: FP); + Self::all_radius(radius, radius, radius, radius) + } + fn all_radius(tl: Radius, tr: Radius, dl: Radius, dr: Radius) -> Self; - fn top_right(&mut self, radius: FP); - fn top_right_elliptical(&mut self, radius_x: FP, radius_y: FP); + fn top_left(&mut self, radius: FP) { + self.top_left_radius(radius.into()); + } + fn top_left_elliptical(&mut self, radius_x: FP, radius_y: FP) { + self.top_left_radius(Radius::Elliptical(radius_x, radius_y)); + } + fn top_left_radius(&mut self, radius: Radius); - fn bottom_left(&mut self, radius: FP); - fn bottom_left_elliptical(&mut self, radius_x: FP, radius_y: FP); + fn top_right(&mut self, radius: FP) { + self.top_right_radius(radius.into()); + } + fn top_right_elliptical(&mut self, radius_x: FP, radius_y: FP) { + self.top_right_radius(Radius::Elliptical(radius_x, radius_y)); + } + fn top_right_radius(&mut self, radius: Radius); - fn bottom_right(&mut self, radius: FP); - fn bottom_right_elliptical(&mut self, radius_x: FP, radius_y: FP); + fn bottom_left(&mut self, radius: FP) { + self.bottom_left_radius(radius.into()); + } + fn bottom_left_elliptical(&mut self, radius_x: FP, radius_y: FP) { + self.bottom_left_radius(Radius::Elliptical(radius_x, radius_y)); + } + fn bottom_left_radius(&mut self, radius: Radius); - //Can be used if the border was initially created with the empty method - fn build(self) -> Option; + fn bottom_right(&mut self, radius: FP) { + self.bottom_right_radius(radius.into()); + } + fn bottom_right_elliptical(&mut self, radius_x: FP, radius_y: FP) { + self.bottom_right_radius(Radius::Elliptical(radius_x, radius_y)); + } + fn bottom_right_radius(&mut self, radius: Radius); } pub trait Transform: Sized + Mul + MulAssign { @@ -227,12 +396,6 @@ pub trait Transform: Sized + Mul + MulAssign { fn pre_rotate_around(self, angle: FP, center: Point) -> Self; - fn pre_skew_x(self, angle: FP) -> Self; - - fn pre_skew_y(self, angle: FP) -> Self; - - fn pre_skew_xy(self, angle_x: FP, angle_y: FP) -> Self; - fn then_scale(self, s: FP) -> Self; fn then_scale_xy(self, sx: FP, sy: FP) -> Self; @@ -243,12 +406,6 @@ pub trait Transform: Sized + Mul + MulAssign { fn then_rotate_around(self, angle: FP, center: Point) -> Self; - fn then_skew_x(self, angle: FP) -> Self; - - fn then_skew_y(self, angle: FP) -> Self; - - fn then_skew_xy(self, angle_x: FP, angle_y: FP) -> Self; - fn as_matrix(&self) -> [FP; 6]; fn from_matrix(matrix: [FP; 6]) -> Self; @@ -263,16 +420,17 @@ pub trait Transform: Sized + Mul + MulAssign { pub trait PreRenderText { fn new(text: String, font: Option>, size: FP) -> Self; + fn with_lh(text: String, font: Option>, size: FP, line_height: FP) -> Self; + fn prerender(&mut self, backend: &B) -> Size; fn value(&self) -> &str; - fn font(&self) -> Option<&[String]>; fn fs(&self) -> FP; //TODO: Who should be responsible for line breaking if the text is too long? } pub trait Text { - fn new(pre: &B::PreRenderText) -> Self; + fn new(pre: &mut B::PreRenderText, backend: &B) -> Self; } pub struct ColorStop { @@ -280,12 +438,12 @@ pub struct ColorStop { pub color: B::Color, } -type ColorStops = SmallVec<[ColorStop; 4]>; +pub type ColorStops = SmallVec<[ColorStop; 4]>; pub trait Gradient { fn new_linear(start: Point, end: Point, stops: ColorStops) -> Self; - fn new_radial( + fn new_radial_two_point( start_center: Point, start_radius: FP, end_center: Point, @@ -293,6 +451,13 @@ pub trait Gradient { stops: ColorStops, ) -> Self; + fn new_radial(center: Point, radius: FP, stops: ColorStops) -> Self + where + Self: Sized, + { + Self::new_radial_two_point(center, radius, center, radius, stops) + } + fn new_sweep(center: Point, start_angle: FP, end_angle: FP, stops: ColorStops) -> Self; } diff --git a/crates/gosub_renderer/src/draw.rs b/crates/gosub_renderer/src/draw.rs index d4e8e9626..0c89ded7b 100644 --- a/crates/gosub_renderer/src/draw.rs +++ b/crates/gosub_renderer/src/draw.rs @@ -161,11 +161,11 @@ impl TreeDrawer { fn render_text( node: &mut RenderTreeNode, - scene: &mut B, + backend: &mut B, pos: &(FP, FP), layout: &Layout, ) { - if let RenderNodeData::Text(text) = &node.data { + if let RenderNodeData::Text(text) = &mut node.data { let color = node .properties .get("color") @@ -183,7 +183,7 @@ fn render_text( let translate = Transform::translate(pos.0 as FP, pos.1 + layout.size.height as FP); - let text = Text::new(&text.prerender); + let text = Text::new(&mut text.prerender, backend); let rect = Rect::new( pos.0 as FP, @@ -200,7 +200,7 @@ fn render_text( brush_transform: None, }; - scene.draw_text(&render_text); + backend.draw_text(&render_text); } } diff --git a/crates/gosub_renderer/src/window.rs b/crates/gosub_renderer/src/window.rs index 68558f1f1..e1247a277 100644 --- a/crates/gosub_renderer/src/window.rs +++ b/crates/gosub_renderer/src/window.rs @@ -81,10 +81,6 @@ impl<'a, D: SceneDrawer, B: RenderBackend> Window<'a, D, B> { }) } - pub fn change_adapter(&mut self, adapter: Arc) { - self.adapter = adapter; - } - /// Starts the window using up the event loop /// Returns Ok(true) if the window was closed /// Returns Ok(false) if the window was already opened diff --git a/crates/gosub_typeface/src/lib.rs b/crates/gosub_typeface/src/lib.rs index fd854513b..a0797252c 100644 --- a/crates/gosub_typeface/src/lib.rs +++ b/crates/gosub_typeface/src/lib.rs @@ -3,13 +3,16 @@ use std::sync::{Arc, Mutex}; use lazy_static::lazy_static; use rust_fontconfig::{FcFontCache, FcPattern}; +pub const DEFAULT_FS: f32 = 12.0; //TODO: these need to be moved to somewhere and made configurable +pub const DEFAULT_LH: f32 = 1.2; + #[derive(Clone, PartialEq, Debug)] -pub struct Font { +pub struct SharedFont { pub data: Arc>, pub ty: FontType, } -impl Font { +impl SharedFont { pub fn new(data: Arc>) -> Self { Self { data, @@ -25,6 +28,12 @@ impl Font { } } +#[derive(Clone, PartialEq, Debug)] +pub struct Font { + pub data: Vec, + pub ty: FontType, +} + #[derive(Clone, PartialEq, Debug)] pub enum FontType { TrueType, @@ -42,26 +51,26 @@ pub struct FcPattern { name: Option, } -pub const DEFAULT_FS: f32 = 12.0; //TODO: this needs to be moved to somewhere and made configurable - #[cfg(not(target_arch = "wasm32"))] lazy_static! { pub static ref FONT_PATH_CACHE: FcFontCache = FcFontCache::build(); } lazy_static! { - pub static ref FONT_RENDERER_CACHE: Mutex = { - let font = Font { - data: Arc::new(include_bytes!("../../../resources/fonts/Roboto-Regular.ttf").to_vec()), - ty: FontType::TrueType, - }; + pub static ref BACKUP_FONT: SharedFont = SharedFont { + data: Arc::new(include_bytes!("../../../resources/fonts/Roboto-Regular.ttf").to_vec()), + ty: FontType::TrueType, + }; +} +lazy_static! { + pub static ref FONT_RENDERER_CACHE: Mutex = { let backup = TextRenderer { pattern: FcPattern { name: Some("Roboto".to_string()), ..Default::default() }, - font, + font: BACKUP_FONT.clone(), sizing: Vec::new(), }; @@ -147,6 +156,33 @@ impl FontRendererCache { index } + fn query_font_no_backup(&mut self, pattern: FcPattern) -> Option { + let font = self.query_no_backup(pattern); + + match font { + IndexNoBackup::Some(index) => Some(SharedFont::clone(&self.renderers[index].font)), + IndexNoBackup::Insert(path) => { + let font_bytes = std::fs::read(&path).expect("Failed to read font file"); + + let font = SharedFont::unknown(Arc::new(font_bytes)); + + let r = TextRenderer { + pattern: FcPattern { + name: Some(path), + ..Default::default() + }, + font: SharedFont::clone(&font), + sizing: Vec::new(), + }; + + self.renderers.push(r); + + Some(font) + } + IndexNoBackup::None => None, + } + } + pub fn query(&mut self, pattern: FcPattern) -> &mut TextRenderer { if self.backup.pattern == pattern { return &mut self.backup; @@ -171,7 +207,7 @@ impl FontRendererCache { return &mut self.backup; }; - let font = Font::new(Arc::new(font_bytes)); + let font = SharedFont::new(Arc::new(font_bytes)); let r = TextRenderer { pattern, @@ -217,7 +253,7 @@ impl FontRendererCache { IndexNoBackup::Some(index) => &mut self.renderers[index], //unreachable, but we handle it just in case IndexNoBackup::Insert(path) => { let font_bytes = std::fs::read(&path).expect("Failed to read font file"); - let font = Font::unknown(Arc::new(font_bytes)); + let font = SharedFont::unknown(Arc::new(font_bytes)); let r = TextRenderer { pattern: FcPattern { @@ -235,12 +271,31 @@ impl FontRendererCache { IndexNoBackup::None => &mut self.backup, } } + + pub fn query_all_shared(&mut self, font_family: Vec) -> Vec>> { + let mut fonts = Vec::with_capacity(font_family.len()); + + for f in font_family { + let pattern = FcPattern { + name: Some(f), + ..Default::default() + }; + + let font = self.query_font_no_backup(pattern); + + if let Some(font) = font { + fonts.push(font.data); + } + } + + fonts + } } #[derive(Clone)] pub struct TextRenderer { pub pattern: FcPattern, - pub font: Font, + pub font: SharedFont, pub sizing: Vec, } diff --git a/crates/gosub_vello/Cargo.toml b/crates/gosub_vello/Cargo.toml new file mode 100644 index 000000000..c08cc4d77 --- /dev/null +++ b/crates/gosub_vello/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "gosub_vello" +version = "0.1.0" +edition = "2021" + +[dependencies] +gosub_shared = { path = "../gosub_shared" } +gosub_render_backend = { path = "../gosub_render_backend" } +gosub_typeface = { path = "../gosub_typeface" } +vello = "0.1.0" +image = "0.25.1" +smallvec = "1.13.2" \ No newline at end of file diff --git a/crates/gosub_vello/src/border.rs b/crates/gosub_vello/src/border.rs new file mode 100644 index 000000000..36393888b --- /dev/null +++ b/crates/gosub_vello/src/border.rs @@ -0,0 +1,557 @@ +use smallvec::SmallVec; +use vello::kurbo::{Arc, BezPath, Cap, Join, RoundedRectRadii, Stroke}; + +use gosub_render_backend::{ + Border as TBorder, BorderRadius as TBorderRadius, BorderSide as TBorderSide, BorderStyle, + Radius, RenderBackend, RenderBorder, FP, +}; + +use crate::{Brush, Rect, Transform, VelloBackend}; + +pub struct Border { + pub(crate) left: BorderSide, + pub(crate) right: BorderSide, + pub(crate) top: BorderSide, + pub(crate) bottom: BorderSide, +} + +enum Side { + Left, + Right, + Top, + Bottom, +} + +fn all() -> [Side; 4] { + [Side::Top, Side::Right, Side::Bottom, Side::Left] +} + +pub struct BorderRenderOptions<'a> { + pub border: &'a RenderBorder, + pub rect: &'a Rect, + pub transform: Option<&'a Transform>, + pub radius: Option<&'a BorderRadius>, +} + +struct BorderRenderSideOptions<'a> { + side: Side, + segment: &'a BorderSide, + transform: Option<&'a Transform>, + radius: Option<(Radius, Radius)>, + rect: &'a Rect, +} + +impl<'a> BorderRenderOptions<'a> { + fn left(&self, transform: Option<&'a Transform>) -> BorderRenderSideOptions { + BorderRenderSideOptions { + side: Side::Left, + segment: &self.border.border.left, + transform, + radius: self.radius.map(|r| (r.top_left, r.bottom_left)), + rect: self.rect, + } + } + + fn right(&self, transform: Option<&'a Transform>) -> BorderRenderSideOptions { + BorderRenderSideOptions { + side: Side::Right, + segment: &self.border.border.right, + transform, + radius: self.radius.map(|r| (r.top_right, r.bottom_right)), + rect: self.rect, + } + } + + fn top(&self, transform: Option<&'a Transform>) -> BorderRenderSideOptions { + BorderRenderSideOptions { + side: Side::Top, + segment: &self.border.border.top, + transform, + radius: self.radius.map(|r| (r.top_left, r.top_right)), + rect: self.rect, + } + } + + fn bottom(&self, transform: Option<&'a Transform>) -> BorderRenderSideOptions { + BorderRenderSideOptions { + side: Side::Bottom, + segment: &self.border.border.bottom, + transform, + radius: self.radius.map(|r| (r.bottom_left, r.bottom_right)), + rect: self.rect, + } + } +} + +impl Border { + pub fn draw(backend: &mut VelloBackend, opts: BorderRenderOptions) { + let transform = match (opts.transform, opts.border.transform.as_ref()) { + (Some(t1), Some(t2)) => Some(*t1 * *t2), + (Some(t1), None) => Some(*t1), + (None, Some(t2)) => Some(*t2), + (None, None) => None, + }; + + let transform = transform.as_ref(); + + let border = &opts.border.border; + + Self::draw_side(backend, opts.left(transform)); + Self::draw_side(backend, opts.right(transform)); + Self::draw_side(backend, opts.top(transform)); + Self::draw_side(backend, opts.bottom(transform)); + } + + fn draw_side(backend: &mut VelloBackend, opts: BorderRenderSideOptions) { + let border_width = opts.segment.width as f64; + let brush = &opts.segment.brush.0; + let style = opts.segment.style; + let radius = opts.radius; + + let width = opts.rect.0.width(); + let height = opts.rect.0.height(); + + let pos = opts.rect.0.origin(); + + let mut path = BezPath::new(); + + match opts.side { + Side::Top => { + match radius { + Some((left, right)) => { + let offset_left = left.offset(); + let offset_right = right.offset(); + + path.move_to(( + pos.x - offset_left.width as f64, + pos.y - offset_left.height as f64, + )); + + let arc = Arc::new( + ( + pos.x + offset_left.width as f64, + pos.y - offset_left.height as f64, + ), + left.radii_f64(), + -std::f64::consts::PI * 3.0 / 4.0, + std::f64::consts::PI / 4.0, + 0.0, + ); + + arc.to_cubic_beziers(0.1, |p1, p2, p3| { + path.curve_to(p1, p2, p3); + }); + + path.line_to(( + pos.x + width - right.radi_x() as f64, + pos.y - offset_right.height as f64, + )); + + let arc = Arc::new( + ( + pos.x + width - right.radi_x() as f64, + pos.y + right.radi_y() as f64, + ), + right.radii_f64(), + 0.0, + std::f64::consts::PI / 4.0, + 0.0, + ); + + arc.to_cubic_beziers(0.1, |p1, p2, p3| { + path.curve_to(p1, p2, p3); + }); + } + None => { + path.move_to((pos.x, pos.y)); + path.line_to((pos.x + width, pos.y)); + } + }; + } + Side::Right => match radius { + Some((top, bottom)) => { + let offset_top = top.offset(); + let offset_bottom = bottom.offset(); + + path.move_to(( + pos.x + width + offset_top.width as f64, + pos.y - offset_top.height as f64, + )); + + let arc = Arc::new( + ( + pos.x + width - offset_top.width as f64, + pos.y + offset_top.height as f64, + ), + top.radii_f64(), + -std::f64::consts::PI / 4.0, + std::f64::consts::PI / 4.0, + 0.0, + ); + + arc.to_cubic_beziers(0.1, |p1, p2, p3| { + path.curve_to(p1, p2, p3); + }); + + path.line_to(( + pos.x + width - offset_bottom.width as f64, + pos.y + height - bottom.radi_y() as f64, + )); + + let arc = Arc::new( + ( + pos.x + width - offset_bottom.width as f64, + pos.y + height - offset_bottom.height as f64, + ), + bottom.radii_f64(), + 0.0, + std::f64::consts::PI / 4.0, + 0.0, + ); + + arc.to_cubic_beziers(0.1, |p1, p2, p3| { + path.curve_to(p1, p2, p3); + }); + } + None => { + path.move_to((pos.x + width, pos.y)); + path.line_to((pos.x + width, pos.y + height)); + } + }, + Side::Bottom => match radius { + Some((left, right)) => { + let offset_left = left.offset(); + let offset_right = right.offset(); + + path.move_to(( + pos.x + width + offset_right.width as f64, + pos.y + height + offset_right.height as f64, + )); + + let arc = Arc::new( + ( + pos.x + width - offset_right.width as f64, + pos.y + height - offset_right.height as f64, + ), + right.radii_f64(), + -std::f64::consts::PI * 7.0 / 4.0, + std::f64::consts::PI / 4.0, + 0.0, + ); + + arc.to_cubic_beziers(0.1, |p1, p2, p3| { + path.curve_to(p1, p2, p3); + }); + + path.line_to(( + pos.x + left.radi_x() as f64, + pos.y + height - offset_left.height as f64, + )); + + let arc = Arc::new( + ( + pos.x + left.radi_x() as f64, + pos.y + height - offset_left.height as f64, + ), + left.radii_f64(), + -std::f64::consts::PI * 3.0 / 2.0, + std::f64::consts::PI / 4.0, + 0.0, + ); + + arc.to_cubic_beziers(0.1, |p1, p2, p3| { + path.curve_to(p1, p2, p3); + }); + } + None => { + path.move_to((pos.x, pos.y + height)); + path.line_to((pos.x + width, pos.y + height)); + } + }, + Side::Left => match radius { + Some((top, bottom)) => { + let offset_top = top.offset(); + let offset_bottom = bottom.offset(); + + path.move_to(( + pos.x - offset_top.width as f64, + pos.y + height + offset_top.height as f64, + )); + + let arc = Arc::new( + ( + pos.x + offset_top.width as f64, + pos.y + height - offset_top.height as f64, + ), + top.radii_f64(), + -std::f64::consts::PI * 5.0 / 4.0, + std::f64::consts::PI / 4.0, + 0.0, + ); + + arc.to_cubic_beziers(0.1, |p1, p2, p3| { + path.curve_to(p1, p2, p3); + }); + + path.line_to(( + pos.x + offset_bottom.width as f64, + pos.y + bottom.radi_y() as f64, + )); + + let arc = Arc::new( + ( + pos.x + offset_bottom.width as f64, + pos.y + bottom.radi_y() as f64, + ), + bottom.radii_f64(), + -std::f64::consts::PI, + std::f64::consts::PI / 4.0, + 0.0, + ); + + arc.to_cubic_beziers(0.1, |p1, p2, p3| { + path.curve_to(p1, p2, p3); + }); + } + None => { + path.move_to((pos.x, pos.y + height)); + path.line_to((pos.x, pos.y)); + } + }, + } + + let cap = match style { + BorderStyle::Dashed => Cap::Square, + BorderStyle::Dotted => Cap::Round, + _ => Cap::Butt, + }; + + let dash_pattern = match style { + BorderStyle::Dashed => SmallVec::from([ + border_width * 3.0, + border_width * 3.0, + border_width * 3.0, + border_width * 3.0, + ]), + BorderStyle::Dotted => { + SmallVec::from([border_width, border_width, border_width, border_width]) + //TODO: somehow this doesn't result in circles. It is more like a rounded rectangle + } + _ => SmallVec::default(), + }; + + let stroke = Stroke { + width: border_width, + join: Join::Bevel, + miter_limit: 0.0, + start_cap: cap, + end_cap: cap, + dash_pattern, + dash_offset: 0.0, + }; + + backend.scene.stroke( + &stroke, + opts.transform.map(|t| t.0).unwrap_or_default(), + brush, + None, + &path, + ); + } +} + +impl TBorder for Border { + fn new(all: BorderSide) -> Self { + Self { + left: all.clone(), + right: all.clone(), + top: all.clone(), + bottom: all, + } + } + + fn all(left: BorderSide, right: BorderSide, top: BorderSide, bottom: BorderSide) -> Self { + Self { + left, + right, + top, + bottom, + } + } + + fn left(&mut self, side: BorderSide) { + self.left = side; + } + + fn right(&mut self, side: BorderSide) { + self.right = side; + } + + fn top(&mut self, side: BorderSide) { + self.top = side; + } + + fn bottom(&mut self, side: BorderSide) { + self.bottom = side; + } +} + +#[derive(Clone)] +pub struct BorderSide { + pub(crate) width: FP, + pub(crate) style: BorderStyle, + pub(crate) brush: Brush, +} + +impl TBorderSide for BorderSide { + fn new(width: FP, style: BorderStyle, brush: Brush) -> Self { + Self { + width, + style, + brush, + } + } +} + +#[derive(Clone)] +pub struct BorderRadius { + pub(crate) top_left: Radius, + pub(crate) top_right: Radius, + pub(crate) bottom_left: Radius, + pub(crate) bottom_right: Radius, +} + +impl From<[FP; 4]> for BorderRadius { + fn from(value: [FP; 4]) -> Self { + Self { + top_left: value[0].into(), + top_right: value[1].into(), + bottom_left: value[2].into(), + bottom_right: value[3].into(), + } + } +} + +impl From<[FP; 8]> for BorderRadius { + fn from(value: [FP; 8]) -> Self { + Self { + top_left: (value[0], value[1]).into(), + top_right: (value[2], value[3]).into(), + bottom_left: (value[4], value[5]).into(), + bottom_right: (value[6], value[7]).into(), + } + } +} + +impl From<(FP, FP, FP, FP)> for BorderRadius { + fn from(value: (FP, FP, FP, FP)) -> Self { + Self { + top_left: value.0.into(), + top_right: value.1.into(), + bottom_left: value.2.into(), + bottom_right: value.3.into(), + } + } +} + +impl From<(FP, FP, FP, FP, FP, FP, FP, FP)> for BorderRadius { + fn from(value: (FP, FP, FP, FP, FP, FP, FP, FP)) -> Self { + Self { + top_left: (value.0, value.1).into(), + top_right: (value.2, value.3).into(), + bottom_left: (value.4, value.5).into(), + bottom_right: (value.6, value.7).into(), + } + } +} + +impl From for BorderRadius { + fn from(value: FP) -> Self { + Self { + top_left: value.into(), + top_right: value.into(), + bottom_left: value.into(), + bottom_right: value.into(), + } + } +} + +impl From for BorderRadius { + fn from(value: Radius) -> Self { + Self { + top_left: value, + top_right: value, + bottom_left: value, + bottom_right: value, + } + } +} + +impl From<[Radius; 4]> for BorderRadius { + fn from(value: [Radius; 4]) -> Self { + Self { + top_left: value[0], + top_right: value[1], + bottom_left: value[2], + bottom_right: value[3], + } + } +} + +impl From<(Radius, Radius, Radius, Radius)> for BorderRadius { + fn from(value: (Radius, Radius, Radius, Radius)) -> Self { + Self { + top_left: value.0, + top_right: value.1, + bottom_left: value.2, + bottom_right: value.3, + } + } +} + +impl TBorderRadius for BorderRadius { + fn uniform_radius(radius: Radius) -> Self { + Self { + top_left: radius, + top_right: radius, + bottom_left: radius, + bottom_right: radius, + } + } + + fn all_radius(tl: Radius, tr: Radius, dl: Radius, dr: Radius) -> Self { + Self { + top_left: tl, + top_right: tr, + bottom_left: dl, + bottom_right: dr, + } + } + + fn top_left_radius(&mut self, radius: Radius) { + self.top_left = radius; + } + + fn top_right_radius(&mut self, radius: Radius) { + self.top_right = radius; + } + + fn bottom_left_radius(&mut self, radius: Radius) { + self.bottom_left = radius; + } + + fn bottom_right_radius(&mut self, radius: Radius) { + self.bottom_right = radius; + } +} + +impl From for RoundedRectRadii { + fn from(value: BorderRadius) -> Self { + RoundedRectRadii::new( + value.top_left.into(), + value.top_right.into(), + value.bottom_right.into(), + value.bottom_left.into(), + ) + } +} diff --git a/crates/gosub_vello/src/brush.rs b/crates/gosub_vello/src/brush.rs new file mode 100644 index 000000000..4128aec22 --- /dev/null +++ b/crates/gosub_vello/src/brush.rs @@ -0,0 +1,26 @@ +use crate::{Color, Gradient, Image, VelloBackend}; +use gosub_render_backend::{Brush as TBrush, RenderBackend}; +use vello::peniko::Brush as VelloBrush; + +#[derive(Clone)] +pub struct Brush(pub(crate) VelloBrush); + +impl From for Brush { + fn from(brush: VelloBrush) -> Self { + Brush(brush) + } +} + +impl TBrush for Brush { + fn gradient(gradient: Gradient) -> Self { + Brush(VelloBrush::Gradient(gradient.0)) + } + + fn color(color: Color) -> Self { + Brush(VelloBrush::Solid(color.0)) + } + + fn image(image: Image) -> Self { + Brush(VelloBrush::Image(image.0)) + } +} diff --git a/crates/gosub_vello/src/color.rs b/crates/gosub_vello/src/color.rs new file mode 100644 index 000000000..c27ca4f35 --- /dev/null +++ b/crates/gosub_vello/src/color.rs @@ -0,0 +1,32 @@ +use gosub_render_backend::Color as TColor; +use vello::peniko::Color as VelloColor; + +pub struct Color(pub(crate) VelloColor); + +impl From for Color { + fn from(color: VelloColor) -> Self { + Color(color) + } +} + +impl Color { + pub const fn rgba8(r: u8, g: u8, b: u8, a: u8) -> Self { + Color(VelloColor::rgba8(r, g, b, a)) + } +} + +impl TColor for Color { + fn with_alpha(r: u8, g: u8, b: u8, a: u8) -> Self { + VelloColor::rgba8(r, g, b, a).into() + } + + const WHITE: Self = Color(VelloColor::WHITE); + const BLACK: Self = Color(VelloColor::BLACK); + const RED: Self = Color(VelloColor::RED); + const GREEN: Self = Color(VelloColor::GREEN); + const BLUE: Self = Color(VelloColor::BLUE); + const YELLOW: Self = Color(VelloColor::YELLOW); + const CYAN: Self = Color(VelloColor::CYAN); + const MAGENTA: Self = Color(VelloColor::MAGENTA); + const TRANSPARENT: Self = Color(VelloColor::TRANSPARENT); +} diff --git a/crates/gosub_vello/src/gradient.rs b/crates/gosub_vello/src/gradient.rs new file mode 100644 index 000000000..93cb807bc --- /dev/null +++ b/crates/gosub_vello/src/gradient.rs @@ -0,0 +1,84 @@ +use vello::peniko::{ + ColorStop as VelloColorStop, ColorStops as VelloColorStops, Gradient as VelloGradient, +}; + +use gosub_render_backend::{ColorStop, ColorStops, Gradient as TGradient, Point, FP}; + +use crate::{Convert, VelloBackend}; + +pub struct Gradient(pub(crate) VelloGradient); + +impl From for Gradient { + fn from(gradient: VelloGradient) -> Self { + Gradient(gradient) + } +} + +impl TGradient for Gradient { + fn new_linear(start: Point, end: Point, stops: ColorStops) -> Self { + let mut gradient = VelloGradient::new_linear(start.convert(), end.convert()); + gradient.stops = stops.convert(); + + Gradient(gradient) + } + + fn new_radial_two_point( + start_center: Point, + start_radius: FP, + end_center: Point, + end_radius: FP, + stops: ColorStops, + ) -> Self { + let mut gradient = VelloGradient::new_two_point_radial( + start_center.convert(), + start_radius, + end_center.convert(), + end_radius, + ); + + gradient.stops = stops.convert(); + + Gradient(gradient) + } + + fn new_radial(center: Point, radius: FP, stops: ColorStops) -> Self + where + Self: Sized, + { + let mut gradient = VelloGradient::new_radial(center.convert(), radius); + gradient.stops = stops.convert(); + + Gradient(gradient) + } + + fn new_sweep( + center: Point, + start_angle: FP, + end_angle: FP, + stops: ColorStops, + ) -> Self { + let mut gradient = VelloGradient::new_sweep(center.convert(), start_angle, end_angle); + gradient.stops = stops.convert(); + + Gradient(gradient) + } +} + +impl Convert for ColorStops { + fn convert(self) -> VelloColorStops { + let mut stops = VelloColorStops::new(); + for stop in self { + stops.push(stop.convert()); + } + stops + } +} + +impl Convert for ColorStop { + fn convert(self) -> VelloColorStop { + VelloColorStop { + offset: self.offset, + color: self.color.0, + } + } +} diff --git a/crates/gosub_vello/src/image.rs b/crates/gosub_vello/src/image.rs new file mode 100644 index 000000000..e33bb449b --- /dev/null +++ b/crates/gosub_vello/src/image.rs @@ -0,0 +1,33 @@ +use gosub_render_backend::{Image as TImage, FP}; +use image::{DynamicImage, GenericImageView}; +use std::sync::Arc; +use vello::peniko::{Blob, Format, Image as VelloImage}; + +pub struct Image(pub(crate) VelloImage); + +impl From for Image { + fn from(image: VelloImage) -> Self { + Image(image) + } +} + +impl TImage for Image { + fn new(size: (FP, FP), data: Vec) -> Self { + let blob = Blob::new(Arc::new(data)); + + Image(VelloImage::new( + blob, + Format::Rgba8, + size.0 as u32, + size.1 as u32, + )) + } + + fn from_img(img: &DynamicImage) -> Self { + let (width, height) = img.dimensions(); + let data = img.to_rgba8().into_raw(); + let blob = Blob::new(Arc::new(data)); + + Image(VelloImage::new(blob, Format::Rgba8, width, height)) + } +} diff --git a/crates/gosub_vello/src/lib.rs b/crates/gosub_vello/src/lib.rs new file mode 100644 index 000000000..c4bde3c7f --- /dev/null +++ b/crates/gosub_vello/src/lib.rs @@ -0,0 +1,101 @@ +use std::fmt::Debug; + +use vello::kurbo::{Point as VelloPoint, RoundedRect, Shape}; +use vello::peniko::Fill; +use vello::Scene; + +pub use border::*; +pub use brush::*; +pub use color::*; +use gosub_render_backend::{Point, RenderBackend, RenderRect, RenderText}; +pub use gradient::*; +pub use image::*; +pub use rect::*; +pub use text::*; +pub use transform::*; + +mod border; +mod brush; +mod color; +mod gradient; +mod image; +mod rect; +mod text; +mod transform; + +pub struct VelloBackend { + scene: Scene, +} + +impl Debug for VelloBackend { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("VelloRenderer").finish() + } +} + +impl RenderBackend for VelloBackend { + type Rect = Rect; + type Border = Border; + type BorderSide = BorderSide; + type BorderRadius = BorderRadius; + type Transform = Transform; + type PreRenderText = PreRenderText; + type Text = Text; + type Gradient = Gradient; + type Color = Color; + type Image = Image; + type Brush = Brush; + + fn draw_rect(&mut self, rect: &RenderRect) { + let affine = rect.transform.as_ref().map(|t| t.0).unwrap_or_default(); + + let brush = &rect.brush.0; + let brush_transform = rect.brush_transform.as_ref().map(|t| t.0); + + if let Some(radius) = &rect.radius { + let shape = RoundedRect::from_rect(rect.rect.0, radius.clone()); + self.scene + .fill(Fill::NonZero, affine, brush, brush_transform, &shape) + } else { + self.scene + .fill(Fill::NonZero, affine, brush, brush_transform, &rect.rect.0) + } + + if let Some(border) = &rect.border { + let opts = BorderRenderOptions { + border, + rect: &rect.rect, + transform: rect.transform.as_ref(), + radius: rect.radius.as_ref(), + }; + + Border::draw(self, opts); + } + } + + fn draw_text(&mut self, text: &RenderText) { + todo!() + } + + fn reset(&mut self) { + self.scene.reset(); + } +} + +impl VelloBackend { + pub fn new() -> Self { + Self { + scene: Scene::new(), + } + } +} + +trait Convert { + fn convert(self) -> T; +} + +impl Convert for Point { + fn convert(self) -> VelloPoint { + VelloPoint::new(self.x as f64, self.y as f64) + } +} diff --git a/crates/gosub_vello/src/rect.rs b/crates/gosub_vello/src/rect.rs new file mode 100644 index 000000000..12f3433d4 --- /dev/null +++ b/crates/gosub_vello/src/rect.rs @@ -0,0 +1,20 @@ +use gosub_render_backend::{Point, Rect as TRect, Size, FP}; +use vello::kurbo::Rect as VelloRect; + +pub struct Rect(pub(crate) VelloRect); + +impl From for Rect { + fn from(rect: VelloRect) -> Self { + Rect(rect) + } +} + +impl TRect for Rect { + fn new(x: FP, y: FP, width: FP, height: FP) -> Self { + VelloRect::new(x as f64, y as f64, width as f64, height as f64).into() + } + + fn from_point(point: Point, size: Size) -> Self { + TRect::new(point.x, point.y, size.width, size.height) + } +} diff --git a/crates/gosub_vello/src/text.rs b/crates/gosub_vello/src/text.rs new file mode 100644 index 000000000..ed9ef1409 --- /dev/null +++ b/crates/gosub_vello/src/text.rs @@ -0,0 +1,166 @@ +use std::ops::Deref; +use vello::glyph::Glyph; +use vello::kurbo::Affine; +use vello::peniko::{Blob, BrushRef, Fill, Font, StyleRef}; +use vello::skrifa::{instance::Size as FSize, FontRef, MetadataProvider}; + +use gosub_render_backend::{PreRenderText as TPreRenderText, RenderText, Size, Text as TText, FP}; +use gosub_typeface::{BACKUP_FONT, DEFAULT_LH, FONT_RENDERER_CACHE}; + +use crate::VelloBackend; + +pub struct Text { + glyphs: Vec, + font: Vec, + fs: FP, +} + +pub struct PreRenderText { + text: String, + fs: FP, + font: Vec, + line_height: FP, + size: Option, + glyphs: Option>, +} + +impl TText for Text { + fn new(pre: &mut PreRenderText, backend: &VelloBackend) -> Self { + if pre.glyphs.is_none() { + pre.prerender(backend); + } + + Text { + glyphs: pre.glyphs.clone().unwrap_or_default(), + font: pre.font.clone(), + fs: pre.fs, + } + } +} + +fn get_fonts_from_family(font_families: Option>) -> Vec { + let mut fonts = Vec::with_capacity(font_families.as_ref().map(|f| f.len()).unwrap_or(1)); + + if let Ok(mut cache) = FONT_RENDERER_CACHE.lock() { + if let Some(ff) = font_families { + let font = cache.query_all_shared(ff); + for (i, f) in font.into_iter().enumerate() { + fonts.push(Font::new(Blob::new(f), i as u32)); + } + } + } else { + fonts.push(Font::new(Blob::new(BACKUP_FONT.data.clone()), 0)); + } + + fonts +} + +impl TPreRenderText for PreRenderText { + fn new(text: String, font: Option>, size: FP) -> Self { + let font = get_fonts_from_family(font); + + PreRenderText { + text, + font, + line_height: DEFAULT_LH, + size: None, + fs: size, + glyphs: None, + } + } + + fn with_lh(text: String, font: Option>, size: FP, line_height: FP) -> Self { + let font = get_fonts_from_family(font); + + PreRenderText { + text, + font, + line_height, + size: None, + fs: size, + glyphs: None, + } + } + + fn prerender(&mut self, backend: &VelloBackend) -> Size { + let font_ref = to_font_ref(&self.font[0]).unwrap(); + + let axes = font_ref.axes(); + let char_map = font_ref.charmap(); + let fs = FSize::new(self.fs); + let variations: &[(&str, f32)] = &[]; // if we have more than an empty slice here we need to change the rendering to the scene + let var_loc = axes.location(variations.iter().copied()); + let glyph_metrics = font_ref.glyph_metrics(fs, &var_loc); + let metrics = font_ref.metrics(fs, &var_loc); + // let line_height = metrics.ascent - metrics.descent + metrics.leading; + + let mut width: f32 = 0.0; + let mut pen_x: f32 = 0.0; + + let glyphs = self + .text + .chars() + .filter_map(|c| { + if c == '\n' { + return None; + } + + let gid = char_map.map(c).unwrap_or_default(); //TODO: here we need to use the next font if the glyph is not found + let advance = glyph_metrics.advance_width(gid).unwrap_or_default(); + let x = pen_x; + pen_x += advance; + + Some(Glyph { + id: gid.to_u16() as u32, + x, + y: 0.0, + }) + }) + .collect(); + + width = width.max(pen_x); + + self.glyphs = Some(glyphs); + + Size { + width, + height: self.line_height, + } + } + + fn value(&self) -> &str { + self.text.as_ref() + } + + fn fs(&self) -> FP { + self.fs + } +} + +impl Text { + fn show(vello: &mut VelloBackend, render: RenderText) { + let brush = render.brush.0; + let style: StyleRef = Fill::NonZero.into(); + + let transform = render.transform.map(|t| t.0).unwrap_or(Affine::IDENTITY); + let brush_transform = render.brush_transform.map(|t| t.0); + + vello + .scene + .draw_glyphs(&render.text.font[0]) + .font_size(render.text.fs) + .transform(transform) + .glyph_transform(brush_transform) + .brush(&brush) + .draw(style, render.text.glyphs.iter().copied()); + } +} + +fn to_font_ref(font: &Font) -> Option> { + use vello::skrifa::raw::FileRef; + let file_ref = FileRef::new(font.data.as_ref()).ok()?; + match file_ref { + FileRef::Font(font) => Some(font), + FileRef::Collection(collection) => collection.get(font.index).ok(), + } +} diff --git a/crates/gosub_vello/src/transform.rs b/crates/gosub_vello/src/transform.rs new file mode 100644 index 000000000..76ca7033c --- /dev/null +++ b/crates/gosub_vello/src/transform.rs @@ -0,0 +1,147 @@ +use std::ops::{Mul, MulAssign}; +use vello::kurbo::Affine; + +use gosub_render_backend::{Point, Transform as TTransform, FP}; + +#[derive(Debug, Clone, Copy, PartialEq)] +pub struct Transform(pub(crate) Affine); + +impl From for Transform { + fn from(transform: Affine) -> Self { + Transform(transform) + } +} + +impl Mul for Transform { + type Output = Self; + + fn mul(self, rhs: Self) -> Self::Output { + Transform(self.0 * rhs.0) + } +} + +impl MulAssign for Transform { + fn mul_assign(&mut self, rhs: Self) { + self.0 *= rhs.0; + } +} + +impl TTransform for Transform { + const IDENTITY: Self = Transform(Affine::IDENTITY); + const FLIP_X: Self = Transform(Affine::FLIP_X); + const FLIP_Y: Self = Transform(Affine::FLIP_Y); + + fn scale(s: FP) -> Self { + Affine::scale(s as f64).into() + } + + fn scale_xy(sx: FP, sy: FP) -> Self { + Affine::scale_non_uniform(sx as f64, sy as f64).into() + } + + fn translate(x: FP, y: FP) -> Self { + Affine::translate((x as f64, y as f64)).into() + } + + fn rotate(angle: FP) -> Self { + Affine::rotate(angle as f64).into() + } + + fn rotate_around(angle: FP, center: Point) -> Self { + Affine::rotate_about(angle as f64, (center.x as f64, center.y as f64).into()).into() + } + + fn skew_x(angle: FP) -> Self { + Affine::skew(angle as f64, 0.0).into() + } + + fn skew_y(angle: FP) -> Self { + Affine::skew(0.0, angle as f64).into() + } + + fn skew_xy(angle_x: FP, angle_y: FP) -> Self { + Affine::skew(angle_x as f64, angle_y as f64).into() + } + + fn pre_scale(self, s: FP) -> Self { + self.0.pre_scale(s as f64).into() + } + + fn pre_scale_xy(self, sx: FP, sy: FP) -> Self { + self.0.pre_scale_non_uniform(sx as f64, sy as f64).into() + } + + fn pre_translate(self, x: FP, y: FP) -> Self { + self.0.pre_translate((x as f64, y as f64).into()).into() + } + + fn pre_rotate(self, angle: FP) -> Self { + self.0.pre_rotate(angle as f64).into() + } + + fn pre_rotate_around(self, angle: FP, center: Point) -> Self { + self.0 + .pre_rotate_about(angle as f64, (center.x as f64, center.y as f64).into()) + .into() + } + + fn then_scale(self, s: FP) -> Self { + self.0.then_scale(s as f64).into() + } + + fn then_scale_xy(self, sx: FP, sy: FP) -> Self { + self.0.then_scale_non_uniform(sx as f64, sy as f64).into() + } + + fn then_translate(self, x: FP, y: FP) -> Self { + self.0.then_translate((x as f64, y as f64).into()).into() + } + + fn then_rotate(self, angle: FP) -> Self { + self.0.then_rotate(angle as f64).into() + } + + fn then_rotate_around(self, angle: FP, center: Point) -> Self { + self.0 + .then_rotate_about(angle as f64, (center.x as f64, center.y as f64).into()) + .into() + } + + fn as_matrix(&self) -> [FP; 6] { + let matrix = self.0.as_coeffs(); + [ + matrix[0] as FP, + matrix[1] as FP, + matrix[2] as FP, + matrix[3] as FP, + matrix[4] as FP, + matrix[5] as FP, + ] + } + + fn from_matrix(matrix: [FP; 6]) -> Self { + Affine::new([ + matrix[0] as f64, + matrix[1] as f64, + matrix[2] as f64, + matrix[3] as f64, + matrix[4] as f64, + matrix[5] as f64, + ]) + .into() + } + + fn determinant(&self) -> FP { + self.0.determinant() as FP + } + + fn inverse(self) -> Self { + self.0.inverse().into() + } + + fn with_translation(&self, translation: (FP, FP)) -> Self { + self.0 + .with_translation((translation.0 as f64, translation.1 as f64).into()) + .into() + } +} diff --git a/src/bin/renderer.rs b/src/bin/renderer.rs index 37038530d..17af896f6 100644 --- a/src/bin/renderer.rs +++ b/src/bin/renderer.rs @@ -12,7 +12,8 @@ use gosub_rendering::layout::generate_taffy_tree; use gosub_shared::bytes::CharIterator; use gosub_shared::bytes::{Confidence, Encoding}; use gosub_shared::types::Result; -use gosub_styling::render_tree::{generate_render_tree, RenderTree as StyleTree, RenderTree}; +use gosub_styling::render_tree::{generate_render_tree, RenderTree as StyleTree}; +use gosub_vello::VelloBackend; fn main() -> Result<()> { let matches = clap::Command::new("Gosub Renderer") @@ -28,7 +29,9 @@ fn main() -> Result<()> { let mut rt = load_html_rendertree(&url)?; - let (taffy_tree, root) = generate_taffy_tree(&mut rt, todo!())?; + let backend = VelloBackend::new(); + + let (taffy_tree, root) = generate_taffy_tree(&mut rt, &backend)?; let render_tree = TreeDrawer::new(rt, taffy_tree, root, Url::parse("https://gosub.io/")?); From de3eb88d69da85620689eaab4ef4a0dd4936f119 Mon Sep 17 00:00:00 2001 From: Shark Date: Sat, 25 May 2024 17:44:19 +0200 Subject: [PATCH 3/9] render text --- crates/gosub_vello/src/lib.rs | 2 +- crates/gosub_vello/src/text.rs | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/crates/gosub_vello/src/lib.rs b/crates/gosub_vello/src/lib.rs index c4bde3c7f..f4bf505f4 100644 --- a/crates/gosub_vello/src/lib.rs +++ b/crates/gosub_vello/src/lib.rs @@ -74,7 +74,7 @@ impl RenderBackend for VelloBackend { } fn draw_text(&mut self, text: &RenderText) { - todo!() + Text::show(self, text) } fn reset(&mut self) { diff --git a/crates/gosub_vello/src/text.rs b/crates/gosub_vello/src/text.rs index ed9ef1409..bb91b0942 100644 --- a/crates/gosub_vello/src/text.rs +++ b/crates/gosub_vello/src/text.rs @@ -138,8 +138,8 @@ impl TPreRenderText for PreRenderText { } impl Text { - fn show(vello: &mut VelloBackend, render: RenderText) { - let brush = render.brush.0; + pub(crate) fn show(vello: &mut VelloBackend, render: &RenderText) { + let brush = &render.brush.0; let style: StyleRef = Fill::NonZero.into(); let transform = render.transform.map(|t| t.0).unwrap_or(Affine::IDENTITY); @@ -151,7 +151,7 @@ impl Text { .font_size(render.text.fs) .transform(transform) .glyph_transform(brush_transform) - .brush(&brush) + .brush(brush) .draw(style, render.text.glyphs.iter().copied()); } } From cd439d8956e5dc436c1bae7ac3c965513c4be5aa Mon Sep 17 00:00:00 2001 From: Shark Date: Sat, 1 Jun 2024 02:21:49 +0200 Subject: [PATCH 4/9] UA Application --- Cargo.lock | 381 +++++++++++++++--- Cargo.toml | 1 + crates/gosub_render_backend/Cargo.toml | 2 + crates/gosub_render_backend/src/lib.rs | 85 ++-- crates/gosub_render_utils/src/layout.rs | 8 +- crates/gosub_render_utils/src/style.rs | 15 +- crates/gosub_render_utils/src/style/parse.rs | 8 +- .../src/style/parse_properties.rs | 29 +- crates/gosub_renderer/Cargo.toml | 1 + crates/gosub_renderer/src/draw.rs | 368 ++++------------- crates/gosub_renderer/src/lib.rs | 4 +- crates/gosub_renderer/src/render_tree.rs | 55 ++- crates/gosub_shared/src/types.rs | 224 ++++++++++ crates/gosub_styling/src/render_tree.rs | 1 - crates/gosub_useragent/Cargo.toml | 14 + crates/gosub_useragent/src/application.rs | 146 +++++++ crates/gosub_useragent/src/event_loop.rs | 66 +++ crates/gosub_useragent/src/lib.rs | 4 + crates/gosub_useragent/src/tabs.rs | 79 ++++ crates/gosub_useragent/src/window.rs | 97 +++++ crates/gosub_vello/Cargo.toml | 6 +- crates/gosub_vello/src/border.rs | 15 +- crates/gosub_vello/src/lib.rs | 137 ++++++- crates/gosub_vello/src/render.rs | 263 ++++++++++++ crates/gosub_vello/src/render/window.rs | 15 + crates/gosub_vello/src/text.rs | 60 +-- crates/gosub_vello/src/transform.rs | 4 +- src/bin/renderer.rs | 26 +- 28 files changed, 1630 insertions(+), 484 deletions(-) create mode 100644 crates/gosub_useragent/Cargo.toml create mode 100644 crates/gosub_useragent/src/application.rs create mode 100644 crates/gosub_useragent/src/event_loop.rs create mode 100644 crates/gosub_useragent/src/lib.rs create mode 100644 crates/gosub_useragent/src/tabs.rs create mode 100644 crates/gosub_useragent/src/window.rs create mode 100644 crates/gosub_vello/src/render.rs create mode 100644 crates/gosub_vello/src/render/window.rs diff --git a/Cargo.lock b/Cargo.lock index 8c3fd0da4..3b48837ad 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -157,16 +157,37 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ee91c0c2905bae44f84bfa4e044536541df26b7703fd0888deeb9060fcc44289" dependencies = [ "android-properties", - "bitflags 2.4.2", + "bitflags 2.5.0", "cc", "cesu8", "jni", "jni-sys", "libc", "log", - "ndk", + "ndk 0.8.0", "ndk-context", - "ndk-sys", + "ndk-sys 0.5.0+25.2.9519653", + "num_enum", + "thiserror", +] + +[[package]] +name = "android-activity" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef6978589202a00cd7e118380c448a08b6ed394c3a8df3a430d0898e3a42d046" +dependencies = [ + "android-properties", + "bitflags 2.5.0", + "cc", + "cesu8", + "jni", + "jni-sys", + "libc", + "log", + "ndk 0.9.0", + "ndk-context", + "ndk-sys 0.6.0+11769913", "num_enum", "thiserror", ] @@ -386,9 +407,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.4.2" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed570934406eb16438a4e976b1b4500774099c13b8cb96eec99f620f05090ddf" +checksum = "cf4b9d6a944f767f8e5e0db018570623c85f3d925ac718db4e06d0187adb21c1" [[package]] name = "bitreader" @@ -436,7 +457,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "15b55663a85f33501257357e6421bb33e769d5c9ffb5ba0921c975a123e35e68" dependencies = [ "block-sys", - "objc2", + "objc2 0.4.1", +] + +[[package]] +name = "block2" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c132eebf10f5cad5289222520a4a058514204aed6d791f1cf4fe8088b82d15f" +dependencies = [ + "objc2 0.5.2", ] [[package]] @@ -511,7 +541,7 @@ version = "0.12.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fba7adb4dd5aa98e5553510223000e7148f621165ec5f9acd7113f6ca4995298" dependencies = [ - "bitflags 2.4.2", + "bitflags 2.5.0", "log", "polling", "rustix", @@ -575,6 +605,12 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fd16c4719339c4530435d38e511904438d07cce7950afa3718a84ac36c10e89e" +[[package]] +name = "cfg_aliases" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" + [[package]] name = "chardet" version = "0.2.4" @@ -946,7 +982,7 @@ version = "0.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3e3d747f100290a1ca24b752186f61f6637e1deffe3bf6320de6fcb29510a307" dependencies = [ - "bitflags 2.4.2", + "bitflags 2.5.0", "libloading 0.8.3", "winapi", ] @@ -1017,6 +1053,12 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ea835d29036a4087793836fa931b08837ad5e957da9e23886b29586fb9b6650" +[[package]] +name = "dpi" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f25c0e292a7ca6d6498557ff1df68f32c99850012b6ea401cf8daf771f22ff53" + [[package]] name = "either" version = "1.10.0" @@ -1431,6 +1473,7 @@ dependencies = [ "gosub_shared", "gosub_styling", "gosub_testing", + "gosub_useragent", "gosub_v8", "gosub_vello", "gosub_webexecutor", @@ -1499,7 +1542,9 @@ dependencies = [ name = "gosub_render_backend" version = "0.1.0" dependencies = [ + "gosub_shared", "image", + "raw-window-handle", "smallvec", ] @@ -1508,6 +1553,7 @@ name = "gosub_renderer" version = "0.1.0" dependencies = [ "anyhow", + "dpi", "futures", "gosub_html5", "gosub_net", @@ -1524,7 +1570,7 @@ dependencies = [ "wasm-bindgen-futures", "web-sys", "wgpu", - "winit", + "winit 0.29.15", ] [[package]] @@ -1597,6 +1643,20 @@ dependencies = [ "rust-fontconfig", ] +[[package]] +name = "gosub_useragent" +version = "0.1.0" +dependencies = [ + "anyhow", + "gosub_render_backend", + "gosub_renderer", + "gosub_shared", + "log", + "slotmap", + "url", + "winit 0.30.0", +] + [[package]] name = "gosub_v8" version = "0.1.0" @@ -1613,12 +1673,16 @@ dependencies = [ name = "gosub_vello" version = "0.1.0" dependencies = [ + "anyhow", + "futures", "gosub_render_backend", "gosub_shared", "gosub_typeface", "image", + "raw-window-handle", "smallvec", "vello", + "wgpu", ] [[package]] @@ -1655,7 +1719,7 @@ version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fbcd2dba93594b227a1f57ee09b8b9da8892c34d55aa332e034a228d0fe6a171" dependencies = [ - "bitflags 2.4.2", + "bitflags 2.5.0", "gpu-alloc-types", ] @@ -1665,7 +1729,7 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "98ff03b468aa837d70984d55f5d3f846f6ec31fe34bbb97c4f85219caeee1ca4" dependencies = [ - "bitflags 2.4.2", + "bitflags 2.5.0", ] [[package]] @@ -1687,7 +1751,7 @@ version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cc11df1ace8e7e564511f53af41f3e42ddc95b56fd07b3f4445d2a6048bc682c" dependencies = [ - "bitflags 2.4.2", + "bitflags 2.5.0", "gpu-descriptor-types", "hashbrown", ] @@ -1698,7 +1762,7 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6bf0b36e6f090b7e1d8a4b49c0cb81c1f8376f72198c65dd3ad9ff3556b8b78c" dependencies = [ - "bitflags 2.4.2", + "bitflags 2.5.0", ] [[package]] @@ -1761,7 +1825,7 @@ version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "af2a7e73e1f34c48da31fb668a907f250794837e08faa144fd24f0b8b741e890" dependencies = [ - "bitflags 2.4.2", + "bitflags 2.5.0", "com", "libc", "libloading 0.8.3", @@ -1904,9 +1968,9 @@ version = "0.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "99d3aaff8a54577104bafdf686ff18565c3b6903ca5782a2026ef06e2c7aa319" dependencies = [ - "block2", + "block2 0.3.0", "dispatch", - "objc2", + "objc2 0.4.1", ] [[package]] @@ -2185,7 +2249,7 @@ version = "0.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3af92c55d7d839293953fcd0fda5ecfe93297cfde6ffbdec13b41d99c0ba6607" dependencies = [ - "bitflags 2.4.2", + "bitflags 2.5.0", "libc", "redox_syscall 0.4.1", ] @@ -2282,7 +2346,7 @@ version = "0.27.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c43f73953f8cbe511f021b58f18c3ce1c3d1ae13fe953293e13345bf83217f25" dependencies = [ - "bitflags 2.4.2", + "bitflags 2.5.0", "block", "core-graphics-types", "foreign-types", @@ -2335,7 +2399,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "50e3524642f53d9af419ab5e8dd29d3ba155708267667c2f3f06c88c9e130843" dependencies = [ "bit-set", - "bitflags 2.4.2", + "bitflags 2.5.0", "codespan-reporting", "hexf-parse", "indexmap", @@ -2354,10 +2418,25 @@ version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2076a31b7010b17a38c01907c45b945e8f11495ee4dd588309718901b1f7a5b7" dependencies = [ - "bitflags 2.4.2", + "bitflags 2.5.0", "jni-sys", "log", - "ndk-sys", + "ndk-sys 0.5.0+25.2.9519653", + "num_enum", + "raw-window-handle", + "thiserror", +] + +[[package]] +name = "ndk" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3f42e7bbe13d351b6bead8286a43aac9534b82bd3cc43e47037f012ebfd62d4" +dependencies = [ + "bitflags 2.5.0", + "jni-sys", + "log", + "ndk-sys 0.6.0+11769913", "num_enum", "raw-window-handle", "thiserror", @@ -2378,6 +2457,15 @@ dependencies = [ "jni-sys", ] +[[package]] +name = "ndk-sys" +version = "0.6.0+11769913" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee6cda3051665f1fb8d9e08fc35c96d5a244fb1be711a03b71118828afc9a873" +dependencies = [ + "jni-sys", +] + [[package]] name = "new_debug_unreachable" version = "1.0.6" @@ -2522,9 +2610,9 @@ dependencies = [ [[package]] name = "objc-sys" -version = "0.3.2" +version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7c71324e4180d0899963fc83d9d241ac39e699609fc1025a850aadac8257459" +checksum = "cdb91bdd390c7ce1a8607f35f3ca7151b65afc0ff5ff3b34fa350f7d7c7e4310" [[package]] name = "objc2" @@ -2533,7 +2621,57 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "559c5a40fdd30eb5e344fbceacf7595a81e242529fb4e21cf5f43fb4f11ff98d" dependencies = [ "objc-sys", - "objc2-encode", + "objc2-encode 3.0.0", +] + +[[package]] +name = "objc2" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46a785d4eeff09c14c487497c162e92766fbb3e4059a71840cecc03d9a50b804" +dependencies = [ + "objc-sys", + "objc2-encode 4.0.3", +] + +[[package]] +name = "objc2-app-kit" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4e89ad9e3d7d297152b17d39ed92cd50ca8063a89a9fa569046d41568891eff" +dependencies = [ + "bitflags 2.5.0", + "block2 0.5.1", + "libc", + "objc2 0.5.2", + "objc2-core-data", + "objc2-core-image", + "objc2-foundation", + "objc2-quartz-core", +] + +[[package]] +name = "objc2-core-data" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "617fbf49e071c178c0b24c080767db52958f716d9eabdf0890523aeae54773ef" +dependencies = [ + "bitflags 2.5.0", + "block2 0.5.1", + "objc2 0.5.2", + "objc2-foundation", +] + +[[package]] +name = "objc2-core-image" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55260963a527c99f1819c4f8e3b47fe04f9650694ef348ffd2227e8196d34c80" +dependencies = [ + "block2 0.5.1", + "objc2 0.5.2", + "objc2-foundation", + "objc2-metal", ] [[package]] @@ -2542,6 +2680,50 @@ version = "3.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d079845b37af429bfe5dfa76e6d087d788031045b25cfc6fd898486fd9847666" +[[package]] +name = "objc2-encode" +version = "4.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7891e71393cd1f227313c9379a26a584ff3d7e6e7159e988851f0934c993f0f8" + +[[package]] +name = "objc2-foundation" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ee638a5da3799329310ad4cfa62fbf045d5f56e3ef5ba4149e7452dcf89d5a8" +dependencies = [ + "bitflags 2.5.0", + "block2 0.5.1", + "dispatch", + "libc", + "objc2 0.5.2", +] + +[[package]] +name = "objc2-metal" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd0cba1276f6023976a406a14ffa85e1fdd19df6b0f737b063b95f6c8c7aadd6" +dependencies = [ + "bitflags 2.5.0", + "block2 0.5.1", + "objc2 0.5.2", + "objc2-foundation", +] + +[[package]] +name = "objc2-quartz-core" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e42bee7bff906b14b167da2bac5efe6b6a07e6f7c0a21a7308d40c960242dc7a" +dependencies = [ + "bitflags 2.5.0", + "block2 0.5.1", + "objc2 0.5.2", + "objc2-foundation", + "objc2-metal", +] + [[package]] name = "objc_exception" version = "0.1.2" @@ -2707,6 +2889,26 @@ dependencies = [ "siphasher", ] +[[package]] +name = "pin-project" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6bf43b791c5b9e34c3d182969b4abb522f9343702850a2e57f460d00d09b4b3" +dependencies = [ + "pin-project-internal", +] + +[[package]] +name = "pin-project-internal" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f38a4412a78282e09a2cf38d195ea5420d15ba0602cb375210efbc877243965" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.60", +] + [[package]] name = "pin-project-lite" version = "0.2.13" @@ -3031,9 +3233,9 @@ dependencies = [ [[package]] name = "raw-window-handle" -version = "0.6.0" +version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42a9830a0e1b9fb145ebb365b8bc4ccd75f290f98c0247deafbbe2c75cefb544" +checksum = "20675572f6f24e9e76ef639bc5552774ed45f1c30e2951e1e99c59888861c539" [[package]] name = "rayon" @@ -3201,7 +3403,7 @@ version = "0.38.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6ea3e1a662af26cd7a3ba09c0297a31af215563ecf42817c98df621387f4e949" dependencies = [ - "bitflags 2.4.2", + "bitflags 2.5.0", "errno", "libc", "linux-raw-sys", @@ -3279,6 +3481,19 @@ dependencies = [ "tiny-skia", ] +[[package]] +name = "sctk-adwaita" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7de61fa7334ee8ee1f5c3c58dcc414fb9361e7e8f5bff9d45f4d69eeb89a7169" +dependencies = [ + "ab_glyph", + "log", + "memmap2", + "smithay-client-toolkit", + "tiny-skia", +] + [[package]] name = "semver" version = "1.0.22" @@ -3409,7 +3624,7 @@ version = "0.18.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "922fd3eeab3bd820d76537ce8f582b1cf951eceb5475c28500c7457d9d17f53a" dependencies = [ - "bitflags 2.4.2", + "bitflags 2.5.0", "calloop", "calloop-wayland-source", "cursor-icon", @@ -3462,7 +3677,7 @@ version = "0.3.0+sdk-1.3.268.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eda41003dc44290527a59b13432d4a0379379fa074b70174882adfbdfd917844" dependencies = [ - "bitflags 2.4.2", + "bitflags 2.5.0", ] [[package]] @@ -3980,7 +4195,7 @@ version = "0.92.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "234589219e37a7496cbce73d971586db8369871be2420372c45a579b6a919b15" dependencies = [ - "bitflags 2.4.2", + "bitflags 2.5.0", "fslock", "gzip-header", "home", @@ -4166,7 +4381,7 @@ version = "0.31.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "82fb96ee935c2cea6668ccb470fb7771f6215d1691746c2d896b447a00ad3f1f" dependencies = [ - "bitflags 2.4.2", + "bitflags 2.5.0", "rustix", "wayland-backend", "wayland-scanner", @@ -4178,7 +4393,7 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "625c5029dbd43d25e6aa9615e88b829a5cad13b2819c4ae129fdbb7c31ab4c7e" dependencies = [ - "bitflags 2.4.2", + "bitflags 2.5.0", "cursor-icon", "wayland-backend", ] @@ -4200,7 +4415,7 @@ version = "0.31.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f81f365b8b4a97f422ac0e8737c438024b5951734506b0e1d775c73030561f4" dependencies = [ - "bitflags 2.4.2", + "bitflags 2.5.0", "wayland-backend", "wayland-client", "wayland-scanner", @@ -4212,7 +4427,7 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "23803551115ff9ea9bce586860c5c5a971e360825a0309264102a9495a5ff479" dependencies = [ - "bitflags 2.4.2", + "bitflags 2.5.0", "wayland-backend", "wayland-client", "wayland-protocols", @@ -4225,7 +4440,7 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ad1f61b76b6c2d8742e10f9ba5c3737f6530b4c243132c2a2ccc8aa96fe25cd6" dependencies = [ - "bitflags 2.4.2", + "bitflags 2.5.0", "wayland-backend", "wayland-client", "wayland-protocols", @@ -4275,6 +4490,16 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "web-time" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + [[package]] name = "webpki-roots" version = "0.26.1" @@ -4292,13 +4517,13 @@ checksum = "53a85b86a771b1c87058196170769dd264f66c0782acf1ae6cc51bfd64b39082" [[package]] name = "wgpu" -version = "0.19.3" +version = "0.19.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4b1213b52478a7631d6e387543ed8f642bc02c578ef4e3b49aca2a29a7df0cb" +checksum = "cbd7311dbd2abcfebaabf1841a2824ed7c8be443a0f29166e5d3c6a53a762c01" dependencies = [ "arrayvec", "cfg-if", - "cfg_aliases", + "cfg_aliases 0.1.1", "js-sys", "log", "naga", @@ -4323,8 +4548,8 @@ checksum = "f9f6b033c2f00ae0bc8ea872c5989777c60bc241aac4e58b24774faa8b391f78" dependencies = [ "arrayvec", "bit-vec", - "bitflags 2.4.2", - "cfg_aliases", + "bitflags 2.5.0", + "cfg_aliases 0.1.1", "codespan-reporting", "indexmap", "log", @@ -4351,9 +4576,9 @@ dependencies = [ "arrayvec", "ash", "bit-set", - "bitflags 2.4.2", + "bitflags 2.5.0", "block", - "cfg_aliases", + "cfg_aliases 0.1.1", "core-graphics-types", "d3d12", "glow", @@ -4369,7 +4594,7 @@ dependencies = [ "log", "metal", "naga", - "ndk-sys", + "ndk-sys 0.5.0+25.2.9519653", "objc", "once_cell", "parking_lot", @@ -4392,7 +4617,7 @@ version = "0.19.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b671ff9fb03f78b46ff176494ee1ebe7d603393f42664be55b64dc8d53969805" dependencies = [ - "bitflags 2.4.2", + "bitflags 2.5.0", "js-sys", "web-sys", ] @@ -4677,12 +4902,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0d59ad965a635657faf09c8f062badd885748428933dad8e8bdd64064d92e5ca" dependencies = [ "ahash", - "android-activity", + "android-activity 0.5.2", "atomic-waker", - "bitflags 2.4.2", + "bitflags 2.5.0", "bytemuck", "calloop", - "cfg_aliases", + "cfg_aliases 0.1.1", "core-foundation", "core-graphics", "cursor-icon", @@ -4691,16 +4916,16 @@ dependencies = [ "libc", "log", "memmap2", - "ndk", - "ndk-sys", - "objc2", + "ndk 0.8.0", + "ndk-sys 0.5.0+25.2.9519653", + "objc2 0.4.1", "once_cell", "orbclient", "percent-encoding", "raw-window-handle", "redox_syscall 0.3.5", "rustix", - "sctk-adwaita", + "sctk-adwaita 0.8.1", "smithay-client-toolkit", "smol_str", "unicode-segmentation", @@ -4711,13 +4936,63 @@ dependencies = [ "wayland-protocols", "wayland-protocols-plasma", "web-sys", - "web-time", + "web-time 0.2.4", "windows-sys 0.48.0", "x11-dl", "x11rb", "xkbcommon-dl", ] +[[package]] +name = "winit" +version = "0.30.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea9e6d5d66cbf702e0dd820302144f51b69a95acdc495dd98ca280ff206562b1" +dependencies = [ + "ahash", + "android-activity 0.6.0", + "atomic-waker", + "bitflags 2.5.0", + "bytemuck", + "calloop", + "cfg_aliases 0.2.1", + "concurrent-queue", + "core-foundation", + "core-graphics", + "cursor-icon", + "dpi", + "js-sys", + "libc", + "memmap2", + "ndk 0.9.0", + "objc2 0.5.2", + "objc2-app-kit", + "objc2-foundation", + "orbclient", + "percent-encoding", + "pin-project", + "raw-window-handle", + "redox_syscall 0.4.1", + "rustix", + "sctk-adwaita 0.9.0", + "smithay-client-toolkit", + "smol_str", + "tracing", + "unicode-segmentation", + "wasm-bindgen", + "wasm-bindgen-futures", + "wayland-backend", + "wayland-client", + "wayland-protocols", + "wayland-protocols-plasma", + "web-sys", + "web-time 1.1.0", + "windows-sys 0.52.0", + "x11-dl", + "x11rb", + "xkbcommon-dl", +] + [[package]] name = "winnow" version = "0.5.40" @@ -4790,7 +5065,7 @@ version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d039de8032a9a8856a6be89cea3e5d12fdd82306ab7c94d74e6deab2460651c5" dependencies = [ - "bitflags 2.4.2", + "bitflags 2.5.0", "dlib", "log", "once_cell", diff --git a/Cargo.toml b/Cargo.toml index d957ca619..6972e9a17 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -34,6 +34,7 @@ gosub_rendering = { path = "crates/gosub_render_utils", features = [] } gosub_renderer = { path = "./crates/gosub_renderer", features = [] } gosub_render_backend = { path = "./crates/gosub_render_backend", features = [] } gosub_vello = { path = "./crates/gosub_vello", features = [] } +gosub_useragent = { path = "./crates/gosub_useragent", features = [] } serde = { version = "1.0", features = ["derive"] } serde_derive = "1.0" derive_more = "0.99" diff --git a/crates/gosub_render_backend/Cargo.toml b/crates/gosub_render_backend/Cargo.toml index 75c52742d..9b20a3418 100644 --- a/crates/gosub_render_backend/Cargo.toml +++ b/crates/gosub_render_backend/Cargo.toml @@ -6,4 +6,6 @@ edition = "2021" [dependencies] smallvec = "1.13.2" image = "0.25.1" +raw-window-handle = "0.6.2" +gosub_shared = { path = "../gosub_shared" } diff --git a/crates/gosub_render_backend/src/lib.rs b/crates/gosub_render_backend/src/lib.rs index c9956a1ba..e77bb6383 100644 --- a/crates/gosub_render_backend/src/lib.rs +++ b/crates/gosub_render_backend/src/lib.rs @@ -1,52 +1,71 @@ +use raw_window_handle::{HasDisplayHandle, HasWindowHandle}; use std::fmt::Debug; use std::ops::{Div, Mul, MulAssign}; use smallvec::SmallVec; +use gosub_shared::types::Result; + +use gosub_shared::types::Size as SizeT; + +pub type Size = SizeT; +pub type SizeU32 = SizeT; + +pub trait WindowHandle: HasDisplayHandle + HasWindowHandle + Send + Sync + Clone {} + +impl WindowHandle for T where T: HasDisplayHandle + HasWindowHandle + Send + Sync + Clone {} + pub trait RenderBackend: Sized + Debug { type Rect: Rect; type Border: Border; type BorderSide: BorderSide; type BorderRadius: BorderRadius; type Transform: Transform; - type PreRenderText: PreRenderText; + type PreRenderText: PreRenderText; type Text: Text; type Gradient: Gradient; type Color: Color; type Image: Image; type Brush: Brush; - fn draw_rect(&mut self, rect: &RenderRect); - fn draw_text(&mut self, text: &RenderText); - fn reset(&mut self); + type ActiveWindowData<'a>; + type WindowData<'a>; + + fn draw_rect(&mut self, data: &mut Self::WindowData<'_>, rect: &RenderRect); + fn draw_text(&mut self, data: &mut Self::WindowData<'_>, text: &RenderText); + fn reset(&mut self, data: &mut Self::WindowData<'_>); + + fn activate_window<'a>( + &mut self, + handle: impl WindowHandle + 'a, + data: &mut Self::WindowData<'_>, + size: SizeU32, + ) -> Result>; + fn suspend_window( + &mut self, + handle: impl WindowHandle, + data: &mut Self::ActiveWindowData<'_>, + window_data: &mut Self::WindowData<'_>, + ) -> Result<()>; + + fn create_window_data<'a>(&mut self, handle: impl WindowHandle) + -> Result>; + + fn resize_window( + &mut self, + window_data: &mut Self::WindowData<'_>, + active_window_data: &mut Self::ActiveWindowData<'_>, + size: SizeU32, + ) -> Result<()>; + fn render( + &mut self, + window_data: &mut Self::WindowData<'_>, + active_data: &mut Self::ActiveWindowData<'_>, + ) -> Result<()>; } pub type FP = f32; - -#[derive(Clone, Copy)] -pub struct Point { - pub x: FP, - pub y: FP, -} - -#[derive(Debug)] -pub struct Size { - pub width: FP, - pub height: FP, -} - -impl Size { - pub fn new(width: FP, height: FP) -> Self { - Self { width, height } - } - - pub fn uniform(size: FP) -> Self { - Self { - width: size, - height: size, - } - } -} +pub type Point = gosub_shared::types::Point; pub struct RenderRect { pub rect: B::Rect, @@ -414,15 +433,15 @@ pub trait Transform: Sized + Mul + MulAssign { fn inverse(self) -> Self; - fn with_translation(&self, translation: (FP, FP)) -> Self; + fn with_translation(&self, translation: Point) -> Self; } -pub trait PreRenderText { +pub trait PreRenderText { fn new(text: String, font: Option>, size: FP) -> Self; fn with_lh(text: String, font: Option>, size: FP, line_height: FP) -> Self; - fn prerender(&mut self, backend: &B) -> Size; + fn prerender(&mut self) -> Size; fn value(&self) -> &str; fn fs(&self) -> FP; @@ -430,7 +449,7 @@ pub trait PreRenderText { } pub trait Text { - fn new(pre: &mut B::PreRenderText, backend: &B) -> Self; + fn new(pre: &mut B::PreRenderText) -> Self; } pub struct ColorStop { diff --git a/crates/gosub_render_utils/src/layout.rs b/crates/gosub_render_utils/src/layout.rs index 6c51f10c2..0628032ac 100644 --- a/crates/gosub_render_utils/src/layout.rs +++ b/crates/gosub_render_utils/src/layout.rs @@ -8,13 +8,12 @@ use crate::style::get_style_from_node; pub fn generate_taffy_tree( rt: &mut RenderTree, - backend: &B, ) -> anyhow::Result<(TaffyTree, NodeId)> { let mut tree: TaffyTree = TaffyTree::with_capacity(rt.nodes.len()); rt.get_root(); - let root = add_children_to_tree(rt, &mut tree, rt.root, backend)?; + let root = add_children_to_tree(rt, &mut tree, rt.root)?; Ok((tree, root)) } @@ -23,7 +22,6 @@ fn add_children_to_tree( rt: &mut RenderTree, tree: &mut TaffyTree, node_id: GosubID, - backend: &B, ) -> anyhow::Result { let Some(node_children) = rt.get_children(node_id) else { return Err(anyhow::anyhow!("Node not found {:?}", node_id)); @@ -33,7 +31,7 @@ fn add_children_to_tree( //clone, so we can drop the borrow of RT, we would be copying the NodeID anyway, so it's not a big deal (only a few bytes) for child in node_children.clone() { - match add_children_to_tree(rt, tree, child, backend) { + match add_children_to_tree(rt, tree, child) { Ok(node) => children.push(node), Err(e) => eprintln!("Error adding child to tree: {:?}", e), } @@ -43,7 +41,7 @@ fn add_children_to_tree( return Err(anyhow::anyhow!("Node not found")); }; - let style = get_style_from_node(node, backend); + let style = get_style_from_node(node); let node = tree .new_with_children(style, &children) diff --git a/crates/gosub_render_utils/src/style.rs b/crates/gosub_render_utils/src/style.rs index d8c2d50ad..665160eaf 100644 --- a/crates/gosub_render_utils/src/style.rs +++ b/crates/gosub_render_utils/src/style.rs @@ -1,20 +1,21 @@ -mod parse; -mod parse_properties; +use taffy::Style; use gosub_render_backend::RenderBackend; use gosub_styling::render_tree::RenderTreeNode; -use taffy::Style; + +mod parse; +mod parse_properties; const SCROLLBAR_WIDTH: f32 = 16.0; -pub fn get_style_from_node(node: &mut RenderTreeNode, backend: &B) -> Style { +pub fn get_style_from_node(node: &mut RenderTreeNode) -> Style { let display = parse_properties::parse_display(node); let overflow = parse_properties::parse_overflow(node); let position = parse_properties::parse_position(node); let inset = parse_properties::parse_inset(node); - let size = parse_properties::parse_size(node, backend); - let min_size = parse_properties::parse_min_size(node, backend); - let max_size = parse_properties::parse_max_size(node, backend); + let size = parse_properties::parse_size(node); + let min_size = parse_properties::parse_min_size(node); + let max_size = parse_properties::parse_max_size(node); let aspect_ratio = parse_properties::parse_aspect_ratio(node); let margin = parse_properties::parse_margin(node); let padding = parse_properties::parse_padding(node); diff --git a/crates/gosub_render_utils/src/style/parse.rs b/crates/gosub_render_utils/src/style/parse.rs index 9d9f57527..614e4b2d4 100644 --- a/crates/gosub_render_utils/src/style/parse.rs +++ b/crates/gosub_render_utils/src/style/parse.rs @@ -72,12 +72,8 @@ pub(crate) fn parse_dimension( } } -pub(crate) fn parse_text_dim( - text: &mut TextData, - name: &str, - backend: &B, -) -> Dimension { - let size = text.prerender.prerender(backend); +pub(crate) fn parse_text_dim(text: &mut TextData, name: &str) -> Dimension { + let size = text.prerender.prerender(); if name == "width" || name == "max-width" || name == "min-width" { Dimension::Length(size.width) diff --git a/crates/gosub_render_utils/src/style/parse_properties.rs b/crates/gosub_render_utils/src/style/parse_properties.rs index 03edae3ad..2a6710205 100644 --- a/crates/gosub_render_utils/src/style/parse_properties.rs +++ b/crates/gosub_render_utils/src/style/parse_properties.rs @@ -1,8 +1,8 @@ -use gosub_render_backend::RenderBackend; use regex::Regex; use taffy::prelude::*; use taffy::{Overflow, Point}; +use gosub_render_backend::RenderBackend; use gosub_styling::css_values::CssValue; use gosub_styling::render_tree::{RenderNodeData, RenderTreeNode}; @@ -96,14 +96,11 @@ pub(crate) fn parse_inset( } } -pub(crate) fn parse_size( - node: &mut RenderTreeNode, - backend: &B, -) -> Size { +pub(crate) fn parse_size(node: &mut RenderTreeNode) -> Size { if let RenderNodeData::Text(t) = &mut node.data { return Size { - width: parse_text_dim(t, "width", backend), - height: parse_text_dim(t, "height", backend), + width: parse_text_dim(t, "width"), + height: parse_text_dim(t, "height"), }; } @@ -113,14 +110,11 @@ pub(crate) fn parse_size( } } -pub(crate) fn parse_min_size( - node: &mut RenderTreeNode, - backend: &B, -) -> Size { +pub(crate) fn parse_min_size(node: &mut RenderTreeNode) -> Size { if let RenderNodeData::Text(t) = &mut node.data { return Size { - width: parse_text_dim(t, "min-width", backend), - height: parse_text_dim(t, "min-height", backend), + width: parse_text_dim(t, "min-width"), + height: parse_text_dim(t, "min-height"), }; } @@ -130,14 +124,11 @@ pub(crate) fn parse_min_size( } } -pub(crate) fn parse_max_size( - node: &mut RenderTreeNode, - backend: &B, -) -> Size { +pub(crate) fn parse_max_size(node: &mut RenderTreeNode) -> Size { if let RenderNodeData::Text(t) = &mut node.data { return Size { - width: parse_text_dim(t, "max-width", backend), - height: parse_text_dim(t, "max-height", backend), + width: parse_text_dim(t, "max-width"), + height: parse_text_dim(t, "max-height"), }; } diff --git a/crates/gosub_renderer/Cargo.toml b/crates/gosub_renderer/Cargo.toml index a12b656db..6eb99435a 100644 --- a/crates/gosub_renderer/Cargo.toml +++ b/crates/gosub_renderer/Cargo.toml @@ -24,6 +24,7 @@ slotmap = "1.0.7" smallvec = "1.13.2" image = "0.25.1" url = "2.5.0" +dpi = "0.1.1" diff --git a/crates/gosub_renderer/src/draw.rs b/crates/gosub_renderer/src/draw.rs index 0c89ded7b..5eff468ae 100644 --- a/crates/gosub_renderer/src/draw.rs +++ b/crates/gosub_renderer/src/draw.rs @@ -2,29 +2,38 @@ use std::io::Read; use anyhow::anyhow; use image::DynamicImage; -use taffy::{AvailableSpace, Layout, NodeId, PrintTree, Size, TaffyTree, TraversePartialTree}; +use taffy::{ + AvailableSpace, Layout, NodeId, PrintTree, Size as TSize, TaffyTree, TraversePartialTree, +}; use url::Url; -use winit::dpi::PhysicalSize; use gosub_html5::node::NodeId as GosubId; use gosub_render_backend::{ - Brush, Color, Image, PreRenderText, Rect, RenderBackend, RenderRect, RenderText, Text, + Brush, Color, Image, PreRenderText, Rect, RenderBackend, RenderRect, RenderText, SizeU32, Text, Transform, FP, }; +use gosub_rendering::layout::generate_taffy_tree; use gosub_rendering::position::PositionTree; +use gosub_shared::types::Result; use gosub_styling::css_colors::RgbColor; use gosub_styling::css_values::CssValue; use gosub_styling::render_tree::{RenderNodeData, RenderTree, RenderTreeNode}; -use crate::render_tree::{NodeID, TreeDrawer}; +use crate::render_tree::{load_html_rendertree, NodeID, TreeDrawer}; pub trait SceneDrawer { - fn draw(&mut self, scene: &mut B, size: PhysicalSize); - fn mouse_move(&mut self, scene: &mut B, x: f64, y: f64); + fn draw(&mut self, backend: &mut B, data: &mut B::WindowData<'_>, size: SizeU32); + fn mouse_move(&mut self, backend: &mut B, data: &mut B::WindowData<'_>, x: f64, y: f64); + + fn from_url(url: Url) -> Result + where + Self: Sized; } +type Point = gosub_shared::types::Point; + impl SceneDrawer for TreeDrawer { - fn draw(&mut self, scene: &mut B, size: PhysicalSize) { + fn draw(&mut self, backend: &mut B, data: &mut B::WindowData<'_>, size: SizeU32) { if self.size == Some(size) { //This check needs to be updated in the future, when the tree is mutable return; @@ -32,11 +41,11 @@ impl SceneDrawer for TreeDrawer { self.size = Some(size); - scene.reset(); - self.render(scene, size); + backend.reset(data); + self.render(backend, data, size); } - fn mouse_move(&mut self, _scene: &mut B, x: f64, y: f64) { + fn mouse_move(&mut self, _backend: &mut B, _data: &mut B::WindowData<'_>, x: f64, y: f64) { if let Some(e) = self.position.find(x as f32, y as f32) { if self.last_hover != Some(e) { self.last_hover = Some(e); @@ -52,11 +61,19 @@ impl SceneDrawer for TreeDrawer { } }; } + + fn from_url(url: Url) -> Result { + let mut rt = load_html_rendertree(url.clone())?; + + let (taffy_tree, root) = generate_taffy_tree(&mut rt)?; + + Ok(Self::new(rt, taffy_tree, root, url)) + } } impl TreeDrawer { - pub(crate) fn render(&mut self, scene: &mut B, size: PhysicalSize) { - let space = Size { + pub(crate) fn render(&mut self, backend: &mut B, data: &mut B::WindowData<'_>, size: SizeU32) { + let space = TSize { width: AvailableSpace::Definite(size.width as f32), height: AvailableSpace::Definite(size.height as f32), }; @@ -81,13 +98,19 @@ impl TreeDrawer { border: None, }; - scene.draw_rect(&rect); + backend.draw_rect(data, &rect); - self.render_node_with_children(self.root, scene, (0.0, 0.0)); + self.render_node_with_children(self.root, backend, data, Point::ZERO); } - fn render_node_with_children(&mut self, id: NodeID, scene: &mut B, mut pos: (FP, FP)) { - let err = self.render_node(id, scene, &mut pos); + fn render_node_with_children( + &mut self, + id: NodeID, + backend: &mut B, + data: &mut B::WindowData<'_>, + mut pos: Point, + ) { + let err = self.render_node(id, backend, data, &mut pos); if let Err(e) = err { eprintln!("Error rendering node: {:?}", e); } @@ -101,11 +124,17 @@ impl TreeDrawer { }; for child in children { - self.render_node_with_children(child, scene, pos); + self.render_node_with_children(child, backend, data, pos); } } - fn render_node(&mut self, id: NodeID, scene: &mut B, pos: &mut (FP, FP)) -> anyhow::Result<()> { + fn render_node( + &mut self, + id: NodeID, + backend: &mut B, + data: &mut B::WindowData<'_>, + pos: &mut Point, + ) -> anyhow::Result<()> { let gosub_id = *self .taffy .get_node_context(id) @@ -118,10 +147,10 @@ impl TreeDrawer { .get_node_mut(gosub_id) .ok_or(anyhow!("Node not found"))?; - pos.0 += layout.location.x as FP; - pos.1 += layout.location.y as FP; + pos.x += layout.location.x as FP; + pos.y += layout.location.y as FP; - let border_radius = render_bg(node, scene, layout, pos, &self.url); + let border_radius = render_bg(node, backend, data, layout, pos, &self.url); if let RenderNodeData::Element(element) = &node.data { if element.name() == "img" { @@ -150,11 +179,11 @@ impl TreeDrawer { .map(|prop| prop.as_str()) .unwrap_or("contain"); - render_image(img, scene, *pos, layout.size, border_radius, fit)?; + render_image(img, backend, data, *pos, layout.size, border_radius, fit)?; } } - render_text(node, scene, pos, layout); + render_text(node, backend, data, pos, layout); Ok(()) } } @@ -162,7 +191,8 @@ impl TreeDrawer { fn render_text( node: &mut RenderTreeNode, backend: &mut B, - pos: &(FP, FP), + data: &mut B::WindowData<'_>, + pos: &Point, layout: &Layout, ) { if let RenderNodeData::Text(text) = &mut node.data { @@ -181,15 +211,15 @@ fn render_text( .map(|color| Color::rgba(color.r as u8, color.g as u8, color.b as u8, color.a as u8)) .unwrap_or(Color::BLACK); - let translate = Transform::translate(pos.0 as FP, pos.1 + layout.size.height as FP); + let translate = Transform::translate(pos.x as FP, pos.y + layout.size.height as FP); - let text = Text::new(&mut text.prerender, backend); + let text = Text::new(&mut text.prerender); let rect = Rect::new( - pos.0 as FP, - pos.1 as FP, - pos.0 + layout.size.width as FP, - pos.1 + layout.size.height as FP, + pos.x as FP, + pos.y as FP, + pos.x + layout.size.width as FP, + pos.y + layout.size.height as FP, ); let render_text = RenderText { @@ -200,15 +230,16 @@ fn render_text( brush_transform: None, }; - backend.draw_text(&render_text); + backend.draw_text(data, &render_text); } } fn render_bg( node: &mut RenderTreeNode, - scene: &mut B, + backend: &mut B, + data: &mut B::WindowData<'_>, layout: &Layout, - pos: &(FP, FP), + pos: &Point, root_url: &Url, ) -> (FP, FP, FP, FP) { let bg_color = node @@ -270,10 +301,10 @@ fn render_bg( if let Some(bg_color) = bg_color { let rect = Rect::new( - pos.0 as FP, - pos.1 as FP, - pos.0 + layout.size.width as FP, - pos.1 + layout.size.height as FP, + pos.x as FP, + pos.y as FP, + pos.x + layout.size.width as FP, + pos.y + layout.size.height as FP, ); let rect = RenderRect { @@ -285,7 +316,7 @@ fn render_bg( border: None, }; - scene.draw_rect(&rect); + backend.draw_rect(data, &rect); } let background_image = node.properties.get("background-image").and_then(|prop| { @@ -316,9 +347,11 @@ fn render_bg( let img = image::load_from_memory(&img).unwrap(); - let _ = render_image(img, scene, *pos, layout.size, border_radius, "fill").map_err(|e| { - eprintln!("Error rendering image: {:?}", e); - }); + let _ = render_image(img, backend, data, *pos, layout.size, border_radius, "fill").map_err( + |e| { + eprintln!("Error rendering image: {:?}", e); + }, + ); } border_radius @@ -346,260 +379,19 @@ impl Side { } } -// ---- This will be needed for the vello backend ---- -// fn render_border( -// node: &mut RenderTreeNode, -// scene: &mut Scene, -// layout: &Layout, -// pos: &(f64, f64), -// border_radius: (f64, f64, f64, f64), -// ) { -// for side in Side::all() { -// let radi = match side { -// Side::Top => border_radius.0, -// Side::Right => border_radius.1, -// Side::Bottom => border_radius.2, -// Side::Left => border_radius.3, -// }; -// render_border_side(node, scene, layout, pos, radi, side); -// } -// } -// -// fn render_border_side( -// node: &mut RenderTreeNode, -// scene: &mut B , -// layout: &Layout, -// pos: &(FP, FP), -// border_radius: FP, -// side: Side, -// ) { -// let border_width = match side { -// Side::Top => layout.border.top, -// Side::Right => layout.border.right, -// Side::Bottom => layout.border.bottom, -// Side::Left => layout.border.left, -// } as f64; -// -// let border_color = node -// .properties -// .get(&format!("border-{}-color", side.to_str())) -// .and_then(|prop| { -// prop.compute_value(); -// -// match &prop.actual { -// CssValue::Color(color) => Some(*color), -// CssValue::String(color) => Some(RgbColor::from(color.as_str())), -// _ => None, -// } -// }) -// .map(|color| Color::rgba8(color.r as u8, color.g as u8, color.b as u8, color.a as u8)); -// -// // let border_radius = 16f64; -// -// let width = layout.size.width as f64; -// let height = layout.size.height as f64; -// -// if let Some(border_color) = border_color { -// let mut path = BezPath::new(); -// -// //draw the border segment with rounded corners -// -// match side { -// Side::Top => { -// let offset = border_radius.powi(2).div(2.0).sqrt() - border_radius; -// -// path.move_to((pos.0 - offset, pos.1 - offset)); -// -// let arc = Arc::new( -// (pos.0 + border_radius, pos.1 + border_radius), -// (border_radius, border_radius), -// -std::f64::consts::PI * 3.0 / 4.0, -// std::f64::consts::PI / 4.0, -// 0.0, -// ); -// -// arc.to_cubic_beziers(0.1, |p1, p2, p3| { -// path.curve_to(p1, p2, p3); -// }); -// -// path.line_to((pos.0 + width - border_radius, pos.1)); -// -// let arc = Arc::new( -// (pos.0 + width - border_radius, pos.1 + border_radius), -// (border_radius, border_radius), -// -std::f64::consts::PI / 2.0, -// std::f64::consts::PI / 4.0, -// 0.0, -// ); -// -// arc.to_cubic_beziers(0.1, |p1, p2, p3| { -// path.curve_to(p1, p2, p3); -// }); -// } -// Side::Right => { -// let offset = border_radius.powi(2).div(2.0).sqrt() - border_radius; -// path.move_to((pos.0 + width + offset, pos.1 - offset)); -// -// let arc = Arc::new( -// (pos.0 + width - border_radius, pos.1 + border_radius), -// (border_radius, border_radius), -// -std::f64::consts::PI / 4.0, -// std::f64::consts::PI / 4.0, -// 0.0, -// ); -// -// arc.to_cubic_beziers(0.1, |p1, p2, p3| { -// path.curve_to(p1, p2, p3); -// }); -// -// path.line_to((pos.0 + width, pos.1 + height - border_radius)); -// -// let arc = Arc::new( -// ( -// pos.0 + width - border_radius, -// pos.1 + height - border_radius, -// ), -// (border_radius, border_radius), -// 0.0, -// std::f64::consts::PI / 4.0, -// 0.0, -// ); -// -// arc.to_cubic_beziers(0.1, |p1, p2, p3| { -// path.curve_to(p1, p2, p3); -// }); -// } -// Side::Bottom => { -// let offset = border_radius.powi(2).div(2.0).sqrt() - border_radius; -// -// path.move_to((pos.0 + width + offset, pos.1 + height + offset)); -// -// let arc = Arc::new( -// ( -// pos.0 + width - border_radius, -// pos.1 + height - border_radius, -// ), -// (border_radius, border_radius), -// -std::f64::consts::PI * 7.0 / 4.0, -// std::f64::consts::PI / 4.0, -// 0.0, -// ); -// -// arc.to_cubic_beziers(0.1, |p1, p2, p3| { -// path.curve_to(p1, p2, p3); -// }); -// -// path.line_to((pos.0 + border_radius, pos.1 + height)); -// -// let arc = Arc::new( -// (pos.0 + border_radius, pos.1 + height - border_radius), -// (border_radius, border_radius), -// -std::f64::consts::PI * 3.0 / 2.0, -// std::f64::consts::PI / 4.0, -// 0.0, -// ); -// -// arc.to_cubic_beziers(0.1, |p1, p2, p3| { -// path.curve_to(p1, p2, p3); -// }); -// } -// Side::Left => { -// let offset = border_radius.powi(2).div(2.0).sqrt() - border_radius; -// -// path.move_to((pos.0 - offset, pos.1 + height + offset)); -// -// let arc = Arc::new( -// (pos.0 + border_radius, pos.1 + height - border_radius), -// (border_radius, border_radius), -// -std::f64::consts::PI * 5.0 / 4.0, -// std::f64::consts::PI / 4.0, -// 0.0, -// ); -// -// arc.to_cubic_beziers(0.1, |p1, p2, p3| { -// path.curve_to(p1, p2, p3); -// }); -// -// path.line_to((pos.0, pos.1 + border_radius)); -// -// let arc = Arc::new( -// (pos.0 + border_radius, pos.1 + border_radius), -// (border_radius, border_radius), -// -std::f64::consts::PI, -// std::f64::consts::PI / 4.0, -// 0.0, -// ); -// -// arc.to_cubic_beziers(0.1, |p1, p2, p3| { -// path.curve_to(p1, p2, p3); -// }); -// } -// } -// -// let Some(border_style) = node -// .properties -// .get(&format!("border-{}-style", side.to_str())) -// .and_then(|prop| { -// prop.compute_value(); -// -// match &prop.actual { -// CssValue::String(style) => Some(style.as_str()), -// _ => None, -// } -// }) -// else { -// return; -// }; -// -// let border_style = BorderStyle::from_str(border_style); -// -// let cap = match border_style { -// BorderStyle::Dashed => Cap::Square, -// BorderStyle::Dotted => Cap::Round, -// _ => Cap::Butt, -// }; -// -// let dash_pattern = match border_style { -// BorderStyle::Dashed => SmallVec::from([ -// border_width * 3.0, -// border_width * 3.0, -// border_width * 3.0, -// border_width * 3.0, -// ]), -// BorderStyle::Dotted => { -// SmallVec::from([border_width, border_width, border_width, border_width]) -// //TODO: somehow this doesn't result in circles. It is more like a rounded rectangle -// } -// _ => SmallVec::default(), -// }; -// -// let stroke = Stroke { -// width: border_width, -// join: Join::Bevel, -// miter_limit: 0.0, -// start_cap: cap, -// end_cap: cap, -// dash_pattern, -// dash_offset: 0.0, -// }; -// -// scene.stroke(&stroke, Affine::IDENTITY, border_color, None, &path); -// } -// } -// ---- This will be needed for the vello backend ---- - fn render_image( img: DynamicImage, - scene: &mut B, - pos: (FP, FP), - size: Size, + backend: &mut B, + data: &mut B::WindowData<'_>, + pos: Point, + size: TSize, radii: (FP, FP, FP, FP), fit: &str, ) -> anyhow::Result<()> { let width = size.width as FP; let height = size.height as FP; - let rect = Rect::new(pos.0, pos.1, pos.0 + width, pos.1 + height); + let rect = Rect::new(pos.x, pos.y, pos.x + width, pos.y + height); let img_size = (img.width() as FP, img.height() as FP); @@ -649,7 +441,7 @@ fn render_image( border: None, }; - scene.draw_rect(&rect); + backend.draw_rect(data, &rect); Ok(()) } diff --git a/crates/gosub_renderer/src/lib.rs b/crates/gosub_renderer/src/lib.rs index 49566aec2..585fc9836 100644 --- a/crates/gosub_renderer/src/lib.rs +++ b/crates/gosub_renderer/src/lib.rs @@ -1,4 +1,4 @@ pub mod draw; pub mod render_tree; -pub mod renderer; -pub mod window; +// pub mod renderer; +// pub mod window; diff --git a/crates/gosub_renderer/src/render_tree.rs b/crates/gosub_renderer/src/render_tree.rs index 11ae90db1..32ffa0b83 100644 --- a/crates/gosub_renderer/src/render_tree.rs +++ b/crates/gosub_renderer/src/render_tree.rs @@ -1,13 +1,18 @@ +use anyhow::bail; +use std::fs; use taffy::{Layout, TaffyTree}; use taffy::{NodeId as TaffyID, NodeId}; use url::Url; -use winit::dpi::PhysicalSize; use gosub_html5::node::NodeId as GosubID; -use gosub_render_backend::RenderBackend; +use gosub_html5::parser::document::{Document, DocumentBuilder}; +use gosub_html5::parser::Html5Parser; +use gosub_net::http::ureq; +use gosub_render_backend::{RenderBackend, SizeU32}; use gosub_rendering::position::PositionTree; +use gosub_shared::bytes::{CharIterator, Confidence, Encoding}; use gosub_styling::css_values::CssProperties; -use gosub_styling::render_tree::{RenderNodeData, RenderTree as StyleTree}; +use gosub_styling::render_tree::{generate_render_tree, RenderNodeData, RenderTree as StyleTree}; pub type NodeID = TaffyID; @@ -15,7 +20,7 @@ pub struct TreeDrawer { pub(crate) style: StyleTree, pub(crate) root: NodeID, pub(crate) taffy: TaffyTree, - pub(crate) size: Option>, + pub(crate) size: Option, pub(crate) url: Url, pub(crate) position: PositionTree, pub(crate) last_hover: Option, @@ -45,3 +50,45 @@ pub struct RenderTreeNode { pub namespace: Option, pub data: RenderNodeData, } + +pub(crate) fn load_html_rendertree( + url: Url, +) -> gosub_shared::types::Result> { + let html = if url.scheme() == "http" || url.scheme() == "https" { + // Fetch the html from the url + let response = ureq::get(url.as_ref()).call()?; + if response.status() != 200 { + bail!(format!( + "Could not get url. Status code {}", + response.status() + )); + } + response.into_string()? + } else if url.scheme() == "file" { + fs::read_to_string(&url.path()[1..])? + } else { + bail!("Unsupported url scheme: {}", url.scheme()); + }; + + let mut chars = CharIterator::new(); + chars.read_from_str(&html, Some(Encoding::UTF8)); + chars.set_confidence(Confidence::Certain); + + let mut doc_handle = DocumentBuilder::new_document(Some(url)); + let _parse_errors = + Html5Parser::parse_document(&mut chars, Document::clone(&doc_handle), None)?; + + let mut doc = doc_handle.get_mut(); + doc.stylesheets + .push(gosub_styling::load_default_useragent_stylesheet()?); + + println!("stylesheets: {:?}", doc.stylesheets.len()); + + for stylesheet in doc.stylesheets.iter() { + println!("stylesheet: {:?}", stylesheet.location); + } + + drop(doc); + + generate_render_tree(Document::clone(&doc_handle)) +} diff --git a/crates/gosub_shared/src/types.rs b/crates/gosub_shared/src/types.rs index 149eb7a2c..c953efeab 100644 --- a/crates/gosub_shared/src/types.rs +++ b/crates/gosub_shared/src/types.rs @@ -38,3 +38,227 @@ pub enum Error { /// Result that can be returned which holds either T or an Error pub type Result = std::result::Result; + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct Size { + pub width: T, + pub height: T, +} + +impl Size { + pub fn new(width: T, height: T) -> Self { + Self { width, height } + } + + pub fn uniform(size: T) -> Self { + Self { + width: size, + height: size, + } + } + + pub fn width(&self) -> &T { + &self.width + } + + pub fn height(&self) -> &T { + &self.height + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct Point { + pub x: T, + pub y: T, +} + +impl Point { + pub fn new(x: T, y: T) -> Self { + Self { x, y } + } + + pub fn x(&self) -> &T { + &self.x + } + + pub fn y(&self) -> &T { + &self.y + } +} + +impl Point { + pub const ZERO: Self = Self { x: 0, y: 0 }; + + pub fn f64(&self) -> Point { + Point::new(self.x as f64, self.y as f64) + } + + pub fn f32(&self) -> Point { + Point::new(self.x as f32, self.y as f32) + } + + pub fn x32(&self) -> f32 { + self.x as f32 + } + + pub fn y32(&self) -> f32 { + self.y as f32 + } + + pub fn x64(&self) -> f64 { + self.x as f64 + } + + pub fn y64(&self) -> f64 { + self.y as f64 + } +} + +impl Point { + pub const ZERO: Self = Self { x: 0.0, y: 0.0 }; + + pub fn u32(&self) -> Point { + Point::new(self.x as u32, self.y as u32) + } + + pub fn f64(&self) -> Point { + Point::new(self.x as f64, self.y as f64) + } + + pub fn x_u32(&self) -> u32 { + self.x as u32 + } + + pub fn y_u32(&self) -> u32 { + self.y as u32 + } + + pub fn x64(&self) -> f64 { + self.x as f64 + } + + pub fn y64(&self) -> f64 { + self.y as f64 + } +} + +impl Point { + pub const ZERO: Self = Self { x: 0.0, y: 0.0 }; + + pub fn u32(&self) -> Point { + Point::new(self.x as u32, self.y as u32) + } + + pub fn f32(&self) -> Point { + Point::new(self.x as f32, self.y as f32) + } + + pub fn x_u32(&self) -> u32 { + self.x as u32 + } + + pub fn y_u32(&self) -> u32 { + self.y as u32 + } + + pub fn x32(&self) -> f32 { + self.x as f32 + } + + pub fn y32(&self) -> f32 { + self.y as f32 + } +} + +impl Size { + pub const ZERO: Self = Self { + width: 0, + height: 0, + }; + + pub fn f64(&self) -> Size { + Size::new(self.width as f64, self.height as f64) + } + + pub fn f32(&self) -> Size { + Size::new(self.width as f32, self.height as f32) + } + + pub fn w32(&self) -> f32 { + self.width as f32 + } + + pub fn h32(&self) -> f32 { + self.height as f32 + } + + pub fn w64(&self) -> f64 { + self.width as f64 + } + + pub fn h64(&self) -> f64 { + self.height as f64 + } +} + +impl Size { + pub const ZERO: Self = Self { + width: 0.0, + height: 0.0, + }; + + pub fn u32(&self) -> Size { + Size::new(self.width as u32, self.height as u32) + } + + pub fn f64(&self) -> Size { + Size::new(self.width as f64, self.height as f64) + } + + pub fn w_u32(&self) -> u32 { + self.width as u32 + } + + pub fn h_u32(&self) -> u32 { + self.height as u32 + } + + pub fn w64(&self) -> f64 { + self.width as f64 + } + + pub fn h64(&self) -> f64 { + self.height as f64 + } +} + +impl Size { + pub const ZERO: Self = Self { + width: 0.0, + height: 0.0, + }; + + pub fn u32(&self) -> Size { + Size::new(self.width as u32, self.height as u32) + } + + pub fn f32(&self) -> Size { + Size::new(self.width as f32, self.height as f32) + } + + pub fn w_u32(&self) -> u32 { + self.width as u32 + } + + pub fn h_u32(&self) -> u32 { + self.height as u32 + } + + pub fn w32(&self) -> f32 { + self.width as f32 + } + + pub fn h32(&self) -> f32 { + self.height as f32 + } +} diff --git a/crates/gosub_styling/src/render_tree.rs b/crates/gosub_styling/src/render_tree.rs index f84e662b1..01fba3bcf 100644 --- a/crates/gosub_styling/src/render_tree.rs +++ b/crates/gosub_styling/src/render_tree.rs @@ -279,7 +279,6 @@ impl Debug for TextData { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_struct("TextData") .field("text", &self.prerender.value()) - .field("font", &self.prerender.font()) .field("fs", &self.prerender.fs()) .finish() } diff --git a/crates/gosub_useragent/Cargo.toml b/crates/gosub_useragent/Cargo.toml new file mode 100644 index 000000000..cd6aea17b --- /dev/null +++ b/crates/gosub_useragent/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "gosub_useragent" +version = "0.1.0" +edition = "2021" + +[dependencies] +gosub_shared = { path = "../gosub_shared" } +gosub_render_backend = { path = "../gosub_render_backend" } +gosub_renderer = { path = "../gosub_renderer" } +winit = "0.30.0" +slotmap = "1.0.7" +log = "0.4.21" +anyhow = "1.0.82" +url = "2.5.0" diff --git a/crates/gosub_useragent/src/application.rs b/crates/gosub_useragent/src/application.rs new file mode 100644 index 000000000..6f25f0bf6 --- /dev/null +++ b/crates/gosub_useragent/src/application.rs @@ -0,0 +1,146 @@ +use anyhow::anyhow; +use std::collections::HashMap; + +use url::Url; +use winit::application::ApplicationHandler; +use winit::event::WindowEvent; +use winit::event_loop::{ActiveEventLoop, EventLoop, EventLoopProxy}; +use winit::window::WindowId; + +use gosub_render_backend::RenderBackend; +use gosub_renderer::draw::SceneDrawer; +use gosub_shared::types::Result; + +use crate::window::Window; + +pub struct Application<'a, D: SceneDrawer, B: RenderBackend> { + open_windows: Vec>, // Vec of Windows, each with a Vec of URLs, representing tabs + windows: HashMap>, + backend: B, + proxy: Option>, +} + +impl<'a, D: SceneDrawer, B: RenderBackend> ApplicationHandler + for Application<'a, D, B> +{ + fn resumed(&mut self, _event_loop: &ActiveEventLoop) { + for window in self.windows.values_mut() { + if let Err(e) = window.resumed(&mut self.backend) { + eprintln!("Error resuming window: {e:?}"); + } + } + } + + fn user_event(&mut self, event_loop: &ActiveEventLoop, event: CustomEvent) { + match event { + CustomEvent::OpenWindow(url) => { + let mut window = match Window::new(event_loop, &mut self.backend, url) { + Ok(window) => window, + Err(e) => { + eprintln!("Error opening window: {e:?}"); + return; + } + }; + + if let Err(e) = window.resumed(&mut self.backend) { + eprintln!("Error resuming window: {e:?}"); + return; + } + self.windows.insert(window.id(), window); + } + CustomEvent::CloseWindow(id) => { + self.windows.remove(&id); + } + CustomEvent::OpenInitial => { + for urls in self.open_windows.drain(..) { + let mut window = + match Window::new(event_loop, &mut self.backend, urls[0].clone()) { + Ok(window) => window, + Err(e) => { + eprintln!("Error opening window: {e:?}"); + return; + } + }; + + if let Err(e) = window.resumed(&mut self.backend) { + eprintln!("Error resuming window: {e:?}"); + return; + } + + self.windows.insert(window.id(), window); + } + } + } + } + + fn window_event( + &mut self, + event_loop: &ActiveEventLoop, + window_id: WindowId, + event: WindowEvent, + ) { + if let Some(window) = self.windows.get_mut(&window_id) { + if let Err(e) = window.event(event_loop, &mut self.backend, event) { + eprintln!("Error handling window event: {e:?}"); + }; + } + } + + fn suspended(&mut self, event_loop: &ActiveEventLoop) { + for window in self.windows.values_mut() { + window.suspended(event_loop, &mut self.backend); + } + } +} + +impl<'a, D: SceneDrawer, B: RenderBackend> Application<'a, D, B> { + pub fn new(backend: B) -> Self { + Self { + windows: HashMap::new(), + backend, + proxy: None, + open_windows: Vec::new(), + } + } + + pub fn initial_tab(&mut self, url: Url) { + self.open_windows.push(vec![url]); + } + + pub fn initial(&mut self, mut windows: Vec>) { + self.open_windows.append(&mut windows); + } + + pub fn add_window(&mut self, window: Window<'a, D, B>) { + self.windows.insert(window.window.id(), window); + } + + pub fn open_window(&mut self, url: Url) { + if let Some(proxy) = &self.proxy { + let _ = proxy.send_event(CustomEvent::OpenWindow(url)); + } + } + + pub fn start(&mut self) -> Result<()> { + let event_loop = EventLoop::with_user_event().build()?; + + let proxy = event_loop.create_proxy(); + + proxy + .send_event(CustomEvent::OpenInitial) + .map_err(|e| anyhow!(e.to_string()))?; + + self.proxy = Some(proxy); + + event_loop.run_app(self)?; + + Ok(()) + } +} + +#[derive(Debug)] +enum CustomEvent { + OpenWindow(Url), + CloseWindow(WindowId), + OpenInitial, +} diff --git a/crates/gosub_useragent/src/event_loop.rs b/crates/gosub_useragent/src/event_loop.rs new file mode 100644 index 000000000..ffcf049e8 --- /dev/null +++ b/crates/gosub_useragent/src/event_loop.rs @@ -0,0 +1,66 @@ +use winit::event::WindowEvent; +use winit::event_loop::ActiveEventLoop; + +use gosub_render_backend::{RenderBackend, SizeU32}; +use gosub_renderer::draw::SceneDrawer; +use gosub_shared::types::Result; + +use crate::window::{Window, WindowState}; + +impl, B: RenderBackend> Window<'_, D, B> { + pub fn event( + &mut self, + el: &ActiveEventLoop, + backend: &mut B, + event: WindowEvent, + ) -> Result<()> { + let WindowState::Active { + surface: active_window_data, + } = &mut self.state + else { + return Ok(()); + }; + + let window = &self.window; + + match event { + WindowEvent::CloseRequested => { + el.exit(); + } + WindowEvent::Resized(size) => { + backend.resize_window( + &mut self.renderer_data, + active_window_data, + SizeU32::new(size.width, size.height), + )?; + window.request_redraw(); + } + WindowEvent::RedrawRequested => { + let size = window.inner_size(); + + let size = SizeU32::new(size.width, size.height); + + let Some(tab) = self.tabs.get_current_tab() else { + return Ok(()); + }; + + tab.data.draw(backend, &mut self.renderer_data, size); + + backend.render(&mut self.renderer_data, active_window_data)?; + } + + WindowEvent::CursorMoved { position, .. } => { + let Some(tab) = self.tabs.get_current_tab() else { + return Ok(()); + }; + + tab.data + .mouse_move(backend, &mut self.renderer_data, position.x, position.y); + } + + _ => {} + } + + Ok(()) + } +} diff --git a/crates/gosub_useragent/src/lib.rs b/crates/gosub_useragent/src/lib.rs new file mode 100644 index 000000000..57096bfa4 --- /dev/null +++ b/crates/gosub_useragent/src/lib.rs @@ -0,0 +1,4 @@ +pub mod application; +pub mod event_loop; +pub mod tabs; +pub mod window; diff --git a/crates/gosub_useragent/src/tabs.rs b/crates/gosub_useragent/src/tabs.rs new file mode 100644 index 000000000..2903ad4a5 --- /dev/null +++ b/crates/gosub_useragent/src/tabs.rs @@ -0,0 +1,79 @@ +use slotmap::{DefaultKey, SlotMap}; +use url::Url; + +use gosub_render_backend::RenderBackend; +use gosub_renderer::draw::SceneDrawer; +use gosub_shared::types::Result; + +pub struct Tabs, B: RenderBackend> { + pub tabs: SlotMap>, + pub active: TabID, + _marker: std::marker::PhantomData, +} + +impl, B: RenderBackend> Tabs { + pub fn new(initial: Tab) -> Self { + let mut tabs = SlotMap::new(); + let active = TabID(tabs.insert(initial)); + + Self { + tabs, + active, + _marker: std::marker::PhantomData, + } + } + + pub fn add_tab(&mut self, tab: Tab) -> TabID { + TabID(self.tabs.insert(tab)) + } + + pub fn remove_tab(&mut self, id: TabID) { + self.tabs.remove(id.0); + } + + pub fn activate_tab(&mut self, id: TabID) { + self.active = id; + } + + pub fn get_current_tab(&mut self) -> Option<&mut Tab> { + self.tabs.get_mut(self.active.0) + } + + pub(crate) fn from_url(url: Url) -> Result { + let tab = Tab::from_url(url)?; + + Ok(Self::new(tab)) + } +} + +pub struct Tab, B: RenderBackend> { + pub title: String, + pub url: Url, + pub data: D, + _marker: std::marker::PhantomData, +} + +impl, B: RenderBackend> Tab { + pub fn new(title: String, url: Url, data: D) -> Self { + Self { + title, + url, + data, + _marker: std::marker::PhantomData, + } + } + + pub fn from_url(url: Url) -> Result { + let data = D::from_url(url.clone())?; + + Ok(Self { + title: url.as_str().to_string(), + url, + data, + _marker: std::marker::PhantomData, + }) + } +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct TabID(pub(crate) DefaultKey); diff --git a/crates/gosub_useragent/src/window.rs b/crates/gosub_useragent/src/window.rs new file mode 100644 index 000000000..ca9cd78c2 --- /dev/null +++ b/crates/gosub_useragent/src/window.rs @@ -0,0 +1,97 @@ +use std::sync::Arc; + +use anyhow::anyhow; +use log::warn; +use winit::dpi::LogicalSize; +use winit::event_loop::ActiveEventLoop; +use winit::window::{Window as WinitWindow, WindowId}; + +use gosub_render_backend::{RenderBackend, SizeU32}; +use gosub_renderer::draw::SceneDrawer; +use gosub_shared::types::Result; +use url::Url; + +use crate::tabs::{Tab, Tabs}; + +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum WindowState<'a, B: RenderBackend> { + Active { surface: B::ActiveWindowData<'a> }, + Suspended, +} + +pub struct Window<'a, D: SceneDrawer, B: RenderBackend> { + pub(crate) state: WindowState<'a, B>, + pub(crate) window: Arc, + pub(crate) renderer_data: B::WindowData<'a>, + pub(crate) tabs: Tabs, +} + +impl<'a, D: SceneDrawer, B: RenderBackend> Window<'a, D, B> { + pub fn new(event_loop: &ActiveEventLoop, backend: &mut B, default_url: Url) -> Result { + let window = create_window(event_loop)?; + + let renderer_data = backend.create_window_data(window.clone())?; + + Ok(Self { + state: WindowState::Suspended, + window, + renderer_data, + tabs: Tabs::from_url(default_url)?, + }) + } + + pub fn resumed(&mut self, backend: &mut B) -> Result<()> { + println!("Resuming window..."); + if !matches!(self.state, WindowState::Suspended) { + return Ok(()); + }; + println!("Resuming window"); + + let size = self.window.inner_size(); + let size = SizeU32::new(size.width, size.height); + + let data = backend.activate_window(self.window.clone(), &mut self.renderer_data, size)?; + + self.state = WindowState::Active { surface: data }; + + Ok(()) + } + + pub fn suspended(&mut self, el: &ActiveEventLoop, backend: &mut B) { + let WindowState::Active { surface: data } = &mut self.state else { + return; + }; + + if let Err(e) = backend.suspend_window(self.window.clone(), data, &mut self.renderer_data) { + warn!("Failed to suspend window: {}", e); + } + + self.state = WindowState::Suspended; + } + + pub fn id(&self) -> WindowId { + self.window.id() + } + + pub fn request_redraw(&self) { + self.window.request_redraw(); + } + + pub fn state(&self) -> &'static str { + match self.state { + WindowState::Active { .. } => "Active", + WindowState::Suspended => "Suspended", + } + } +} + +fn create_window(event_loop: &ActiveEventLoop) -> Result> { + let attributes = WinitWindow::default_attributes() + .with_title("Gosub Browser") + .with_inner_size(LogicalSize::new(1920, 1080)); + + event_loop + .create_window(attributes) + .map_err(|e| anyhow!(e.to_string())) + .map(Arc::new) +} diff --git a/crates/gosub_vello/Cargo.toml b/crates/gosub_vello/Cargo.toml index c08cc4d77..532c3aa65 100644 --- a/crates/gosub_vello/Cargo.toml +++ b/crates/gosub_vello/Cargo.toml @@ -9,4 +9,8 @@ gosub_render_backend = { path = "../gosub_render_backend" } gosub_typeface = { path = "../gosub_typeface" } vello = "0.1.0" image = "0.25.1" -smallvec = "1.13.2" \ No newline at end of file +smallvec = "1.13.2" +anyhow = "1.0.82" +wgpu = "0.19.4" +raw-window-handle = "0.6.2" +futures = "0.3.30" \ No newline at end of file diff --git a/crates/gosub_vello/src/border.rs b/crates/gosub_vello/src/border.rs index 36393888b..35474034d 100644 --- a/crates/gosub_vello/src/border.rs +++ b/crates/gosub_vello/src/border.rs @@ -1,5 +1,6 @@ use smallvec::SmallVec; use vello::kurbo::{Arc, BezPath, Cap, Join, RoundedRectRadii, Stroke}; +use vello::Scene; use gosub_render_backend::{ Border as TBorder, BorderRadius as TBorderRadius, BorderSide as TBorderSide, BorderStyle, @@ -84,7 +85,7 @@ impl<'a> BorderRenderOptions<'a> { } impl Border { - pub fn draw(backend: &mut VelloBackend, opts: BorderRenderOptions) { + pub fn draw(scene: &mut Scene, opts: BorderRenderOptions) { let transform = match (opts.transform, opts.border.transform.as_ref()) { (Some(t1), Some(t2)) => Some(*t1 * *t2), (Some(t1), None) => Some(*t1), @@ -96,13 +97,13 @@ impl Border { let border = &opts.border.border; - Self::draw_side(backend, opts.left(transform)); - Self::draw_side(backend, opts.right(transform)); - Self::draw_side(backend, opts.top(transform)); - Self::draw_side(backend, opts.bottom(transform)); + Self::draw_side(scene, opts.left(transform)); + Self::draw_side(scene, opts.right(transform)); + Self::draw_side(scene, opts.top(transform)); + Self::draw_side(scene, opts.bottom(transform)); } - fn draw_side(backend: &mut VelloBackend, opts: BorderRenderSideOptions) { + fn draw_side(scene: &mut Scene, opts: BorderRenderSideOptions) { let border_width = opts.segment.width as f64; let brush = &opts.segment.brush.0; let style = opts.segment.style; @@ -350,7 +351,7 @@ impl Border { dash_offset: 0.0, }; - backend.scene.stroke( + scene.stroke( &stroke, opts.transform.map(|t| t.0).unwrap_or_default(), brush, diff --git a/crates/gosub_vello/src/lib.rs b/crates/gosub_vello/src/lib.rs index f4bf505f4..fc2512fa3 100644 --- a/crates/gosub_vello/src/lib.rs +++ b/crates/gosub_vello/src/lib.rs @@ -1,31 +1,36 @@ use std::fmt::Debug; +use std::sync::Arc; +use anyhow::anyhow; use vello::kurbo::{Point as VelloPoint, RoundedRect, Shape}; -use vello::peniko::Fill; -use vello::Scene; +use vello::peniko::{Color as VelloColor, Fill}; +use vello::{AaConfig, RenderParams, Scene}; +use crate::render::{InstanceAdapter, Renderer, RendererOptions}; pub use border::*; pub use brush::*; pub use color::*; -use gosub_render_backend::{Point, RenderBackend, RenderRect, RenderText}; +use gosub_render_backend::{Point, RenderBackend, RenderRect, RenderText, SizeU32, WindowHandle}; +use gosub_shared::types::Result; pub use gradient::*; pub use image::*; pub use rect::*; pub use text::*; pub use transform::*; +use crate::render::window::{ActiveWindowData, WindowData}; + mod border; mod brush; mod color; mod gradient; mod image; mod rect; +mod render; mod text; mod transform; -pub struct VelloBackend { - scene: Scene, -} +pub struct VelloBackend; impl Debug for VelloBackend { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { @@ -45,8 +50,10 @@ impl RenderBackend for VelloBackend { type Color = Color; type Image = Image; type Brush = Brush; + type ActiveWindowData<'a> = ActiveWindowData<'a>; + type WindowData<'a> = WindowData; - fn draw_rect(&mut self, rect: &RenderRect) { + fn draw_rect(&mut self, data: &mut Self::WindowData<'_>, rect: &RenderRect) { let affine = rect.transform.as_ref().map(|t| t.0).unwrap_or_default(); let brush = &rect.brush.0; @@ -54,10 +61,10 @@ impl RenderBackend for VelloBackend { if let Some(radius) = &rect.radius { let shape = RoundedRect::from_rect(rect.rect.0, radius.clone()); - self.scene + data.scene .fill(Fill::NonZero, affine, brush, brush_transform, &shape) } else { - self.scene + data.scene .fill(Fill::NonZero, affine, brush, brush_transform, &rect.rect.0) } @@ -69,24 +76,120 @@ impl RenderBackend for VelloBackend { radius: rect.radius.as_ref(), }; - Border::draw(self, opts); + Border::draw(&mut data.scene, opts); } } - fn draw_text(&mut self, text: &RenderText) { - Text::show(self, text) + fn draw_text(&mut self, data: &mut Self::WindowData<'_>, text: &RenderText) { + Text::show(&mut data.scene, text) + } + + fn reset(&mut self, data: &mut Self::WindowData<'_>) { + data.scene.reset(); + } + + fn activate_window<'a>( + &mut self, + handle: impl WindowHandle + 'a, + data: &mut Self::WindowData<'_>, + size: SizeU32, + ) -> Result> { + let surface = data.adapter.create_surface( + handle, + size.width, + size.height, + wgpu::PresentMode::AutoVsync, + )?; + + let renderer = data.adapter.create_renderer(Some(surface.config.format))?; + + data.renderer = renderer; + + Ok(ActiveWindowData { surface }) + } + + fn suspend_window( + &mut self, + _handle: impl WindowHandle, + _data: &mut Self::ActiveWindowData<'_>, + _window_data: &mut Self::WindowData<'_>, + ) -> Result<()> { + Ok(()) + } + + fn create_window_data<'a>( + &mut self, + _handle: impl WindowHandle, + ) -> Result> { + let renderer = futures::executor::block_on(Renderer::new(RendererOptions::default()))?; + + let adapter = renderer.instance_adapter; + + let renderer = adapter.create_renderer(None)?; + + Ok(WindowData { + adapter, + renderer, + scene: Scene::new(), + }) + } + + fn resize_window<'a>( + &mut self, + window_data: &mut Self::WindowData<'a>, + active_window_data: &mut Self::ActiveWindowData<'a>, + size: SizeU32, + ) -> Result<()> { + window_data.adapter.resize_surface( + &mut active_window_data.surface, + size.width, + size.height, + ); + + Ok(()) } - fn reset(&mut self) { - self.scene.reset(); + fn render<'a>( + &mut self, + window_data: &mut Self::WindowData<'a>, + active_data: &mut Self::ActiveWindowData<'a>, + ) -> Result<()> { + let height = active_data.surface.config.height; + let width = active_data.surface.config.width; + + let surface_texture = active_data.surface.surface.get_current_texture()?; + + window_data + .renderer + .render_to_surface( + &window_data.adapter.device, + &window_data.adapter.queue, + &window_data.scene, + &surface_texture, + &RenderParams { + base_color: VelloColor::BLACK, + width, + height, + antialiasing_method: AaConfig::Msaa16, + }, + ) + .map_err(|e| anyhow!(e.to_string()))?; + + surface_texture.present(); + + Ok(()) } } impl VelloBackend { pub fn new() -> Self { - Self { - scene: Scene::new(), - } + Self + } +} + +impl Default for VelloBackend { + fn default() -> Self { + Self::new() } } diff --git a/crates/gosub_vello/src/render.rs b/crates/gosub_vello/src/render.rs new file mode 100644 index 000000000..f58bfb5f9 --- /dev/null +++ b/crates/gosub_vello/src/render.rs @@ -0,0 +1,263 @@ +use std::num::NonZeroUsize; +use std::sync::Arc; + +use anyhow::anyhow; +use gosub_render_backend::WindowHandle; +use vello::{AaSupport, Renderer as VelloRenderer, RendererOptions as VelloRendererOptions}; +use wgpu::util::{ + backend_bits_from_env, dx12_shader_compiler_from_env, gles_minor_version_from_env, + power_preference_from_env, +}; +use wgpu::{ + Adapter, Backends, CompositeAlphaMode, Device, Dx12Compiler, Gles3MinorVersion, Instance, + InstanceDescriptor, PowerPreference, Queue, Surface, SurfaceConfiguration, SurfaceTarget, + TextureFormat, +}; + +use gosub_shared::types::Result; + +pub mod window; + +const DEFAULT_POWER_PREFERENCE: PowerPreference = PowerPreference::None; +const DEFAULT_BACKENDS: Backends = Backends::PRIMARY; +const DEFAULT_DX12COMPILER: Dx12Compiler = Dx12Compiler::Dxc { + dxil_path: None, + dxc_path: None, +}; + +const DEFAULT_GLES3_MINOR_VERSION: Gles3MinorVersion = Gles3MinorVersion::Automatic; + +pub const RENDERER_CONF: VelloRendererOptions = VelloRendererOptions { + surface_format: None, + use_cpu: false, + antialiasing_support: AaSupport { + area: true, + msaa8: true, + msaa16: true, + }, + num_init_threads: NonZeroUsize::new(4), +}; + +pub struct Renderer { + pub instance_adapter: Arc, +} + +pub struct InstanceAdapter { + pub instance: Instance, + pub adapter: Adapter, + pub device: Device, + pub queue: Queue, +} + +pub struct RendererOptions { + pub power_preference: Option, + pub backends: Option, + pub dx12compiler: Option, + pub gles3minor_version: Option, + #[cfg(not(target_arch = "wasm32"))] + pub adapter: Option, + #[cfg(not(target_arch = "wasm32"))] + pub crash_on_invalid_adapter: bool, +} + +impl Default for RendererOptions { + fn default() -> Self { + Self { + power_preference: power_preference_from_env(), + backends: backend_bits_from_env(), + dx12compiler: dx12_shader_compiler_from_env(), + gles3minor_version: gles_minor_version_from_env(), + #[cfg(not(target_arch = "wasm32"))] + adapter: std::env::var("WGPU_ADAPTER_NAME").ok(), + #[cfg(not(target_arch = "wasm32"))] + crash_on_invalid_adapter: false, + } + } +} + +struct RenderConfig { + pub power_preference: PowerPreference, + pub backends: Backends, + pub dx12compiler: Dx12Compiler, + pub gles3minor_version: Gles3MinorVersion, + #[cfg(not(target_arch = "wasm32"))] + pub adapter: Option, + #[cfg(not(target_arch = "wasm32"))] + pub crash_on_invalid_adapter: bool, +} + +impl From for RenderConfig { + fn from(opts: RendererOptions) -> Self { + Self { + power_preference: opts + .power_preference + .unwrap_or(power_preference_from_env().unwrap_or(DEFAULT_POWER_PREFERENCE)), + backends: opts + .backends + .unwrap_or(backend_bits_from_env().unwrap_or(DEFAULT_BACKENDS)), + dx12compiler: opts + .dx12compiler + .unwrap_or(dx12_shader_compiler_from_env().unwrap_or(DEFAULT_DX12COMPILER)), + gles3minor_version: opts + .gles3minor_version + .unwrap_or(gles_minor_version_from_env().unwrap_or(DEFAULT_GLES3_MINOR_VERSION)), + #[cfg(not(target_arch = "wasm32"))] + adapter: opts.adapter, + #[cfg(not(target_arch = "wasm32"))] + crash_on_invalid_adapter: opts.crash_on_invalid_adapter, + } + } +} + +impl Renderer { + pub async fn new(opts: RendererOptions) -> Result { + let config = RenderConfig::from(opts); + + Ok(Self { + instance_adapter: Arc::new(Self::get_adapter(config).await?), + }) + } + + async fn get_adapter(config: RenderConfig) -> Result { + let instance = Instance::new(InstanceDescriptor { + backends: config.backends, + dx12_shader_compiler: config.dx12compiler, + gles_minor_version: config.gles3minor_version, + ..Default::default() + }); + + #[cfg(not(target_arch = "wasm32"))] + let mut adapter = config.adapter.and_then(|adapter_name| { + let adapters = instance.enumerate_adapters(Backends::all()); + let adapter_name = adapter_name.to_lowercase(); + + let mut chosen_adapter = None; + for adapter in adapters { + let info = adapter.get_info(); + + if info.name.to_lowercase().contains(&adapter_name) { + chosen_adapter = Some(adapter); + break; + } + } + + if chosen_adapter.is_none() && config.crash_on_invalid_adapter { + eprintln!("No adapter found with name: {}", adapter_name); + std::process::exit(1); + } + + chosen_adapter + }); + + #[cfg(target_arch = "wasm32")] + let mut adapter = None; + + if adapter.is_none() { + adapter = instance + .request_adapter(&wgpu::RequestAdapterOptions { + power_preference: config.power_preference, + force_fallback_adapter: false, + compatible_surface: None, + }) + .await; + } + + if adapter.is_none() { + adapter = instance + .request_adapter(&wgpu::RequestAdapterOptions { + power_preference: config.power_preference, + force_fallback_adapter: true, + compatible_surface: None, + }) + .await; + } + + let adapter = adapter.ok_or(anyhow!("No adapter found"))?; + + let info = adapter.get_info(); + + let mut features = adapter.features(); + + if info.device_type == wgpu::DeviceType::DiscreteGpu { + features -= wgpu::Features::MAPPABLE_PRIMARY_BUFFERS; + } + + features -= wgpu::Features::RAY_QUERY; + features -= wgpu::Features::RAY_TRACING_ACCELERATION_STRUCTURE; + + let (device, queue) = adapter + .request_device( + &wgpu::DeviceDescriptor { + label: None, + required_features: Default::default(), + required_limits: Default::default(), + }, + None, + ) + .await + .map_err(|e| anyhow!(e.to_string()))?; + + Ok(InstanceAdapter { + instance, + adapter, + device, + queue, + }) + } +} + +pub struct SurfaceWrapper<'a> { + pub surface: Surface<'a>, + pub config: SurfaceConfiguration, +} + +impl InstanceAdapter { + pub fn create_renderer(&self, surface_format: Option) -> Result { + let mut conf = RENDERER_CONF; + conf.surface_format = surface_format; + + VelloRenderer::new(&self.device, conf).map_err(|e| anyhow!(e.to_string())) + } + pub fn create_surface<'a>( + &self, + window: impl WindowHandle + 'a, + width: u32, + height: u32, + present_mode: wgpu::PresentMode, + ) -> Result> { + let surface = self.instance.create_surface(window)?; + let capabilities = surface.get_capabilities(&self.adapter); + let format = capabilities + .formats + .into_iter() + .find(|it| matches!(it, TextureFormat::Rgba8Unorm | TextureFormat::Bgra8Unorm)) + .ok_or(anyhow!("surface should support Rgba8Unorm or Bgra8Unorm"))?; + + let config = SurfaceConfiguration { + usage: wgpu::TextureUsages::RENDER_ATTACHMENT, + format, + width, + height, + present_mode, + desired_maximum_frame_latency: 2, + alpha_mode: CompositeAlphaMode::Auto, + view_formats: vec![], + }; + + let surface = SurfaceWrapper { surface, config }; + + self.configure_surface(&surface); + + Ok(surface) + } + + pub fn resize_surface(&self, surface: &mut SurfaceWrapper, width: u32, height: u32) { + surface.config.width = width; + surface.config.height = height; + self.configure_surface(surface); + } + + fn configure_surface(&self, surface: &SurfaceWrapper) { + surface.surface.configure(&self.device, &surface.config); + } +} diff --git a/crates/gosub_vello/src/render/window.rs b/crates/gosub_vello/src/render/window.rs new file mode 100644 index 000000000..cf4df6ed3 --- /dev/null +++ b/crates/gosub_vello/src/render/window.rs @@ -0,0 +1,15 @@ +use std::sync::Arc; + +use vello::{Renderer, Scene}; + +use super::{InstanceAdapter, SurfaceWrapper}; + +pub struct WindowData { + pub(crate) adapter: Arc, + pub(crate) renderer: Renderer, + pub(crate) scene: Scene, +} + +pub struct ActiveWindowData<'a> { + pub(crate) surface: SurfaceWrapper<'a>, +} diff --git a/crates/gosub_vello/src/text.rs b/crates/gosub_vello/src/text.rs index bb91b0942..5f4b561d8 100644 --- a/crates/gosub_vello/src/text.rs +++ b/crates/gosub_vello/src/text.rs @@ -1,12 +1,15 @@ -use std::ops::Deref; use vello::glyph::Glyph; use vello::kurbo::Affine; -use vello::peniko::{Blob, BrushRef, Fill, Font, StyleRef}; +use vello::peniko::{Blob, Fill, Font, StyleRef}; use vello::skrifa::{instance::Size as FSize, FontRef, MetadataProvider}; +use vello::Scene; -use gosub_render_backend::{PreRenderText as TPreRenderText, RenderText, Size, Text as TText, FP}; +use gosub_render_backend::{ + PreRenderText as TPreRenderText, RenderBackend, RenderText, Size, Text as TText, FP, +}; use gosub_typeface::{BACKUP_FONT, DEFAULT_LH, FONT_RENDERER_CACHE}; +use crate::render::window::WindowData; use crate::VelloBackend; pub struct Text { @@ -20,18 +23,14 @@ pub struct PreRenderText { fs: FP, font: Vec, line_height: FP, - size: Option, - glyphs: Option>, + size: Size, + glyphs: Vec, } impl TText for Text { - fn new(pre: &mut PreRenderText, backend: &VelloBackend) -> Self { - if pre.glyphs.is_none() { - pre.prerender(backend); - } - + fn new(pre: &mut PreRenderText) -> Self { Text { - glyphs: pre.glyphs.clone().unwrap_or_default(), + glyphs: pre.glyphs.clone(), font: pre.font.clone(), fs: pre.fs, } @@ -55,34 +54,42 @@ fn get_fonts_from_family(font_families: Option>) -> Vec { fonts } -impl TPreRenderText for PreRenderText { +impl TPreRenderText for PreRenderText { fn new(text: String, font: Option>, size: FP) -> Self { let font = get_fonts_from_family(font); - PreRenderText { + let mut this = PreRenderText { text, font, line_height: DEFAULT_LH, - size: None, + size: Size::ZERO, fs: size, - glyphs: None, - } + glyphs: Vec::new(), + }; + + this.prerender(); + + this } fn with_lh(text: String, font: Option>, size: FP, line_height: FP) -> Self { let font = get_fonts_from_family(font); - PreRenderText { + let mut this = PreRenderText { text, font, line_height, - size: None, + size: Size::ZERO, fs: size, - glyphs: None, - } + glyphs: Vec::new(), + }; + + this.prerender(); + + this } - fn prerender(&mut self, backend: &VelloBackend) -> Size { + fn prerender(&mut self) -> Size { let font_ref = to_font_ref(&self.font[0]).unwrap(); let axes = font_ref.axes(); @@ -91,13 +98,13 @@ impl TPreRenderText for PreRenderText { let variations: &[(&str, f32)] = &[]; // if we have more than an empty slice here we need to change the rendering to the scene let var_loc = axes.location(variations.iter().copied()); let glyph_metrics = font_ref.glyph_metrics(fs, &var_loc); - let metrics = font_ref.metrics(fs, &var_loc); + // let metrics = font_ref.metrics(fs, &var_loc); // let line_height = metrics.ascent - metrics.descent + metrics.leading; let mut width: f32 = 0.0; let mut pen_x: f32 = 0.0; - let glyphs = self + self.glyphs = self .text .chars() .filter_map(|c| { @@ -120,8 +127,6 @@ impl TPreRenderText for PreRenderText { width = width.max(pen_x); - self.glyphs = Some(glyphs); - Size { width, height: self.line_height, @@ -138,15 +143,14 @@ impl TPreRenderText for PreRenderText { } impl Text { - pub(crate) fn show(vello: &mut VelloBackend, render: &RenderText) { + pub(crate) fn show(scene: &mut Scene, render: &RenderText) { let brush = &render.brush.0; let style: StyleRef = Fill::NonZero.into(); let transform = render.transform.map(|t| t.0).unwrap_or(Affine::IDENTITY); let brush_transform = render.brush_transform.map(|t| t.0); - vello - .scene + scene .draw_glyphs(&render.text.font[0]) .font_size(render.text.fs) .transform(transform) diff --git a/crates/gosub_vello/src/transform.rs b/crates/gosub_vello/src/transform.rs index 76ca7033c..adfc18013 100644 --- a/crates/gosub_vello/src/transform.rs +++ b/crates/gosub_vello/src/transform.rs @@ -139,9 +139,9 @@ impl TTransform for Transform { self.0.inverse().into() } - fn with_translation(&self, translation: (FP, FP)) -> Self { + fn with_translation(&self, translation: Point) -> Self { self.0 - .with_translation((translation.0 as f64, translation.1 as f64).into()) + .with_translation((translation.x64(), translation.x64()).into()) .into() } } diff --git a/src/bin/renderer.rs b/src/bin/renderer.rs index 17af896f6..4f4e490b5 100644 --- a/src/bin/renderer.rs +++ b/src/bin/renderer.rs @@ -7,12 +7,11 @@ use gosub_html5::parser::document::{Document, DocumentBuilder}; use gosub_html5::parser::Html5Parser; use gosub_render_backend::RenderBackend; use gosub_renderer::render_tree::TreeDrawer; -use gosub_renderer::renderer::{Renderer, RendererOptions}; -use gosub_rendering::layout::generate_taffy_tree; use gosub_shared::bytes::CharIterator; use gosub_shared::bytes::{Confidence, Encoding}; use gosub_shared::types::Result; use gosub_styling::render_tree::{generate_render_tree, RenderTree as StyleTree}; +use gosub_useragent::application::Application; use gosub_vello::VelloBackend; fn main() -> Result<()> { @@ -27,19 +26,24 @@ fn main() -> Result<()> { let url: String = matches.get_one::("url").expect("url").to_string(); - let mut rt = load_html_rendertree(&url)?; + // let mut rt = load_html_rendertree(&url)?; - let backend = VelloBackend::new(); + let mut application: Application, VelloBackend> = + Application::new(VelloBackend::new()); - let (taffy_tree, root) = generate_taffy_tree(&mut rt, &backend)?; + application.initial_tab(Url::parse(&url)?); - let render_tree = TreeDrawer::new(rt, taffy_tree, root, Url::parse("https://gosub.io/")?); + application.start()?; - let render_tree = render_tree; - - let renderer = futures::executor::block_on(Renderer::new(RendererOptions::default()))?; - - renderer.start(render_tree)?; + // let (taffy_tree, root) = generate_taffy_tree(&mut rt, &backend)?; + // + // let render_tree = TreeDrawer::new(rt, taffy_tree, root, Url::parse("https://gosub.io/")?); + // + // let render_tree = render_tree; + // + // let renderer = futures::executor::block_on(Renderer::new(RendererOptions::default()))?; + // + // renderer.start(render_tree)?; Ok(()) } From f74721c76114363e500a6028571d53d8d8368a67 Mon Sep 17 00:00:00 2001 From: Shark Date: Sat, 1 Jun 2024 03:05:58 +0200 Subject: [PATCH 5/9] fix it not adding Document to the root --- crates/gosub_render_utils/src/layout.rs | 2 -- crates/gosub_renderer/src/draw.rs | 4 ++-- crates/gosub_renderer/src/render_tree.rs | 15 ++++++--------- crates/gosub_styling/src/render_tree.rs | 5 +++-- src/bin/resources/gosub.html | 1 + 5 files changed, 12 insertions(+), 15 deletions(-) diff --git a/crates/gosub_render_utils/src/layout.rs b/crates/gosub_render_utils/src/layout.rs index 0628032ac..c196cd18e 100644 --- a/crates/gosub_render_utils/src/layout.rs +++ b/crates/gosub_render_utils/src/layout.rs @@ -11,8 +11,6 @@ pub fn generate_taffy_tree( ) -> anyhow::Result<(TaffyTree, NodeId)> { let mut tree: TaffyTree = TaffyTree::with_capacity(rt.nodes.len()); - rt.get_root(); - let root = add_children_to_tree(rt, &mut tree, rt.root)?; Ok((tree, root)) diff --git a/crates/gosub_renderer/src/draw.rs b/crates/gosub_renderer/src/draw.rs index 5eff468ae..c28cb0dec 100644 --- a/crates/gosub_renderer/src/draw.rs +++ b/crates/gosub_renderer/src/draw.rs @@ -87,13 +87,13 @@ impl TreeDrawer { self.position = PositionTree::from_taffy(&self.taffy, self.root); - let bg = B::Rect::new(0.0, 0.0, size.width as FP, size.height as FP); + let bg = Rect::new(0.0, 0.0, size.width as FP, size.height as FP); let rect = RenderRect { rect: bg, transform: None, radius: None, - brush: B::Brush::color(B::Color::WHITE), + brush: Brush::color(Color::BLACK), brush_transform: None, border: None, }; diff --git a/crates/gosub_renderer/src/render_tree.rs b/crates/gosub_renderer/src/render_tree.rs index 32ffa0b83..9a2881814 100644 --- a/crates/gosub_renderer/src/render_tree.rs +++ b/crates/gosub_renderer/src/render_tree.rs @@ -65,7 +65,7 @@ pub(crate) fn load_html_rendertree( } response.into_string()? } else if url.scheme() == "file" { - fs::read_to_string(&url.path()[1..])? + fs::read_to_string(url.as_str().trim_start_matches("file://"))? } else { bail!("Unsupported url scheme: {}", url.scheme()); }; @@ -75,19 +75,16 @@ pub(crate) fn load_html_rendertree( chars.set_confidence(Confidence::Certain); let mut doc_handle = DocumentBuilder::new_document(Some(url)); - let _parse_errors = - Html5Parser::parse_document(&mut chars, Document::clone(&doc_handle), None)?; + let parse_errors = Html5Parser::parse_document(&mut chars, Document::clone(&doc_handle), None)?; + + for error in parse_errors { + eprintln!("Parse error: {:?}", error); + } let mut doc = doc_handle.get_mut(); doc.stylesheets .push(gosub_styling::load_default_useragent_stylesheet()?); - println!("stylesheets: {:?}", doc.stylesheets.len()); - - for stylesheet in doc.stylesheets.iter() { - println!("stylesheet: {:?}", stylesheet.location); - } - drop(doc); generate_render_tree(Document::clone(&doc_handle)) diff --git a/crates/gosub_styling/src/render_tree.rs b/crates/gosub_styling/src/render_tree.rs index 01fba3bcf..072dd95c2 100644 --- a/crates/gosub_styling/src/render_tree.rs +++ b/crates/gosub_styling/src/render_tree.rs @@ -338,6 +338,7 @@ impl RenderNodeData { RenderNodeData::Text(Box::new(text)) } + NodeData::Document(_) => RenderNodeData::Document, _ => return ControlFlow::Drop, }) } @@ -381,8 +382,8 @@ impl RenderTreeNode { /// Generates a render tree for the given document based on its loaded stylesheets pub fn generate_render_tree(document: DocumentHandle) -> Result> { - let mut render_tree = RenderTree::from_document(document); - render_tree.remove_unrenderable_nodes(); + let render_tree = RenderTree::from_document(document); + Ok(render_tree) } diff --git a/src/bin/resources/gosub.html b/src/bin/resources/gosub.html index e3df2af27..c61fe2e0b 100644 --- a/src/bin/resources/gosub.html +++ b/src/bin/resources/gosub.html @@ -1,3 +1,4 @@ + From ec145d50ba1530fac0c00b750321d0f8a5f6bd8e Mon Sep 17 00:00:00 2001 From: Shark Date: Tue, 4 Jun 2024 12:07:02 +0200 Subject: [PATCH 6/9] fix transform and img rendering do a bunch of error handling Signed-off-by: Shark --- crates/gosub_render_utils/src/layout.rs | 2 +- crates/gosub_renderer/src/draw.rs | 59 +++++++++++++++---------- crates/gosub_vello/src/rect.rs | 8 +++- crates/gosub_vello/src/text.rs | 4 +- crates/gosub_vello/src/transform.rs | 2 +- 5 files changed, 48 insertions(+), 27 deletions(-) diff --git a/crates/gosub_render_utils/src/layout.rs b/crates/gosub_render_utils/src/layout.rs index c196cd18e..d2a2a4c3c 100644 --- a/crates/gosub_render_utils/src/layout.rs +++ b/crates/gosub_render_utils/src/layout.rs @@ -31,7 +31,7 @@ fn add_children_to_tree( for child in node_children.clone() { match add_children_to_tree(rt, tree, child) { Ok(node) => children.push(node), - Err(e) => eprintln!("Error adding child to tree: {:?}", e), + Err(e) => eprintln!("Error adding child to tree: {:?}", e.to_string()), } } diff --git a/crates/gosub_renderer/src/draw.rs b/crates/gosub_renderer/src/draw.rs index c28cb0dec..d4b21b5ea 100644 --- a/crates/gosub_renderer/src/draw.rs +++ b/crates/gosub_renderer/src/draw.rs @@ -83,7 +83,7 @@ impl TreeDrawer { return; } - print_tree(&self.taffy, self.root, &self.style); + // print_tree(&self.taffy, self.root, &self.style); self.position = PositionTree::from_taffy(&self.taffy, self.root); @@ -97,7 +97,7 @@ impl TreeDrawer { brush_transform: None, border: None, }; - + // backend.draw_rect(data, &rect); self.render_node_with_children(self.root, backend, data, Point::ZERO); @@ -161,17 +161,23 @@ impl TreeDrawer { let url = Url::parse(src.as_str()).or_else(|_| self.url.join(src.as_str()))?; - let res = gosub_net::http::ureq::get(url.as_str()).call()?; + let img = if url.scheme() == "file" { + let path = url.as_str().trim_start_matches("file://"); + + image::open(path)? + } else { + let res = gosub_net::http::ureq::get(url.as_str()).call()?; - let mut img = Vec::with_capacity( - res.header("Content-Length") - .unwrap_or("1024") - .parse::()?, - ); + let mut img = Vec::with_capacity( + res.header("Content-Length") + .unwrap_or("1024") + .parse::()?, + ); - res.into_reader().read_to_end(&mut img)?; + res.into_reader().read_to_end(&mut img)?; - let img = image::load_from_memory(&img)?; + image::load_from_memory(&img)? + }; let fit = element .attributes @@ -179,6 +185,9 @@ impl TreeDrawer { .map(|prop| prop.as_str()) .unwrap_or("contain"); + println!("Rendering image at: {:?}", pos); + println!("with size: {:?}", layout.size); + render_image(img, backend, data, *pos, layout.size, border_radius, fit)?; } } @@ -218,8 +227,8 @@ fn render_text( let rect = Rect::new( pos.x as FP, pos.y as FP, - pos.x + layout.size.width as FP, - pos.y + layout.size.height as FP, + layout.size.width as FP, + layout.size.height as FP, ); let render_text = RenderText { @@ -303,8 +312,8 @@ fn render_bg( let rect = Rect::new( pos.x as FP, pos.y as FP, - pos.x + layout.size.width as FP, - pos.y + layout.size.height as FP, + layout.size.width as FP, + layout.size.height as FP, ); let rect = RenderRect { @@ -334,18 +343,22 @@ fn render_bg( return border_radius; }; - let res = gosub_net::http::ureq::get(url.as_str()).call().unwrap(); + let Ok(res) = gosub_net::http::ureq::get(url.as_str()).call() else { + return border_radius; + }; let mut img = Vec::with_capacity( res.header("Content-Length") .unwrap_or("1024") .parse::() - .unwrap(), + .unwrap_or(1024), ); - res.into_reader().read_to_end(&mut img).unwrap(); + let _ = res.into_reader().read_to_end(&mut img); //TODO: handle error - let img = image::load_from_memory(&img).unwrap(); + let Ok(img) = image::load_from_memory(&img) else { + return border_radius; + }; let _ = render_image(img, backend, data, *pos, layout.size, border_radius, "fill").map_err( |e| { @@ -408,7 +421,7 @@ fn render_image( let scale = scale_x.min(scale_y); - Transform::scale_xy(scale, scale) + Transform::scale(scale) } "cover" => { let scale_x = width / img_size.0; @@ -416,7 +429,7 @@ fn render_image( let scale = scale_x.max(scale_y); - Transform::scale_xy(scale, scale) + Transform::scale(scale) } "scale-down" => { let scale_x = width / img_size.0; @@ -425,7 +438,7 @@ fn render_image( let scale = scale_x.min(scale_y); let scale = scale.min(1.0); - Transform::scale_xy(scale, scale) + Transform::scale(scale) } _ => Transform::IDENTITY, }; @@ -434,10 +447,10 @@ fn render_image( let rect = RenderRect { rect, - transform: Some(transform), + transform: None, radius: Some(B::BorderRadius::from(radii)), brush: Brush::image(Image::new(img_size, img.into_rgba8().into_raw())), - brush_transform: None, + brush_transform: Some(transform), border: None, }; diff --git a/crates/gosub_vello/src/rect.rs b/crates/gosub_vello/src/rect.rs index 12f3433d4..d1c8f79fa 100644 --- a/crates/gosub_vello/src/rect.rs +++ b/crates/gosub_vello/src/rect.rs @@ -11,7 +11,13 @@ impl From for Rect { impl TRect for Rect { fn new(x: FP, y: FP, width: FP, height: FP) -> Self { - VelloRect::new(x as f64, y as f64, width as f64, height as f64).into() + VelloRect::new( + x as f64, + y as f64, + x as f64 + width as f64, + y as f64 + height as f64, + ) + .into() } fn from_point(point: Point, size: Size) -> Self { diff --git a/crates/gosub_vello/src/text.rs b/crates/gosub_vello/src/text.rs index 5f4b561d8..d127658e3 100644 --- a/crates/gosub_vello/src/text.rs +++ b/crates/gosub_vello/src/text.rs @@ -9,7 +9,6 @@ use gosub_render_backend::{ }; use gosub_typeface::{BACKUP_FONT, DEFAULT_LH, FONT_RENDERER_CACHE}; -use crate::render::window::WindowData; use crate::VelloBackend; pub struct Text { @@ -46,6 +45,9 @@ fn get_fonts_from_family(font_families: Option>) -> Vec { for (i, f) in font.into_iter().enumerate() { fonts.push(Font::new(Blob::new(f), i as u32)); } + if fonts.is_empty() { + fonts.push(Font::new(Blob::new(BACKUP_FONT.data.clone()), 0)); + } } } else { fonts.push(Font::new(Blob::new(BACKUP_FONT.data.clone()), 0)); diff --git a/crates/gosub_vello/src/transform.rs b/crates/gosub_vello/src/transform.rs index adfc18013..e17132c14 100644 --- a/crates/gosub_vello/src/transform.rs +++ b/crates/gosub_vello/src/transform.rs @@ -141,7 +141,7 @@ impl TTransform for Transform { fn with_translation(&self, translation: Point) -> Self { self.0 - .with_translation((translation.x64(), translation.x64()).into()) + .with_translation((translation.x64(), translation.y64()).into()) .into() } } From c7c5237eb800b5d52322010f424635713f64045f Mon Sep 17 00:00:00 2001 From: Shark Date: Tue, 4 Jun 2024 18:51:33 +0200 Subject: [PATCH 7/9] add border rendering again --- crates/gosub_render_backend/src/lib.rs | 21 ++++ crates/gosub_renderer/src/draw.rs | 147 ++++++++++++++++++------- crates/gosub_vello/src/border.rs | 105 ++++++++++++------ 3 files changed, 197 insertions(+), 76 deletions(-) diff --git a/crates/gosub_render_backend/src/lib.rs b/crates/gosub_render_backend/src/lib.rs index e77bb6383..63e21aced 100644 --- a/crates/gosub_render_backend/src/lib.rs +++ b/crates/gosub_render_backend/src/lib.rs @@ -171,6 +171,8 @@ pub trait Rect { pub trait Border { fn new(all: B::BorderSide) -> Self; + fn empty() -> Self; + fn all( left: B::BorderSide, right: B::BorderSide, @@ -205,6 +207,25 @@ pub enum BorderStyle { Hidden, } +impl BorderStyle { + #[allow(clippy::should_implement_trait)] + pub fn from_str(style: &str) -> Self { + match style { + "none" => Self::None, + "hidden" => Self::Hidden, + "dotted" => Self::Dotted, + "dashed" => Self::Dashed, + "solid" => Self::Solid, + "double" => Self::Double, + "groove" => Self::Groove, + "ridge" => Self::Ridge, + "inset" => Self::Inset, + "outset" => Self::Outset, + _ => Self::None, + } + } +} + #[derive(Clone, Copy)] pub enum Radius { Uniform(FP), diff --git a/crates/gosub_renderer/src/draw.rs b/crates/gosub_renderer/src/draw.rs index d4b21b5ea..0f87e45ad 100644 --- a/crates/gosub_renderer/src/draw.rs +++ b/crates/gosub_renderer/src/draw.rs @@ -9,8 +9,8 @@ use url::Url; use gosub_html5::node::NodeId as GosubId; use gosub_render_backend::{ - Brush, Color, Image, PreRenderText, Rect, RenderBackend, RenderRect, RenderText, SizeU32, Text, - Transform, FP, + Border, BorderSide, BorderStyle, Brush, Color, Image, PreRenderText, Rect, RenderBackend, + RenderBorder, RenderRect, RenderText, SizeU32, Text, Transform, FP, }; use gosub_rendering::layout::generate_taffy_tree; use gosub_rendering::position::PositionTree; @@ -220,8 +220,6 @@ fn render_text( .map(|color| Color::rgba(color.r as u8, color.g as u8, color.b as u8, color.a as u8)) .unwrap_or(Color::BLACK); - let translate = Transform::translate(pos.x as FP, pos.y + layout.size.height as FP); - let text = Text::new(&mut text.prerender); let rect = Rect::new( @@ -234,7 +232,7 @@ fn render_text( let render_text = RenderText { text, rect, - transform: Some(translate), + transform: None, brush: Brush::color(color), brush_transform: None, }; @@ -308,6 +306,8 @@ fn render_bg( border_radius_left as FP, ); + let border = get_border(node).map(|border| RenderBorder::new(border)); + if let Some(bg_color) = bg_color { let rect = Rect::new( pos.x as FP, @@ -322,7 +322,25 @@ fn render_bg( radius: Some(B::BorderRadius::from(border_radius)), brush: Brush::color(bg_color), brush_transform: None, - border: None, + border, + }; + + backend.draw_rect(data, &rect); + } else if let Some(border) = border { + let rect = Rect::new( + pos.x as FP, + pos.y as FP, + layout.size.width as FP, + layout.size.height as FP, + ); + + let rect = RenderRect { + rect, + transform: None, + radius: Some(B::BorderRadius::from(border_radius)), + brush: Brush::color(Color::TRANSPARENT), + brush_transform: None, + border: Some(border), }; backend.draw_rect(data, &rect); @@ -459,40 +477,6 @@ fn render_image( Ok(()) } -#[derive(Debug)] -enum BorderStyle { - None, - Hidden, - Dotted, - Dashed, - Solid, - Double, - Groove, - Ridge, - Inset, - Outset, - //DotDash, //TODO: should we support these? - //DotDotDash, -} - -impl BorderStyle { - fn from_str(style: &str) -> Self { - match style { - "none" => Self::None, - "hidden" => Self::Hidden, - "dotted" => Self::Dotted, - "dashed" => Self::Dashed, - "solid" => Self::Solid, - "double" => Self::Double, - "groove" => Self::Groove, - "ridge" => Self::Ridge, - "inset" => Self::Inset, - "outset" => Self::Outset, - _ => Self::None, - } - } -} - //just for debugging pub fn print_tree( tree: &TaffyTree, @@ -562,3 +546,86 @@ pub fn print_tree( } } } + +fn get_border(node: &mut RenderTreeNode) -> Option { + let left = get_border_side(node, Side::Left); + let right = get_border_side(node, Side::Right); + let top = get_border_side(node, Side::Top); + let bottom = get_border_side(node, Side::Bottom); + + if left.is_none() && right.is_none() && top.is_none() && bottom.is_none() { + return None; + } + + let mut border = B::Border::empty(); + + if let Some(left) = left { + border.left(left) + } + + if let Some(right) = right { + border.right(right) + } + + if let Some(top) = top { + border.top(top) + } + + if let Some(bottom) = bottom { + border.bottom(bottom) + } + + Some(border) +} + +fn get_border_side( + node: &mut RenderTreeNode, + side: Side, +) -> Option { + let Some(width) = node + .properties + .get(&format!("border-{}-width", side.to_str())) + .map(|prop| { + prop.compute_value(); + prop.actual.unit_to_px() + }) + else { + return None; + }; + + let Some(color) = node + .properties + .get(&format!("border-{}-color", side.to_str())) + .and_then(|prop| { + prop.compute_value(); + + match &prop.actual { + CssValue::Color(color) => Some(*color), + CssValue::String(color) => Some(RgbColor::from(color.as_str())), + _ => None, + } + }) + else { + return None; + }; + + let style = node + .properties + .get(&format!("border-{}-style", side.to_str())) + .map(|prop| { + prop.compute_value(); + prop.actual.to_string() + }) + .unwrap_or("none".to_string()); + + let style = BorderStyle::from_str(&style); + + let brush = Brush::color(Color::rgba( + color.r as u8, + color.g as u8, + color.b as u8, + color.a as u8, + )); + + Some(BorderSide::new(width as FP, style, brush)) +} diff --git a/crates/gosub_vello/src/border.rs b/crates/gosub_vello/src/border.rs index 35474034d..97dbae2a8 100644 --- a/crates/gosub_vello/src/border.rs +++ b/crates/gosub_vello/src/border.rs @@ -10,10 +10,10 @@ use gosub_render_backend::{ use crate::{Brush, Rect, Transform, VelloBackend}; pub struct Border { - pub(crate) left: BorderSide, - pub(crate) right: BorderSide, - pub(crate) top: BorderSide, - pub(crate) bottom: BorderSide, + pub(crate) left: Option, + pub(crate) right: Option, + pub(crate) top: Option, + pub(crate) bottom: Option, } enum Side { @@ -43,44 +43,60 @@ struct BorderRenderSideOptions<'a> { } impl<'a> BorderRenderOptions<'a> { - fn left(&self, transform: Option<&'a Transform>) -> BorderRenderSideOptions { - BorderRenderSideOptions { + fn left(&self, transform: Option<&'a Transform>) -> Option { + let Some(segment) = self.border.border.left.as_ref() else { + return None; + }; + + Some(BorderRenderSideOptions { side: Side::Left, - segment: &self.border.border.left, + segment, transform, radius: self.radius.map(|r| (r.top_left, r.bottom_left)), rect: self.rect, - } + }) } - fn right(&self, transform: Option<&'a Transform>) -> BorderRenderSideOptions { - BorderRenderSideOptions { + fn right(&self, transform: Option<&'a Transform>) -> Option { + let Some(segment) = self.border.border.right.as_ref() else { + return None; + }; + + Some(BorderRenderSideOptions { side: Side::Right, - segment: &self.border.border.right, + segment, transform, radius: self.radius.map(|r| (r.top_right, r.bottom_right)), rect: self.rect, - } + }) } - fn top(&self, transform: Option<&'a Transform>) -> BorderRenderSideOptions { - BorderRenderSideOptions { + fn top(&self, transform: Option<&'a Transform>) -> Option { + let Some(segment) = self.border.border.top.as_ref() else { + return None; + }; + + Some(BorderRenderSideOptions { side: Side::Top, - segment: &self.border.border.top, + segment, transform, radius: self.radius.map(|r| (r.top_left, r.top_right)), rect: self.rect, - } + }) } - fn bottom(&self, transform: Option<&'a Transform>) -> BorderRenderSideOptions { - BorderRenderSideOptions { + fn bottom(&self, transform: Option<&'a Transform>) -> Option { + let Some(segment) = self.border.border.bottom.as_ref() else { + return None; + }; + + Some(BorderRenderSideOptions { side: Side::Bottom, - segment: &self.border.border.bottom, + segment, transform, radius: self.radius.map(|r| (r.bottom_left, r.bottom_right)), rect: self.rect, - } + }) } } @@ -97,10 +113,18 @@ impl Border { let border = &opts.border.border; - Self::draw_side(scene, opts.left(transform)); - Self::draw_side(scene, opts.right(transform)); - Self::draw_side(scene, opts.top(transform)); - Self::draw_side(scene, opts.bottom(transform)); + if let Some(segment) = opts.left(transform) { + Self::draw_side(scene, segment); + } + if let Some(segment) = opts.right(transform) { + Self::draw_side(scene, segment); + } + if let Some(segment) = opts.top(transform) { + Self::draw_side(scene, segment); + } + if let Some(segment) = opts.bottom(transform) { + Self::draw_side(scene, segment); + } } fn draw_side(scene: &mut Scene, opts: BorderRenderSideOptions) { @@ -364,36 +388,45 @@ impl Border { impl TBorder for Border { fn new(all: BorderSide) -> Self { Self { - left: all.clone(), - right: all.clone(), - top: all.clone(), - bottom: all, + left: Some(all.clone()), + right: Some(all.clone()), + top: Some(all.clone()), + bottom: Some(all), + } + } + + fn empty() -> Self { + Self { + left: None, + right: None, + top: None, + bottom: None, } } fn all(left: BorderSide, right: BorderSide, top: BorderSide, bottom: BorderSide) -> Self { Self { - left, - right, - top, - bottom, + left: Some(left), + right: Some(right), + top: Some(top), + bottom: Some(bottom), } } fn left(&mut self, side: BorderSide) { - self.left = side; + self.left = Some(side); } fn right(&mut self, side: BorderSide) { - self.right = side; + self.right = Some(side); } fn top(&mut self, side: BorderSide) { - self.top = side; + self.top = Some(side); } fn bottom(&mut self, side: BorderSide) { - self.bottom = side; + self.bottom = Some(side); } } From eebccca34e1f1097365bc69492d132fe9f784b0a Mon Sep 17 00:00:00 2001 From: Shark Date: Tue, 4 Jun 2024 19:15:57 +0200 Subject: [PATCH 8/9] fix text layouting --- crates/gosub_renderer/src/draw.rs | 2 -- crates/gosub_vello/src/text.rs | 11 +++++++---- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/crates/gosub_renderer/src/draw.rs b/crates/gosub_renderer/src/draw.rs index 0f87e45ad..30e175975 100644 --- a/crates/gosub_renderer/src/draw.rs +++ b/crates/gosub_renderer/src/draw.rs @@ -83,8 +83,6 @@ impl TreeDrawer { return; } - // print_tree(&self.taffy, self.root, &self.style); - self.position = PositionTree::from_taffy(&self.taffy, self.root); let bg = Rect::new(0.0, 0.0, size.width as FP, size.height as FP); diff --git a/crates/gosub_vello/src/text.rs b/crates/gosub_vello/src/text.rs index d127658e3..ac2c04e43 100644 --- a/crates/gosub_vello/src/text.rs +++ b/crates/gosub_vello/src/text.rs @@ -128,11 +128,9 @@ impl TPreRenderText for PreRenderText { .collect(); width = width.max(pen_x); + let height = self.line_height.max(self.fs); //HACK: we need to get the actual height of the font - Size { - width, - height: self.line_height, - } + Size { width, height } } fn value(&self) -> &str { @@ -152,6 +150,11 @@ impl Text { let transform = render.transform.map(|t| t.0).unwrap_or(Affine::IDENTITY); let brush_transform = render.brush_transform.map(|t| t.0); + let x = render.rect.0.x0; + let y = render.rect.0.y0 + render.rect.0.height(); + + let transform = transform.with_translation((x, y).into()); + scene .draw_glyphs(&render.text.font[0]) .font_size(render.text.fs) From d2292c150d51c83862882ac954088b9caeb9448d Mon Sep 17 00:00:00 2001 From: Shark Date: Wed, 5 Jun 2024 00:10:14 +0200 Subject: [PATCH 9/9] make clippy happy --- crates/gosub_renderer/src/draw.rs | 20 +++----- crates/gosub_useragent/src/application.rs | 6 +++ crates/gosub_useragent/src/window.rs | 4 +- crates/gosub_vello/src/border.rs | 24 ++------- crates/gosub_vello/src/brush.rs | 2 +- crates/gosub_vello/src/lib.rs | 5 +- crates/gosub_vello/src/render.rs | 3 +- crates/gosub_vello/src/text.rs | 10 ++-- src/bin/renderer.rs | 59 ----------------------- src/bin/resources/gosub.html | 2 +- src/bin/style-parser.rs | 25 +++++----- 11 files changed, 42 insertions(+), 118 deletions(-) diff --git a/crates/gosub_renderer/src/draw.rs b/crates/gosub_renderer/src/draw.rs index 30e175975..69410a129 100644 --- a/crates/gosub_renderer/src/draw.rs +++ b/crates/gosub_renderer/src/draw.rs @@ -162,6 +162,8 @@ impl TreeDrawer { let img = if url.scheme() == "file" { let path = url.as_str().trim_start_matches("file://"); + println!("Loading image from: {:?}", path); + image::open(path)? } else { let res = gosub_net::http::ureq::get(url.as_str()).call()?; @@ -394,10 +396,6 @@ enum Side { } impl Side { - fn all() -> [Side; 4] { - [Side::Top, Side::Right, Side::Bottom, Side::Left] - } - fn to_str(&self) -> &'static str { match self { Side::Top => "top", @@ -580,18 +578,15 @@ fn get_border_side( node: &mut RenderTreeNode, side: Side, ) -> Option { - let Some(width) = node + let width = node .properties .get(&format!("border-{}-width", side.to_str())) .map(|prop| { prop.compute_value(); prop.actual.unit_to_px() - }) - else { - return None; - }; + })?; - let Some(color) = node + let color = node .properties .get(&format!("border-{}-color", side.to_str())) .and_then(|prop| { @@ -602,10 +597,7 @@ fn get_border_side( CssValue::String(color) => Some(RgbColor::from(color.as_str())), _ => None, } - }) - else { - return None; - }; + })?; let style = node .properties diff --git a/crates/gosub_useragent/src/application.rs b/crates/gosub_useragent/src/application.rs index 6f25f0bf6..8d698fae7 100644 --- a/crates/gosub_useragent/src/application.rs +++ b/crates/gosub_useragent/src/application.rs @@ -136,6 +136,12 @@ impl<'a, D: SceneDrawer, B: RenderBackend> Application<'a, D, B> { Ok(()) } + + pub fn close_window(&mut self, id: WindowId) { + if let Some(proxy) = &self.proxy { + let _ = proxy.send_event(CustomEvent::CloseWindow(id)); + } + } } #[derive(Debug)] diff --git a/crates/gosub_useragent/src/window.rs b/crates/gosub_useragent/src/window.rs index ca9cd78c2..3ff51670a 100644 --- a/crates/gosub_useragent/src/window.rs +++ b/crates/gosub_useragent/src/window.rs @@ -11,7 +11,7 @@ use gosub_renderer::draw::SceneDrawer; use gosub_shared::types::Result; use url::Url; -use crate::tabs::{Tab, Tabs}; +use crate::tabs::Tabs; #[derive(Debug, Clone, PartialEq, Eq)] pub enum WindowState<'a, B: RenderBackend> { @@ -57,7 +57,7 @@ impl<'a, D: SceneDrawer, B: RenderBackend> Window<'a, D, B> { Ok(()) } - pub fn suspended(&mut self, el: &ActiveEventLoop, backend: &mut B) { + pub fn suspended(&mut self, _el: &ActiveEventLoop, backend: &mut B) { let WindowState::Active { surface: data } = &mut self.state else { return; }; diff --git a/crates/gosub_vello/src/border.rs b/crates/gosub_vello/src/border.rs index 97dbae2a8..9ab9e6e62 100644 --- a/crates/gosub_vello/src/border.rs +++ b/crates/gosub_vello/src/border.rs @@ -4,7 +4,7 @@ use vello::Scene; use gosub_render_backend::{ Border as TBorder, BorderRadius as TBorderRadius, BorderSide as TBorderSide, BorderStyle, - Radius, RenderBackend, RenderBorder, FP, + Radius, RenderBorder, FP, }; use crate::{Brush, Rect, Transform, VelloBackend}; @@ -23,10 +23,6 @@ enum Side { Bottom, } -fn all() -> [Side; 4] { - [Side::Top, Side::Right, Side::Bottom, Side::Left] -} - pub struct BorderRenderOptions<'a> { pub border: &'a RenderBorder, pub rect: &'a Rect, @@ -44,9 +40,7 @@ struct BorderRenderSideOptions<'a> { impl<'a> BorderRenderOptions<'a> { fn left(&self, transform: Option<&'a Transform>) -> Option { - let Some(segment) = self.border.border.left.as_ref() else { - return None; - }; + let segment = self.border.border.left.as_ref()?; Some(BorderRenderSideOptions { side: Side::Left, @@ -58,9 +52,7 @@ impl<'a> BorderRenderOptions<'a> { } fn right(&self, transform: Option<&'a Transform>) -> Option { - let Some(segment) = self.border.border.right.as_ref() else { - return None; - }; + let segment = self.border.border.right.as_ref()?; Some(BorderRenderSideOptions { side: Side::Right, @@ -72,9 +64,7 @@ impl<'a> BorderRenderOptions<'a> { } fn top(&self, transform: Option<&'a Transform>) -> Option { - let Some(segment) = self.border.border.top.as_ref() else { - return None; - }; + let segment = self.border.border.top.as_ref()?; Some(BorderRenderSideOptions { side: Side::Top, @@ -86,9 +76,7 @@ impl<'a> BorderRenderOptions<'a> { } fn bottom(&self, transform: Option<&'a Transform>) -> Option { - let Some(segment) = self.border.border.bottom.as_ref() else { - return None; - }; + let segment = self.border.border.bottom.as_ref()?; Some(BorderRenderSideOptions { side: Side::Bottom, @@ -111,8 +99,6 @@ impl Border { let transform = transform.as_ref(); - let border = &opts.border.border; - if let Some(segment) = opts.left(transform) { Self::draw_side(scene, segment); } diff --git a/crates/gosub_vello/src/brush.rs b/crates/gosub_vello/src/brush.rs index 4128aec22..3febf0aca 100644 --- a/crates/gosub_vello/src/brush.rs +++ b/crates/gosub_vello/src/brush.rs @@ -1,5 +1,5 @@ use crate::{Color, Gradient, Image, VelloBackend}; -use gosub_render_backend::{Brush as TBrush, RenderBackend}; +use gosub_render_backend::Brush as TBrush; use vello::peniko::Brush as VelloBrush; #[derive(Clone)] diff --git a/crates/gosub_vello/src/lib.rs b/crates/gosub_vello/src/lib.rs index fc2512fa3..5a560248e 100644 --- a/crates/gosub_vello/src/lib.rs +++ b/crates/gosub_vello/src/lib.rs @@ -1,12 +1,11 @@ use std::fmt::Debug; -use std::sync::Arc; use anyhow::anyhow; -use vello::kurbo::{Point as VelloPoint, RoundedRect, Shape}; +use vello::kurbo::{Point as VelloPoint, RoundedRect}; use vello::peniko::{Color as VelloColor, Fill}; use vello::{AaConfig, RenderParams, Scene}; -use crate::render::{InstanceAdapter, Renderer, RendererOptions}; +use crate::render::{Renderer, RendererOptions}; pub use border::*; pub use brush::*; pub use color::*; diff --git a/crates/gosub_vello/src/render.rs b/crates/gosub_vello/src/render.rs index f58bfb5f9..59eb4a5e9 100644 --- a/crates/gosub_vello/src/render.rs +++ b/crates/gosub_vello/src/render.rs @@ -10,8 +10,7 @@ use wgpu::util::{ }; use wgpu::{ Adapter, Backends, CompositeAlphaMode, Device, Dx12Compiler, Gles3MinorVersion, Instance, - InstanceDescriptor, PowerPreference, Queue, Surface, SurfaceConfiguration, SurfaceTarget, - TextureFormat, + InstanceDescriptor, PowerPreference, Queue, Surface, SurfaceConfiguration, TextureFormat, }; use gosub_shared::types::Result; diff --git a/crates/gosub_vello/src/text.rs b/crates/gosub_vello/src/text.rs index ac2c04e43..2ee25151f 100644 --- a/crates/gosub_vello/src/text.rs +++ b/crates/gosub_vello/src/text.rs @@ -4,9 +4,7 @@ use vello::peniko::{Blob, Fill, Font, StyleRef}; use vello::skrifa::{instance::Size as FSize, FontRef, MetadataProvider}; use vello::Scene; -use gosub_render_backend::{ - PreRenderText as TPreRenderText, RenderBackend, RenderText, Size, Text as TText, FP, -}; +use gosub_render_backend::{PreRenderText as TPreRenderText, RenderText, Size, Text as TText, FP}; use gosub_typeface::{BACKUP_FONT, DEFAULT_LH, FONT_RENDERER_CACHE}; use crate::VelloBackend; @@ -130,7 +128,11 @@ impl TPreRenderText for PreRenderText { width = width.max(pen_x); let height = self.line_height.max(self.fs); //HACK: we need to get the actual height of the font - Size { width, height } + let size = Size { width, height }; + + self.size = size; + + size } fn value(&self) -> &str { diff --git a/src/bin/renderer.rs b/src/bin/renderer.rs index 4f4e490b5..d5584dbd0 100644 --- a/src/bin/renderer.rs +++ b/src/bin/renderer.rs @@ -1,16 +1,7 @@ -use std::fs; - -use anyhow::bail; use url::Url; -use gosub_html5::parser::document::{Document, DocumentBuilder}; -use gosub_html5::parser::Html5Parser; -use gosub_render_backend::RenderBackend; use gosub_renderer::render_tree::TreeDrawer; -use gosub_shared::bytes::CharIterator; -use gosub_shared::bytes::{Confidence, Encoding}; use gosub_shared::types::Result; -use gosub_styling::render_tree::{generate_render_tree, RenderTree as StyleTree}; use gosub_useragent::application::Application; use gosub_vello::VelloBackend; @@ -35,55 +26,5 @@ fn main() -> Result<()> { application.start()?; - // let (taffy_tree, root) = generate_taffy_tree(&mut rt, &backend)?; - // - // let render_tree = TreeDrawer::new(rt, taffy_tree, root, Url::parse("https://gosub.io/")?); - // - // let render_tree = render_tree; - // - // let renderer = futures::executor::block_on(Renderer::new(RendererOptions::default()))?; - // - // renderer.start(render_tree)?; Ok(()) } - -fn load_html_rendertree(str_url: &str) -> Result> { - let url = Url::parse(str_url)?; - let html = if url.scheme() == "http" || url.scheme() == "https" { - // Fetch the html from the url - let response = ureq::get(url.as_ref()).call()?; - if response.status() != 200 { - bail!(format!( - "Could not get url. Status code {}", - response.status() - )); - } - response.into_string()? - } else if url.scheme() == "file" { - fs::read_to_string(str_url.trim_start_matches("file://"))? - } else { - bail!("Unsupported url scheme: {}", url.scheme()); - }; - - let mut chars = CharIterator::new(); - chars.read_from_str(&html, Some(Encoding::UTF8)); - chars.set_confidence(Confidence::Certain); - - let mut doc_handle = DocumentBuilder::new_document(Some(url)); - let _parse_errors = - Html5Parser::parse_document(&mut chars, Document::clone(&doc_handle), None)?; - - let mut doc = doc_handle.get_mut(); - doc.stylesheets - .push(gosub_styling::load_default_useragent_stylesheet()?); - - println!("stylesheets: {:?}", doc.stylesheets.len()); - - for stylesheet in doc.stylesheets.iter() { - println!("stylesheet: {:?}", stylesheet.location); - } - - drop(doc); - - generate_render_tree(Document::clone(&doc_handle)) -} diff --git a/src/bin/resources/gosub.html b/src/bin/resources/gosub.html index c61fe2e0b..ec06ee3a0 100644 --- a/src/bin/resources/gosub.html +++ b/src/bin/resources/gosub.html @@ -138,7 +138,7 @@