diff --git a/frontend/src/app/render_wasm/api.cljs b/frontend/src/app/render_wasm/api.cljs index 9b04a28c5f..65e2372b66 100644 --- a/frontend/src/app/render_wasm/api.cljs +++ b/frontend/src/app/render_wasm/api.cljs @@ -8,6 +8,7 @@ "A WASM based render API" (:require ["react-dom/server" :as rds] + [app.common.data :as d] [app.common.data.macros :as dm] [app.common.geom.matrix :as gmt] [app.common.math :as mth] @@ -17,6 +18,8 @@ [app.config :as cf] [app.main.refs :as refs] [app.main.render :as render] + [app.main.store :as st] + [app.main.ui.shapes.text.fontfaces :as fonts] [app.render-wasm.helpers :as h] [app.util.debug :as dbg] [app.util.http :as http] @@ -24,6 +27,8 @@ [beicon.v2.core :as rx] [cuerdas.core :as str] [goog.object :as gobj] + [lambdaisland.uri :as u] + [okulary.core :as l] [promesa.core :as p] [rumext.v2 :as mf])) @@ -165,35 +170,50 @@ (defn- get-string-length [string] (+ (count string) 1)) ;; IMPORTANT: It should be noted that only TTF fonts can be stored. -;; Do not remove, this is going to be useful -;; when we implement text rendering. -#_(defn- store-font - [family-name font-array-buffer] - (let [family-name-size (get-string-length family-name) - font-array-buffer-size (.-byteLength font-array-buffer) - size (+ font-array-buffer-size family-name-size) - ptr (h/call internal-module "_alloc_bytes" size) - family-name-ptr (+ ptr font-array-buffer-size) - heap (gobj/get ^js internal-module "HEAPU8") - mem (js/Uint8Array. (.-buffer heap) ptr size)] - (.set mem (js/Uint8Array. font-array-buffer)) - (h/call internal-module "stringToUTF8" family-name family-name-ptr family-name-size) - (h/call internal-module "_store_font" family-name-size font-array-buffer-size))) +(defn- store-font-buffer + [font-data font-array-buffer] + (let [id-buffer (:family-id-buffer font-data) + size (.-byteLength font-array-buffer) + ptr (h/call internal-module "_alloc_bytes" size) + heap (gobj/get ^js internal-module "HEAPU8") + mem (js/Uint8Array. (.-buffer heap) ptr size)] + (.set mem (js/Uint8Array. font-array-buffer)) + (h/call internal-module "_store_font" + (aget id-buffer 0) + (aget id-buffer 1) + (aget id-buffer 2) + (aget id-buffer 3) + (:weight font-data) + (:style font-data)) + true)) -;; This doesn't work -#_(store-font-url "roboto-thin-italic" "https://fonts.gstatic.com/s/roboto/v32/KFOiCnqEu92Fr1Mu51QrEzAdLw.woff2") -;; This does -#_(store-font-url "sourcesanspro-regular" "http://localhost:3449/fonts/sourcesanspro-regular.ttf") -;; Do not remove, this is going to be useful -;; when we implement text rendering. -#_(defn- store-font-url - [family-name font-url] - (-> (p/then (js/fetch font-url) - (fn [response] (.arrayBuffer response))) - (p/then (fn [array-buffer] (store-font family-name array-buffer))))) +(defn- store-font-url + [font-data font-url] + (->> (http/send! {:method :get + :uri font-url + :response-type :blob}) + (rx/map :body) + (rx/mapcat wapi/read-file-as-array-buffer) + (rx/map (fn [array-buffer] (store-font-buffer font-data array-buffer))))) + +(defn- store-font-id + [font-data asset-id] + (when asset-id + (let [uri (str (u/join cf/public-uri "assets/by-id/" asset-id)) + id-buffer (uuid/get-u32 (:family-id font-data)) + font-data (assoc font-data :family-id-buffer id-buffer) + font-stored? (not= 0 (h/call internal-module "_is_font_uploaded" + (aget id-buffer 0) + (aget id-buffer 1) + (aget id-buffer 2) + (aget id-buffer 3) + (:weight font-data) + (:style font-data)))] + (when-not font-stored? (store-font-url font-data uri))))) (defn- store-image [id] + (let [buffer (uuid/get-u32 id) url (cf/resolve-file-media {:id id})] (->> (http/send! {:method :get @@ -663,6 +683,55 @@ (let [encoder (js/TextEncoder.)] (.encode encoder text))) +(def ^:private fonts + (l/derived :fonts st/state)) + +(defn ^:private font->ttf-id [font-uuid font-style font-weight] + (let [matching-font (d/seek (fn [[_ font]] + (and (= (:font-id font) font-uuid) + (= (:font-style font) font-style) + (= (:font-weight font) font-weight))) + (seq @fonts))] + (when matching-font + (:ttf-file-id (second matching-font))))) + +(defn- serialize-font-style + [font-style] + (case font-style + "normal" 0 + "regular" 0 + "italic" 1 + 0)) + +(defn- serialize-font-id + [font-id] + (let [no-prefix (subs font-id (inc (str/index-of font-id "-"))) + as-uuid (uuid/uuid no-prefix)] + (uuid/get-u32 as-uuid))) + +(defn- serialize-font-weight + [font-weight] + (js/Number font-weight)) + +(defn- add-text-leaf [leaf] + (let [text (dm/get-prop leaf :text) + font-id (serialize-font-id (dm/get-prop leaf :font-id)) + font-style (serialize-font-style (dm/get-prop leaf :font-style)) + font-weight (serialize-font-weight (dm/get-prop leaf :font-weight)) + font-size (js/Number (dm/get-prop leaf :font-size)) + buffer (utf8->buffer text) + size (.-byteLength buffer) + ptr (h/call internal-module "_alloc_bytes" size) + heap (gobj/get ^js internal-module "HEAPU8") + mem (js/Uint8Array. (.-buffer heap) ptr size)] + (.set mem buffer) + (h/call internal-module "_add_text_leaf" + (aget font-id 0) + (aget font-id 1) + (aget font-id 2) + (aget font-id 3) + font-weight font-style font-size))) + (defn set-shape-text-content [content] (h/call internal-module "_clear_shape_text") (let [paragraph-set (first (dm/get-prop content :children)) @@ -677,17 +746,8 @@ (h/call internal-module "_add_text_paragraph") (loop [index-leaves 0] (when (< index-leaves total-leaves) - (let [leaf (nth leaves index-leaves) - text (dm/get-prop leaf :text) - buffer (utf8->buffer text) - ;; set up buffer array from - size (.-byteLength buffer) - ptr (h/call internal-module "_alloc_bytes" size) - heap (gobj/get ^js internal-module "HEAPU8") - mem (js/Uint8Array. (.-buffer heap) ptr size)] - - (.set mem buffer) - (h/call internal-module "_add_text_leaf") + (let [leaf (nth leaves index-leaves)] + (add-text-leaf leaf) (recur (inc index-leaves)))))) (recur (inc index)))))) @@ -699,8 +759,34 @@ (defn clear-cache [] (h/call internal-module "_clear_cache")) +(defn- store-all-fonts + [fonts] + (keep (fn [font] + (let [font-id (dm/get-prop font :font-id) + font-variant (dm/get-prop font :font-variant-id) + variant-parts (str/split font-variant #"\-") + style (first variant-parts) + weight (serialize-font-weight (last variant-parts)) + font-id (subs font-id (inc (str/index-of font-id "-"))) + font-id (uuid/uuid font-id) + ttf-id (font->ttf-id font-id style weight) + font-data {:family-id font-id + :style (serialize-font-style style) + :weight weight}] + (store-font-id font-data ttf-id))) fonts)) + +(defn set-fonts + [objects] + (let [fonts (fonts/shapes->fonts (into [] (vals objects))) + pending (into [] (store-all-fonts fonts))] + (->> (rx/from pending) + (rx/mapcat identity) + (rx/reduce conj []) + (rx/subs! request-render)))) + (defn set-objects [objects] + (set-fonts objects) (let [shapes (into [] (vals objects)) total-shapes (count shapes) pending diff --git a/render-wasm/docs/serialization.md b/render-wasm/docs/serialization.md index 094e72f94a..b4ea254d14 100644 --- a/render-wasm/docs/serialization.md +++ b/render-wasm/docs/serialization.md @@ -4,19 +4,18 @@ Shape types are serialized as `u8`: -| Value | Field | -| ----- | ---------- | -| 0 | Frame | -| 1 | Group | -| 2 | Bool | -| 3 | Rect | -| 4 | Path | -| 5 | Text | -| 6 | Circle | -| 7 | SvgRaw | -| 8 | Image | -| \_ | Rect | - +| Value | Field | +| ----- | ------ | +| 0 | Frame | +| 1 | Group | +| 2 | Bool | +| 3 | Rect | +| 4 | Path | +| 5 | Text | +| 6 | Circle | +| 7 | SvgRaw | +| 8 | Image | +| \_ | Rect | ## Horizontal Constraint @@ -31,7 +30,6 @@ Horizontal constraints are serialized as `u8`: | 4 | Scale | | \_ | None | - ## Vertical Constraint Vertical constraints are serialized as `u8`: @@ -45,7 +43,6 @@ Vertical constraints are serialized as `u8`: | 4 | Scale | | \_ | None | - ## Paths Paths are made of segments of **28 bytes** each. The layout (assuming positions in a `Uint8Array`) is the following: @@ -143,64 +140,65 @@ Shadow styles are serialized as `u8`: | 1 | Inner Shadow | | \_ | Drop Shadow | -## Layout - Direction +## Layout + +### Direction | Value | Field | -| ----- | --------------| +| ----- | ------------- | | 0 | Row | | 1 | RowReverse | | 2 | Column | | 3 | ColumnReverse | | \_ | error | -## Layout - Align Items +### Align Items | Value | Field | -| ----- | --------| +| ----- | ------- | | 0 | Start | | 1 | End | | 2 | Center | | 3 | Stretch | | \_ | error | -## Layout - Align Content +### Align Content | Value | Field | | ----- | ------------- | | 0 | Start | | 1 | End | | 2 | Center | -| 3 | Space between | +| 3 | Space between | | 4 | Space around | | 5 | Space evenly | | 6 | Stretch | | \_ | error | -## Layout - Justify items +### Justify items | Value | Field | -| ----- | --------| +| ----- | ------- | | 0 | Start | | 1 | End | | 2 | Center | | 3 | Stretch | | \_ | error | -## Layout - Justify content - +### Justify content | Value | Field | | ----- | ------------- | | 0 | Start | | 1 | End | | 2 | Center | -| 3 | Space between | +| 3 | Space between | | 4 | Space around | | 5 | Space evenly | | 6 | Stretch | | \_ | error | -## Layout - Wrap type +### Wrap type | Value | Field | | ----- | ------- | @@ -208,11 +206,21 @@ Shadow styles are serialized as `u8`: | 1 | No Wrap | | \_ | error | -## Layout - Sizing +### Sizing | Value | Field | -| ----- | ------| +| ----- | ----- | | 0 | Fill | | 1 | Fix | | 2 | Auto | | \_ | error | + +## Font + +### Style + +| Value | Variant | +| ----- | ------- | +| 0 | Normal | +| 1 | Italic | +| \_ | Normal | diff --git a/render-wasm/src/main.rs b/render-wasm/src/main.rs index 9d431c1cf1..b49ff216ae 100644 --- a/render-wasm/src/main.rs +++ b/render-wasm/src/main.rs @@ -293,31 +293,6 @@ pub extern "C" fn add_shape_fill_stops() { mem::free_bytes(); } -#[no_mangle] -pub extern "C" fn store_font(family_name_size: u32, font_size: u32) { - with_state!(state, { - unsafe { - let font_bytes = Vec::::from_raw_parts( - mem::buffer_ptr(), - font_size as usize, - font_size as usize, - ); - let family_name = String::from_raw_parts( - mem::buffer_ptr().add(font_size as usize), - family_name_size as usize, - family_name_size as usize, - ); - match state.render_state().add_font(family_name, &font_bytes) { - Err(msg) => { - eprintln!("{}", msg); - } - _ => {} - } - mem::free_bytes(); - } - }); -} - #[no_mangle] pub extern "C" fn store_image(a: u32, b: u32, c: u32, d: u32) { with_state!(state, { diff --git a/render-wasm/src/mem.rs b/render-wasm/src/mem.rs index cd1ddea53f..f9a71c4a2b 100644 --- a/render-wasm/src/mem.rs +++ b/render-wasm/src/mem.rs @@ -38,14 +38,6 @@ pub extern "C" fn free_bytes() { std::mem::drop(guard); } -pub fn buffer_ptr() -> *mut u8 { - let guard = BUFFERU8.lock().unwrap(); - - guard - .as_ref() - .map_or(std::ptr::null_mut(), |buffer| buffer.as_ptr() as *mut u8) -} - pub fn bytes() -> Vec { let mut guard = BUFFERU8.lock().unwrap(); diff --git a/render-wasm/src/render.rs b/render-wasm/src/render.rs index 12f83db2fe..eabaa1491c 100644 --- a/render-wasm/src/render.rs +++ b/render-wasm/src/render.rs @@ -1,4 +1,4 @@ -use skia_safe::{self as skia, Contains, FontMgr, Matrix, RRect, Rect}; +use skia_safe::{self as skia, Contains, Matrix, RRect, Rect}; use std::collections::HashMap; use uuid::Uuid; @@ -8,6 +8,7 @@ mod blend; mod cache; mod debug; mod fills; +mod fonts; mod gpu_state; mod images; mod options; @@ -23,11 +24,9 @@ use options::RenderOptions; use surfaces::{SurfaceId, Surfaces}; pub use blend::BlendMode; +pub use fonts::*; pub use images::*; -const DEFAULT_FONT_BYTES: &[u8] = - include_bytes!("../../frontend/resources/fonts/RobotoMono-Regular.ttf"); - const MAX_BLOCKING_TIME_MS: i32 = 32; const NODE_BATCH_THRESHOLD: i32 = 10; @@ -86,9 +85,10 @@ pub(crate) struct RenderState { gpu_state: GpuState, pub options: RenderOptions, pub surfaces: Surfaces, + fonts: FontStore, // TODO: we should probably have only one of these - pub font_provider: skia::textlayout::TypefaceFontProvider, - pub font_collection: skia::textlayout::FontCollection, + // pub font_provider: skia::textlayout::TypefaceFontProvider, + // pub font_collection: skia::textlayout::FontCollection, // ---- pub cached_surface_image: Option, pub viewbox: Viewbox, @@ -112,15 +112,8 @@ impl RenderState { let sampling_options = skia::SamplingOptions::new(skia::FilterMode::Linear, skia::MipmapMode::Nearest); let surfaces = Surfaces::new(&mut gpu_state, (width, height), sampling_options); - let mut font_provider = skia::textlayout::TypefaceFontProvider::new(); - let default_font = skia::FontMgr::default() - .new_from_data(DEFAULT_FONT_BYTES, None) - .expect("Failed to load font"); - font_provider.register_typeface(default_font, "robotomono-regular"); - let mut font_collection = skia::textlayout::FontCollection::new(); - let font_manager = FontMgr::from(font_provider.clone()); - font_collection.set_default_font_manager(FontMgr::default(), None); - font_collection.set_dynamic_font_manager(font_manager); + + let fonts = FontStore::new(); // This is used multiple times everywhere so instead of creating new instances every // time we reuse this one. @@ -129,8 +122,9 @@ impl RenderState { gpu_state, surfaces, cached_surface_image: None, - font_provider, - font_collection, + // font_provider, + // font_collection, + fonts, options: RenderOptions::default(), viewbox: Viewbox::new(width as f32, height as f32), images: ImageStore::new(), @@ -143,13 +137,11 @@ impl RenderState { } } - pub fn add_font(&mut self, family_name: String, font_data: &[u8]) -> Result<(), String> { - let typeface = skia::FontMgr::default() - .new_from_data(font_data, None) - .expect("Failed to add font"); - self.font_provider - .register_typeface(typeface, family_name.as_ref()); - Ok(()) + pub fn fonts(&self) -> &FontStore { + &self.fonts + } + pub fn fonts_mut(&mut self) -> &mut FontStore { + &mut self.fonts } pub fn add_image(&mut self, id: Uuid, image_data: &[u8]) -> Result<(), String> { @@ -353,7 +345,7 @@ impl RenderState { if let Some(svg) = shape.svg.as_ref() { svg.render(self.surfaces.canvas(SurfaceId::Fills)) } else { - let font_manager = skia::FontMgr::from(self.font_provider.clone()); + let font_manager = skia::FontMgr::from(self.fonts().font_provider().clone()); let dom_result = skia::svg::Dom::from_str(sr.content.to_string(), font_manager); match dom_result { Ok(dom) => { diff --git a/render-wasm/src/render/debug.rs b/render-wasm/src/render/debug.rs index 8e13ce6bcb..176f1aec60 100644 --- a/render-wasm/src/render/debug.rs +++ b/render-wasm/src/render/debug.rs @@ -23,18 +23,17 @@ fn render_debug_view(render_state: &mut RenderState) { } pub fn render_wasm_label(render_state: &mut RenderState) { - let canvas = render_state.surfaces.canvas(SurfaceId::Current); + let font_provider = render_state.fonts().font_provider(); + let typeface = font_provider + .match_family_style("robotomono-regular", skia::FontStyle::default()) + .unwrap(); + let canvas = render_state.surfaces.canvas(SurfaceId::Current); let skia::ISize { width, height } = canvas.base_layer_size(); let p = skia::Point::new(width as f32 - 100.0, height as f32 - 25.0); let mut paint = skia::Paint::default(); paint.set_color(skia::Color::from_argb(100, 0, 0, 0)); - let font_provider = &render_state.font_provider; - let typeface = font_provider - .match_family_style("robotomono-regular", skia::FontStyle::default()) - .unwrap(); - let font = skia::Font::new(typeface, 10.0); canvas.draw_str("WASM RENDERER", p, &font, &paint); } diff --git a/render-wasm/src/render/fonts.rs b/render-wasm/src/render/fonts.rs new file mode 100644 index 0000000000..dcaf545a6f --- /dev/null +++ b/render-wasm/src/render/fonts.rs @@ -0,0 +1,70 @@ +use skia_safe::{self as skia, textlayout, FontMgr}; + +use crate::shapes::FontFamily; + +const DEFAULT_FONT_BYTES: &[u8] = include_bytes!("../fonts/RobotoMono-Regular.ttf"); + +pub struct FontStore { + // TODO: we should probably have just one of those + font_provider: textlayout::TypefaceFontProvider, + font_collection: textlayout::FontCollection, +} + +impl FontStore { + pub fn new() -> Self { + let mut font_provider = skia::textlayout::TypefaceFontProvider::new(); + + let default_font = skia::FontMgr::default() + .new_from_data(DEFAULT_FONT_BYTES, None) + .expect("Failed to load font"); + + font_provider.register_typeface(default_font, "robotomono-regular"); + + let mut font_collection = skia::textlayout::FontCollection::new(); + font_collection.set_default_font_manager(FontMgr::default(), None); + font_collection.set_dynamic_font_manager(FontMgr::from(font_provider.clone())); + + Self { + font_provider, + font_collection, + } + } + + pub fn font_provider(&self) -> &textlayout::TypefaceFontProvider { + &self.font_provider + } + + pub fn font_collection(&self) -> &textlayout::FontCollection { + &self.font_collection + } + + pub fn add(&mut self, family: FontFamily, font_data: &[u8]) -> Result<(), String> { + if self.has_family(&family) { + return Ok(()); + } + + let alias = format!("{}", family); + let typeface = skia::FontMgr::default() + .new_from_data(font_data, None) + .ok_or("Failed to create typeface")?; + + self.font_provider + .register_typeface(typeface, alias.as_str()); + self.refresh_font_collection(); + + Ok(()) + } + + pub fn has_family(&self, family: &FontFamily) -> bool { + let serialized = format!("{}", family); + self.font_provider.family_names().any(|x| x == serialized) + } + + fn refresh_font_collection(&mut self) { + self.font_collection = skia::textlayout::FontCollection::new(); + self.font_collection + .set_default_font_manager(FontMgr::default(), None); + self.font_collection + .set_dynamic_font_manager(FontMgr::from(self.font_provider.clone())); + } +} diff --git a/render-wasm/src/render/text.rs b/render-wasm/src/render/text.rs index 563775f9c0..c494dcb9dc 100644 --- a/render-wasm/src/render/text.rs +++ b/render-wasm/src/render/text.rs @@ -3,8 +3,9 @@ use crate::shapes::TextContent; pub fn render(render_state: &mut RenderState, text: &TextContent) { let mut offset_y = 0.0; - for mut skia_paragraph in text.to_paragraphs(&render_state.font_collection) { + for mut skia_paragraph in text.to_paragraphs(&render_state.fonts().font_collection()) { skia_paragraph.layout(text.width()); + let xy = (text.x(), text.y() + offset_y); skia_paragraph.paint(render_state.surfaces.canvas(SurfaceId::Fills), xy); offset_y += skia_paragraph.height(); diff --git a/render-wasm/src/shapes.rs b/render-wasm/src/shapes.rs index 02dbcb7100..fd21ccc496 100644 --- a/render-wasm/src/shapes.rs +++ b/render-wasm/src/shapes.rs @@ -9,6 +9,7 @@ mod blurs; mod bools; mod corners; mod fills; +mod fonts; mod frames; mod groups; mod layouts; @@ -25,6 +26,7 @@ pub use blurs::*; pub use bools::*; pub use corners::*; pub use fills::*; +pub use fonts::*; pub use frames::*; pub use groups::*; pub use layouts::*; @@ -588,10 +590,15 @@ impl Shape { } } - pub fn add_text_leaf(&mut self, text_str: &str) -> Result<(), String> { + pub fn add_text_leaf( + &mut self, + text_str: String, + font_family: FontFamily, + font_size: f32, + ) -> Result<(), String> { match self.shape_type { Type::Text(ref mut text) => { - text.add_leaf(text_str)?; + text.add_leaf(text_str, font_family, font_size)?; Ok(()) } _ => Err("Shape is not a text".to_string()), diff --git a/render-wasm/src/shapes/fonts.rs b/render-wasm/src/shapes/fonts.rs new file mode 100644 index 0000000000..58104af062 --- /dev/null +++ b/render-wasm/src/shapes/fonts.rs @@ -0,0 +1,48 @@ +use std::fmt; + +use uuid::Uuid; + +#[derive(Debug, PartialEq, Clone, Copy)] +pub enum FontStyle { + Normal, + Italic, +} + +impl From for FontStyle { + fn from(value: u8) -> Self { + match value { + 0 => Self::Normal, + 1 => Self::Italic, + _ => Self::Normal, + } + } +} + +impl fmt::Display for FontStyle { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let txt = match self { + Self::Normal => "normal", + Self::Italic => "italic", + }; + write!(f, "{}", txt) + } +} + +#[derive(Debug, Clone, PartialEq, Copy)] +pub struct FontFamily { + id: Uuid, + style: FontStyle, + weight: u32, +} + +impl FontFamily { + pub fn new(id: Uuid, weight: u32, style: FontStyle) -> Self { + Self { id, style, weight } + } +} + +impl fmt::Display for FontFamily { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{} {} {}", self.id, self.weight, self.style) + } +} diff --git a/render-wasm/src/shapes/text.rs b/render-wasm/src/shapes/text.rs index 69abfd4ed4..5e204716cd 100644 --- a/render-wasm/src/shapes/text.rs +++ b/render-wasm/src/shapes/text.rs @@ -4,6 +4,8 @@ use skia_safe::{ textlayout::{FontCollection, ParagraphBuilder}, }; +use super::FontFamily; + #[derive(Debug, PartialEq, Clone)] pub struct TextContent { paragraphs: Vec, @@ -38,15 +40,18 @@ impl TextContent { self.paragraphs.push(p); } - pub fn add_leaf(&mut self, text: &str) -> Result<(), String> { + pub fn add_leaf( + &mut self, + text: String, + font_family: FontFamily, + font_size: f32, + ) -> Result<(), String> { let paragraph = self .paragraphs .last_mut() .ok_or("No paragraph to add text leaf to")?; - paragraph.add_leaf(TextLeaf { - text: text.to_owned(), - }); + paragraph.add_leaf(TextLeaf::new(text, font_family, font_size)); Ok(()) } @@ -100,15 +105,29 @@ impl Paragraph { #[derive(Debug, PartialEq, Clone)] pub struct TextLeaf { text: String, + font_family: FontFamily, + font_size: f32, } impl TextLeaf { + pub fn new(text: String, font_family: FontFamily, font_size: f32) -> Self { + Self { + text, + font_family, + font_size, + } + } + pub fn to_style(&self) -> skia::textlayout::TextStyle { let mut style = skia::textlayout::TextStyle::default(); - // TODO: read text style info from the shape style.set_color(skia::Color::BLACK); - style.set_font_size(16.0); + style.set_font_size(self.font_size); + style.set_font_families(&[self.serialized_font_family()]); style } + + fn serialized_font_family(&self) -> String { + format!("{}", self.font_family) + } } diff --git a/render-wasm/src/wasm.rs b/render-wasm/src/wasm.rs index 481c63accf..31ae8a7c1d 100644 --- a/render-wasm/src/wasm.rs +++ b/render-wasm/src/wasm.rs @@ -1 +1,2 @@ +pub mod fonts; pub mod text; diff --git a/render-wasm/src/wasm/fonts.rs b/render-wasm/src/wasm/fonts.rs new file mode 100644 index 0000000000..205830bdc7 --- /dev/null +++ b/render-wasm/src/wasm/fonts.rs @@ -0,0 +1,35 @@ +use crate::mem; +use crate::utils::uuid_from_u32_quartet; +use crate::with_state; +use crate::STATE; + +use crate::shapes::FontFamily; + +#[no_mangle] +pub extern "C" fn store_font(a: u32, b: u32, c: u32, d: u32, weight: u32, style: u8) { + with_state!(state, { + let id = uuid_from_u32_quartet(a, b, c, d); + let font_bytes = mem::bytes(); + + let family = FontFamily::new(id, weight, style.into()); + + let res = state.render_state().fonts_mut().add(family, &font_bytes); + match res { + Err(msg) => { + eprintln!("{}", msg); + } + _ => {} + } + }); +} + +#[no_mangle] +pub extern "C" fn is_font_uploaded(a: u32, b: u32, c: u32, d: u32, weight: u32, style: u8) -> bool { + with_state!(state, { + let id = uuid_from_u32_quartet(a, b, c, d); + let family = FontFamily::new(id, weight, style.into()); + let res = state.render_state().fonts().has_family(&family); + + return res; + }); +} diff --git a/render-wasm/src/wasm/text.rs b/render-wasm/src/wasm/text.rs index 43869f2c4c..8ab9062ea0 100644 --- a/render-wasm/src/wasm/text.rs +++ b/render-wasm/src/wasm/text.rs @@ -1,4 +1,6 @@ use crate::mem; +use crate::shapes::FontFamily; +use crate::utils::uuid_from_u32_quartet; use crate::with_current_shape; use crate::STATE; @@ -20,14 +22,25 @@ pub extern "C" fn add_text_paragraph() { } #[no_mangle] -pub extern "C" fn add_text_leaf() { +pub extern "C" fn add_text_leaf( + a: u32, + b: u32, + c: u32, + d: u32, + weight: u32, + style: u8, + font_size: f32, +) { + let font_id = uuid_from_u32_quartet(a, b, c, d); + let font_family = FontFamily::new(font_id, weight, style.into()); let bytes = mem::bytes(); + let text = unsafe { String::from_utf8_unchecked(bytes) // TODO: handle this error }; with_current_shape!(state, |shape: &mut Shape| { - let res = shape.add_text_leaf(&text); + let res = shape.add_text_leaf(text, font_family, font_size); if let Err(err) = res { eprintln!("{}", err); }