diff --git a/frontend/src/app/main/ui/workspace/viewport_wasm.cljs b/frontend/src/app/main/ui/workspace/viewport_wasm.cljs index b07e3893dd..5830dcad06 100644 --- a/frontend/src/app/main/ui/workspace/viewport_wasm.cljs +++ b/frontend/src/app/main/ui/workspace/viewport_wasm.cljs @@ -283,6 +283,7 @@ ;; canvas, even though we are not using `page-id` inside the hook. ;; We think moving this out to a handler will make the render code ;; harder to follow through. + (mf/with-effect [page-id] (when-let [canvas (mf/ref-val canvas-ref)] (->> wasm.api/module diff --git a/frontend/src/app/render_wasm/api.cljs b/frontend/src/app/render_wasm/api.cljs index e28cc6c77e..caff9874e4 100644 --- a/frontend/src/app/render_wasm/api.cljs +++ b/frontend/src/app/render_wasm/api.cljs @@ -99,8 +99,6 @@ (h/call wasm/internal-module "_render" timestamp) (set! wasm/internal-frame-id nil)) - - (defn cancel-render [_] (when wasm/internal-frame-id @@ -606,15 +604,22 @@ (h/call wasm/internal-module "_clear_shape_text") (let [paragraph-set (first (dm/get-prop content :children)) paragraphs (dm/get-prop paragraph-set :children) - fonts (fonts/get-content-fonts content)] + fonts (fonts/get-content-fonts content) + emoji? (atom false)] (loop [index 0] (when (< index (count paragraphs)) (let [paragraph (nth paragraphs index) leaves (dm/get-prop paragraph :children)] (when (seq leaves) - (t/write-shape-text leaves paragraph) + (let [text (apply str (map :text leaves))] + (when (and (not @emoji?) (t/contains-emoji? text)) + (reset! emoji? true)) + (t/write-shape-text leaves paragraph text)) (recur (inc index)))))) - (f/store-fonts fonts))) + (let [fonts (if @emoji? + (f/add-emoji-font fonts) + fonts)] + (f/store-fonts fonts)))) (defn set-view-box [zoom vbox] diff --git a/frontend/src/app/render_wasm/api/fonts.cljs b/frontend/src/app/render_wasm/api/fonts.cljs index 1caf62202b..acb323e598 100644 --- a/frontend/src/app/render_wasm/api/fonts.cljs +++ b/frontend/src/app/render_wasm/api/fonts.cljs @@ -77,7 +77,7 @@ ;; IMPORTANT: It should be noted that only TTF fonts can be stored. (defn- store-font-buffer - [font-data font-array-buffer] + [font-data font-array-buffer emoji?] (let [id-buffer (:family-id-buffer font-data) size (.-byteLength font-array-buffer) ptr (h/call wasm/internal-module "_alloc_bytes" size) @@ -90,17 +90,18 @@ (aget id-buffer 2) (aget id-buffer 3) (:weight font-data) - (:style font-data)) + (:style font-data) + emoji?) true)) (defn- store-font-url - [font-data font-url] + [font-data font-url emoji?] (->> (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))))) + (rx/map (fn [array-buffer] (store-font-buffer font-data array-buffer emoji?))))) (defn- google-font-ttf-url [font-id font-variant-id] @@ -120,7 +121,7 @@ (dm/str (u/join cf/public-uri "fonts/" asset-id)))) (defn- store-font-id - [font-data asset-id] + [font-data asset-id emoji?] (when asset-id (let [uri (font-id->ttf-url (:font-id font-data) asset-id (:font-variant-id font-data)) id-buffer (uuid/get-u32 (:wasm-id font-data)) @@ -132,7 +133,7 @@ (aget id-buffer 3) (:weight font-data) (:style font-data)))] - (when-not font-stored? (store-font-url font-data uri))))) + (when-not font-stored? (store-font-url font-data uri emoji?))))) (defn serialize-font-style [font-style] @@ -155,24 +156,36 @@ [font-weight] (js/Number font-weight)) +(defn store-font + [font] + (let [font-id (dm/get-prop font :font-id) + font-variant-id (dm/get-prop font :font-variant-id) + wasm-id (font-id->uuid font-id) + raw-weight (or (:weight (font-db-data font-id font-variant-id)) 400) + + weight (serialize-font-weight raw-weight) + + style (serialize-font-style (cond + (str/includes? font-variant-id "italic") "italic" + :else "normal")) + asset-id (font-id->asset-id font-id font-variant-id) + font-data {:wasm-id wasm-id + :font-id font-id + :font-variant-id font-variant-id + :style style + :weight weight} + emoji? (dm/get-prop font :emoji?)] + (store-font-id font-data asset-id emoji?))) + (defn store-fonts [fonts] - (keep (fn [font] - (let [font-id (dm/get-prop font :font-id) - font-variant-id (dm/get-prop font :font-variant-id) - wasm-id (font-id->uuid font-id) - raw-weight (or (:weight (font-db-data font-id font-variant-id)) 400) + (keep (fn [font] (store-font font)) fonts)) - weight (serialize-font-weight raw-weight) - - style (serialize-font-style (cond - (str/includes? font-variant-id "italic") "italic" - :else "normal")) - asset-id (font-id->asset-id font-id font-variant-id) - font-data {:wasm-id wasm-id - :font-id font-id - :font-variant-id font-variant-id - :style style - :weight weight}] - (store-font-id font-data asset-id))) fonts)) +(defn add-emoji-font + [fonts] + (conj fonts {:font-id "gfont-noto-color-emoji" + :font-variant-id "regular" + :style 0 + :weight 400 + :emoji? true})) \ No newline at end of file diff --git a/frontend/src/app/render_wasm/api/texts.cljs b/frontend/src/app/render_wasm/api/texts.cljs index 69c790670f..a70d5019b3 100644 --- a/frontend/src/app/render_wasm/api/texts.cljs +++ b/frontend/src/app/render_wasm/api/texts.cljs @@ -20,13 +20,12 @@ (defn write-shape-text ;; buffer has the following format: ;; [ ] - [leaves paragraph] + [leaves paragraph text] (let [leaves (filter #(not (str/blank? (:text %))) leaves) num-leaves (count leaves) paragraph-attr-size 48 leaf-attr-size 52 metadata-size (+ 1 paragraph-attr-size (* num-leaves leaf-attr-size)) - text (apply str (map :text leaves)) text-buffer (utf8->buffer text) text-size (.-byteLength text-buffer) buffer (js/ArrayBuffer. (+ metadata-size text-size)) @@ -106,3 +105,8 @@ (.set heap (js/Uint8Array. buffer) metadata-offset))) (h/call wasm/internal-module "_set_shape_text_content")) + +(def emoji-pattern #"[\uD83C-\uDBFF][\uDC00-\uDFFF]") + +(defn contains-emoji? [s] + (boolean (re-find emoji-pattern s))) diff --git a/render-wasm/src/render/fonts.rs b/render-wasm/src/render/fonts.rs index dd8d24f2b3..7738ca6118 100644 --- a/render-wasm/src/render/fonts.rs +++ b/render-wasm/src/render/fonts.rs @@ -3,7 +3,6 @@ use skia_safe::{self as skia, textlayout, Font, FontMgr}; use crate::shapes::{FontFamily, FontStyle}; use crate::uuid::Uuid; -const EMOJI_FONT_BYTES: &[u8] = include_bytes!("../fonts/NotoColorEmoji-Regular.ttf"); pub static DEFAULT_EMOJI_FONT: &'static str = "noto-color-emoji"; const DEFAULT_FONT_BYTES: &[u8] = include_bytes!("../fonts/sourcesanspro-regular.ttf"); @@ -27,15 +26,7 @@ pub struct FontStore { impl FontStore { pub fn new() -> Self { let font_mgr = FontMgr::new(); - - let mut font_provider = load_default_provider(&font_mgr); - - // TODO: Load emoji font lazily - let emoji_font = font_mgr - .new_from_data(EMOJI_FONT_BYTES, None) - .expect("Failed to load font"); - font_provider.register_typeface(emoji_font, DEFAULT_EMOJI_FONT); - + let font_provider = load_default_provider(&font_mgr); let mut font_collection = skia::textlayout::FontCollection::new(); font_collection.set_default_font_manager(FontMgr::from(font_provider.clone()), None); @@ -65,22 +56,30 @@ impl FontStore { &self.debug_font } - pub fn add(&mut self, family: FontFamily, font_data: &[u8]) -> Result<(), String> { + pub fn add( + &mut self, + family: FontFamily, + font_data: &[u8], + is_emoji: bool, + ) -> Result<(), String> { if self.has_family(&family) { return Ok(()); } - let alias = format!("{}", family); let typeface = self .font_mgr .new_from_data(font_data, None) .ok_or("Failed to create typeface")?; - self.font_provider - .register_typeface(typeface, alias.as_str()); + let alias = format!("{}", family); + let font_name = if is_emoji { + DEFAULT_EMOJI_FONT + } else { + alias.as_str() + }; + self.font_provider.register_typeface(typeface, font_name); self.font_collection.clear_caches(); - Ok(()) } diff --git a/render-wasm/src/wasm/fonts.rs b/render-wasm/src/wasm/fonts.rs index 205830bdc7..8de9a12d40 100644 --- a/render-wasm/src/wasm/fonts.rs +++ b/render-wasm/src/wasm/fonts.rs @@ -6,14 +6,24 @@ 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) { +pub extern "C" fn store_font( + a: u32, + b: u32, + c: u32, + d: u32, + weight: u32, + style: u8, + is_emoji: bool, +) { 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, is_emoji); - let res = state.render_state().fonts_mut().add(family, &font_bytes); match res { Err(msg) => { eprintln!("{}", msg);