mirror of
https://github.com/penpot/penpot.git
synced 2025-07-25 16:57:20 +02:00
✨ Load emoji font dynamically when initializing
This commit is contained in:
parent
56ecacee21
commit
2306df5fb7
6 changed files with 80 additions and 48 deletions
|
@ -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
|
||||||
|
|
|
@ -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]
|
||||||
|
|
|
@ -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}))
|
|
@ -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)))
|
||||||
|
|
|
@ -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(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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);
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue