mirror of
https://github.com/penpot/penpot.git
synced 2025-05-25 22:36:12 +02:00
🎉 Store custom fonts (ttfs) and use them to write texts (wasm) (#6050)
This commit is contained in:
parent
e4c9b736f7
commit
eb6d2fb0eb
14 changed files with 386 additions and 140 deletions
|
@ -8,6 +8,7 @@
|
||||||
"A WASM based render API"
|
"A WASM based render API"
|
||||||
(:require
|
(:require
|
||||||
["react-dom/server" :as rds]
|
["react-dom/server" :as rds]
|
||||||
|
[app.common.data :as d]
|
||||||
[app.common.data.macros :as dm]
|
[app.common.data.macros :as dm]
|
||||||
[app.common.geom.matrix :as gmt]
|
[app.common.geom.matrix :as gmt]
|
||||||
[app.common.math :as mth]
|
[app.common.math :as mth]
|
||||||
|
@ -17,6 +18,8 @@
|
||||||
[app.config :as cf]
|
[app.config :as cf]
|
||||||
[app.main.refs :as refs]
|
[app.main.refs :as refs]
|
||||||
[app.main.render :as render]
|
[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.render-wasm.helpers :as h]
|
||||||
[app.util.debug :as dbg]
|
[app.util.debug :as dbg]
|
||||||
[app.util.http :as http]
|
[app.util.http :as http]
|
||||||
|
@ -24,6 +27,8 @@
|
||||||
[beicon.v2.core :as rx]
|
[beicon.v2.core :as rx]
|
||||||
[cuerdas.core :as str]
|
[cuerdas.core :as str]
|
||||||
[goog.object :as gobj]
|
[goog.object :as gobj]
|
||||||
|
[lambdaisland.uri :as u]
|
||||||
|
[okulary.core :as l]
|
||||||
[promesa.core :as p]
|
[promesa.core :as p]
|
||||||
[rumext.v2 :as mf]))
|
[rumext.v2 :as mf]))
|
||||||
|
|
||||||
|
@ -165,35 +170,50 @@
|
||||||
(defn- get-string-length [string] (+ (count string) 1))
|
(defn- get-string-length [string] (+ (count string) 1))
|
||||||
|
|
||||||
;; IMPORTANT: It should be noted that only TTF fonts can be stored.
|
;; IMPORTANT: It should be noted that only TTF fonts can be stored.
|
||||||
;; Do not remove, this is going to be useful
|
(defn- store-font-buffer
|
||||||
;; when we implement text rendering.
|
[font-data font-array-buffer]
|
||||||
#_(defn- store-font
|
(let [id-buffer (:family-id-buffer font-data)
|
||||||
[family-name font-array-buffer]
|
size (.-byteLength font-array-buffer)
|
||||||
(let [family-name-size (get-string-length family-name)
|
ptr (h/call internal-module "_alloc_bytes" size)
|
||||||
font-array-buffer-size (.-byteLength font-array-buffer)
|
heap (gobj/get ^js internal-module "HEAPU8")
|
||||||
size (+ font-array-buffer-size family-name-size)
|
mem (js/Uint8Array. (.-buffer heap) ptr size)]
|
||||||
ptr (h/call internal-module "_alloc_bytes" size)
|
(.set mem (js/Uint8Array. font-array-buffer))
|
||||||
family-name-ptr (+ ptr font-array-buffer-size)
|
(h/call internal-module "_store_font"
|
||||||
heap (gobj/get ^js internal-module "HEAPU8")
|
(aget id-buffer 0)
|
||||||
mem (js/Uint8Array. (.-buffer heap) ptr size)]
|
(aget id-buffer 1)
|
||||||
(.set mem (js/Uint8Array. font-array-buffer))
|
(aget id-buffer 2)
|
||||||
(h/call internal-module "stringToUTF8" family-name family-name-ptr family-name-size)
|
(aget id-buffer 3)
|
||||||
(h/call internal-module "_store_font" family-name-size font-array-buffer-size)))
|
(:weight font-data)
|
||||||
|
(:style font-data))
|
||||||
|
true))
|
||||||
|
|
||||||
;; This doesn't work
|
(defn- store-font-url
|
||||||
#_(store-font-url "roboto-thin-italic" "https://fonts.gstatic.com/s/roboto/v32/KFOiCnqEu92Fr1Mu51QrEzAdLw.woff2")
|
[font-data font-url]
|
||||||
;; This does
|
(->> (http/send! {:method :get
|
||||||
#_(store-font-url "sourcesanspro-regular" "http://localhost:3449/fonts/sourcesanspro-regular.ttf")
|
:uri font-url
|
||||||
;; Do not remove, this is going to be useful
|
:response-type :blob})
|
||||||
;; when we implement text rendering.
|
(rx/map :body)
|
||||||
#_(defn- store-font-url
|
(rx/mapcat wapi/read-file-as-array-buffer)
|
||||||
[family-name font-url]
|
(rx/map (fn [array-buffer] (store-font-buffer font-data array-buffer)))))
|
||||||
(-> (p/then (js/fetch font-url)
|
|
||||||
(fn [response] (.arrayBuffer response)))
|
(defn- store-font-id
|
||||||
(p/then (fn [array-buffer] (store-font family-name array-buffer)))))
|
[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
|
(defn- store-image
|
||||||
[id]
|
[id]
|
||||||
|
|
||||||
(let [buffer (uuid/get-u32 id)
|
(let [buffer (uuid/get-u32 id)
|
||||||
url (cf/resolve-file-media {:id id})]
|
url (cf/resolve-file-media {:id id})]
|
||||||
(->> (http/send! {:method :get
|
(->> (http/send! {:method :get
|
||||||
|
@ -663,6 +683,55 @@
|
||||||
(let [encoder (js/TextEncoder.)]
|
(let [encoder (js/TextEncoder.)]
|
||||||
(.encode encoder text)))
|
(.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]
|
(defn set-shape-text-content [content]
|
||||||
(h/call internal-module "_clear_shape_text")
|
(h/call internal-module "_clear_shape_text")
|
||||||
(let [paragraph-set (first (dm/get-prop content :children))
|
(let [paragraph-set (first (dm/get-prop content :children))
|
||||||
|
@ -677,17 +746,8 @@
|
||||||
(h/call internal-module "_add_text_paragraph")
|
(h/call internal-module "_add_text_paragraph")
|
||||||
(loop [index-leaves 0]
|
(loop [index-leaves 0]
|
||||||
(when (< index-leaves total-leaves)
|
(when (< index-leaves total-leaves)
|
||||||
(let [leaf (nth leaves index-leaves)
|
(let [leaf (nth leaves index-leaves)]
|
||||||
text (dm/get-prop leaf :text)
|
(add-text-leaf leaf)
|
||||||
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")
|
|
||||||
(recur (inc index-leaves))))))
|
(recur (inc index-leaves))))))
|
||||||
(recur (inc index))))))
|
(recur (inc index))))))
|
||||||
|
|
||||||
|
@ -699,8 +759,34 @@
|
||||||
(defn clear-cache []
|
(defn clear-cache []
|
||||||
(h/call internal-module "_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
|
(defn set-objects
|
||||||
[objects]
|
[objects]
|
||||||
|
(set-fonts objects)
|
||||||
(let [shapes (into [] (vals objects))
|
(let [shapes (into [] (vals objects))
|
||||||
total-shapes (count shapes)
|
total-shapes (count shapes)
|
||||||
pending
|
pending
|
||||||
|
|
|
@ -4,19 +4,18 @@
|
||||||
|
|
||||||
Shape types are serialized as `u8`:
|
Shape types are serialized as `u8`:
|
||||||
|
|
||||||
| Value | Field |
|
| Value | Field |
|
||||||
| ----- | ---------- |
|
| ----- | ------ |
|
||||||
| 0 | Frame |
|
| 0 | Frame |
|
||||||
| 1 | Group |
|
| 1 | Group |
|
||||||
| 2 | Bool |
|
| 2 | Bool |
|
||||||
| 3 | Rect |
|
| 3 | Rect |
|
||||||
| 4 | Path |
|
| 4 | Path |
|
||||||
| 5 | Text |
|
| 5 | Text |
|
||||||
| 6 | Circle |
|
| 6 | Circle |
|
||||||
| 7 | SvgRaw |
|
| 7 | SvgRaw |
|
||||||
| 8 | Image |
|
| 8 | Image |
|
||||||
| \_ | Rect |
|
| \_ | Rect |
|
||||||
|
|
||||||
|
|
||||||
## Horizontal Constraint
|
## Horizontal Constraint
|
||||||
|
|
||||||
|
@ -31,7 +30,6 @@ Horizontal constraints are serialized as `u8`:
|
||||||
| 4 | Scale |
|
| 4 | Scale |
|
||||||
| \_ | None |
|
| \_ | None |
|
||||||
|
|
||||||
|
|
||||||
## Vertical Constraint
|
## Vertical Constraint
|
||||||
|
|
||||||
Vertical constraints are serialized as `u8`:
|
Vertical constraints are serialized as `u8`:
|
||||||
|
@ -45,7 +43,6 @@ Vertical constraints are serialized as `u8`:
|
||||||
| 4 | Scale |
|
| 4 | Scale |
|
||||||
| \_ | None |
|
| \_ | None |
|
||||||
|
|
||||||
|
|
||||||
## Paths
|
## Paths
|
||||||
|
|
||||||
Paths are made of segments of **28 bytes** each. The layout (assuming positions in a `Uint8Array`) is the following:
|
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 |
|
| 1 | Inner Shadow |
|
||||||
| \_ | Drop Shadow |
|
| \_ | Drop Shadow |
|
||||||
|
|
||||||
## Layout - Direction
|
## Layout
|
||||||
|
|
||||||
|
### Direction
|
||||||
|
|
||||||
| Value | Field |
|
| Value | Field |
|
||||||
| ----- | --------------|
|
| ----- | ------------- |
|
||||||
| 0 | Row |
|
| 0 | Row |
|
||||||
| 1 | RowReverse |
|
| 1 | RowReverse |
|
||||||
| 2 | Column |
|
| 2 | Column |
|
||||||
| 3 | ColumnReverse |
|
| 3 | ColumnReverse |
|
||||||
| \_ | error |
|
| \_ | error |
|
||||||
|
|
||||||
## Layout - Align Items
|
### Align Items
|
||||||
|
|
||||||
| Value | Field |
|
| Value | Field |
|
||||||
| ----- | --------|
|
| ----- | ------- |
|
||||||
| 0 | Start |
|
| 0 | Start |
|
||||||
| 1 | End |
|
| 1 | End |
|
||||||
| 2 | Center |
|
| 2 | Center |
|
||||||
| 3 | Stretch |
|
| 3 | Stretch |
|
||||||
| \_ | error |
|
| \_ | error |
|
||||||
|
|
||||||
## Layout - Align Content
|
### Align Content
|
||||||
|
|
||||||
| Value | Field |
|
| Value | Field |
|
||||||
| ----- | ------------- |
|
| ----- | ------------- |
|
||||||
| 0 | Start |
|
| 0 | Start |
|
||||||
| 1 | End |
|
| 1 | End |
|
||||||
| 2 | Center |
|
| 2 | Center |
|
||||||
| 3 | Space between |
|
| 3 | Space between |
|
||||||
| 4 | Space around |
|
| 4 | Space around |
|
||||||
| 5 | Space evenly |
|
| 5 | Space evenly |
|
||||||
| 6 | Stretch |
|
| 6 | Stretch |
|
||||||
| \_ | error |
|
| \_ | error |
|
||||||
|
|
||||||
## Layout - Justify items
|
### Justify items
|
||||||
|
|
||||||
| Value | Field |
|
| Value | Field |
|
||||||
| ----- | --------|
|
| ----- | ------- |
|
||||||
| 0 | Start |
|
| 0 | Start |
|
||||||
| 1 | End |
|
| 1 | End |
|
||||||
| 2 | Center |
|
| 2 | Center |
|
||||||
| 3 | Stretch |
|
| 3 | Stretch |
|
||||||
| \_ | error |
|
| \_ | error |
|
||||||
|
|
||||||
## Layout - Justify content
|
### Justify content
|
||||||
|
|
||||||
|
|
||||||
| Value | Field |
|
| Value | Field |
|
||||||
| ----- | ------------- |
|
| ----- | ------------- |
|
||||||
| 0 | Start |
|
| 0 | Start |
|
||||||
| 1 | End |
|
| 1 | End |
|
||||||
| 2 | Center |
|
| 2 | Center |
|
||||||
| 3 | Space between |
|
| 3 | Space between |
|
||||||
| 4 | Space around |
|
| 4 | Space around |
|
||||||
| 5 | Space evenly |
|
| 5 | Space evenly |
|
||||||
| 6 | Stretch |
|
| 6 | Stretch |
|
||||||
| \_ | error |
|
| \_ | error |
|
||||||
|
|
||||||
## Layout - Wrap type
|
### Wrap type
|
||||||
|
|
||||||
| Value | Field |
|
| Value | Field |
|
||||||
| ----- | ------- |
|
| ----- | ------- |
|
||||||
|
@ -208,11 +206,21 @@ Shadow styles are serialized as `u8`:
|
||||||
| 1 | No Wrap |
|
| 1 | No Wrap |
|
||||||
| \_ | error |
|
| \_ | error |
|
||||||
|
|
||||||
## Layout - Sizing
|
### Sizing
|
||||||
|
|
||||||
| Value | Field |
|
| Value | Field |
|
||||||
| ----- | ------|
|
| ----- | ----- |
|
||||||
| 0 | Fill |
|
| 0 | Fill |
|
||||||
| 1 | Fix |
|
| 1 | Fix |
|
||||||
| 2 | Auto |
|
| 2 | Auto |
|
||||||
| \_ | error |
|
| \_ | error |
|
||||||
|
|
||||||
|
## Font
|
||||||
|
|
||||||
|
### Style
|
||||||
|
|
||||||
|
| Value | Variant |
|
||||||
|
| ----- | ------- |
|
||||||
|
| 0 | Normal |
|
||||||
|
| 1 | Italic |
|
||||||
|
| \_ | Normal |
|
||||||
|
|
|
@ -293,31 +293,6 @@ pub extern "C" fn add_shape_fill_stops() {
|
||||||
mem::free_bytes();
|
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::<u8>::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]
|
#[no_mangle]
|
||||||
pub extern "C" fn store_image(a: u32, b: u32, c: u32, d: u32) {
|
pub extern "C" fn store_image(a: u32, b: u32, c: u32, d: u32) {
|
||||||
with_state!(state, {
|
with_state!(state, {
|
||||||
|
|
|
@ -38,14 +38,6 @@ pub extern "C" fn free_bytes() {
|
||||||
std::mem::drop(guard);
|
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<u8> {
|
pub fn bytes() -> Vec<u8> {
|
||||||
let mut guard = BUFFERU8.lock().unwrap();
|
let mut guard = BUFFERU8.lock().unwrap();
|
||||||
|
|
||||||
|
|
|
@ -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 std::collections::HashMap;
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
|
@ -8,6 +8,7 @@ mod blend;
|
||||||
mod cache;
|
mod cache;
|
||||||
mod debug;
|
mod debug;
|
||||||
mod fills;
|
mod fills;
|
||||||
|
mod fonts;
|
||||||
mod gpu_state;
|
mod gpu_state;
|
||||||
mod images;
|
mod images;
|
||||||
mod options;
|
mod options;
|
||||||
|
@ -23,11 +24,9 @@ use options::RenderOptions;
|
||||||
use surfaces::{SurfaceId, Surfaces};
|
use surfaces::{SurfaceId, Surfaces};
|
||||||
|
|
||||||
pub use blend::BlendMode;
|
pub use blend::BlendMode;
|
||||||
|
pub use fonts::*;
|
||||||
pub use images::*;
|
pub use images::*;
|
||||||
|
|
||||||
const DEFAULT_FONT_BYTES: &[u8] =
|
|
||||||
include_bytes!("../../frontend/resources/fonts/RobotoMono-Regular.ttf");
|
|
||||||
|
|
||||||
const MAX_BLOCKING_TIME_MS: i32 = 32;
|
const MAX_BLOCKING_TIME_MS: i32 = 32;
|
||||||
const NODE_BATCH_THRESHOLD: i32 = 10;
|
const NODE_BATCH_THRESHOLD: i32 = 10;
|
||||||
|
|
||||||
|
@ -86,9 +85,10 @@ pub(crate) struct RenderState {
|
||||||
gpu_state: GpuState,
|
gpu_state: GpuState,
|
||||||
pub options: RenderOptions,
|
pub options: RenderOptions,
|
||||||
pub surfaces: Surfaces,
|
pub surfaces: Surfaces,
|
||||||
|
fonts: FontStore,
|
||||||
// TODO: we should probably have only one of these
|
// TODO: we should probably have only one of these
|
||||||
pub font_provider: skia::textlayout::TypefaceFontProvider,
|
// pub font_provider: skia::textlayout::TypefaceFontProvider,
|
||||||
pub font_collection: skia::textlayout::FontCollection,
|
// pub font_collection: skia::textlayout::FontCollection,
|
||||||
// ----
|
// ----
|
||||||
pub cached_surface_image: Option<CachedSurfaceImage>,
|
pub cached_surface_image: Option<CachedSurfaceImage>,
|
||||||
pub viewbox: Viewbox,
|
pub viewbox: Viewbox,
|
||||||
|
@ -112,15 +112,8 @@ impl RenderState {
|
||||||
let sampling_options =
|
let sampling_options =
|
||||||
skia::SamplingOptions::new(skia::FilterMode::Linear, skia::MipmapMode::Nearest);
|
skia::SamplingOptions::new(skia::FilterMode::Linear, skia::MipmapMode::Nearest);
|
||||||
let surfaces = Surfaces::new(&mut gpu_state, (width, height), sampling_options);
|
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()
|
let fonts = FontStore::new();
|
||||||
.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);
|
|
||||||
|
|
||||||
// This is used multiple times everywhere so instead of creating new instances every
|
// This is used multiple times everywhere so instead of creating new instances every
|
||||||
// time we reuse this one.
|
// time we reuse this one.
|
||||||
|
@ -129,8 +122,9 @@ impl RenderState {
|
||||||
gpu_state,
|
gpu_state,
|
||||||
surfaces,
|
surfaces,
|
||||||
cached_surface_image: None,
|
cached_surface_image: None,
|
||||||
font_provider,
|
// font_provider,
|
||||||
font_collection,
|
// font_collection,
|
||||||
|
fonts,
|
||||||
options: RenderOptions::default(),
|
options: RenderOptions::default(),
|
||||||
viewbox: Viewbox::new(width as f32, height as f32),
|
viewbox: Viewbox::new(width as f32, height as f32),
|
||||||
images: ImageStore::new(),
|
images: ImageStore::new(),
|
||||||
|
@ -143,13 +137,11 @@ impl RenderState {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn add_font(&mut self, family_name: String, font_data: &[u8]) -> Result<(), String> {
|
pub fn fonts(&self) -> &FontStore {
|
||||||
let typeface = skia::FontMgr::default()
|
&self.fonts
|
||||||
.new_from_data(font_data, None)
|
}
|
||||||
.expect("Failed to add font");
|
pub fn fonts_mut(&mut self) -> &mut FontStore {
|
||||||
self.font_provider
|
&mut self.fonts
|
||||||
.register_typeface(typeface, family_name.as_ref());
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn add_image(&mut self, id: Uuid, image_data: &[u8]) -> Result<(), String> {
|
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() {
|
if let Some(svg) = shape.svg.as_ref() {
|
||||||
svg.render(self.surfaces.canvas(SurfaceId::Fills))
|
svg.render(self.surfaces.canvas(SurfaceId::Fills))
|
||||||
} else {
|
} 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);
|
let dom_result = skia::svg::Dom::from_str(sr.content.to_string(), font_manager);
|
||||||
match dom_result {
|
match dom_result {
|
||||||
Ok(dom) => {
|
Ok(dom) => {
|
||||||
|
|
|
@ -23,18 +23,17 @@ fn render_debug_view(render_state: &mut RenderState) {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn render_wasm_label(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 skia::ISize { width, height } = canvas.base_layer_size();
|
||||||
let p = skia::Point::new(width as f32 - 100.0, height as f32 - 25.0);
|
let p = skia::Point::new(width as f32 - 100.0, height as f32 - 25.0);
|
||||||
let mut paint = skia::Paint::default();
|
let mut paint = skia::Paint::default();
|
||||||
paint.set_color(skia::Color::from_argb(100, 0, 0, 0));
|
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);
|
let font = skia::Font::new(typeface, 10.0);
|
||||||
canvas.draw_str("WASM RENDERER", p, &font, &paint);
|
canvas.draw_str("WASM RENDERER", p, &font, &paint);
|
||||||
}
|
}
|
||||||
|
|
70
render-wasm/src/render/fonts.rs
Normal file
70
render-wasm/src/render/fonts.rs
Normal file
|
@ -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()));
|
||||||
|
}
|
||||||
|
}
|
|
@ -3,8 +3,9 @@ use crate::shapes::TextContent;
|
||||||
|
|
||||||
pub fn render(render_state: &mut RenderState, text: &TextContent) {
|
pub fn render(render_state: &mut RenderState, text: &TextContent) {
|
||||||
let mut offset_y = 0.0;
|
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());
|
skia_paragraph.layout(text.width());
|
||||||
|
|
||||||
let xy = (text.x(), text.y() + offset_y);
|
let xy = (text.x(), text.y() + offset_y);
|
||||||
skia_paragraph.paint(render_state.surfaces.canvas(SurfaceId::Fills), xy);
|
skia_paragraph.paint(render_state.surfaces.canvas(SurfaceId::Fills), xy);
|
||||||
offset_y += skia_paragraph.height();
|
offset_y += skia_paragraph.height();
|
||||||
|
|
|
@ -9,6 +9,7 @@ mod blurs;
|
||||||
mod bools;
|
mod bools;
|
||||||
mod corners;
|
mod corners;
|
||||||
mod fills;
|
mod fills;
|
||||||
|
mod fonts;
|
||||||
mod frames;
|
mod frames;
|
||||||
mod groups;
|
mod groups;
|
||||||
mod layouts;
|
mod layouts;
|
||||||
|
@ -25,6 +26,7 @@ pub use blurs::*;
|
||||||
pub use bools::*;
|
pub use bools::*;
|
||||||
pub use corners::*;
|
pub use corners::*;
|
||||||
pub use fills::*;
|
pub use fills::*;
|
||||||
|
pub use fonts::*;
|
||||||
pub use frames::*;
|
pub use frames::*;
|
||||||
pub use groups::*;
|
pub use groups::*;
|
||||||
pub use layouts::*;
|
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 {
|
match self.shape_type {
|
||||||
Type::Text(ref mut text) => {
|
Type::Text(ref mut text) => {
|
||||||
text.add_leaf(text_str)?;
|
text.add_leaf(text_str, font_family, font_size)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
_ => Err("Shape is not a text".to_string()),
|
_ => Err("Shape is not a text".to_string()),
|
||||||
|
|
48
render-wasm/src/shapes/fonts.rs
Normal file
48
render-wasm/src/shapes/fonts.rs
Normal file
|
@ -0,0 +1,48 @@
|
||||||
|
use std::fmt;
|
||||||
|
|
||||||
|
use uuid::Uuid;
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq, Clone, Copy)]
|
||||||
|
pub enum FontStyle {
|
||||||
|
Normal,
|
||||||
|
Italic,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<u8> 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)
|
||||||
|
}
|
||||||
|
}
|
|
@ -4,6 +4,8 @@ use skia_safe::{
|
||||||
textlayout::{FontCollection, ParagraphBuilder},
|
textlayout::{FontCollection, ParagraphBuilder},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
use super::FontFamily;
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Clone)]
|
#[derive(Debug, PartialEq, Clone)]
|
||||||
pub struct TextContent {
|
pub struct TextContent {
|
||||||
paragraphs: Vec<Paragraph>,
|
paragraphs: Vec<Paragraph>,
|
||||||
|
@ -38,15 +40,18 @@ impl TextContent {
|
||||||
self.paragraphs.push(p);
|
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
|
let paragraph = self
|
||||||
.paragraphs
|
.paragraphs
|
||||||
.last_mut()
|
.last_mut()
|
||||||
.ok_or("No paragraph to add text leaf to")?;
|
.ok_or("No paragraph to add text leaf to")?;
|
||||||
|
|
||||||
paragraph.add_leaf(TextLeaf {
|
paragraph.add_leaf(TextLeaf::new(text, font_family, font_size));
|
||||||
text: text.to_owned(),
|
|
||||||
});
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -100,15 +105,29 @@ impl Paragraph {
|
||||||
#[derive(Debug, PartialEq, Clone)]
|
#[derive(Debug, PartialEq, Clone)]
|
||||||
pub struct TextLeaf {
|
pub struct TextLeaf {
|
||||||
text: String,
|
text: String,
|
||||||
|
font_family: FontFamily,
|
||||||
|
font_size: f32,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TextLeaf {
|
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 {
|
pub fn to_style(&self) -> skia::textlayout::TextStyle {
|
||||||
let mut style = skia::textlayout::TextStyle::default();
|
let mut style = skia::textlayout::TextStyle::default();
|
||||||
// TODO: read text style info from the shape
|
|
||||||
style.set_color(skia::Color::BLACK);
|
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
|
style
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn serialized_font_family(&self) -> String {
|
||||||
|
format!("{}", self.font_family)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1 +1,2 @@
|
||||||
|
pub mod fonts;
|
||||||
pub mod text;
|
pub mod text;
|
||||||
|
|
35
render-wasm/src/wasm/fonts.rs
Normal file
35
render-wasm/src/wasm/fonts.rs
Normal file
|
@ -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;
|
||||||
|
});
|
||||||
|
}
|
|
@ -1,4 +1,6 @@
|
||||||
use crate::mem;
|
use crate::mem;
|
||||||
|
use crate::shapes::FontFamily;
|
||||||
|
use crate::utils::uuid_from_u32_quartet;
|
||||||
use crate::with_current_shape;
|
use crate::with_current_shape;
|
||||||
use crate::STATE;
|
use crate::STATE;
|
||||||
|
|
||||||
|
@ -20,14 +22,25 @@ pub extern "C" fn add_text_paragraph() {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[no_mangle]
|
#[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 bytes = mem::bytes();
|
||||||
|
|
||||||
let text = unsafe {
|
let text = unsafe {
|
||||||
String::from_utf8_unchecked(bytes) // TODO: handle this error
|
String::from_utf8_unchecked(bytes) // TODO: handle this error
|
||||||
};
|
};
|
||||||
|
|
||||||
with_current_shape!(state, |shape: &mut Shape| {
|
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 {
|
if let Err(err) = res {
|
||||||
eprintln!("{}", err);
|
eprintln!("{}", err);
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue