Load emoji font dynamically when initializing

This commit is contained in:
Elena Torro 2025-04-25 13:40:17 +02:00
parent 56ecacee21
commit 2306df5fb7
6 changed files with 80 additions and 48 deletions

View file

@ -283,6 +283,7 @@
;; canvas, even though we are not using `page-id` inside the hook. ;; 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 ;; We think moving this out to a handler will make the render code
;; harder to follow through. ;; harder to follow through.
(mf/with-effect [page-id] (mf/with-effect [page-id]
(when-let [canvas (mf/ref-val canvas-ref)] (when-let [canvas (mf/ref-val canvas-ref)]
(->> wasm.api/module (->> wasm.api/module

View file

@ -99,8 +99,6 @@
(h/call wasm/internal-module "_render" timestamp) (h/call wasm/internal-module "_render" timestamp)
(set! wasm/internal-frame-id nil)) (set! wasm/internal-frame-id nil))
(defn cancel-render (defn cancel-render
[_] [_]
(when wasm/internal-frame-id (when wasm/internal-frame-id
@ -606,15 +604,22 @@
(h/call wasm/internal-module "_clear_shape_text") (h/call wasm/internal-module "_clear_shape_text")
(let [paragraph-set (first (dm/get-prop content :children)) (let [paragraph-set (first (dm/get-prop content :children))
paragraphs (dm/get-prop paragraph-set :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] (loop [index 0]
(when (< index (count paragraphs)) (when (< index (count paragraphs))
(let [paragraph (nth paragraphs index) (let [paragraph (nth paragraphs index)
leaves (dm/get-prop paragraph :children)] leaves (dm/get-prop paragraph :children)]
(when (seq leaves) (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)))))) (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 (defn set-view-box
[zoom vbox] [zoom vbox]

View file

@ -77,7 +77,7 @@
;; IMPORTANT: It should be noted that only TTF fonts can be stored. ;; IMPORTANT: It should be noted that only TTF fonts can be stored.
(defn- store-font-buffer (defn- store-font-buffer
[font-data font-array-buffer] [font-data font-array-buffer emoji?]
(let [id-buffer (:family-id-buffer font-data) (let [id-buffer (:family-id-buffer font-data)
size (.-byteLength font-array-buffer) size (.-byteLength font-array-buffer)
ptr (h/call wasm/internal-module "_alloc_bytes" size) ptr (h/call wasm/internal-module "_alloc_bytes" size)
@ -90,17 +90,18 @@
(aget id-buffer 2) (aget id-buffer 2)
(aget id-buffer 3) (aget id-buffer 3)
(:weight font-data) (:weight font-data)
(:style font-data)) (:style font-data)
emoji?)
true)) true))
(defn- store-font-url (defn- store-font-url
[font-data font-url] [font-data font-url emoji?]
(->> (http/send! {:method :get (->> (http/send! {:method :get
:uri font-url :uri font-url
:response-type :blob}) :response-type :blob})
(rx/map :body) (rx/map :body)
(rx/mapcat wapi/read-file-as-array-buffer) (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 (defn- google-font-ttf-url
[font-id font-variant-id] [font-id font-variant-id]
@ -120,7 +121,7 @@
(dm/str (u/join cf/public-uri "fonts/" asset-id)))) (dm/str (u/join cf/public-uri "fonts/" asset-id))))
(defn- store-font-id (defn- store-font-id
[font-data asset-id] [font-data asset-id emoji?]
(when asset-id (when asset-id
(let [uri (font-id->ttf-url (:font-id font-data) asset-id (:font-variant-id font-data)) (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)) id-buffer (uuid/get-u32 (:wasm-id font-data))
@ -132,7 +133,7 @@
(aget id-buffer 3) (aget id-buffer 3)
(:weight font-data) (:weight font-data)
(:style 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 (defn serialize-font-style
[font-style] [font-style]
@ -155,24 +156,36 @@
[font-weight] [font-weight]
(js/Number 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 (defn store-fonts
[fonts] [fonts]
(keep (fn [font] (keep (fn [font] (store-font font)) fonts))
(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}]
(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}))

View file

@ -20,13 +20,12 @@
(defn write-shape-text (defn write-shape-text
;; buffer has the following format: ;; buffer has the following format:
;; [<num-leaves> <paragraph_attributes> <leaves_attributes> <text>] ;; [<num-leaves> <paragraph_attributes> <leaves_attributes> <text>]
[leaves paragraph] [leaves paragraph text]
(let [leaves (filter #(not (str/blank? (:text %))) leaves) (let [leaves (filter #(not (str/blank? (:text %))) leaves)
num-leaves (count leaves) num-leaves (count leaves)
paragraph-attr-size 48 paragraph-attr-size 48
leaf-attr-size 52 leaf-attr-size 52
metadata-size (+ 1 paragraph-attr-size (* num-leaves leaf-attr-size)) metadata-size (+ 1 paragraph-attr-size (* num-leaves leaf-attr-size))
text (apply str (map :text leaves))
text-buffer (utf8->buffer text) text-buffer (utf8->buffer text)
text-size (.-byteLength text-buffer) text-size (.-byteLength text-buffer)
buffer (js/ArrayBuffer. (+ metadata-size text-size)) buffer (js/ArrayBuffer. (+ metadata-size text-size))
@ -106,3 +105,8 @@
(.set heap (js/Uint8Array. buffer) metadata-offset))) (.set heap (js/Uint8Array. buffer) metadata-offset)))
(h/call wasm/internal-module "_set_shape_text_content")) (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)))

View file

@ -3,7 +3,6 @@ use skia_safe::{self as skia, textlayout, Font, FontMgr};
use crate::shapes::{FontFamily, FontStyle}; use crate::shapes::{FontFamily, FontStyle};
use crate::uuid::Uuid; 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"; pub static DEFAULT_EMOJI_FONT: &'static str = "noto-color-emoji";
const DEFAULT_FONT_BYTES: &[u8] = include_bytes!("../fonts/sourcesanspro-regular.ttf"); const DEFAULT_FONT_BYTES: &[u8] = include_bytes!("../fonts/sourcesanspro-regular.ttf");
@ -27,15 +26,7 @@ pub struct FontStore {
impl FontStore { impl FontStore {
pub fn new() -> Self { pub fn new() -> Self {
let font_mgr = FontMgr::new(); let font_mgr = FontMgr::new();
let font_provider = load_default_provider(&font_mgr);
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 mut font_collection = skia::textlayout::FontCollection::new(); let mut font_collection = skia::textlayout::FontCollection::new();
font_collection.set_default_font_manager(FontMgr::from(font_provider.clone()), None); font_collection.set_default_font_manager(FontMgr::from(font_provider.clone()), None);
@ -65,22 +56,30 @@ impl FontStore {
&self.debug_font &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) { if self.has_family(&family) {
return Ok(()); return Ok(());
} }
let alias = format!("{}", family);
let typeface = self let typeface = self
.font_mgr .font_mgr
.new_from_data(font_data, None) .new_from_data(font_data, None)
.ok_or("Failed to create typeface")?; .ok_or("Failed to create typeface")?;
self.font_provider let alias = format!("{}", family);
.register_typeface(typeface, alias.as_str()); 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(); self.font_collection.clear_caches();
Ok(()) Ok(())
} }

View file

@ -6,14 +6,24 @@ use crate::STATE;
use crate::shapes::FontFamily; use crate::shapes::FontFamily;
#[no_mangle] #[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, { with_state!(state, {
let id = uuid_from_u32_quartet(a, b, c, d); let id = uuid_from_u32_quartet(a, b, c, d);
let font_bytes = mem::bytes(); let font_bytes = mem::bytes();
let family = FontFamily::new(id, weight, style.into()); 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 { match res {
Err(msg) => { Err(msg) => {
eprintln!("{}", msg); eprintln!("{}", msg);