mirror of
https://github.com/enso-org/enso.git
synced 2024-12-22 16:41:45 +03:00
Better fonts support. (#3616)
This commit is contained in:
parent
e6e4692692
commit
4b96b4887c
122
Cargo.lock
generated
122
Cargo.lock
generated
@ -818,6 +818,24 @@ dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bincode"
|
||||
version = "2.0.0-rc.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f609ceb2c41b0d0277314a789ef0e7eb14593d5485f7c67320bed3924ebb1b33"
|
||||
dependencies = [
|
||||
"bincode_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bincode_derive"
|
||||
version = "2.0.0-rc.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "913287a8f3e00db4c7ae1b87e9b9b8cebd6b89217eaadfc281fa5c897da35dc3"
|
||||
dependencies = [
|
||||
"virtue",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bit_field"
|
||||
version = "0.10.1"
|
||||
@ -1604,7 +1622,7 @@ dependencies = [
|
||||
"ensogl-list-view",
|
||||
"ensogl-scroll-area",
|
||||
"ensogl-selector",
|
||||
"ensogl-text-msdf-sys",
|
||||
"ensogl-text-msdf",
|
||||
"ide-view-component-group",
|
||||
"wasm-bindgen",
|
||||
]
|
||||
@ -1619,7 +1637,7 @@ dependencies = [
|
||||
"ensogl-hardcoded-theme",
|
||||
"ensogl-list-view",
|
||||
"ensogl-selector",
|
||||
"ensogl-text-msdf-sys",
|
||||
"ensogl-text-msdf",
|
||||
"ide-view-component-group",
|
||||
"ide-view-component-list-panel",
|
||||
"js-sys",
|
||||
@ -1644,7 +1662,7 @@ dependencies = [
|
||||
"enso-frp",
|
||||
"ensogl",
|
||||
"ensogl-hardcoded-theme",
|
||||
"ensogl-text-msdf-sys",
|
||||
"ensogl-text-msdf",
|
||||
"ide-view",
|
||||
"parser",
|
||||
"span-tree",
|
||||
@ -1659,7 +1677,7 @@ dependencies = [
|
||||
"enso-frp",
|
||||
"ensogl",
|
||||
"ensogl-hardcoded-theme",
|
||||
"ensogl-text-msdf-sys",
|
||||
"ensogl-text-msdf",
|
||||
"ide-view",
|
||||
"js-sys",
|
||||
"nalgebra 0.26.2",
|
||||
@ -1945,6 +1963,7 @@ version = "0.1.0"
|
||||
dependencies = [
|
||||
"path-clean",
|
||||
"reqwest 0.10.10",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -2073,7 +2092,7 @@ dependencies = [
|
||||
"ensogl-drop-manager",
|
||||
"ensogl-examples",
|
||||
"ensogl-hardcoded-theme",
|
||||
"ensogl-text-msdf-sys",
|
||||
"ensogl-text-msdf",
|
||||
"failure",
|
||||
"flo_stream",
|
||||
"futures 0.3.21",
|
||||
@ -2122,6 +2141,7 @@ version = "0.3.1"
|
||||
dependencies = [
|
||||
"enso-prelude",
|
||||
"enso-shapely",
|
||||
"ifmt",
|
||||
"js-sys",
|
||||
"wasm-bindgen",
|
||||
"web-sys",
|
||||
@ -2141,7 +2161,7 @@ dependencies = [
|
||||
name = "enso-metamodel"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"bincode",
|
||||
"bincode 1.3.3",
|
||||
"derivative",
|
||||
"derive_more",
|
||||
]
|
||||
@ -2150,7 +2170,7 @@ dependencies = [
|
||||
name = "enso-metamodel-lexpr"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"bincode",
|
||||
"bincode 1.3.3",
|
||||
"derivative",
|
||||
"enso-metamodel",
|
||||
"enso-reflect",
|
||||
@ -2169,7 +2189,7 @@ dependencies = [
|
||||
name = "enso-parser"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"bincode",
|
||||
"bincode 1.3.3",
|
||||
"enso-data-structures",
|
||||
"enso-metamodel",
|
||||
"enso-metamodel-lexpr",
|
||||
@ -2200,7 +2220,7 @@ dependencies = [
|
||||
name = "enso-parser-jni"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"bincode",
|
||||
"bincode 1.3.3",
|
||||
"enso-parser",
|
||||
"enso-prelude",
|
||||
"jni",
|
||||
@ -2231,6 +2251,7 @@ dependencies = [
|
||||
"enclose",
|
||||
"enso-reflect",
|
||||
"enso-shapely",
|
||||
"enso-web",
|
||||
"failure",
|
||||
"futures 0.3.21",
|
||||
"ifmt",
|
||||
@ -2337,7 +2358,9 @@ dependencies = [
|
||||
"paste 0.1.18",
|
||||
"rustversion",
|
||||
"shrinkwraprs 0.3.0",
|
||||
"wasm-bindgen",
|
||||
"wasm-bindgen-test",
|
||||
"web-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -2398,14 +2421,14 @@ version = "0.1.0"
|
||||
dependencies = [
|
||||
"async-std",
|
||||
"console_error_panic_hook",
|
||||
"enso-data-structures",
|
||||
"derivative",
|
||||
"enso-debug-api",
|
||||
"enso-logger",
|
||||
"enso-prelude",
|
||||
"enso-shapely",
|
||||
"failure",
|
||||
"gloo-timers",
|
||||
"js-sys",
|
||||
"nalgebra 0.26.2",
|
||||
"tracing",
|
||||
"wasm-bindgen",
|
||||
"wasm-bindgen-test",
|
||||
"web-sys",
|
||||
@ -2470,7 +2493,7 @@ dependencies = [
|
||||
"enso-types",
|
||||
"enso-web",
|
||||
"ensogl-text-embedded-fonts",
|
||||
"ensogl-text-msdf-sys",
|
||||
"ensogl-text-msdf",
|
||||
"enum_dispatch",
|
||||
"failure",
|
||||
"itertools 0.10.3",
|
||||
@ -2531,7 +2554,7 @@ dependencies = [
|
||||
"enso-frp",
|
||||
"enso-prelude",
|
||||
"ensogl-core",
|
||||
"ensogl-text-msdf-sys",
|
||||
"ensogl-text-msdf",
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
@ -2592,7 +2615,7 @@ dependencies = [
|
||||
"ensogl-core",
|
||||
"ensogl-text",
|
||||
"ensogl-text-embedded-fonts",
|
||||
"ensogl-text-msdf-sys",
|
||||
"ensogl-text-msdf",
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
@ -2605,7 +2628,7 @@ dependencies = [
|
||||
"ensogl-core",
|
||||
"ensogl-grid-view",
|
||||
"ensogl-hardcoded-theme",
|
||||
"ensogl-text-msdf-sys",
|
||||
"ensogl-text-msdf",
|
||||
"itertools 0.10.3",
|
||||
"wasm-bindgen",
|
||||
]
|
||||
@ -2619,7 +2642,7 @@ dependencies = [
|
||||
"ensogl-core",
|
||||
"ensogl-hardcoded-theme",
|
||||
"ensogl-list-view",
|
||||
"ensogl-text-msdf-sys",
|
||||
"ensogl-text-msdf",
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
@ -2629,7 +2652,7 @@ version = "0.1.0"
|
||||
dependencies = [
|
||||
"enso-frp",
|
||||
"ensogl-core",
|
||||
"ensogl-text-msdf-sys",
|
||||
"ensogl-text-msdf",
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
@ -2649,7 +2672,7 @@ dependencies = [
|
||||
"ensogl-hardcoded-theme",
|
||||
"ensogl-sequence-diagram",
|
||||
"ensogl-text",
|
||||
"ensogl-text-msdf-sys",
|
||||
"ensogl-text-msdf",
|
||||
"ensogl-tooltip",
|
||||
"futures 0.3.21",
|
||||
"qstring",
|
||||
@ -2673,7 +2696,7 @@ dependencies = [
|
||||
"ensogl-flame-graph",
|
||||
"ensogl-hardcoded-theme",
|
||||
"ensogl-text",
|
||||
"ensogl-text-msdf-sys",
|
||||
"ensogl-text-msdf",
|
||||
"ensogl-tooltip",
|
||||
"futures 0.3.21",
|
||||
"wasm-bindgen",
|
||||
@ -2688,7 +2711,7 @@ dependencies = [
|
||||
"ensogl-core",
|
||||
"ensogl-hardcoded-theme",
|
||||
"ensogl-scroll-area",
|
||||
"ensogl-text-msdf-sys",
|
||||
"ensogl-text-msdf",
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
@ -2708,7 +2731,7 @@ dependencies = [
|
||||
"ensogl-core",
|
||||
"ensogl-hardcoded-theme",
|
||||
"ensogl-selector",
|
||||
"ensogl-text-msdf-sys",
|
||||
"ensogl-text-msdf",
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
@ -2738,7 +2761,7 @@ dependencies = [
|
||||
"ensogl-hardcoded-theme",
|
||||
"ensogl-text",
|
||||
"ensogl-text-embedded-fonts",
|
||||
"ensogl-text-msdf-sys",
|
||||
"ensogl-text-msdf",
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
@ -2819,7 +2842,6 @@ name = "ensogl-hardcoded-theme"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"ensogl-core",
|
||||
"ensogl-text-embedded-fonts",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -2907,6 +2929,7 @@ dependencies = [
|
||||
name = "ensogl-text"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"bincode 2.0.0-rc.1",
|
||||
"const_format",
|
||||
"enso-frp",
|
||||
"enso-prelude",
|
||||
@ -2915,7 +2938,11 @@ dependencies = [
|
||||
"enso-types",
|
||||
"ensogl-core",
|
||||
"ensogl-text-embedded-fonts",
|
||||
"ensogl-text-msdf-sys",
|
||||
"ensogl-text-font-family",
|
||||
"ensogl-text-msdf",
|
||||
"ordered-float",
|
||||
"owned_ttf_parser",
|
||||
"serde",
|
||||
"wasm-bindgen-test",
|
||||
"xi-rope",
|
||||
]
|
||||
@ -2926,25 +2953,34 @@ version = "0.1.0"
|
||||
dependencies = [
|
||||
"enso-build-utilities",
|
||||
"enso-prelude",
|
||||
"ensogl-text-embedded-fonts-names",
|
||||
"ensogl-text-font-family",
|
||||
"owned_ttf_parser",
|
||||
"zip 0.5.13",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ensogl-text-embedded-fonts-names"
|
||||
name = "ensogl-text-font-family"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"enso-prelude",
|
||||
"owned_ttf_parser",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ensogl-text-msdf-sys"
|
||||
name = "ensogl-text-msdf"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"enso-build-utilities",
|
||||
"enso-prelude",
|
||||
"enso-types",
|
||||
"ensogl-text-embedded-fonts",
|
||||
"ensogl-text-embedded-fonts-names",
|
||||
"ensogl-text-font-family",
|
||||
"failure",
|
||||
"futures 0.3.21",
|
||||
"js-sys",
|
||||
"nalgebra 0.26.2",
|
||||
"owned_ttf_parser",
|
||||
"serde",
|
||||
"wasm-bindgen",
|
||||
"wasm-bindgen-test",
|
||||
]
|
||||
@ -3827,7 +3863,7 @@ dependencies = [
|
||||
"anyhow",
|
||||
"async-compression",
|
||||
"async-trait",
|
||||
"bincode",
|
||||
"bincode 1.3.3",
|
||||
"byte-unit",
|
||||
"bytes 1.1.0",
|
||||
"cached 0.34.0",
|
||||
@ -3919,7 +3955,7 @@ dependencies = [
|
||||
"ensogl-gui-component",
|
||||
"ensogl-hardcoded-theme",
|
||||
"ensogl-text",
|
||||
"ensogl-text-msdf-sys",
|
||||
"ensogl-text-msdf",
|
||||
"ide-view-component-browser",
|
||||
"ide-view-graph-editor",
|
||||
"js-sys",
|
||||
@ -4002,7 +4038,7 @@ dependencies = [
|
||||
"ensogl-component",
|
||||
"ensogl-drop-manager",
|
||||
"ensogl-hardcoded-theme",
|
||||
"ensogl-text-msdf-sys",
|
||||
"ensogl-text-msdf",
|
||||
"failure",
|
||||
"js-sys",
|
||||
"nalgebra 0.26.2",
|
||||
@ -5002,6 +5038,15 @@ dependencies = [
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "owned_ttf_parser"
|
||||
version = "0.15.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "07ef1a404ae479dd6906f4fa2c88b3c94028f1284beb42a47c183a7c27ee9a3e"
|
||||
dependencies = [
|
||||
"ttf-parser",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "parking"
|
||||
version = "2.0.0"
|
||||
@ -5782,6 +5827,7 @@ dependencies = [
|
||||
"percent-encoding 2.1.0",
|
||||
"pin-project-lite 0.2.9",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"serde_urlencoded",
|
||||
"tokio 0.2.25",
|
||||
"tokio-tls 0.3.1",
|
||||
@ -7109,6 +7155,12 @@ version = "0.2.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "59547bce71d9c38b83d9c0e92b6066c4253371f15005def0c30d9657f50c7642"
|
||||
|
||||
[[package]]
|
||||
name = "ttf-parser"
|
||||
version = "0.15.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7b3e06c9b9d80ed6b745c7159c40b311ad2916abb34a49e9be2653b90db0d8dd"
|
||||
|
||||
[[package]]
|
||||
name = "typeable"
|
||||
version = "0.1.2"
|
||||
@ -7290,6 +7342,12 @@ version = "0.9.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
|
||||
|
||||
[[package]]
|
||||
name = "virtue"
|
||||
version = "0.0.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "757cfbfe0d17ee6f22fe97e536d463047d451b47cf9d11e2b7d1398b0ef274dd"
|
||||
|
||||
[[package]]
|
||||
name = "void"
|
||||
version = "1.0.2"
|
||||
|
@ -25,7 +25,7 @@ enso-web = { path = "../../lib/rust/web" }
|
||||
ensogl = { path = "../../lib/rust/ensogl" }
|
||||
ensogl-examples = { path = "../../lib/rust/ensogl/example" }
|
||||
ensogl-component = { path = "../../lib/rust/ensogl/component" }
|
||||
ensogl-text-msdf-sys = { path = "../../lib/rust/ensogl/component/text/msdf-sys" }
|
||||
ensogl-text-msdf = { path = "../../lib/rust/ensogl/component/text/src/font/msdf" }
|
||||
ensogl-hardcoded-theme = { path = "../../lib/rust/ensogl/app/theme/hardcoded" }
|
||||
ensogl-drop-manager = { path = "../../lib/rust/ensogl/component/drop-manager" }
|
||||
fuzzly = { path = "../../lib/rust/fuzzly" }
|
||||
|
@ -32,7 +32,7 @@ wasm-bindgen-test = { version = "0.3.8" }
|
||||
enso-web = { path = "../../../../lib/rust/web" }
|
||||
|
||||
[build-dependencies]
|
||||
enso-build-utilities = { path = "../../../../lib/rust/build-utils" }
|
||||
enso-build-utilities = { path = "../../../../build/build-utils" }
|
||||
bytes = { version = "0.5.4" }
|
||||
flatc-rust = { version = "0.1.2" }
|
||||
futures = { version = "0.3.1" }
|
||||
|
@ -31,7 +31,7 @@ wasm-bindgen = { version = "0.2.78" }
|
||||
wasm-bindgen-test = { version = "0.3.8" }
|
||||
|
||||
[build-dependencies]
|
||||
enso-build-utilities = { path = "../../../../lib/rust/build-utils" }
|
||||
enso-build-utilities = { path = "../../../../build/build-utils" }
|
||||
bytes = { version = "0.5.4" }
|
||||
futures = { version = "0.3.1" }
|
||||
reqwest = { version = "0.10.1" }
|
||||
|
@ -2,12 +2,12 @@
|
||||
#![deny(non_ascii_idents)]
|
||||
#![warn(unsafe_code)]
|
||||
|
||||
use enso_prelude::*;
|
||||
use wasm_bindgen::prelude::*;
|
||||
use ast::crumbs::PatternMatchCrumb::*;
|
||||
use ast::crumbs::*;
|
||||
use enso_prelude::*;
|
||||
use enso_text::traits::*;
|
||||
use span_tree::*;
|
||||
use wasm_bindgen::prelude::*;
|
||||
|
||||
use enso_web as web;
|
||||
use span_tree::builder::Builder;
|
||||
@ -17,14 +17,8 @@ use uuid::Uuid;
|
||||
|
||||
|
||||
|
||||
#[wasm_bindgen]
|
||||
#[entry_point(span_tree)]
|
||||
#[allow(dead_code)]
|
||||
pub fn entry_point_span_tree() {
|
||||
web::forward_panic_hook_to_console();
|
||||
web::set_stack_trace_limit();
|
||||
main();
|
||||
}
|
||||
|
||||
pub fn main() {
|
||||
let pattern_cr = vec![Seq { right: false }, Or, Or, Build];
|
||||
let val = ast::crumbs::SegmentMatchCrumb::Body { val: pattern_cr };
|
||||
|
@ -24,7 +24,6 @@ use flo_stream::Subscriber;
|
||||
use parser::Parser;
|
||||
|
||||
|
||||
|
||||
// ==============
|
||||
// === Export ===
|
||||
// ==============
|
||||
|
@ -61,7 +61,7 @@ impl Initializer {
|
||||
pub async fn start(self) -> Result<Ide, FailedIde> {
|
||||
info!(self.logger, "Starting IDE with the following config: {self.config:?}");
|
||||
|
||||
ensogl_text_msdf_sys::initialized().await;
|
||||
ensogl_text_msdf::initialized().await;
|
||||
let ensogl_app = ensogl::application::Application::new(self.config.dom_parent_id());
|
||||
register_views(&ensogl_app);
|
||||
let view = ensogl_app.new_view::<ide_view::root::View>();
|
||||
|
@ -138,11 +138,10 @@ mod profile_workflow;
|
||||
// ===================
|
||||
|
||||
/// IDE startup function.
|
||||
#[entry_point(ide)]
|
||||
#[profile(Objective)]
|
||||
#[wasm_bindgen]
|
||||
#[allow(dead_code)]
|
||||
pub fn entry_point_ide() {
|
||||
init_tracing(WARN);
|
||||
pub fn main() {
|
||||
// Logging of build information.
|
||||
#[cfg(debug_assertions)]
|
||||
let debug_mode = true;
|
||||
@ -154,7 +153,7 @@ pub fn entry_point_ide() {
|
||||
analytics::AnonymousData(debug_mode),
|
||||
);
|
||||
let config =
|
||||
crate::config::Startup::from_web_arguments().expect("Failed to read configuration.");
|
||||
crate::config::Startup::from_web_arguments().expect("Failed to read configuration");
|
||||
let executor = crate::ide::initializer::setup_global_executor();
|
||||
let initializer = crate::ide::initializer::Initializer::new(config);
|
||||
executor::global::spawn(async move {
|
||||
|
@ -4,7 +4,6 @@ use crate::integration_test::prelude::*;
|
||||
use wasm_bindgen::prelude::*;
|
||||
|
||||
use enso_debug_api as debug_api;
|
||||
use enso_web as web;
|
||||
|
||||
|
||||
|
||||
@ -13,11 +12,9 @@ use enso_web as web;
|
||||
// ===================
|
||||
|
||||
/// Startup function for running and profiling a test workflow.
|
||||
#[wasm_bindgen]
|
||||
#[entry_point(profile)]
|
||||
#[allow(dead_code)] // Used from JavaScript.
|
||||
pub async fn entry_point_profile() {
|
||||
web::forward_panic_hook_to_console();
|
||||
|
||||
pub async fn main() {
|
||||
// Run selected workflow.
|
||||
let need_workflow = "`profile` entry point requires --workflow argument. \
|
||||
Try --workflow=help to see a list of options.";
|
||||
|
@ -180,11 +180,11 @@ impl Model {
|
||||
pub fn new(socket: web_sys::WebSocket, logger: Logger) -> Model {
|
||||
socket.set_binary_type(BinaryType::Arraybuffer);
|
||||
Model {
|
||||
on_close: Slot::new(&socket, &logger),
|
||||
on_message: Slot::new(&socket, &logger),
|
||||
on_open: Slot::new(&socket, &logger),
|
||||
on_error: Slot::new(&socket, &logger),
|
||||
on_close_internal: Slot::new(&socket, &logger),
|
||||
on_close: Slot::new(&socket),
|
||||
on_message: Slot::new(&socket),
|
||||
on_open: Slot::new(&socket),
|
||||
on_error: Slot::new(&socket),
|
||||
on_close_internal: Slot::new(&socket),
|
||||
auto_reconnect: true,
|
||||
logger,
|
||||
socket,
|
||||
|
@ -19,7 +19,7 @@ ensogl = { path = "../../../lib/rust/ensogl" }
|
||||
ensogl-component = { path = "../../../lib/rust/ensogl/component" }
|
||||
ensogl-gui-component = { path = "../../../lib/rust/ensogl/component/gui" }
|
||||
ensogl-text = { path = "../../../lib/rust/ensogl/component/text" }
|
||||
ensogl-text-msdf-sys = { path = "../../../lib/rust/ensogl/component/text/msdf-sys" }
|
||||
ensogl-text-msdf = { path = "../../../lib/rust/ensogl/component/text/src/font/msdf" }
|
||||
ensogl-hardcoded-theme = { path = "../../../lib/rust/ensogl/app/theme/hardcoded" }
|
||||
ide-view-component-browser = { path = "component-browser" }
|
||||
ide-view-graph-editor = { path = "graph-editor" }
|
||||
|
@ -15,6 +15,6 @@ ensogl-selector = { path = "../../../../../lib/rust/ensogl/component/selector" }
|
||||
ensogl-hardcoded-theme = { path = "../../../../../lib/rust/ensogl/app/theme/hardcoded" }
|
||||
ensogl-scroll-area = { path = "../../../../../lib/rust/ensogl/component/scroll-area" }
|
||||
ensogl-list-view = { path = "../../../../../lib/rust/ensogl/component/list-view" }
|
||||
ensogl-text-msdf-sys = { path = "../../../../../lib/rust/ensogl/component/text/msdf-sys" }
|
||||
ensogl-text-msdf = { path = "../../../../../lib/rust/ensogl/component/text/src/font/msdf" }
|
||||
ide-view-component-group = { path = "../../component-browser/component-group" }
|
||||
wasm-bindgen = { version = "0.2.78", features = ["nightly"] }
|
||||
|
@ -27,7 +27,7 @@ use ensogl_list_view as list_view;
|
||||
use ensogl_list_view::entry::GlyphHighlightedLabelModel;
|
||||
use ensogl_scroll_area::ScrollArea;
|
||||
use ensogl_selector as selector;
|
||||
use ensogl_text_msdf_sys::run_once_initialized;
|
||||
use ensogl_text_msdf::run_once_initialized;
|
||||
use ide_view_component_group as component_group;
|
||||
use ide_view_component_group::entry;
|
||||
use ide_view_component_group::icon;
|
||||
|
@ -13,7 +13,7 @@ ensogl-core = { path = "../../../../../lib/rust/ensogl/core" }
|
||||
ensogl-hardcoded-theme = { path = "../../../../../lib/rust/ensogl/app/theme/hardcoded" }
|
||||
ensogl-list-view = { path = "../../../../../lib/rust/ensogl/component/list-view" }
|
||||
ensogl-selector = { path = "../../../../../lib/rust/ensogl/component/selector" }
|
||||
ensogl-text-msdf-sys = { path = "../../../../../lib/rust/ensogl/component/text/msdf-sys" }
|
||||
ensogl-text-msdf = { path = "../../../../../lib/rust/ensogl/component/text/src/font/msdf" }
|
||||
ide-view-component-group = { path = "../../component-browser/component-group" }
|
||||
ide-view-component-list-panel = { path = "../../component-browser/searcher-list-panel" }
|
||||
wasm-bindgen = { version = "0.2.78", features = ["nightly"] }
|
||||
|
@ -195,8 +195,7 @@ fn init_local_scope_section(searcher_list_panel: &ComponentBrowserPanel) {
|
||||
#[entry_point]
|
||||
#[allow(dead_code)]
|
||||
pub fn main() {
|
||||
init_tracing(WARN);
|
||||
ensogl_text_msdf_sys::run_once_initialized(|| {
|
||||
ensogl_text_msdf::run_once_initialized(|| {
|
||||
let app = &Application::new("root");
|
||||
theme::builtin::light::register(&app);
|
||||
theme::builtin::light::enable(&app);
|
||||
|
@ -12,7 +12,7 @@ ast = { path = "../../../language/ast/impl" }
|
||||
enso-frp = { path = "../../../../../lib/rust/frp" }
|
||||
ensogl = { path = "../../../../../lib/rust/ensogl" }
|
||||
ensogl-hardcoded-theme = { path = "../../../../../lib/rust/ensogl/app/theme/hardcoded" }
|
||||
ensogl-text-msdf-sys = { path = "../../../../../lib/rust/ensogl/component/text/msdf-sys" }
|
||||
ensogl-text-msdf = { path = "../../../../../lib/rust/ensogl/component/text/src/font/msdf" }
|
||||
ide-view = { path = "../.." }
|
||||
parser = { path = "../../../language/parser" }
|
||||
span-tree = { path = "../../../language/span-tree" }
|
||||
|
@ -26,7 +26,7 @@ use ensogl::display::shape::StyleWatch;
|
||||
use ensogl::gui::text;
|
||||
use ensogl::system::web;
|
||||
use ensogl_hardcoded_theme as theme;
|
||||
use ensogl_text_msdf_sys::run_once_initialized;
|
||||
use ensogl_text_msdf::run_once_initialized;
|
||||
use ide_view::graph_editor;
|
||||
use ide_view::graph_editor::component::node::vcs;
|
||||
use ide_view::graph_editor::component::node::Expression;
|
||||
|
@ -11,7 +11,7 @@ crate-type = ["cdylib", "rlib"]
|
||||
enso-frp = { path = "../../../../../lib/rust/frp" }
|
||||
ensogl = { path = "../../../../../lib/rust/ensogl" }
|
||||
ensogl-hardcoded-theme = { path = "../../../../../lib/rust/ensogl/app/theme/hardcoded" }
|
||||
ensogl-text-msdf-sys = { path = "../../../../../lib/rust/ensogl/component/text/msdf-sys" }
|
||||
ensogl-text-msdf = { path = "../../../../../lib/rust/ensogl/component/text/src/font/msdf" }
|
||||
ide-view = { path = "../.." }
|
||||
js-sys = { version = "0.3.28" }
|
||||
nalgebra = { version = "0.26.1" }
|
||||
|
@ -19,7 +19,7 @@ use ensogl::animation;
|
||||
use ensogl::application::Application;
|
||||
use ensogl::display::navigation::navigator::Navigator;
|
||||
use ensogl::system::web;
|
||||
use ensogl_text_msdf_sys::run_once_initialized;
|
||||
use ensogl_text_msdf::run_once_initialized;
|
||||
use ide_view::graph_editor::component::visualization;
|
||||
use ide_view::graph_editor::component::visualization::Data;
|
||||
use ide_view::graph_editor::component::visualization::Registry;
|
||||
|
@ -21,7 +21,7 @@ enso-shapely = { path = "../../../../lib/rust/shapely" }
|
||||
enso-text = { version = "0.1.0", path = "../../../../lib/rust/text" }
|
||||
ensogl = { version = "0.1.0", path = "../../../../lib/rust/ensogl" }
|
||||
ensogl-component = { path = "../../../../lib/rust/ensogl/component" }
|
||||
ensogl-text-msdf-sys = { version = "0.1.0", path = "../../../../lib/rust/ensogl/component/text/msdf-sys" }
|
||||
ensogl-text-msdf = { version = "0.1.0", path = "../../../../lib/rust/ensogl/component/text/src/font/msdf" }
|
||||
ensogl-hardcoded-theme = { version = "0.1.0", path = "../../../../lib/rust/ensogl/app/theme/hardcoded" }
|
||||
ensogl-drop-manager = { version = "0.1.0", path = "../../../../lib/rust/ensogl/component/drop-manager" }
|
||||
failure = { version = "0.1.8" }
|
||||
|
@ -755,117 +755,64 @@ function ok(value: any) {
|
||||
}
|
||||
|
||||
class Config {
|
||||
public entry: string
|
||||
public project: string
|
||||
public project_manager: string
|
||||
public language_server_rpc: string
|
||||
public language_server_data: string
|
||||
public namespace: string
|
||||
public platform: string
|
||||
public frame: boolean
|
||||
public theme: string
|
||||
public dark_theme: boolean
|
||||
public high_contrast: boolean
|
||||
public use_loader: boolean
|
||||
public wasm_url: string
|
||||
public wasm_glue_url: string
|
||||
public node_labels: boolean
|
||||
public crash_report_host: string
|
||||
public data_gathering: boolean
|
||||
public is_in_cloud: boolean
|
||||
public verbose: boolean
|
||||
public authentication_enabled: boolean
|
||||
public email: string
|
||||
public application_config_url: string
|
||||
public test_workflow: string
|
||||
public skip_min_version_check: boolean
|
||||
public preferred_engine_version: SemVer
|
||||
public enable_new_component_browser: boolean
|
||||
|
||||
static default() {
|
||||
let config = new Config()
|
||||
config.use_loader = true
|
||||
config.wasm_url = '/assets/ide.wasm'
|
||||
config.wasm_glue_url = '/assets/wasm_imports.js'
|
||||
config.crash_report_host = defaultLogServerHost
|
||||
config.data_gathering = true
|
||||
config.is_in_cloud = false
|
||||
config.entry = null
|
||||
config.authentication_enabled = true
|
||||
config.application_config_url =
|
||||
'https://raw.githubusercontent.com/enso-org/ide/develop/config.json'
|
||||
config.skip_min_version_check = Versions.isDevVersion()
|
||||
config.preferred_engine_version = Versions.ideVersion
|
||||
config.enable_new_component_browser = false
|
||||
return config
|
||||
}
|
||||
public entry: string = undefined
|
||||
public project: string = undefined
|
||||
public project_manager: string = undefined
|
||||
public language_server_rpc: string = undefined
|
||||
public language_server_data: string = undefined
|
||||
public namespace: string = undefined
|
||||
public platform: string = undefined
|
||||
public frame: boolean = false
|
||||
public theme: string = undefined
|
||||
public dark_theme: boolean = false
|
||||
public high_contrast: boolean = false
|
||||
public use_loader: boolean = true
|
||||
public wasm_url: string = '/assets/ide.wasm'
|
||||
public wasm_glue_url: string = '/assets/wasm_imports.js'
|
||||
public node_labels: boolean = false
|
||||
public crash_report_host: string = defaultLogServerHost
|
||||
public data_gathering: boolean = true
|
||||
public is_in_cloud: boolean = false
|
||||
public verbose: boolean = false
|
||||
public authentication_enabled: boolean = true
|
||||
public email: string = undefined
|
||||
public application_config_url: string =
|
||||
'https://raw.githubusercontent.com/enso-org/ide/develop/config.json'
|
||||
public test_workflow: string = undefined
|
||||
public skip_min_version_check: boolean = Versions.isDevVersion()
|
||||
public preferred_engine_version: SemVer = Versions.ideVersion
|
||||
public enable_new_component_browser: boolean = false
|
||||
|
||||
updateFromObject(other: any) {
|
||||
if (!ok(other)) {
|
||||
return
|
||||
}
|
||||
this.entry = ok(other.entry) ? tryAsString(other.entry) : this.entry
|
||||
this.project = ok(other.project) ? tryAsString(other.project) : this.project
|
||||
this.project_manager = ok(other.project_manager)
|
||||
? tryAsString(other.project_manager)
|
||||
: this.project_manager
|
||||
this.language_server_rpc = ok(other.language_server_rpc)
|
||||
? tryAsString(other.language_server_rpc)
|
||||
: this.language_server_rpc
|
||||
this.language_server_data = ok(other.language_server_data)
|
||||
? tryAsString(other.language_server_data)
|
||||
: this.language_server_data
|
||||
this.namespace = ok(other.namespace) ? tryAsString(other.namespace) : this.namespace
|
||||
this.platform = ok(other.platform) ? tryAsString(other.platform) : this.platform
|
||||
this.frame = ok(other.frame) ? tryAsBoolean(other.frame) : this.frame
|
||||
this.theme = ok(other.theme) ? tryAsString(other.theme) : this.theme
|
||||
this.dark_theme = ok(other.dark_theme) ? tryAsBoolean(other.dark_theme) : this.dark_theme
|
||||
this.high_contrast = ok(other.high_contrast)
|
||||
? tryAsBoolean(other.high_contrast)
|
||||
: this.high_contrast
|
||||
this.use_loader = ok(other.use_loader) ? tryAsBoolean(other.use_loader) : this.use_loader
|
||||
this.wasm_url = ok(other.wasm_url) ? tryAsString(other.wasm_url) : this.wasm_url
|
||||
this.wasm_glue_url = ok(other.wasm_glue_url)
|
||||
? tryAsString(other.wasm_glue_url)
|
||||
: this.wasm_glue_url
|
||||
this.node_labels = ok(other.node_labels)
|
||||
? tryAsBoolean(other.node_labels)
|
||||
: this.node_labels
|
||||
this.crash_report_host = ok(other.crash_report_host)
|
||||
? tryAsString(other.crash_report_host)
|
||||
: this.crash_report_host
|
||||
this.data_gathering = parseBoolean(other.data_gathering) ?? this.data_gathering
|
||||
this.is_in_cloud = ok(other.is_in_cloud)
|
||||
? tryAsBoolean(other.is_in_cloud)
|
||||
: this.is_in_cloud
|
||||
this.verbose = ok(other.verbose) ? tryAsBoolean(other.verbose) : this.verbose
|
||||
this.authentication_enabled = ok(other.authentication_enabled)
|
||||
? tryAsBoolean(other.authentication_enabled)
|
||||
: this.authentication_enabled
|
||||
this.email = ok(other.email) ? tryAsString(other.email) : this.email
|
||||
this.application_config_url = ok(other.application_config_url)
|
||||
? tryAsString(other.application_config_url)
|
||||
: this.application_config_url
|
||||
this.test_workflow = ok(other.test_workflow)
|
||||
? tryAsString(other.test_workflow)
|
||||
: this.test_workflow
|
||||
this.skip_min_version_check = ok(other.skip_min_version_check)
|
||||
? tryAsBoolean(other.skip_min_version_check)
|
||||
: this.skip_min_version_check
|
||||
this.preferred_engine_version =
|
||||
semver.parse(other.preferred_engine_version) ?? this.preferred_engine_version
|
||||
this.enable_new_component_browser =
|
||||
parseBoolean(other.enable_new_component_browser) ?? this.enable_new_component_browser
|
||||
}
|
||||
}
|
||||
|
||||
function parseBoolean(value: any): boolean | null {
|
||||
if (value === 'true' || value === true) {
|
||||
return true
|
||||
} else if (value === 'false' || value === false) {
|
||||
return false
|
||||
} else {
|
||||
return null
|
||||
for (let key of Object.keys(this)) {
|
||||
let self: any = this
|
||||
let otherVal = other[key]
|
||||
let selfVal = self[key]
|
||||
if (ok(otherVal)) {
|
||||
if (typeof selfVal === 'boolean') {
|
||||
let val = tryAsBoolean(otherVal)
|
||||
if (val === null) {
|
||||
console.error(
|
||||
`Invalid value for ${key}: ${otherVal}. Expected boolean. Reverting to the default value of `
|
||||
)
|
||||
} else {
|
||||
self[key] = val
|
||||
}
|
||||
} else if (selfVal instanceof SemVer) {
|
||||
let val = semver.parse(otherVal)
|
||||
if (val === null) {
|
||||
console.error(`Invalid value for ${key}: ${otherVal}. Expected semver.`)
|
||||
} else {
|
||||
self[key] = val
|
||||
}
|
||||
} else {
|
||||
self[key] = tryAsString(otherVal)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -881,10 +828,9 @@ function parseBooleanOrLeaveAsIs(value: any): any {
|
||||
return value
|
||||
}
|
||||
|
||||
function tryAsBoolean(value: any): boolean {
|
||||
function tryAsBoolean(value: any): boolean | null {
|
||||
value = parseBooleanOrLeaveAsIs(value)
|
||||
assert(typeof value == 'boolean')
|
||||
return value
|
||||
return typeof value == 'boolean' ? value : null
|
||||
}
|
||||
|
||||
function tryAsString(value: any): string {
|
||||
@ -963,12 +909,12 @@ API.main = async function (inputConfig: any) {
|
||||
// @ts-ignore
|
||||
const urlConfig = Object.fromEntries(urlParams.entries())
|
||||
|
||||
const config = Config.default()
|
||||
const config = new Config()
|
||||
config.updateFromObject(inputConfig)
|
||||
config.updateFromObject(urlConfig)
|
||||
|
||||
if (await checkMinSupportedVersion(config)) {
|
||||
if (config.authentication_enabled) {
|
||||
if (config.authentication_enabled && !config.entry) {
|
||||
new FirebaseAuthentication(function (user: any) {
|
||||
config.email = user.email
|
||||
runEntryPoint(config)
|
||||
|
@ -1,6 +1,6 @@
|
||||
# Options intended to be common for all developers.
|
||||
|
||||
wasm-size-limit: 5.29 MiB
|
||||
wasm-size-limit: 14.44 MiB
|
||||
|
||||
required-versions:
|
||||
cargo-watch: ^8.1.1
|
||||
|
@ -6,7 +6,8 @@ edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
path-clean = "0.1.0"
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
|
||||
[dependencies.reqwest]
|
||||
version = "0.10.6"
|
||||
features = ['blocking']
|
||||
features = ["blocking", "json"]
|
164
build/build-utils/src/lib.rs
Normal file
164
build/build-utils/src/lib.rs
Normal file
@ -0,0 +1,164 @@
|
||||
//! A crate with many utilities for build scripts, for example downloading packages form GitHub or
|
||||
//! easier management of env vars and paths.
|
||||
|
||||
// === Features ===
|
||||
#![feature(trait_alias)]
|
||||
// === Standard Linter Configuration ===
|
||||
#![deny(non_ascii_idents)]
|
||||
#![warn(unsafe_code)]
|
||||
// === Non-Standard Linter Configuration ===
|
||||
#![allow(clippy::option_map_unit_fn)]
|
||||
#![allow(clippy::precedence)]
|
||||
#![allow(dead_code)]
|
||||
#![deny(unconditional_recursion)]
|
||||
#![warn(missing_copy_implementations)]
|
||||
#![warn(missing_debug_implementations)]
|
||||
#![warn(missing_docs)]
|
||||
#![warn(trivial_casts)]
|
||||
#![warn(trivial_numeric_casts)]
|
||||
#![warn(unused_import_braces)]
|
||||
#![warn(unused_qualifications)]
|
||||
|
||||
use reqwest::header::HeaderMap;
|
||||
use std::fmt::Display;
|
||||
use std::io::ErrorKind;
|
||||
use std::path;
|
||||
|
||||
|
||||
// =====================
|
||||
// === GithubRelease ===
|
||||
// =====================
|
||||
|
||||
/// Types that can yield a reference to std::path::Path.
|
||||
pub trait PathRef = AsRef<path::Path>;
|
||||
|
||||
/// A structure describing a concrete release package on GitHub. The [`project_url`] should be a
|
||||
/// project's main page on GitHub.
|
||||
#[derive(Debug)]
|
||||
#[allow(missing_docs)]
|
||||
pub struct GithubRelease<T> {
|
||||
pub project_url: T,
|
||||
pub version: T,
|
||||
pub filename: T,
|
||||
}
|
||||
|
||||
impl<T: AsRef<str> + Display> GithubRelease<T> {
|
||||
/// Download the release package from GitHub. If the target file already exists, it will be
|
||||
/// removed first.
|
||||
pub fn download(&self, destination_dir: &path::Path) {
|
||||
let url =
|
||||
format!("{}/releases/download/{}/{}", self.project_url, self.version, self.filename);
|
||||
let destination_file = destination_dir.join(self.filename.as_ref());
|
||||
remove_old_file(&destination_file);
|
||||
let mut resp = reqwest::blocking::get(&url).expect("Download failed.");
|
||||
let mut out = std::fs::File::create(destination_file).expect("Failed to create file.");
|
||||
std::io::copy(&mut resp, &mut out).expect("Failed to copy file content.");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
// =====================
|
||||
// === GithubRelease ===
|
||||
// =====================
|
||||
|
||||
/// A file description of a GitHub repository.
|
||||
#[derive(Debug, Clone, serde::Deserialize)]
|
||||
#[allow(missing_docs)]
|
||||
pub struct GithubFile {
|
||||
pub name: String,
|
||||
}
|
||||
|
||||
/// A phantom structure aggregating Google Fonts related utilities.
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct GoogleFontsRelease;
|
||||
|
||||
impl GoogleFontsRelease {
|
||||
/// Download the font files from Google Fonts. If the target file already exists, it will be
|
||||
/// removed first.
|
||||
pub fn download(name: &str, destination_dir: &path::Path) -> Vec<GithubFile> {
|
||||
let url = format!("https://api.github.com/repos/google/fonts/contents/ofl/{}", name);
|
||||
let raw_url = format!("https://raw.githubusercontent.com/google/fonts/master/ofl/{}", name);
|
||||
let mut client = reqwest::blocking::Client::builder().user_agent("request");
|
||||
|
||||
if let Ok(token) = std::env::var("GITHUB_TOKEN") {
|
||||
let mut header_map = HeaderMap::new();
|
||||
let value = format!("Bearer {}", token).parse().unwrap();
|
||||
header_map.append(reqwest::header::AUTHORIZATION, value);
|
||||
client = client.default_headers(header_map);
|
||||
}
|
||||
|
||||
let request = client.build().unwrap();
|
||||
let resp = request.get(&url).send().expect("Failed to get GitHub response.");
|
||||
let files: Vec<GithubFile> = resp.json().expect("Failed to parse JSON.");
|
||||
let font_files: Vec<_> = files.into_iter().filter(|f| f.name.ends_with(".ttf")).collect();
|
||||
for file in &font_files {
|
||||
let file_url = format!("{}/{}", raw_url, file.name);
|
||||
let destination_file = destination_dir.join(&file.name);
|
||||
remove_old_file(&destination_file);
|
||||
let mut resp = reqwest::blocking::get(&file_url).expect("Download failed.");
|
||||
let mut out = std::fs::File::create(destination_file).expect("Failed to create file.");
|
||||
std::io::copy(&mut resp, &mut out).expect("Failed to copy file content.");
|
||||
}
|
||||
font_files
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
// ==================
|
||||
// === File Utils ===
|
||||
// ==================
|
||||
|
||||
/// Remove the old file if it exists.
|
||||
fn remove_old_file(file: &path::Path) {
|
||||
let result = std::fs::remove_file(&file);
|
||||
let error = result.err();
|
||||
let fatal_error = error.filter(|err| err.kind() != ErrorKind::NotFound);
|
||||
assert!(fatal_error.is_none());
|
||||
}
|
||||
|
||||
|
||||
|
||||
// =======================
|
||||
// === Path Conversion ===
|
||||
// =======================
|
||||
|
||||
/// Converts path to an absolute form.
|
||||
pub fn absolute_path(path: impl PathRef) -> std::io::Result<path::PathBuf> {
|
||||
use path_clean::PathClean;
|
||||
let path = path.as_ref();
|
||||
if path.is_absolute() {
|
||||
Ok(path.to_path_buf().clean())
|
||||
} else {
|
||||
Ok(std::env::current_dir()?.join(path).clean())
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
// ======================
|
||||
// === Env Management ===
|
||||
// ======================
|
||||
|
||||
/// Get the environment variable or panic if not available.
|
||||
pub fn env_var_or_panic(var_name: &str) -> String {
|
||||
match std::env::var(var_name) {
|
||||
Ok(var) => var,
|
||||
Err(e) => panic!("Failed to read environment variable {}: {}.", var_name, e),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
// ==========================
|
||||
// === Build Target Utils ===
|
||||
// ==========================
|
||||
|
||||
/// Checks if the current build is targeting wasm32.
|
||||
///
|
||||
/// Relies on `TARGET` environment variable set by cargo for build scripts.
|
||||
pub fn targeting_wasm() -> bool {
|
||||
let target = env_var_or_panic("TARGET");
|
||||
target.contains("wasm32")
|
||||
}
|
@ -1,74 +0,0 @@
|
||||
//! A crate with many utilities for build scripts, for example downloading packages form GitHub or
|
||||
//! easier management of env vars and paths.
|
||||
|
||||
// === Features ===
|
||||
#![feature(trait_alias)]
|
||||
// === Standard Linter Configuration ===
|
||||
#![deny(non_ascii_idents)]
|
||||
#![warn(unsafe_code)]
|
||||
|
||||
use std::fmt::Display;
|
||||
use std::io::ErrorKind;
|
||||
use std::path;
|
||||
|
||||
|
||||
|
||||
/// Types that can yield a reference to std::path::Path.
|
||||
pub trait PathRef = AsRef<path::Path>;
|
||||
|
||||
/// A structure describing a concrete release package on GitHub.
|
||||
pub struct GithubRelease<T> {
|
||||
pub project_url: T,
|
||||
pub version: T,
|
||||
pub filename: T,
|
||||
}
|
||||
|
||||
impl<T: AsRef<str> + Display> GithubRelease<T> {
|
||||
/// Download the release package from GitHub if the target file was missing. Returns true if
|
||||
/// the file was downloaded or false if it already existed.
|
||||
///
|
||||
/// The project_url should be a project's main page on GitHub.
|
||||
pub fn download(&self, destination_dir: &path::Path) {
|
||||
let url =
|
||||
format!("{}/releases/download/{}/{}", self.project_url, self.version, self.filename);
|
||||
let destination_file = destination_dir.join(self.filename.as_ref());
|
||||
Self::remove_old_file(&destination_file);
|
||||
let mut resp = reqwest::blocking::get(&url).expect("Download failed.");
|
||||
let mut out = std::fs::File::create(destination_file).expect("Failed to create file.");
|
||||
std::io::copy(&mut resp, &mut out).expect("Failed to copy file content.");
|
||||
}
|
||||
|
||||
fn remove_old_file(file: &path::Path) {
|
||||
let result = std::fs::remove_file(&file);
|
||||
let error = result.err();
|
||||
let fatal_error = error.filter(|err| err.kind() != ErrorKind::NotFound);
|
||||
assert!(fatal_error.is_none());
|
||||
}
|
||||
}
|
||||
|
||||
/// Converts path to an absolute form.
|
||||
pub fn absolute_path(path: impl PathRef) -> std::io::Result<path::PathBuf> {
|
||||
use path_clean::PathClean;
|
||||
let path = path.as_ref();
|
||||
if path.is_absolute() {
|
||||
Ok(path.to_path_buf().clean())
|
||||
} else {
|
||||
Ok(std::env::current_dir()?.join(path).clean())
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the environment variable or panic if not available.
|
||||
pub fn env_var_or_panic(var_name: &str) -> String {
|
||||
match std::env::var(var_name) {
|
||||
Ok(var) => var,
|
||||
Err(e) => panic!("Failed to read environment variable {}: {}.", var_name, e),
|
||||
}
|
||||
}
|
||||
|
||||
/// Checks if the current build is targeting wasm32.
|
||||
///
|
||||
/// Relies on `TARGET` environment variable set by cargo for build scripts.
|
||||
pub fn targeting_wasm() -> bool {
|
||||
let target = env_var_or_panic("TARGET");
|
||||
target.contains("wasm32")
|
||||
}
|
@ -8,5 +8,4 @@ edition = "2021"
|
||||
crate-type = ["rlib", "cdylib"]
|
||||
|
||||
[dependencies]
|
||||
ensogl-core = { version = "0.1.0", path = "../../../core" }
|
||||
ensogl-text-embedded-fonts = { version = "0.1.0", path = "../../../../../../lib/rust/ensogl/component/text/embedded-fonts" }
|
||||
ensogl-core = { path = "../../../core" }
|
||||
|
@ -14,8 +14,6 @@
|
||||
#![warn(missing_debug_implementations)]
|
||||
|
||||
use ensogl_core::prelude::ImString;
|
||||
use ensogl_text_embedded_fonts::DefaultFamily as DefaultFontFamily;
|
||||
use ensogl_text_embedded_fonts::Family;
|
||||
|
||||
|
||||
|
||||
@ -198,7 +196,7 @@ define_themes! { [light:0, dark:1]
|
||||
section_heading_size = 16.0, 16.0;
|
||||
section_heading_offset = 50.0, 50.0;
|
||||
section_heading_text_offset = 13.0, 13.0;
|
||||
section_heading_font = "Causten-Semibold", "Causten-Semibold";
|
||||
section_heading_font = "default", "default";
|
||||
section_heading_color = Rgb(0.4510, 0.4510, 0.4510), Rgb(0.4510, 0.4510, 0.4510);
|
||||
section_divider_color = Rgb(0.4510, 0.4510, 0.4510), Rgb(0.4510, 0.4510, 0.4510);
|
||||
|
||||
@ -243,7 +241,7 @@ define_themes! { [light:0, dark:1]
|
||||
component_group {
|
||||
header {
|
||||
text {
|
||||
font = DefaultFontFamily::bold(), DefaultFontFamily::bold();
|
||||
font = "default", "default";
|
||||
size = 12.0, 12.0;
|
||||
color_intensity = 1.0, 1.0;
|
||||
}
|
||||
@ -279,7 +277,7 @@ define_themes! { [light:0, dark:1]
|
||||
highlight = Rgba::new(1.0, 0.0, 0.0, 0.5), Rgba::new(1.0, 0.0, 0.0, 0.5);
|
||||
selected_color = Rgba::white(), Rgba::white();
|
||||
text {
|
||||
font = DefaultFontFamily::regular(), DefaultFontFamily::regular();
|
||||
font = "default", "default";
|
||||
size = 12.0, 12.0;
|
||||
color = Rgba(0.4,0.4,0.4,1.0), Rgba(0.4,0.4,0.4,1.0);
|
||||
highlight_bold = 0.02, 0.02;
|
||||
@ -609,7 +607,7 @@ define_themes! { [light:0, dark:1]
|
||||
text = Lcha(0.0,0.0,0.0,0.7) , Lcha(1.0,0.0,0.0,0.7);
|
||||
text {
|
||||
selection = Lcha(0.7,0.0,0.125,0.7) , Lcha(0.7,0.0,0.125,0.7);
|
||||
font = DefaultFontFamily::mono(), DefaultFontFamily::mono();
|
||||
font = "default-mono", "default-mono";
|
||||
size = 12.0, 12.0;
|
||||
highlight_bold = 0.02, 0.02;
|
||||
}
|
||||
@ -636,7 +634,7 @@ define_themes! { [light:0, dark:1]
|
||||
text {
|
||||
offset = 00.0, 00.0;
|
||||
size = 12.0, 12.0;
|
||||
font = "DejaVuSans", "DejaVuSans";
|
||||
font = "default", "default";
|
||||
}
|
||||
padding_outer = 20.0, 20.0;
|
||||
padding_inner_x = 10.0, 10.0;
|
||||
|
@ -3,10 +3,10 @@
|
||||
use crate::prelude::*;
|
||||
|
||||
use crate::header;
|
||||
use crate::header::WeakLayers;
|
||||
use crate::selectable;
|
||||
use crate::Entry;
|
||||
|
||||
use crate::header::WeakLayers;
|
||||
use enso_frp as frp;
|
||||
use ensogl_core::application::command::FrpNetworkProvider;
|
||||
use ensogl_core::application::Application;
|
||||
@ -15,6 +15,7 @@ use ensogl_core::display::scene::Layer;
|
||||
use ensogl_scroll_area::ScrollArea;
|
||||
|
||||
|
||||
|
||||
// ================
|
||||
// === GridView ===
|
||||
// ================
|
||||
|
@ -44,7 +44,7 @@ impl Default for EntryParams {
|
||||
bg_margin: 0.0,
|
||||
hover_color: color::Rgba(0.9, 0.9, 0.9, 1.0),
|
||||
selection_color: color::Rgba(0.8, 0.8, 0.8, 1.0),
|
||||
font: text::typeface::font::DEFAULT_FONT.into(),
|
||||
font: text::font::DEFAULT_FONT.into(),
|
||||
text_offset: 7.0,
|
||||
text_size: text::Size(14.0),
|
||||
text_color: color::Rgba(0.0, 0.0, 0.0, 1.0),
|
||||
|
@ -14,10 +14,15 @@ enso-shapely = { path = "../../../shapely" }
|
||||
enso-text = { path = "../../../text" }
|
||||
enso-types = { path = "../../../types" }
|
||||
ensogl-core = { path = "../../core" }
|
||||
ensogl-text-embedded-fonts = { path = "embedded-fonts" }
|
||||
ensogl-text-msdf-sys = { path = "msdf-sys" }
|
||||
ensogl-text-embedded-fonts = { path = "src/font/embedded" }
|
||||
ensogl-text-msdf = { path = "src/font/msdf" }
|
||||
const_format = "0.2.22"
|
||||
xi-rope = { version = "0.3.0" }
|
||||
owned_ttf_parser = "0.15.1"
|
||||
bincode = "2.0.0-rc.1"
|
||||
serde = { version = "1", features = ["rc"] }
|
||||
ordered-float = "3.0.0"
|
||||
ensogl-text-font-family = { path = "src/font/family" }
|
||||
|
||||
[dev-dependencies]
|
||||
wasm-bindgen-test = { version = "0.3.8" }
|
||||
|
@ -1,115 +0,0 @@
|
||||
//! Downloader of fonts considered as "embedded" into the application.
|
||||
|
||||
// === Features ===
|
||||
#![feature(const_trait_impl)]
|
||||
|
||||
use std::env;
|
||||
use std::fs;
|
||||
use std::io;
|
||||
use std::io::Write;
|
||||
use std::path;
|
||||
|
||||
|
||||
|
||||
// =====================
|
||||
// === FillMapRsFile ===
|
||||
// =====================
|
||||
|
||||
/// Generated file with code filling embedded fonts map
|
||||
pub struct FillMapRsFile {
|
||||
file: fs::File,
|
||||
}
|
||||
|
||||
impl FillMapRsFile {
|
||||
fn create<P: AsRef<path::Path>>(path: P) -> io::Result<FillMapRsFile> {
|
||||
let mut file = fs::File::create(path)?;
|
||||
writeln!(file, "{{")?;
|
||||
Ok(FillMapRsFile { file })
|
||||
}
|
||||
|
||||
fn add_font_inserting_line(&mut self, font_name: &str, font_file: &str) -> io::Result<()> {
|
||||
writeln!(
|
||||
self.file,
|
||||
" font_data_by_name.insert(\"{font_name}\", include_bytes!(\"{font_file}\"));",
|
||||
font_name = font_name,
|
||||
font_file = font_file
|
||||
)
|
||||
}
|
||||
|
||||
fn close_block(&mut self) -> io::Result<()> {
|
||||
writeln!(self.file, "}}")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
// ===================
|
||||
// === DejaVu Font ===
|
||||
// ===================
|
||||
|
||||
mod deja_vu {
|
||||
use crate::FillMapRsFile;
|
||||
|
||||
use enso_build_utilities::GithubRelease;
|
||||
use ensogl_text_embedded_fonts_names::DejaVuSans;
|
||||
use ensogl_text_embedded_fonts_names::FontFamily;
|
||||
use std::path;
|
||||
|
||||
pub const PACKAGE: GithubRelease<&str> = GithubRelease {
|
||||
project_url: "https://github.com/dejavu-fonts/dejavu-fonts/",
|
||||
version: "version_2_37",
|
||||
filename: "dejavu-fonts-ttf-2.37.zip",
|
||||
};
|
||||
|
||||
pub const PACKAGE_FONTS_PREFIX: &str = "dejavu-fonts-ttf-2.37/ttf";
|
||||
|
||||
pub fn font_file_from_font_name(font_name: &str) -> String {
|
||||
return format!("{}.ttf", font_name);
|
||||
}
|
||||
|
||||
pub fn extract_font(package_path: &path::Path, font_name: &str) {
|
||||
let font_file = font_file_from_font_name(font_name);
|
||||
let font_in_package_path = format!("{}/{}", PACKAGE_FONTS_PREFIX, font_file);
|
||||
let package_dir = package_path.parent().unwrap();
|
||||
let output_path = package_dir.join(font_file);
|
||||
|
||||
let archive_file = std::fs::File::open(package_path).unwrap();
|
||||
let mut archive = zip::ZipArchive::new(archive_file).unwrap();
|
||||
let mut input_stream = archive.by_name(font_in_package_path.as_str()).unwrap();
|
||||
let mut output_stream = std::fs::File::create(output_path).unwrap();
|
||||
std::io::copy(&mut input_stream, &mut output_stream).unwrap();
|
||||
}
|
||||
|
||||
pub const FONTS_TO_EXTRACT: &[&str] =
|
||||
&[DejaVuSans::regular(), DejaVuSans::bold(), DejaVuSans::mono(), DejaVuSans::mono_bold()];
|
||||
|
||||
pub fn extract_all_fonts(package_path: &path::Path) {
|
||||
for font_name in FONTS_TO_EXTRACT {
|
||||
extract_font(package_path, font_name);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn download_and_extract_all_fonts(out_dir: &path::Path) {
|
||||
let package_path = out_dir.join(PACKAGE.filename);
|
||||
PACKAGE.download(out_dir);
|
||||
extract_all_fonts(package_path.as_path());
|
||||
}
|
||||
|
||||
pub fn add_entries_to_fill_map_rs(file: &mut FillMapRsFile) {
|
||||
for font_name in FONTS_TO_EXTRACT {
|
||||
let font_file = font_file_from_font_name(font_name);
|
||||
file.add_font_inserting_line(font_name, font_file.as_str()).unwrap();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
println!("cargo:rerun-if-changed=build.rs");
|
||||
let out = env::var("OUT_DIR").unwrap();
|
||||
let out_dir = path::Path::new(&out);
|
||||
deja_vu::download_and_extract_all_fonts(out_dir);
|
||||
let fill_map_rs_path = out_dir.join("fill_map.rs");
|
||||
let mut fill_map_rs_file = FillMapRsFile::create(fill_map_rs_path).unwrap();
|
||||
deja_vu::add_entries_to_fill_map_rs(&mut fill_map_rs_file);
|
||||
fill_map_rs_file.close_block().unwrap();
|
||||
}
|
@ -1,8 +0,0 @@
|
||||
[package]
|
||||
name = "ensogl-text-embedded-fonts-names"
|
||||
version = "0.1.0"
|
||||
authors = ["Enso Team <contact@enso.org>"]
|
||||
edition = "2021"
|
||||
|
||||
[lib]
|
||||
crate-type = ["cdylib", "rlib"]
|
@ -1,55 +0,0 @@
|
||||
//! Font families containing the names of the fonts embedded in the app.
|
||||
|
||||
// === Features ===
|
||||
#![feature(const_trait_impl)]
|
||||
// === Standard Linter Configuration ===
|
||||
#![deny(non_ascii_idents)]
|
||||
#![warn(unsafe_code)]
|
||||
// === Non-Standard Linter Configuration ===
|
||||
#![warn(missing_copy_implementations)]
|
||||
#![warn(missing_debug_implementations)]
|
||||
#![warn(missing_docs)]
|
||||
#![warn(trivial_casts)]
|
||||
#![warn(trivial_numeric_casts)]
|
||||
#![warn(unused_import_braces)]
|
||||
#![warn(unused_qualifications)]
|
||||
|
||||
|
||||
|
||||
// ==================
|
||||
// === FontFamily ===
|
||||
// ==================
|
||||
|
||||
/// Trait with methods returning names of fonts in a font family.
|
||||
#[allow(missing_docs)]
|
||||
pub trait FontFamily {
|
||||
fn regular() -> &'static str;
|
||||
fn bold() -> &'static str;
|
||||
fn mono() -> &'static str;
|
||||
fn mono_bold() -> &'static str;
|
||||
}
|
||||
|
||||
|
||||
|
||||
// ==================
|
||||
// === DejaVuSans ===
|
||||
// ==================
|
||||
|
||||
/// A type with methods returning names of fonts in the DejaVuSans font family.
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub struct DejaVuSans {}
|
||||
|
||||
impl const FontFamily for DejaVuSans {
|
||||
fn regular() -> &'static str {
|
||||
"DejaVuSans"
|
||||
}
|
||||
fn bold() -> &'static str {
|
||||
"DejaVuSans-Bold"
|
||||
}
|
||||
fn mono() -> &'static str {
|
||||
"DejaVuSansMono"
|
||||
}
|
||||
fn mono_bold() -> &'static str {
|
||||
"DejaVuSansMono-Bold"
|
||||
}
|
||||
}
|
@ -1,88 +0,0 @@
|
||||
//! Implementation of embedded fonts loading and a definition of the [`DefaultFamily`] used in the
|
||||
//! app.
|
||||
|
||||
// === Standard Linter Configuration ===
|
||||
#![deny(non_ascii_idents)]
|
||||
#![warn(unsafe_code)]
|
||||
// === Non-Standard Linter Configuration ===
|
||||
#![warn(missing_copy_implementations)]
|
||||
#![warn(missing_debug_implementations)]
|
||||
#![warn(missing_docs)]
|
||||
#![warn(trivial_casts)]
|
||||
#![warn(trivial_numeric_casts)]
|
||||
#![warn(unused_import_braces)]
|
||||
#![warn(unused_qualifications)]
|
||||
|
||||
use enso_prelude::*;
|
||||
|
||||
use ensogl_text_embedded_fonts_names as embedded_fonts_names;
|
||||
|
||||
|
||||
// ==============
|
||||
// === Export ===
|
||||
// ==============
|
||||
|
||||
pub use embedded_fonts_names::FontFamily as Family;
|
||||
|
||||
|
||||
|
||||
/// The default font family used in the app.
|
||||
pub type DefaultFamily = embedded_fonts_names::DejaVuSans;
|
||||
|
||||
|
||||
|
||||
// =====================
|
||||
// === EmbeddedFonts ===
|
||||
// =====================
|
||||
|
||||
/// A base of built-in fonts in application
|
||||
///
|
||||
/// The structure keeps only a binary data in ttf format. The data should be then interpreted by
|
||||
/// user (e.g. by using msdf-sys crate).
|
||||
///
|
||||
/// For list of embedded fonts, see FONTS_TO_EXTRACT constant in `build.rs`.
|
||||
#[allow(missing_docs)]
|
||||
pub struct EmbeddedFonts {
|
||||
pub font_data_by_name: HashMap<&'static str, &'static [u8]>,
|
||||
}
|
||||
|
||||
impl EmbeddedFonts {
|
||||
/// Creates an embedded fonts base filled with data.
|
||||
///
|
||||
/// For list of embedded fonts, see `FONTS_TO_EXTRACT` constant in `build.rs`
|
||||
pub fn create_and_fill() -> EmbeddedFonts {
|
||||
let mut font_data_by_name = HashMap::<&'static str, &'static [u8]>::new();
|
||||
include!(concat!(env!("OUT_DIR"), "/fill_map.rs"));
|
||||
EmbeddedFonts { font_data_by_name }
|
||||
}
|
||||
}
|
||||
|
||||
impl Debug for EmbeddedFonts {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.write_str("<Embedded fonts>")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
// =============
|
||||
// === Tests ===
|
||||
// =============
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use crate::*;
|
||||
|
||||
use ensogl_text_embedded_fonts_names::DejaVuSans;
|
||||
use ensogl_text_embedded_fonts_names::FontFamily;
|
||||
|
||||
#[test]
|
||||
fn loading_embedded_fonts() {
|
||||
let fonts = EmbeddedFonts::create_and_fill();
|
||||
let example_font = fonts.font_data_by_name.get(DejaVuSans::regular()).unwrap();
|
||||
|
||||
assert_eq!(0x00, example_font[0]);
|
||||
assert_eq!(0x01, example_font[1]);
|
||||
assert_eq!(0x1d, example_font[example_font.len() - 1]);
|
||||
}
|
||||
}
|
@ -1,2 +0,0 @@
|
||||
# A thirdparty downloaded js
|
||||
msdfgen_wasm.js
|
@ -1,23 +0,0 @@
|
||||
[package]
|
||||
name = "ensogl-text-msdf-sys"
|
||||
version = "0.1.0"
|
||||
authors = ["Enso Team <contact@enso.org>"]
|
||||
edition = "2021"
|
||||
|
||||
[lib]
|
||||
crate-type = ["cdylib", "rlib"]
|
||||
|
||||
[dependencies]
|
||||
enso-prelude = { path = "../../../../prelude" }
|
||||
js-sys = { version = "0.3" }
|
||||
nalgebra = { version = "0.26.1" }
|
||||
wasm-bindgen = { version = "0.2.78" }
|
||||
|
||||
[dev-dependencies]
|
||||
wasm-bindgen-test = { version = "0.3.8" }
|
||||
futures = { version = "0.3.1" }
|
||||
ensogl-text-embedded-fonts = { path = "../embedded-fonts" }
|
||||
ensogl-text-embedded-fonts-names = { path = "../embedded-fonts/names" }
|
||||
|
||||
[build-dependencies]
|
||||
enso-build-utilities = { path = "../../../../build-utils" }
|
@ -12,12 +12,12 @@ use crate::buffer::Transform;
|
||||
use crate::component::selection;
|
||||
use crate::component::Selection;
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
use crate::typeface;
|
||||
use crate::font;
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
use crate::typeface::glyph;
|
||||
use crate::typeface::glyph::Glyph;
|
||||
use crate::font::glyph;
|
||||
use crate::font::glyph::Glyph;
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
use crate::typeface::pen;
|
||||
use crate::font::pen;
|
||||
|
||||
use enso_frp as frp;
|
||||
use enso_frp::io::keyboard::Key;
|
||||
@ -29,10 +29,6 @@ use ensogl_core::display;
|
||||
use ensogl_core::gui::cursor;
|
||||
use ensogl_core::system::web::clipboard;
|
||||
use ensogl_core::DEPRECATED_Animation;
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
use ensogl_text_embedded_fonts as embedded_fonts;
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
use ensogl_text_embedded_fonts::Family;
|
||||
use std::ops::Not;
|
||||
|
||||
|
||||
@ -270,7 +266,7 @@ ensogl_core::define_endpoints! {
|
||||
set_default_color (color::Rgba),
|
||||
set_selection_color (color::Rgb),
|
||||
set_default_text_size (style::Size),
|
||||
/// Set font in the text area. The name will be looked up in [`typeface::font::Registry`].
|
||||
/// Set font in the text area. The name will be looked up in [`font::Registry`].
|
||||
///
|
||||
/// Note, that this is a relatively heavy operation - it requires not only redrawing all
|
||||
/// lines, but also re-load internal structures for rendering (like WebGL buffers,
|
||||
@ -612,9 +608,9 @@ impl AreaModel {
|
||||
let display_object = display::object::Instance::new(&logger);
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
let glyph_system = {
|
||||
let fonts = scene.extension::<typeface::font::Registry>();
|
||||
let font = fonts.load(embedded_fonts::DefaultFamily::mono());
|
||||
let glyph_system = typeface::glyph::System::new(&scene, font);
|
||||
let fonts = scene.extension::<font::Registry>();
|
||||
let font = fonts.load(font::DEFAULT_FONT_MONO);
|
||||
let glyph_system = font::glyph::System::new(&scene, font);
|
||||
display_object.add_child(&glyph_system);
|
||||
Rc::new(RefCell::new(glyph_system))
|
||||
};
|
||||
@ -824,7 +820,14 @@ impl AreaModel {
|
||||
let next = iter.next();
|
||||
let style = line_style.next().unwrap_or_default();
|
||||
let chr_size = style.size.raw;
|
||||
let char_info = next.as_ref().map(|t| pen::CharInfo::new(t.1, chr_size));
|
||||
let char_info = next.as_ref().map(|(glyph, ch)| {
|
||||
pen::CharInfo::new(
|
||||
*ch,
|
||||
chr_size,
|
||||
glyph.properties.get(),
|
||||
glyph.variations.borrow().clone(),
|
||||
)
|
||||
});
|
||||
let info = pen.advance(char_info);
|
||||
|
||||
cursor_map.get(&column).for_each(|id| {
|
||||
@ -843,14 +846,25 @@ impl AreaModel {
|
||||
Some((glyph, chr)) => {
|
||||
let chr_bytes: Bytes = chr.len_utf8().into();
|
||||
line_style.drop(chr_bytes - 1.bytes());
|
||||
let glyph_info = glyph_system.font.glyph_info(chr);
|
||||
let glyph_id = glyph_system
|
||||
.font
|
||||
.glyph_id_of_code_point(
|
||||
glyph.properties.get(),
|
||||
&glyph.variations.borrow(),
|
||||
chr,
|
||||
)
|
||||
.unwrap(); // FIXME[WD] to be fixed in https://www.pivotaltracker.com/story/show/182746060
|
||||
let glyph_info = glyph_system
|
||||
.font
|
||||
.glyph_info(glyph.properties.get(), &glyph.variations.borrow(), glyph_id)
|
||||
.unwrap(); // FIXME[WD] to be fixed in https://www.pivotaltracker.com/story/show/182746060
|
||||
let glyph_offset = glyph_info.offset.scale(chr_size);
|
||||
let glyph_x = info.offset + glyph_offset.x;
|
||||
let glyph_y = glyph_offset.y;
|
||||
glyph.set_position_xy(Vector2(glyph_x, glyph_y));
|
||||
glyph.set_char(chr);
|
||||
glyph.set_color(style.color);
|
||||
glyph.set_bold(style.bold.raw);
|
||||
// glyph.set_bold(style.bold.raw); // FIXME[WD] to be fixed in https://www.pivotaltracker.com/story/show/182746060
|
||||
glyph.set_sdf_bold(style.sdf_bold.raw);
|
||||
glyph.set_font_size(chr_size);
|
||||
match &last_cursor {
|
||||
@ -878,6 +892,7 @@ impl AreaModel {
|
||||
line.to_string()
|
||||
}
|
||||
|
||||
// FIXME: to be rewritten with the new line layouter. https://www.pivotaltracker.com/story/show/182746060
|
||||
/// Truncate a `line` of text if its length on screen exceeds `max_width_px` when rendered
|
||||
/// using the current font at `font_size`. Return the truncated string with an ellipsis ("…")
|
||||
/// character appended, or `content` if not truncated.
|
||||
@ -888,32 +903,33 @@ impl AreaModel {
|
||||
fn line_truncated_with_ellipsis(
|
||||
&self,
|
||||
line: &str,
|
||||
font_size: style::Size,
|
||||
max_width_px: f32,
|
||||
_font_size: style::Size,
|
||||
_max_width_px: f32,
|
||||
) -> String {
|
||||
const ELLIPSIS: char = '\u{2026}';
|
||||
let mut pen = pen::Pen::new(&self.glyph_system.borrow().font);
|
||||
let mut truncation_point = 0.bytes();
|
||||
let truncate = line.char_indices().any(|(i, ch)| {
|
||||
let char_info = pen::CharInfo::new(ch, font_size.raw);
|
||||
let pen_info = pen.advance(Some(char_info));
|
||||
let next_width = pen_info.offset + char_info.size;
|
||||
if next_width > max_width_px {
|
||||
return true;
|
||||
}
|
||||
let width_of_ellipsis = pen::CharInfo::new(ELLIPSIS, font_size.raw).size;
|
||||
let char_length: Bytes = ch.len_utf8().into();
|
||||
if next_width + width_of_ellipsis <= max_width_px {
|
||||
truncation_point = Bytes::from(i) + char_length;
|
||||
}
|
||||
false
|
||||
});
|
||||
if truncate {
|
||||
let truncated_content = line[..truncation_point.as_usize()].to_string();
|
||||
truncated_content + String::from(ELLIPSIS).as_str()
|
||||
} else {
|
||||
line.to_string()
|
||||
}
|
||||
// const ELLIPSIS: char = '\u{2026}';
|
||||
// let mut pen = pen::Pen::new(&self.glyph_system.borrow().font);
|
||||
// let mut truncation_point = 0.bytes();
|
||||
// let truncate = line.char_indices().any(|(i, ch)| {
|
||||
// let char_info = pen::CharInfo::new(ch, font_size.raw);
|
||||
// let pen_info = pen.advance(Some(char_info));
|
||||
// let next_width = pen_info.offset + char_info.size;
|
||||
// if next_width > max_width_px {
|
||||
// return true;
|
||||
// }
|
||||
// let width_of_ellipsis = pen::CharInfo::new(ELLIPSIS, font_size.raw).size;
|
||||
// let char_length: Bytes = ch.len_utf8().into();
|
||||
// if next_width + width_of_ellipsis <= max_width_px {
|
||||
// truncation_point = Bytes::from(i) + char_length;
|
||||
// }
|
||||
// false
|
||||
// });
|
||||
// if truncate {
|
||||
// let truncated_content = line[..truncation_point.as_usize()].to_string();
|
||||
// truncated_content + String::from(ELLIPSIS).as_str()
|
||||
// } else {
|
||||
// line.to_string()
|
||||
// }
|
||||
line.to_string()
|
||||
}
|
||||
|
||||
/// Truncate trailing characters on every line of `text` that exceeds `max_width_px` when
|
||||
@ -1028,9 +1044,9 @@ impl AreaModel {
|
||||
fn set_font(&self, font_name: &str) {
|
||||
let app = &self.app;
|
||||
let scene = &app.display.default_scene;
|
||||
let fonts = scene.extension::<typeface::font::Registry>();
|
||||
let fonts = scene.extension::<font::Registry>();
|
||||
let font = fonts.load(font_name);
|
||||
let glyph_system = typeface::glyph::System::new(&scene, font);
|
||||
let glyph_system = font::glyph::System::new(&scene, font);
|
||||
self.display_object.add_child(&glyph_system);
|
||||
let old_glyph_system = self.glyph_system.replace(glyph_system);
|
||||
self.display_object.remove_child(&old_glyph_system);
|
||||
|
657
lib/rust/ensogl/component/text/src/font.rs
Normal file
657
lib/rust/ensogl/component/text/src/font.rs
Normal file
@ -0,0 +1,657 @@
|
||||
//! Definition of font, font face, and font registry. Aggregates information and utilities for
|
||||
//! working with fonts.
|
||||
|
||||
use crate::prelude::*;
|
||||
|
||||
use enso_shapely::shared;
|
||||
use ensogl_core::display::scene;
|
||||
use ensogl_text_embedded_fonts::Embedded;
|
||||
use ensogl_text_msdf as msdf;
|
||||
use ordered_float::NotNan;
|
||||
use owned_ttf_parser as ttf;
|
||||
use std::collections::hash_map::Entry;
|
||||
use ttf::AsFaceRef;
|
||||
|
||||
|
||||
// ==============
|
||||
// === Export ===
|
||||
// ==============
|
||||
|
||||
pub mod glyph;
|
||||
pub mod glyph_render_info;
|
||||
pub mod pen;
|
||||
|
||||
pub use ensogl_text_font_family as family;
|
||||
pub use family::Name;
|
||||
pub use family::NonVariableFaceHeader;
|
||||
pub use glyph_render_info::GlyphRenderInfo;
|
||||
pub use ttf::GlyphId;
|
||||
pub use ttf::Style;
|
||||
pub use ttf::Tag;
|
||||
pub use ttf::Weight;
|
||||
pub use ttf::Width;
|
||||
|
||||
|
||||
|
||||
// =================
|
||||
// === Constants ===
|
||||
// =================
|
||||
|
||||
/// TTF files can contain multiple face definitions. We support only the first defined, just as
|
||||
/// most web browsers (you cannot define `@font-face` in CSS for multiple faces of the same file).
|
||||
const TTF_FONT_FACE_INDEX: u32 = 0;
|
||||
|
||||
/// A string literal that means a default non-monospace font.
|
||||
pub const DEFAULT_FONT: &str = "default";
|
||||
|
||||
/// A string literal that means a default monospace font.
|
||||
pub const DEFAULT_FONT_MONO: &str = "default-mono";
|
||||
|
||||
|
||||
|
||||
// =====================
|
||||
// === VariationAxis ===
|
||||
// =====================
|
||||
|
||||
/// A variation axis of variable fonts. The axis name is [`Tag`], which is a 4-bytes identifier
|
||||
/// constructed from the axis name, e.g. by `Tag::from_bytes(b"ital")`. See the following link to
|
||||
/// learn more:
|
||||
/// https://docs.microsoft.com/en-us/typography/opentype/spec/dvaraxisreg#registered-axis-tags
|
||||
#[allow(missing_docs)]
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
pub struct VariationAxis {
|
||||
tag: Tag,
|
||||
value: NotNan<f32>,
|
||||
}
|
||||
|
||||
impl VariationAxis {
|
||||
/// Constructor
|
||||
pub fn new(tag: Tag, value: NotNan<f32>) -> Self {
|
||||
Self { tag, value }
|
||||
}
|
||||
|
||||
/// Constructor.
|
||||
pub fn from_bytes(bytes: &[u8; 4], value: NotNan<f32>) -> Self {
|
||||
let tag = Tag::from_bytes(bytes);
|
||||
Self { tag, value }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
// =====================
|
||||
// === VariationAxes ===
|
||||
// =====================
|
||||
|
||||
/// Variation axes of variable fonts.
|
||||
#[allow(missing_docs)]
|
||||
#[derive(Debug, Default, Clone, PartialEq, Eq, Hash)]
|
||||
pub struct VariationAxes {
|
||||
pub vec: Vec<VariationAxis>,
|
||||
}
|
||||
|
||||
impl VariationAxes {
|
||||
/// Map a function over all standard axes. Not all fonts have to support them, but it is a good
|
||||
/// idea to set these values when loading a font. Otherwise, some fonts might not be visible
|
||||
/// on the screen, as for example their width might default to zero.
|
||||
pub fn with_default_axes_values(f: impl Fn(VariationAxis)) {
|
||||
let mut axes = Self::default();
|
||||
axes.set_weight(Weight::Normal);
|
||||
axes.set_width(Width::Normal);
|
||||
axes.set_style(Style::Normal);
|
||||
axes.with_axes(f);
|
||||
}
|
||||
|
||||
/// Map a function over all changed axes.
|
||||
pub fn with_axes(&self, f: impl Fn(VariationAxis)) {
|
||||
for axis in &self.vec {
|
||||
f(*axis);
|
||||
}
|
||||
}
|
||||
|
||||
/// Variation axis setter.
|
||||
pub fn set(&mut self, axis: VariationAxis) {
|
||||
if let Some(index) = self.vec.iter().position(|a| a.tag == axis.tag) {
|
||||
self.vec[index] = axis;
|
||||
} else {
|
||||
self.vec.push(axis);
|
||||
}
|
||||
}
|
||||
|
||||
/// Variation axis setter. “Italic” (`ital` in CSS) is an axis found in some variable fonts. It
|
||||
/// controls the font file’s italic parameter, with italics either turned “off” or “on”, rather
|
||||
/// than gradually changing over a range. The Google Fonts CSS v2 API defines the axis as:
|
||||
/// Default: 0 Min: 0 Max: 1 Step: 0.1
|
||||
/// https://fonts.google.com/knowledge/glossary/italic_axis
|
||||
pub fn set_ital(&mut self, value: NotNan<f32>) {
|
||||
self.set(VariationAxis::from_bytes(b"ital", value));
|
||||
}
|
||||
|
||||
/// Variation axis setter. “Optical Size” (controlled with `font-optical-sizing` or
|
||||
/// `font-variation-setting`: ‘opsz’ VALUE in CSS) is an axis found in some variable fonts. It
|
||||
/// controls the font file’s optical size optimizations. The Google Fonts CSS v2 API defines the
|
||||
/// axis as:
|
||||
/// Default: 14 Min: 6 Max: 144 Step: 0.1
|
||||
/// https://fonts.google.com/knowledge/glossary/optical_size_axis
|
||||
pub fn set_opsz(&mut self, value: NotNan<f32>) {
|
||||
self.set(VariationAxis::from_bytes(b"opsz", value));
|
||||
}
|
||||
|
||||
/// Variation axis setter. Slant (`slnt` in CSS) is an axis found in some variable fonts. It
|
||||
/// controls the font file’s slant parameter for oblique styles. The Google Fonts CSS v2 API
|
||||
/// defines the axis as:
|
||||
/// Default: 0 Min: -90 Max: 90 Step: 1
|
||||
/// https://fonts.google.com/knowledge/glossary/slant_axis
|
||||
pub fn set_slnt(&mut self, value: NotNan<f32>) {
|
||||
self.set(VariationAxis::from_bytes(b"slnt", value));
|
||||
}
|
||||
|
||||
/// Variation axis setter. “Weight” (`wght` in CSS) is an axis found in many variable fonts. It
|
||||
/// controls the font file’s weight parameter. The Google Fonts CSS v2 API defines the axis as:
|
||||
/// Default: 400 Min: 1 Max: 1000 Step: 1
|
||||
/// https://fonts.google.com/knowledge/glossary/weight_axis
|
||||
pub fn set_wght(&mut self, value: NotNan<f32>) {
|
||||
self.set(VariationAxis::from_bytes(b"wght", value));
|
||||
}
|
||||
|
||||
/// Variation axis setter. “Width” (`wdth` in CSS) is an axis found in some variable fonts. It
|
||||
/// controls the font file’s width parameter. The Google Fonts CSS v2 API defines the axis as:
|
||||
/// Default: 100 Min: 25 Max: 200 Step: 0.1
|
||||
/// https://fonts.google.com/knowledge/glossary/width_axis
|
||||
pub fn set_wdth(&mut self, value: NotNan<f32>) {
|
||||
self.set(VariationAxis::from_bytes(b"wdth", value));
|
||||
}
|
||||
|
||||
/// Weight setter.
|
||||
pub fn set_weight(&mut self, value: Weight) {
|
||||
self.set_wght(value.to_number().into());
|
||||
}
|
||||
|
||||
/// Width setter.
|
||||
pub fn set_width(&mut self, value: Width) {
|
||||
let wdth = match value {
|
||||
Width::UltraCondensed => 25.0,
|
||||
Width::ExtraCondensed => 43.75,
|
||||
Width::Condensed => 62.5,
|
||||
Width::SemiCondensed => 81.25,
|
||||
Width::Normal => 100.0,
|
||||
Width::SemiExpanded => 118.75,
|
||||
Width::Expanded => 137.5,
|
||||
Width::ExtraExpanded => 156.25,
|
||||
Width::UltraExpanded => 175.0,
|
||||
};
|
||||
self.set_wdth(NotNan::new(wdth).unwrap());
|
||||
}
|
||||
|
||||
/// Style setter.
|
||||
pub fn set_style(&mut self, value: Style) {
|
||||
match value {
|
||||
Style::Normal => {
|
||||
self.set_ital(0_u16.into());
|
||||
self.set_slnt(0_u16.into());
|
||||
}
|
||||
Style::Italic => {
|
||||
self.set_ital(1_u16.into());
|
||||
self.set_slnt(0_u16.into());
|
||||
}
|
||||
Style::Oblique => {
|
||||
self.set_ital(0_u16.into());
|
||||
self.set_slnt(90_u16.into());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
// ============
|
||||
// === Face ===
|
||||
// ============
|
||||
|
||||
/// A face of a font. In case of non-variable fonts, a face corresponds to a font variation defined
|
||||
/// as a triple (width, weight, style), see [`NonVariableFaceHeader`]. In case of variable fonts,
|
||||
/// the font variation ([`VariationAxes`]) is set up at runtime, so only one face is needed.
|
||||
///
|
||||
/// The face consists of a [ttf face](ttf::OwnedFace) and [MSDF one](msdf::OwnedFace). The former
|
||||
/// contains all information needed to layout glyphs, like kerning. The latter is used to generate
|
||||
/// MSDF textures for glyphs.
|
||||
#[allow(missing_docs)]
|
||||
#[derive(Debug)]
|
||||
pub struct Face {
|
||||
pub msdf: msdf::OwnedFace,
|
||||
pub ttf: ttf::OwnedFace,
|
||||
}
|
||||
|
||||
impl Face {
|
||||
/// Load the font face from memory. Corrupted faces will be reported.
|
||||
fn load_from_memory(name: &str, embedded: &Embedded) -> Option<Face> {
|
||||
embedded.data.get(name).and_then(|data| {
|
||||
let result =
|
||||
ttf::OwnedFace::from_vec((**data).into(), TTF_FONT_FACE_INDEX).map(|ttf| {
|
||||
let msdf = msdf::OwnedFace::load_from_memory(data);
|
||||
Face { msdf, ttf }
|
||||
});
|
||||
result.map_err(|err| event!(ERROR, "Error parsing font: {}", err)).ok()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
// ==============
|
||||
// === Family ===
|
||||
// ==============
|
||||
|
||||
/// A generalization of a font family, a set of font faces. Allows borrowing a font face based on
|
||||
/// variations.
|
||||
pub trait Family {
|
||||
/// For non-variable fonts, variations is a triple (width, weight, style), see
|
||||
/// [`NonVariableFaceHeader`] to learn more. For variable faces, the variation is
|
||||
/// [`VariationAxes`], however, as variable fonts have one face only, this parameter is not
|
||||
/// used while borrowing the face.
|
||||
type Variations: Eq + Hash + Clone + Debug;
|
||||
|
||||
/// Update MSDFgen settings for given variations. For non-variable fonts, this function is a
|
||||
/// no-op.
|
||||
fn update_msdfgen_variations(&self, variations: &Self::Variations);
|
||||
/// Run the function with borrowed font face for the given variations set. For non-variable
|
||||
/// fonts, the function will not be run if variations do not match a known definition. For
|
||||
/// variable fonts, the function always succeeds.
|
||||
fn with_borrowed_face<F, T>(&self, variations: &Self::Variations, f: F) -> Option<T>
|
||||
where F: for<'a> FnOnce(&'a Face) -> T;
|
||||
}
|
||||
|
||||
/// A non-variable font family.
|
||||
#[allow(missing_docs)]
|
||||
#[derive(Debug)]
|
||||
pub struct NonVariableFamily {
|
||||
pub definition: family::NonVariableDefinition,
|
||||
pub faces: Rc<RefCell<HashMap<NonVariableFaceHeader, Face>>>,
|
||||
}
|
||||
|
||||
/// A variable font family. Contains font family definition and the font face. The face is kept in
|
||||
/// an `Option` because it is created after the family initialization. Currently, it could be
|
||||
/// simplified, but it is already designed in this way to support on-demand face loading (served
|
||||
/// from server when needed).
|
||||
#[allow(missing_docs)]
|
||||
#[derive(Debug)]
|
||||
pub struct VariableFamily {
|
||||
pub definition: family::VariableDefinition,
|
||||
pub face: Rc<RefCell<Option<Face>>>,
|
||||
/// Most recent axes used to generate MSDF textures. If axes change, MSDFgen parameters need to
|
||||
/// be updated, which involves a non-zero cost (mostly due to Rust <> JS interop). Thus, we
|
||||
/// want to refresh them only when needed. This field is a cache allowing us to check if
|
||||
/// axes changed.
|
||||
pub last_axes: Rc<RefCell<Option<VariationAxes>>>,
|
||||
}
|
||||
|
||||
impl NonVariableFamily {
|
||||
/// Load all font faces from the embedded font data. Corrupted faces will be reported and
|
||||
/// ignored.
|
||||
fn load_all_faces(&self, embedded: &Embedded) {
|
||||
for (header, file_name) in &self.definition.map {
|
||||
if let Some(face) = Face::load_from_memory(&*file_name, embedded) {
|
||||
self.faces.borrow_mut().insert(*header, face);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl VariableFamily {
|
||||
/// Load all font faces from the embedded font data. Corrupted faces will be reported and
|
||||
/// ignored.
|
||||
fn load_all_faces(&self, embedded: &Embedded) {
|
||||
if let Some(face) = Face::load_from_memory(&self.definition.file_name, embedded) {
|
||||
// Set default variation axes during face initialization. This is needed to make some
|
||||
// fonts appear on the screen. In case some axes are not found, warnings will be
|
||||
// silenced.
|
||||
VariationAxes::with_default_axes_values(|axis| {
|
||||
face.msdf.set_variation_axis(axis.tag, axis.value.into_inner() as f64).ok();
|
||||
});
|
||||
self.face.borrow_mut().replace(face);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Family for NonVariableFamily {
|
||||
type Variations = NonVariableFaceHeader;
|
||||
fn update_msdfgen_variations(&self, _variations: &Self::Variations) {}
|
||||
fn with_borrowed_face<F, T>(&self, variations: &Self::Variations, f: F) -> Option<T>
|
||||
where F: for<'a> FnOnce(&'a Face) -> T {
|
||||
self.faces.borrow().get(variations).map(f)
|
||||
}
|
||||
}
|
||||
|
||||
impl Family for VariableFamily {
|
||||
type Variations = VariationAxes;
|
||||
fn update_msdfgen_variations(&self, variations: &Self::Variations) {
|
||||
if let Some(face) = self.face.borrow().as_ref() {
|
||||
if self.last_axes.borrow().as_ref() != Some(variations) {
|
||||
self.last_axes.borrow_mut().replace(variations.clone());
|
||||
variations.with_axes(|axis| {
|
||||
let value = axis.value.into_inner() as f64;
|
||||
face.msdf
|
||||
.set_variation_axis(axis.tag, value)
|
||||
.map_err(|err| {
|
||||
event!(WARN, "Error setting font variation axis: {}", err);
|
||||
})
|
||||
.ok();
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn with_borrowed_face<F, T>(&self, _variations: &Self::Variations, f: F) -> Option<T>
|
||||
where F: for<'a> FnOnce(&'a Face) -> T {
|
||||
self.face.borrow().as_ref().map(f)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&family::VariableDefinition> for VariableFamily {
|
||||
fn from(definition: &family::VariableDefinition) -> Self {
|
||||
let definition = definition.clone();
|
||||
Self { definition, face: default(), last_axes: default() }
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&family::NonVariableDefinition> for NonVariableFamily {
|
||||
fn from(definition: &family::NonVariableDefinition) -> Self {
|
||||
let definition = definition.clone();
|
||||
Self { definition, faces: default() }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
// ============
|
||||
// === Font ===
|
||||
// ============
|
||||
|
||||
/// A typeface, commonly referred to as a font.
|
||||
#[allow(missing_docs)]
|
||||
#[derive(Debug, Clone, CloneRef, From)]
|
||||
pub enum Font {
|
||||
NonVariable(NonVariableFont),
|
||||
Variable(VariableFont),
|
||||
}
|
||||
|
||||
/// A non-variable version of [`Font`].
|
||||
pub type NonVariableFont = FontTemplate<NonVariableFamily>;
|
||||
|
||||
/// A variable version of [`Font`].
|
||||
pub type VariableFont = FontTemplate<VariableFamily>;
|
||||
|
||||
impl Font {
|
||||
/// List all possible weights. In case of variable fonts, [`None`] will be returned.
|
||||
pub fn possible_weights(&self) -> Option<Vec<Weight>> {
|
||||
match self {
|
||||
Font::NonVariable(font) => Some(font.family.definition.possible_weights()),
|
||||
Font::Variable(_) => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Get render info for one character, generating one if not found.
|
||||
pub fn glyph_info(
|
||||
&self,
|
||||
non_variable_font_variations: NonVariableFaceHeader,
|
||||
variable_font_variations: &VariationAxes,
|
||||
glyph_id: GlyphId,
|
||||
) -> Option<GlyphRenderInfo> {
|
||||
match self {
|
||||
Font::NonVariable(font) => font.glyph_info(&non_variable_font_variations, glyph_id),
|
||||
Font::Variable(font) => font.glyph_info(variable_font_variations, glyph_id),
|
||||
}
|
||||
}
|
||||
|
||||
// FIXME[WD]: Remove after all APIs will use GlyphIds (incl. pen API).
|
||||
// https://www.pivotaltracker.com/story/show/182746060
|
||||
/// Get the glyph id of the provided code point.
|
||||
pub fn glyph_id_of_code_point(
|
||||
&self,
|
||||
non_variable_font_variations: NonVariableFaceHeader,
|
||||
variable_font_variations: &VariationAxes,
|
||||
code_point: char,
|
||||
) -> Option<GlyphId> {
|
||||
match self {
|
||||
Font::NonVariable(font) =>
|
||||
font.glyph_id_of_code_point(&non_variable_font_variations, code_point),
|
||||
Font::Variable(font) =>
|
||||
font.glyph_id_of_code_point(variable_font_variations, code_point),
|
||||
}
|
||||
}
|
||||
|
||||
/// Get number of rows in MSDF texture.
|
||||
pub fn msdf_texture_rows(&self) -> usize {
|
||||
match self {
|
||||
Font::NonVariable(font) => font.msdf_texture_rows(),
|
||||
Font::Variable(font) => font.msdf_texture_rows(),
|
||||
}
|
||||
}
|
||||
|
||||
/// A whole MSDF texture bound for this font.
|
||||
pub fn with_borrowed_msdf_texture_data<R>(&self, operation: impl FnOnce(&[u8]) -> R) -> R {
|
||||
match self {
|
||||
Font::NonVariable(font) => font.with_borrowed_msdf_texture_data(operation),
|
||||
Font::Variable(font) => font.with_borrowed_msdf_texture_data(operation),
|
||||
}
|
||||
}
|
||||
|
||||
/// Get kerning between two characters.
|
||||
pub fn kerning(
|
||||
&self,
|
||||
non_variable_font_variations: NonVariableFaceHeader,
|
||||
variable_font_variations: &VariationAxes,
|
||||
left: GlyphId,
|
||||
right: GlyphId,
|
||||
) -> f32 {
|
||||
match self {
|
||||
Font::NonVariable(font) => font.kerning(&non_variable_font_variations, left, right),
|
||||
Font::Variable(font) => font.kerning(variable_font_variations, left, right),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
// ====================
|
||||
// === FontTemplate ===
|
||||
// ====================
|
||||
|
||||
/// Internal representation of [`Font`]. It contains references to the font family definition,
|
||||
/// a texture with MSDF-encoded glyph shapes, and a cache for common glyph properties, used to
|
||||
/// layout glyphs.
|
||||
#[derive(Deref, Derivative, CloneRef, Debug)]
|
||||
#[derivative(Clone(bound = ""))]
|
||||
pub struct FontTemplate<F: Family> {
|
||||
rc: Rc<FontTemplateData<F>>,
|
||||
}
|
||||
|
||||
/// Internal representation of [`FontTemplate`].
|
||||
#[derive(Debug)]
|
||||
#[allow(missing_docs)]
|
||||
pub struct FontTemplateData<F: Family> {
|
||||
pub name: Name,
|
||||
pub family: F,
|
||||
pub atlas: msdf::Texture,
|
||||
pub cache: RefCell<HashMap<F::Variations, FontDataCache>>,
|
||||
// FIXME[WD]: Remove after all APIs will use GlyphIds (incl. pen API).
|
||||
// https://www.pivotaltracker.com/story/show/182746060
|
||||
pub glyph_id_to_code_point: RefCell<HashMap<GlyphId, char>>,
|
||||
}
|
||||
|
||||
/// A cache for common glyph properties, used to layout glyphs.
|
||||
#[derive(Debug, Default)]
|
||||
pub struct FontDataCache {
|
||||
kerning: HashMap<(GlyphId, GlyphId), f32>,
|
||||
glyphs: HashMap<GlyphId, GlyphRenderInfo>,
|
||||
}
|
||||
|
||||
impl<F: Family> From<FontTemplateData<F>> for FontTemplate<F> {
|
||||
fn from(t: FontTemplateData<F>) -> Self {
|
||||
let rc = Rc::new(t);
|
||||
Self { rc }
|
||||
}
|
||||
}
|
||||
|
||||
impl<F: Family> FontTemplate<F> {
|
||||
/// Constructor.
|
||||
pub fn new(name: Name, family: impl Into<F>) -> Self {
|
||||
let atlas = default();
|
||||
let cache = default();
|
||||
let family = family.into();
|
||||
let glyph_id_to_code_point = default();
|
||||
let data = FontTemplateData { name, family, atlas, cache, glyph_id_to_code_point };
|
||||
Self { rc: Rc::new(data) }
|
||||
}
|
||||
|
||||
// FIXME[WD]: Remove after all APIs will use GlyphIds (incl. pen API).
|
||||
// https://www.pivotaltracker.com/story/show/182746060
|
||||
/// Get the glyph id of the provided code point.
|
||||
pub fn glyph_id_of_code_point(
|
||||
&self,
|
||||
variations: &F::Variations,
|
||||
code_point: char,
|
||||
) -> Option<GlyphId> {
|
||||
self.family
|
||||
.with_borrowed_face(variations, |face| {
|
||||
face.ttf.as_face_ref().glyph_index(code_point).map(|id| {
|
||||
self.glyph_id_to_code_point.borrow_mut().insert(id, code_point);
|
||||
id
|
||||
})
|
||||
})
|
||||
.flatten()
|
||||
}
|
||||
|
||||
/// Get render info for one character, generating one if not found.
|
||||
pub fn glyph_info(
|
||||
&self,
|
||||
variations: &F::Variations,
|
||||
glyph_id: GlyphId,
|
||||
) -> Option<GlyphRenderInfo> {
|
||||
let opt_render_info =
|
||||
self.cache.borrow().get(variations).and_then(|t| t.glyphs.get(&glyph_id)).copied();
|
||||
if opt_render_info.is_some() {
|
||||
opt_render_info
|
||||
} else {
|
||||
self.family.update_msdfgen_variations(variations);
|
||||
self.family.with_borrowed_face(variations, |face| {
|
||||
let render_info = GlyphRenderInfo::load(&face.msdf, glyph_id, &self.atlas);
|
||||
if !self.cache.borrow().contains_key(variations) {
|
||||
self.cache.borrow_mut().insert(variations.clone(), default());
|
||||
}
|
||||
let mut borrowed_cache = self.cache.borrow_mut();
|
||||
let font_data_cache = borrowed_cache.get_mut(variations).unwrap();
|
||||
font_data_cache.glyphs.insert(glyph_id, render_info);
|
||||
render_info
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// Get kerning between two characters.
|
||||
pub fn kerning(&self, variations: &F::Variations, left: GlyphId, right: GlyphId) -> f32 {
|
||||
self.family
|
||||
.with_borrowed_face(variations, |face| {
|
||||
if !self.cache.borrow().contains_key(variations) {
|
||||
self.cache.borrow_mut().insert(variations.clone(), default());
|
||||
}
|
||||
let mut borrowed_cache = self.cache.borrow_mut();
|
||||
let font_data_cache = borrowed_cache.get_mut(variations).unwrap();
|
||||
*font_data_cache.kerning.entry((left, right)).or_insert_with(|| {
|
||||
let tables = face.ttf.as_face_ref().tables();
|
||||
let units_per_em = tables.head.units_per_em;
|
||||
let kern_table = tables.kern.and_then(|t| t.subtables.into_iter().next());
|
||||
let kerning = kern_table.and_then(|t| t.glyphs_kerning(left, right));
|
||||
kerning.unwrap_or_default() as f32 / units_per_em as f32
|
||||
})
|
||||
})
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
/// A whole MSDF texture bound for this font.
|
||||
pub fn with_borrowed_msdf_texture_data<R>(&self, operation: impl FnOnce(&[u8]) -> R) -> R {
|
||||
self.atlas.with_borrowed_data(operation)
|
||||
}
|
||||
|
||||
/// Get number of rows in MSDF texture.
|
||||
pub fn msdf_texture_rows(&self) -> usize {
|
||||
self.atlas.rows()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
// ================
|
||||
// === Registry ===
|
||||
// ================
|
||||
|
||||
shared! { Registry
|
||||
/// Structure keeping all fonts loaded from different sources.
|
||||
#[derive(Debug)]
|
||||
pub struct RegistryData {
|
||||
embedded: Embedded,
|
||||
fonts: HashMap<Name,Font>,
|
||||
}
|
||||
|
||||
impl {
|
||||
/// Load the default font. See the docs of [`load`] to learn more.
|
||||
pub fn load_default(&mut self) -> Font {
|
||||
self.load(DEFAULT_FONT)
|
||||
}
|
||||
|
||||
/// Load a font by name. The font can be loaded either from cache or from the embedded fonts'
|
||||
/// registry if not used before. Returns the default font if the name is missing in both cache
|
||||
/// and embedded font list.
|
||||
pub fn load(&mut self, name:impl Into<Name>) -> Font {
|
||||
let name = name.into();
|
||||
self.try_load(&name).unwrap_or_else(|| {
|
||||
event!(WARN, "Font '{name}' not found. Loading the default font.");
|
||||
self.try_load(DEFAULT_FONT).expect("Default font not found.")
|
||||
})
|
||||
}
|
||||
|
||||
/// Load a font by name. The font can be loaded either from cache or from the embedded fonts'
|
||||
/// registry if not used before. Returns [`None`] if the name is missing in both cache and
|
||||
/// embedded font list.
|
||||
pub fn try_load(&mut self, name:impl Into<Name>) -> Option<Font> {
|
||||
let name = name.into();
|
||||
event!(DEBUG, "Loading font: {:?}", name);
|
||||
match self.fonts.entry(name.clone()) {
|
||||
Entry::Occupied (entry) => Some(entry.get().clone_ref()),
|
||||
Entry::Vacant (entry) => {
|
||||
self.embedded.definitions.get(&name).map(|definition| {
|
||||
let font: Font = match definition {
|
||||
family::Definition::NonVariable(definition) => {
|
||||
let family = NonVariableFamily::from(definition);
|
||||
family.load_all_faces(&self.embedded);
|
||||
NonVariableFont::new(name, family).into()
|
||||
}
|
||||
family::Definition::Variable(definition) => {
|
||||
let family = VariableFamily::from(definition);
|
||||
family.load_all_faces(&self.embedded);
|
||||
VariableFont::new(name, family).into()
|
||||
}
|
||||
};
|
||||
entry.insert(font.clone_ref());
|
||||
font
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}}
|
||||
|
||||
impl Registry {
|
||||
/// Constructor.
|
||||
pub fn init_and_load_embedded_fonts() -> Registry {
|
||||
let embedded = Embedded::init_and_load_embedded_fonts();
|
||||
let fonts = HashMap::new();
|
||||
let data = RegistryData { embedded, fonts };
|
||||
let rc = Rc::new(RefCell::new(data));
|
||||
Self { rc }
|
||||
}
|
||||
}
|
||||
|
||||
impl scene::Extension for Registry {
|
||||
fn init(_scene: &scene::Scene) -> Self {
|
||||
Self::init_and_load_embedded_fonts()
|
||||
}
|
||||
}
|
@ -9,10 +9,11 @@ crate-type = ["cdylib", "rlib"]
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
[dependencies]
|
||||
enso-prelude = { path = "../../../../prelude" }
|
||||
ensogl-text-embedded-fonts-names = { path = "names" }
|
||||
enso-prelude = { path = "../../../../../../prelude" }
|
||||
ensogl-text-font-family = { path = "../../font/family" }
|
||||
|
||||
[build-dependencies]
|
||||
enso-build-utilities = { path = "../../../../build-utils" }
|
||||
ensogl-text-embedded-fonts-names = { path = "names" }
|
||||
enso-build-utilities = { path = "../../../../../../../../build/build-utils" }
|
||||
ensogl-text-font-family = { path = "../../font/family" }
|
||||
zip = { version = "0.5" }
|
||||
owned_ttf_parser = "0.15.1"
|
239
lib/rust/ensogl/component/text/src/font/embedded/build.rs
Normal file
239
lib/rust/ensogl/component/text/src/font/embedded/build.rs
Normal file
@ -0,0 +1,239 @@
|
||||
//! Downloader of fonts considered as "embedded" into the application.
|
||||
|
||||
// === Features ===
|
||||
#![feature(const_trait_impl)]
|
||||
|
||||
use owned_ttf_parser::AsFaceRef;
|
||||
use owned_ttf_parser::OwnedFace;
|
||||
use std::env;
|
||||
use std::fmt::Write;
|
||||
use std::fs;
|
||||
use std::fs::File;
|
||||
use std::io;
|
||||
use std::io::BufReader;
|
||||
use std::io::Read;
|
||||
use std::io::Write as IoWrite;
|
||||
use std::path;
|
||||
|
||||
|
||||
|
||||
// =============
|
||||
// === Utils ===
|
||||
// =============
|
||||
|
||||
/// Add a new code line to the string buffer.
|
||||
macro_rules! ln {
|
||||
($ident:expr, $out:expr, $($ts:tt)*) => {
|
||||
writeln!($out, "{}", format!("{}{}", " ".repeat($ident), format!($($ts)*))).ok();
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
|
||||
// =====================
|
||||
// === CodeGenerator ===
|
||||
// =====================
|
||||
|
||||
/// Generated file with code filling embedded fonts map.
|
||||
#[derive(Debug, Default)]
|
||||
pub struct CodeGenerator {
|
||||
embeds: String,
|
||||
definitions: String,
|
||||
}
|
||||
|
||||
impl CodeGenerator {
|
||||
fn add_font_data(&mut self, file_name: &str) {
|
||||
ln!(1, &mut self.embeds, "map.insert(\"{file_name}\", include_bytes!(\"{file_name}\"));");
|
||||
}
|
||||
|
||||
fn add_variable_font_definition(&mut self, family: &str, file: &str) {
|
||||
let key = format!("\"{family}\".into()");
|
||||
let family_def = format!("family::VariableDefinition::new(\"{file}\")");
|
||||
let value = format!("family::Definition::Variable({})", family_def);
|
||||
ln!(1, &mut self.definitions, "map.insert({},{});", key, value);
|
||||
}
|
||||
|
||||
fn add_non_variable_font_definition(&mut self, family_name: &str, def: &str) {
|
||||
ln!(1, &mut self.definitions, "map.insert(\"{family_name}\".into(), {def});");
|
||||
}
|
||||
|
||||
fn body(&self) -> String {
|
||||
let mut body = String::new();
|
||||
ln!(0, body, "/// Mapping between file name and embedded fonts data.");
|
||||
ln!(0, body, "pub fn embedded_fonts_data() -> HashMap<&'static str, &'static [u8]> {{");
|
||||
ln!(1, body, "let mut map = HashMap::<&'static str, &'static [u8]>::new();");
|
||||
write!(body, "{}", self.embeds).ok();
|
||||
ln!(1, body, "map");
|
||||
ln!(0, body, "}}");
|
||||
ln!(0, body, "");
|
||||
ln!(0, body, "/// Definitions of embedded font families.");
|
||||
ln!(0, body, "pub fn embedded_family_definitions()");
|
||||
ln!(0, body, "-> HashMap<family::Name, family::Definition> {{");
|
||||
ln!(1, body, "let mut map = HashMap::new();");
|
||||
write!(body, "{}", self.definitions).ok();
|
||||
ln!(1, body, "map");
|
||||
ln!(0, body, "}}");
|
||||
body
|
||||
}
|
||||
|
||||
fn write<P: AsRef<path::Path>>(&self, path: P) -> io::Result<()> {
|
||||
let mut file = fs::File::create(path)?;
|
||||
writeln!(file, "{}", self.body())
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
// ===================
|
||||
// === DejaVu Font ===
|
||||
// ===================
|
||||
|
||||
mod deja_vu {
|
||||
use crate::CodeGenerator;
|
||||
|
||||
use enso_build_utilities::GithubRelease;
|
||||
use std::path;
|
||||
|
||||
pub const PACKAGE: GithubRelease<&str> = GithubRelease {
|
||||
project_url: "https://github.com/dejavu-fonts/dejavu-fonts/",
|
||||
version: "version_2_37",
|
||||
filename: "dejavu-fonts-ttf-2.37.zip",
|
||||
};
|
||||
|
||||
pub const PACKAGE_FONTS_PREFIX: &str = "dejavu-fonts-ttf-2.37/ttf";
|
||||
|
||||
pub fn extract_font(package_path: &path::Path, file_name: &str) {
|
||||
let font_in_package_path = format!("{}/{}", PACKAGE_FONTS_PREFIX, file_name);
|
||||
let package_dir = package_path.parent().unwrap();
|
||||
let output_path = package_dir.join(file_name);
|
||||
|
||||
let archive_file = std::fs::File::open(package_path).unwrap();
|
||||
let mut archive = zip::ZipArchive::new(archive_file).unwrap();
|
||||
let mut input_stream = archive.by_name(font_in_package_path.as_str()).unwrap();
|
||||
let mut output_stream = std::fs::File::create(output_path).unwrap();
|
||||
std::io::copy(&mut input_stream, &mut output_stream).unwrap();
|
||||
}
|
||||
|
||||
const FILE_NAMES: [&str; 4] =
|
||||
["DejaVuSans.ttf", "DejaVuSans-Bold.ttf", "DejaVuSansMono.ttf", "DejaVuSansMono-Bold.ttf"];
|
||||
|
||||
pub fn extract_all_fonts(package_path: &path::Path) {
|
||||
for file_name in FILE_NAMES {
|
||||
extract_font(package_path, file_name);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn download_and_extract_all_fonts(out_dir: &path::Path) {
|
||||
let package_path = out_dir.join(PACKAGE.filename);
|
||||
PACKAGE.download(out_dir);
|
||||
extract_all_fonts(package_path.as_path());
|
||||
}
|
||||
|
||||
pub fn add_entries_to_fill_map_rs(file: &mut CodeGenerator) {
|
||||
for file_name in FILE_NAMES {
|
||||
file.add_font_data(file_name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
// ====================
|
||||
// === Google Fonts ===
|
||||
// ====================
|
||||
|
||||
mod google_fonts {
|
||||
use super::*;
|
||||
use crate::CodeGenerator;
|
||||
|
||||
use enso_build_utilities::GithubFile;
|
||||
use enso_build_utilities::GoogleFontsRelease;
|
||||
use std::path;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct FaceDefinition {
|
||||
file_name: String,
|
||||
face: OwnedFace,
|
||||
}
|
||||
|
||||
pub fn download_files(name: impl AsRef<str>, out_dir: &path::Path) -> Vec<GithubFile> {
|
||||
GoogleFontsRelease::download(name.as_ref(), out_dir)
|
||||
}
|
||||
|
||||
pub fn load(out_dir: &path::Path, buffer: &mut CodeGenerator, family_name: &str) {
|
||||
let files = download_files(family_name, out_dir);
|
||||
|
||||
for file in &files {
|
||||
buffer.add_font_data(&file.name)
|
||||
}
|
||||
|
||||
let font_faces: Vec<FaceDefinition> = files
|
||||
.into_iter()
|
||||
.map(|file| {
|
||||
let file_name = file.name;
|
||||
let path = out_dir.join(&file_name);
|
||||
let err = |action: &str| format!("Cannot {} file {:?}", action, path);
|
||||
let handle = File::open(&path).unwrap_or_else(|_| panic!("{}", err("read")));
|
||||
let mut reader = BufReader::new(handle);
|
||||
let mut bytes = Vec::new();
|
||||
reader.read_to_end(&mut bytes).unwrap_or_else(|_| panic!("{}", err("read")));
|
||||
let face = OwnedFace::from_vec(bytes, 0);
|
||||
let face = face.unwrap_or_else(|_| panic!("{}", err("parse")));
|
||||
FaceDefinition { file_name, face }
|
||||
})
|
||||
.collect();
|
||||
|
||||
if font_faces.is_empty() {
|
||||
panic!("No font faces found for family {}.", family_name);
|
||||
} else if font_faces.len() == 1 && font_faces[0].face.as_face_ref().is_variable() {
|
||||
let file_name = &font_faces[0].file_name;
|
||||
buffer.add_variable_font_definition(family_name, file_name);
|
||||
} else {
|
||||
if font_faces.iter().any(|def| def.face.as_face_ref().is_variable()) {
|
||||
let err1 = "is a variable font with multiple source files.";
|
||||
let err2 = "This is intentionally not supported.";
|
||||
let err3 = "CSS does not support it either,";
|
||||
let err4 = "see: https://developer.mozilla.org/en-US/docs/Web/CSS/@font-face";
|
||||
panic!("Family {} {} {} {} {}", family_name, err1, err2, err3, err4);
|
||||
}
|
||||
let mut code = String::new();
|
||||
let fam_def = "family::Definition::NonVariable";
|
||||
ln!(1, code, "{}(family::NonVariableDefinition::from_iter([", fam_def);
|
||||
for def in font_faces {
|
||||
let file_name = &def.file_name;
|
||||
let face_ref = def.face.as_face_ref();
|
||||
ln!(2, code, "(");
|
||||
ln!(3, code, "family::NonVariableFaceHeader::new(");
|
||||
ln!(4, code, "family::Width::{:?},", face_ref.width());
|
||||
ln!(4, code, "family::Weight::{:?},", face_ref.weight());
|
||||
ln!(4, code, "family::Style::{:?},", face_ref.style());
|
||||
ln!(3, code, "),");
|
||||
ln!(3, code, "\"{file_name}\".to_string(),");
|
||||
ln!(2, code, "),");
|
||||
}
|
||||
ln!(1, code, "]))");
|
||||
buffer.add_non_variable_font_definition(family_name, &code);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
// ============
|
||||
// === Main ===
|
||||
// ============
|
||||
|
||||
fn main() {
|
||||
println!("cargo:rerun-if-changed=build.rs");
|
||||
let out = env::var("OUT_DIR").unwrap();
|
||||
let out_dir = path::Path::new(&out);
|
||||
deja_vu::download_and_extract_all_fonts(out_dir);
|
||||
|
||||
let mut code_gen = CodeGenerator::default();
|
||||
google_fonts::load(out_dir, &mut code_gen, "mplus1");
|
||||
google_fonts::load(out_dir, &mut code_gen, "mplus1p");
|
||||
|
||||
let out_path = out_dir.join("embedded_fonts_data.rs");
|
||||
deja_vu::add_entries_to_fill_map_rs(&mut code_gen);
|
||||
code_gen.write(out_path).unwrap();
|
||||
}
|
137
lib/rust/ensogl/component/text/src/font/embedded/src/lib.rs
Normal file
137
lib/rust/ensogl/component/text/src/font/embedded/src/lib.rs
Normal file
@ -0,0 +1,137 @@
|
||||
//! Collection of embedded fonts generated by the build.rs script.
|
||||
|
||||
// === Standard Linter Configuration ===
|
||||
#![deny(non_ascii_idents)]
|
||||
#![warn(unsafe_code)]
|
||||
// === Non-Standard Linter Configuration ===
|
||||
#![allow(clippy::option_map_unit_fn)]
|
||||
#![allow(clippy::precedence)]
|
||||
#![allow(dead_code)]
|
||||
#![deny(unconditional_recursion)]
|
||||
#![warn(missing_copy_implementations)]
|
||||
#![warn(missing_debug_implementations)]
|
||||
#![warn(missing_docs)]
|
||||
#![warn(trivial_casts)]
|
||||
#![warn(trivial_numeric_casts)]
|
||||
#![warn(unused_import_braces)]
|
||||
#![warn(unused_qualifications)]
|
||||
|
||||
use enso_prelude::*;
|
||||
|
||||
use ensogl_text_font_family as family;
|
||||
|
||||
|
||||
|
||||
// ==============
|
||||
// === Export ===
|
||||
// ==============
|
||||
|
||||
include!(concat!(env!("OUT_DIR"), "/embedded_fonts_data.rs"));
|
||||
|
||||
|
||||
|
||||
// ================
|
||||
// === Embedded ===
|
||||
// ================
|
||||
|
||||
/// A base of built-in fonts in application.
|
||||
///
|
||||
/// The structure keeps a map from a font name to its binary ttf representation. The binary data can
|
||||
/// be further interpreted by such libs as the MSDF-gen one.
|
||||
///
|
||||
/// For list of embedded fonts, see FONTS_TO_EXTRACT constant in `build.rs`.
|
||||
#[allow(missing_docs)]
|
||||
#[derive(Clone)]
|
||||
pub struct Embedded {
|
||||
pub definitions: HashMap<family::Name, family::Definition>,
|
||||
pub data: HashMap<&'static str, &'static [u8]>,
|
||||
}
|
||||
|
||||
impl Embedded {
|
||||
/// Construct and load all the embedded fonts to memory.
|
||||
pub fn init_and_load_embedded_fonts() -> Self {
|
||||
let data = embedded_fonts_data();
|
||||
let definitions = embedded_family_definitions_ext();
|
||||
Self { data, definitions }
|
||||
}
|
||||
}
|
||||
|
||||
impl Debug for Embedded {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.write_str("<Embedded fonts>")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
// =============
|
||||
// === Tests ===
|
||||
// =============
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use crate::*;
|
||||
|
||||
#[test]
|
||||
fn loading_embedded_fonts() {
|
||||
let fonts = Embedded::init_and_load_embedded_fonts();
|
||||
let example_font = fonts.data.get("DejaVuSans.ttf").unwrap();
|
||||
|
||||
assert_eq!(0x00, example_font[0]);
|
||||
assert_eq!(0x01, example_font[1]);
|
||||
assert_eq!(0x1d, example_font[example_font.len() - 1]);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// ======================
|
||||
// === Embedded Fonts ===
|
||||
// ======================
|
||||
|
||||
/// List of embedded fonts. The list is extended with hardcoded "DejaVuSans" font. It should be
|
||||
/// generated from the build.rs script in the future.
|
||||
pub fn embedded_family_definitions_ext() -> HashMap<family::Name, family::Definition> {
|
||||
let mut map = embedded_family_definitions();
|
||||
let dejavusans = family::Definition::NonVariable(family::NonVariableDefinition::from_iter([
|
||||
(
|
||||
family::NonVariableFaceHeader::new(
|
||||
family::Width::Normal,
|
||||
family::Weight::Normal,
|
||||
family::Style::Normal,
|
||||
),
|
||||
"DejaVuSans.ttf".to_string(),
|
||||
),
|
||||
(
|
||||
family::NonVariableFaceHeader::new(
|
||||
family::Width::Normal,
|
||||
family::Weight::Bold,
|
||||
family::Style::Normal,
|
||||
),
|
||||
"DejaVuSans-Bold.ttf".to_string(),
|
||||
),
|
||||
]));
|
||||
let dejavusansmono =
|
||||
family::Definition::NonVariable(family::NonVariableDefinition::from_iter([
|
||||
(
|
||||
family::NonVariableFaceHeader::new(
|
||||
family::Width::Normal,
|
||||
family::Weight::Normal,
|
||||
family::Style::Normal,
|
||||
),
|
||||
"DejaVuSansMono.ttf".to_string(),
|
||||
),
|
||||
(
|
||||
family::NonVariableFaceHeader::new(
|
||||
family::Width::Normal,
|
||||
family::Weight::Bold,
|
||||
family::Style::Normal,
|
||||
),
|
||||
"DejaVuSansMono-Bold.ttf".to_string(),
|
||||
),
|
||||
]));
|
||||
map.insert("dejavusans".into(), dejavusans.clone());
|
||||
map.insert("dejavusansmono".into(), dejavusansmono.clone());
|
||||
map.insert("default".into(), dejavusans);
|
||||
map.insert("default-mono".into(), dejavusansmono);
|
||||
map
|
||||
}
|
12
lib/rust/ensogl/component/text/src/font/family/Cargo.toml
Normal file
12
lib/rust/ensogl/component/text/src/font/family/Cargo.toml
Normal file
@ -0,0 +1,12 @@
|
||||
[package]
|
||||
name = "ensogl-text-font-family"
|
||||
version = "0.1.0"
|
||||
authors = ["Enso Team <contact@enso.org>"]
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
owned_ttf_parser = "0.15.1"
|
||||
enso-prelude = { path = "../../../../../../prelude" }
|
||||
|
||||
[lib]
|
||||
crate-type = ["cdylib", "rlib"]
|
193
lib/rust/ensogl/component/text/src/font/family/src/lib.rs
Normal file
193
lib/rust/ensogl/component/text/src/font/family/src/lib.rs
Normal file
@ -0,0 +1,193 @@
|
||||
//! Definition of a font family, a set of related font faces.
|
||||
//!
|
||||
//! # One font face per file
|
||||
//! The implementation of this library has an important limitation that should not cause any
|
||||
//! problems, however, it is important to be aware of it. In case of non-variable fonts, only one
|
||||
//! font face is supported per file. A font face is identified by (width, weight, style) triple (see
|
||||
//! [`NonVariableFaceHeader`]) to learn more. If you want to use font faces defined in the same
|
||||
//! file (e.g. ".ttf" file), you have to split them into multiple files first. All major browsers
|
||||
//! have the same limitation. For example, you are unable to define in CSS a new font family by
|
||||
//! loading different faces from the same file with the `@font-face` rule
|
||||
//! (https://developer.mozilla.org/en-US/docs/Web/CSS/@font-face).
|
||||
|
||||
// === Features ===
|
||||
#![allow(incomplete_features)]
|
||||
#![feature(negative_impls)]
|
||||
#![feature(associated_type_defaults)]
|
||||
#![feature(bool_to_option)]
|
||||
#![feature(cell_update)]
|
||||
#![feature(const_type_id)]
|
||||
#![feature(drain_filter)]
|
||||
#![feature(entry_insert)]
|
||||
#![feature(fn_traits)]
|
||||
#![feature(marker_trait_attr)]
|
||||
#![feature(specialization)]
|
||||
#![feature(trait_alias)]
|
||||
#![feature(type_alias_impl_trait)]
|
||||
#![feature(unboxed_closures)]
|
||||
#![feature(trace_macros)]
|
||||
#![feature(const_trait_impl)]
|
||||
#![feature(slice_as_chunks)]
|
||||
// === Standard Linter Configuration ===
|
||||
#![deny(non_ascii_idents)]
|
||||
#![warn(unsafe_code)]
|
||||
// === Non-Standard Linter Configuration ===
|
||||
#![allow(clippy::option_map_unit_fn)]
|
||||
#![allow(clippy::precedence)]
|
||||
#![allow(dead_code)]
|
||||
#![deny(unconditional_recursion)]
|
||||
#![warn(missing_copy_implementations)]
|
||||
#![warn(missing_debug_implementations)]
|
||||
#![warn(missing_docs)]
|
||||
#![warn(trivial_casts)]
|
||||
#![warn(trivial_numeric_casts)]
|
||||
#![warn(unused_import_braces)]
|
||||
#![warn(unused_qualifications)]
|
||||
|
||||
use enso_prelude::*;
|
||||
|
||||
use std::collections::HashMap;
|
||||
|
||||
|
||||
// ==============
|
||||
// === Export ===
|
||||
// ==============
|
||||
|
||||
pub use owned_ttf_parser::Style;
|
||||
pub use owned_ttf_parser::Weight;
|
||||
pub use owned_ttf_parser::Width;
|
||||
|
||||
|
||||
|
||||
// ============
|
||||
// === Name ===
|
||||
// ============
|
||||
|
||||
/// A name of a font. The name is normalized to case-insensitive form during construction to
|
||||
/// eliminate accidental mistakes, the same way as it's done in CSS:
|
||||
/// https://stackoverflow.com/questions/17967371/are-property-values-in-css-case-sensitive
|
||||
#[allow(missing_docs)]
|
||||
#[derive(Clone, Debug, Display, Hash, PartialEq, Eq)]
|
||||
pub struct Name {
|
||||
pub normalized: String,
|
||||
}
|
||||
|
||||
impl From<&Name> for Name {
|
||||
fn from(name: &Name) -> Self {
|
||||
name.clone()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&str> for Name {
|
||||
fn from(name: &str) -> Self {
|
||||
let normalized = name.to_lowercase();
|
||||
Name { normalized }
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&String> for Name {
|
||||
fn from(name: &String) -> Self {
|
||||
name.as_str().into()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<String> for Name {
|
||||
fn from(name: String) -> Self {
|
||||
(&name).into()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
// ==================
|
||||
// === Definition ===
|
||||
// ==================
|
||||
|
||||
/// Definition of a font family. Font family consist of one font face in case of variable fonts or
|
||||
/// multiple font faces in case of non-variable ones.
|
||||
#[allow(missing_docs)]
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub enum Definition {
|
||||
Variable(VariableDefinition),
|
||||
NonVariable(NonVariableDefinition),
|
||||
}
|
||||
|
||||
|
||||
|
||||
// ==========================
|
||||
// === VariableDefinition ===
|
||||
// ==========================
|
||||
|
||||
/// Definition of a variable font family. See the following link to learn more about variable fonts:
|
||||
/// https://docs.microsoft.com/en-us/windows/win32/directwrite/opentype-variable-fonts
|
||||
#[allow(missing_docs)]
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub struct VariableDefinition {
|
||||
/// Name of the file that the font data was read from. It contains the file extension, for
|
||||
/// example `MPLUS1[wght].ttf`.
|
||||
pub file_name: String,
|
||||
}
|
||||
|
||||
impl VariableDefinition {
|
||||
/// Constructor.
|
||||
pub fn new(file_name: impl Into<String>) -> Self {
|
||||
let file_name = file_name.into();
|
||||
Self { file_name }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
// =============================
|
||||
// === NonVariableDefinition ===
|
||||
// =============================
|
||||
|
||||
/// Definition of a non-variable font family. Contains mapping between (width, weight, style) triple
|
||||
/// (see [`NonVariableFaceHeader`]) to learn more) and file names.
|
||||
#[allow(missing_docs)]
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub struct NonVariableDefinition {
|
||||
pub map: HashMap<NonVariableFaceHeader, String>,
|
||||
}
|
||||
|
||||
impl NonVariableDefinition {
|
||||
/// Constructor.
|
||||
pub fn new(map: HashMap<NonVariableFaceHeader, String>) -> Self {
|
||||
Self { map }
|
||||
}
|
||||
|
||||
/// All weights defined in this font family.
|
||||
pub fn possible_weights(&self) -> Vec<Weight> {
|
||||
self.map.keys().map(|header| header.weight).collect()
|
||||
}
|
||||
}
|
||||
|
||||
impl FromIterator<(NonVariableFaceHeader, String)> for NonVariableDefinition {
|
||||
fn from_iter<T>(iter: T) -> Self
|
||||
where T: IntoIterator<Item = (NonVariableFaceHeader, String)> {
|
||||
Self::new(iter.into_iter().collect())
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
// =============================
|
||||
// === NonVariableFaceHeader ===
|
||||
// =============================
|
||||
|
||||
/// Combination of all information allowing mapping the font face to a font file for non-variable
|
||||
/// fonts. For variable fonts, there is just one definition for any combination of the parameters.
|
||||
#[allow(missing_docs)]
|
||||
#[derive(Clone, Copy, Default, Debug, PartialEq, Eq, Hash)]
|
||||
pub struct NonVariableFaceHeader {
|
||||
pub width: Width,
|
||||
pub weight: Weight,
|
||||
pub style: Style,
|
||||
}
|
||||
|
||||
impl NonVariableFaceHeader {
|
||||
/// Constructor.
|
||||
pub fn new(width: Width, weight: Weight, style: Style) -> Self {
|
||||
Self { width, weight, style }
|
||||
}
|
||||
}
|
@ -1,6 +1,3 @@
|
||||
// A factor describing much the bold letters will be fattened, expressed as the fraction of font size.
|
||||
const float BOLD_FATTING = 0.04;
|
||||
|
||||
highp float median(highp vec3 v) {
|
||||
return max(min(v.x, v.y), min(max(v.x, v.y), v.z));
|
||||
}
|
||||
@ -23,10 +20,9 @@ highp vec2 get_texture_coord() {
|
||||
}
|
||||
|
||||
highp float get_fatting() {
|
||||
bool glyph_is_bold = (input_style & STYLE_BOLD_FLAG) != 0;
|
||||
highp vec2 local_to_px_ratio = 1.0 / fwidth(input_local.xy);
|
||||
highp float font_size_px = input_font_size * (local_to_px_ratio.x + local_to_px_ratio.y) / 2.0;
|
||||
highp float fatting = (glyph_is_bold ? BOLD_FATTING : 0.0) + input_sdf_bold;
|
||||
highp float fatting = input_sdf_bold;
|
||||
return font_size_px * fatting;
|
||||
}
|
||||
|
@ -1,6 +1,3 @@
|
||||
// A factor describing much the bold letters will be fattened, expressed as the fraction of font size.
|
||||
const float BOLD_FATTING = 0.04;
|
||||
|
||||
highp float median(highp vec3 v) {
|
||||
return max(min(v.x, v.y), min(max(v.x, v.y), v.z));
|
||||
}
|
||||
@ -24,10 +21,9 @@ highp vec2 get_texture_coord() {
|
||||
}
|
||||
|
||||
highp float get_fatting() {
|
||||
bool glyph_is_bold = (input_style & STYLE_BOLD_FLAG) != 0;
|
||||
highp vec2 local_to_px_ratio = 1.0 / fwidth(input_local.xy);
|
||||
highp float font_size_px = input_font_size * (local_to_px_ratio.x + local_to_px_ratio.y) / 2.0;
|
||||
highp float fatting = (glyph_is_bold ? BOLD_FATTING : 0.0) + input_sdf_bold;
|
||||
highp float fatting = input_sdf_bold;
|
||||
return font_size_px * fatting;
|
||||
}
|
||||
|
@ -4,9 +4,9 @@
|
||||
use crate::prelude::*;
|
||||
use ensogl_core::display::world::*;
|
||||
|
||||
use crate::typeface::font;
|
||||
use crate::font;
|
||||
use crate::font::VariationAxes;
|
||||
|
||||
use const_format::concatcp;
|
||||
use ensogl_core::data::color::Rgba;
|
||||
use ensogl_core::display;
|
||||
use ensogl_core::display::layout::Alignment;
|
||||
@ -17,20 +17,7 @@ use ensogl_core::system::gpu;
|
||||
use ensogl_core::system::gpu::texture;
|
||||
use font::Font;
|
||||
use font::GlyphRenderInfo;
|
||||
|
||||
|
||||
|
||||
// =================
|
||||
// === Constants ===
|
||||
// =================
|
||||
|
||||
mod style_flag {
|
||||
use const_format::concatcp;
|
||||
|
||||
pub const BOLD: i32 = 1 << 0;
|
||||
|
||||
pub const GLSL_DEFINITIONS: &str = concatcp!("const int STYLE_BOLD_FLAG = ", BOLD, ";\n");
|
||||
}
|
||||
use owned_ttf_parser::GlyphId;
|
||||
|
||||
|
||||
|
||||
@ -44,79 +31,166 @@ pub type Texture = gpu::Texture<texture::GpuOnly, texture::Rgb, u8>;
|
||||
/// A glyph rendered on screen.
|
||||
///
|
||||
/// The underlying sprite's size is automatically adjusted depending on char and font size set.
|
||||
#[derive(Clone, CloneRef, Debug)]
|
||||
#[derive(Clone, CloneRef, Debug, Deref)]
|
||||
pub struct Glyph {
|
||||
sprite: Sprite,
|
||||
context: Context,
|
||||
font: Font,
|
||||
font_size: Attribute<f32>,
|
||||
color: Attribute<Vector4<f32>>,
|
||||
style: Attribute<i32>,
|
||||
sdf_bold: Attribute<f32>,
|
||||
atlas_index: Attribute<f32>,
|
||||
atlas: Uniform<Texture>,
|
||||
char: Rc<Cell<char>>,
|
||||
data: Rc<GlyphData>,
|
||||
}
|
||||
|
||||
/// Internal structure of [`Glyph`].
|
||||
#[allow(missing_docs)]
|
||||
#[derive(Debug)]
|
||||
pub struct GlyphData {
|
||||
pub glyph_id: Cell<GlyphId>,
|
||||
pub sprite: Sprite,
|
||||
pub context: Context,
|
||||
pub font: Font,
|
||||
pub properties: Cell<font::family::NonVariableFaceHeader>,
|
||||
pub variations: RefCell<VariationAxes>,
|
||||
pub font_size: Attribute<f32>,
|
||||
pub color: Attribute<Vector4<f32>>,
|
||||
pub sdf_bold: Attribute<f32>,
|
||||
pub atlas_index: Attribute<f32>,
|
||||
pub atlas: Uniform<Texture>,
|
||||
}
|
||||
|
||||
|
||||
// === Properties getters and setters ===
|
||||
|
||||
macro_rules! define_prop_setters_and_getters {
|
||||
($prop:ident ($($variant:ident),* $(,)?)) => { paste! {
|
||||
#[doc = "Setter of the glyph `"]
|
||||
#[doc = stringify!($prop)]
|
||||
#[doc = "` property."]
|
||||
pub fn [<set_ $prop:snake:lower>](&self, value: font::$prop) {
|
||||
self.properties.modify(|p| p.[<$prop:snake:lower>] = value);
|
||||
self.variations.borrow_mut().[<set_ $prop:snake:lower>](value);
|
||||
self.refresh();
|
||||
}
|
||||
|
||||
$(
|
||||
#[doc = "Set the `"]
|
||||
#[doc = stringify!($prop)]
|
||||
#[doc = "` property to `"]
|
||||
#[doc = stringify!($variant)]
|
||||
#[doc = "`."]
|
||||
pub fn [<set_ $prop:snake:lower _ $variant:snake:lower>](&self) {
|
||||
self.[<set_ $prop:snake:lower>](font::$prop::$variant)
|
||||
}
|
||||
|
||||
#[doc = "Checks whether the `"]
|
||||
#[doc = stringify!($prop)]
|
||||
#[doc = "` property is set to `"]
|
||||
#[doc = stringify!($variant)]
|
||||
#[doc = "`."]
|
||||
pub fn [<is_ $prop:snake:lower _ $variant:snake:lower>](&self) -> bool {
|
||||
self.properties.get().[<$prop:snake:lower>] == font::$prop::$variant
|
||||
}
|
||||
)*
|
||||
}};
|
||||
}
|
||||
|
||||
impl Glyph {
|
||||
/// Glyph color attribute accessor.
|
||||
define_prop_setters_and_getters![Weight(
|
||||
Thin, ExtraLight, Light, Normal, Medium, SemiBold, Bold, ExtraBold, Black
|
||||
)];
|
||||
|
||||
define_prop_setters_and_getters![Width(
|
||||
UltraCondensed,
|
||||
ExtraCondensed,
|
||||
Condensed,
|
||||
SemiCondensed,
|
||||
Normal,
|
||||
SemiExpanded,
|
||||
Expanded,
|
||||
ExtraExpanded,
|
||||
UltraExpanded
|
||||
)];
|
||||
|
||||
define_prop_setters_and_getters![Style(Normal, Italic, Oblique)];
|
||||
|
||||
/// Color getter.
|
||||
pub fn color(&self) -> Rgba {
|
||||
self.color.get().into()
|
||||
}
|
||||
|
||||
/// Color setter.
|
||||
pub fn set_color(&self, color: impl Into<Rgba>) {
|
||||
self.color.set(color.into().into())
|
||||
}
|
||||
|
||||
pub fn is_bold(&self) -> bool {
|
||||
self.style.get() & style_flag::BOLD != 0
|
||||
}
|
||||
|
||||
pub fn set_bold(&self, value: bool) {
|
||||
self.style.modify(|v| if value { *v |= style_flag::BOLD } else { *v &= !style_flag::BOLD });
|
||||
}
|
||||
|
||||
/// SDF-based glyph thickness adjustment. Values greater than 0 make the glyph thicker, while
|
||||
/// values lower than 0 makes it thinner.
|
||||
pub fn sdf_bold(&self) -> f32 {
|
||||
self.sdf_bold.get()
|
||||
}
|
||||
|
||||
/// SDF-based glyph thickness getter.
|
||||
pub fn set_sdf_bold(&self, value: f32) {
|
||||
self.sdf_bold.set(value);
|
||||
}
|
||||
|
||||
/// Size getter.
|
||||
pub fn font_size(&self) -> f32 {
|
||||
self.font_size.get()
|
||||
}
|
||||
|
||||
/// Size setter.
|
||||
pub fn set_font_size(&self, size: f32) {
|
||||
self.font_size.set(size);
|
||||
let glyph_info = self.font.glyph_info(self.char.get());
|
||||
self.sprite.size.set(glyph_info.scale.scale(size));
|
||||
let opt_glyph_info = self.font.glyph_info(
|
||||
self.properties.get(),
|
||||
&self.variations.borrow(),
|
||||
self.glyph_id.get(),
|
||||
);
|
||||
if let Some(glyph_info) = opt_glyph_info {
|
||||
self.sprite.size.set(glyph_info.scale.scale(size))
|
||||
}
|
||||
}
|
||||
|
||||
/// Change the displayed character.
|
||||
pub fn set_char(&self, ch: char) {
|
||||
self.char.set(ch);
|
||||
let glyph_info = self.font.glyph_info(ch);
|
||||
self.atlas_index.set(glyph_info.msdf_texture_glyph_id as f32);
|
||||
self.update_msdf_texture();
|
||||
let font_size = self.font_size();
|
||||
self.sprite.size.set(glyph_info.scale.scale(font_size));
|
||||
let opt_glyph_id =
|
||||
self.font.glyph_id_of_code_point(self.properties.get(), &self.variations.borrow(), ch);
|
||||
if let Some(glyph_id) = opt_glyph_id {
|
||||
self.set_glyph_id(glyph_id)
|
||||
}
|
||||
// FIXME[WD]: display not found char. https://www.pivotaltracker.com/story/show/182746060
|
||||
}
|
||||
|
||||
// FIXME: How does it work? Replace with better checking.
|
||||
fn update_msdf_texture(&self) {
|
||||
let texture_changed = self.atlas.with_content(|texture| {
|
||||
texture.storage().height != self.font.msdf_texture_rows() as i32
|
||||
});
|
||||
/// Change the displayed character.
|
||||
pub fn set_glyph_id(&self, glyph_id: GlyphId) {
|
||||
self.glyph_id.set(glyph_id);
|
||||
let opt_glyph_info =
|
||||
self.font.glyph_info(self.properties.get(), &self.variations.borrow(), glyph_id);
|
||||
if let Some(glyph_info) = opt_glyph_info {
|
||||
self.atlas_index.set(glyph_info.msdf_texture_glyph_id as f32);
|
||||
self.update_atlas();
|
||||
let font_size = self.font_size();
|
||||
self.sprite.size.set(glyph_info.scale.scale(font_size));
|
||||
} else {
|
||||
// FIXME[WD]: This should display a bad character. https://www.pivotaltracker.com/story/show/182746060
|
||||
panic!()
|
||||
}
|
||||
}
|
||||
|
||||
/// Check whether the CPU-bound texture changed and if so, upload it to GPU.
|
||||
fn update_atlas(&self) {
|
||||
let cpu_tex_height = self.font.msdf_texture_rows() as i32;
|
||||
let gpu_tex_height = self.atlas.with_content(|texture| texture.storage().height);
|
||||
let texture_changed = cpu_tex_height != gpu_tex_height;
|
||||
if texture_changed {
|
||||
let width = font::msdf::Texture::WIDTH as i32;
|
||||
let height = self.font.msdf_texture_rows() as i32;
|
||||
let texture = Texture::new(&self.context, (width, height));
|
||||
let cpu_tex_width = font::msdf::Texture::WIDTH as i32;
|
||||
let texture = Texture::new(&self.context, (cpu_tex_width, cpu_tex_height));
|
||||
self.font.with_borrowed_msdf_texture_data(|data| texture.reload_with_content(data));
|
||||
self.atlas.set(texture);
|
||||
}
|
||||
}
|
||||
|
||||
/// Check whether a new glyph should be baked to the atlas and reload the texture if needed.
|
||||
/// This is useful for example after changing the width of the glyph.
|
||||
fn refresh(&self) {
|
||||
self.set_glyph_id(self.glyph_id.get());
|
||||
}
|
||||
}
|
||||
|
||||
impl display::Object for Glyph {
|
||||
@ -141,7 +215,6 @@ pub struct System {
|
||||
pub font: Font,
|
||||
font_size: Buffer<f32>,
|
||||
color: Buffer<Vector4<f32>>,
|
||||
style: Buffer<i32>,
|
||||
sdf_bold: Buffer<f32>,
|
||||
atlas_index: Buffer<f32>,
|
||||
atlas: Uniform<Texture>,
|
||||
@ -174,7 +247,6 @@ impl System {
|
||||
atlas: symbol.variables().add_or_panic("atlas", texture),
|
||||
font_size: mesh.instance_scope().add_buffer("font_size"),
|
||||
color: mesh.instance_scope().add_buffer("color"),
|
||||
style: mesh.instance_scope().add_buffer("style"),
|
||||
sdf_bold: mesh.instance_scope().add_buffer("sdf_bold"),
|
||||
atlas_index: mesh.instance_scope().add_buffer("atlas_index"),
|
||||
}
|
||||
@ -188,15 +260,30 @@ impl System {
|
||||
let instance_id = sprite.instance_id;
|
||||
let font_size = self.font_size.at(instance_id);
|
||||
let color = self.color.at(instance_id);
|
||||
let style = self.style.at(instance_id);
|
||||
let sdf_bold = self.sdf_bold.at(instance_id);
|
||||
let atlas_index = self.atlas_index.at(instance_id);
|
||||
let font = self.font.clone_ref();
|
||||
let atlas = self.atlas.clone();
|
||||
let char = default();
|
||||
let glyph_id = default();
|
||||
let properties = default();
|
||||
let variations = default();
|
||||
color.set(Vector4::new(0.0, 0.0, 0.0, 0.0));
|
||||
atlas_index.set(0.0);
|
||||
Glyph { sprite, context, font, font_size, color, style, sdf_bold, atlas_index, atlas, char }
|
||||
Glyph {
|
||||
data: Rc::new(GlyphData {
|
||||
sprite,
|
||||
context,
|
||||
font,
|
||||
font_size,
|
||||
color,
|
||||
sdf_bold,
|
||||
atlas_index,
|
||||
atlas,
|
||||
glyph_id,
|
||||
properties,
|
||||
variations,
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
/// Get underlying sprite system.
|
||||
@ -214,10 +301,9 @@ impl display::Object for System {
|
||||
|
||||
// === Material ===
|
||||
#[cfg(target_os = "macos")]
|
||||
const FUNCTIONS: &str =
|
||||
concatcp!(style_flag::GLSL_DEFINITIONS, include_str!("glsl/glyph_mac.glsl"));
|
||||
const FUNCTIONS: &str = include_str!("glsl/glyph_mac.glsl");
|
||||
#[cfg(not(target_os = "macos"))]
|
||||
const FUNCTIONS: &str = concatcp!(style_flag::GLSL_DEFINITIONS, include_str!("glsl/glyph.glsl"));
|
||||
const FUNCTIONS: &str = include_str!("glsl/glyph.glsl");
|
||||
|
||||
const MAIN: &str = "output_color = color_from_msdf(); output_id=vec4(0.0,0.0,0.0,0.0);";
|
||||
|
||||
@ -233,7 +319,6 @@ impl System {
|
||||
material.add_input("msdf_range", GlyphRenderInfo::MSDF_PARAMS.range as f32);
|
||||
material.add_input("font_size", 10.0);
|
||||
material.add_input("color", Vector4::new(0.0, 0.0, 0.0, 1.0));
|
||||
material.add_input("style", 0);
|
||||
material.add_input("sdf_bold", 0.0);
|
||||
// FIXME We need to use this output, as we need to declare the same amount of shader
|
||||
// FIXME outputs as the number of attachments to framebuffer. We should manage this more
|
92
lib/rust/ensogl/component/text/src/font/glyph_render_info.rs
Normal file
92
lib/rust/ensogl/component/text/src/font/glyph_render_info.rs
Normal file
@ -0,0 +1,92 @@
|
||||
//! Defines a helper structure containing information about a glyph's render layout.
|
||||
|
||||
|
||||
|
||||
/// Commonly used types and functions.
|
||||
pub mod prelude {
|
||||
pub use ensogl_core::prelude::*;
|
||||
}
|
||||
|
||||
use owned_ttf_parser::GlyphId;
|
||||
use prelude::*;
|
||||
|
||||
use crate::font::msdf;
|
||||
use ensogl_text_msdf as msdf_sys;
|
||||
use msdf_sys::Msdf;
|
||||
use msdf_sys::MsdfParameters;
|
||||
|
||||
|
||||
|
||||
// =======================
|
||||
// === GlyphRenderInfo ===
|
||||
// =======================
|
||||
|
||||
/// Data used for rendering a single glyph.
|
||||
///
|
||||
/// Each distance and transformation values are expressed in normalized coordinates, where
|
||||
/// (0.0, 0.0) is initial pen position for a character, and `y` = 1.0 is an _ascender_.
|
||||
///
|
||||
/// The `offset` and `scale` fields transforms the _base square_ for a character, such the glyph
|
||||
/// will be rendered correctly with assigned MSDF texture. The _base square_ corners are (0.0, 0.0),
|
||||
/// (1.0, 1.0).
|
||||
///
|
||||
/// For explanation of various font-rendering terms, see the
|
||||
/// [freetype documentation](https://www.freetype.org/freetype2/docs/glyphs/glyphs-3.html#section-1)
|
||||
#[derive(Copy, Clone, Debug, serde::Serialize, serde::Deserialize)]
|
||||
pub struct GlyphRenderInfo {
|
||||
/// An index of glyph in a msdf texture (counted from the top of column). For details, see
|
||||
/// msdf::Texture documentation.
|
||||
pub msdf_texture_glyph_id: usize,
|
||||
|
||||
/// A required offset of the _base square_. See structure documentation for details.
|
||||
pub offset: Vector2<f32>,
|
||||
|
||||
/// A required scale of the _base square_. See structure documentation for details.
|
||||
pub scale: Vector2<f32>,
|
||||
|
||||
/// Distance between two successive pen positions for specific glyph.
|
||||
pub advance: f32,
|
||||
}
|
||||
|
||||
impl GlyphRenderInfo {
|
||||
/// See `MSDF_PARAMS` docs.
|
||||
pub const MAX_MSDF_SHRINK_FACTOR: f64 = 4.0;
|
||||
|
||||
/// See `MSDF_PARAMS` docs.
|
||||
pub const MAX_MSDF_GLYPH_SCALE: f64 = 2.0;
|
||||
|
||||
/// Parameters used for MSDF generation.
|
||||
///
|
||||
/// The range was picked such way, that we avoid fitting range in one rendered pixel.
|
||||
/// Otherwise, the antialiasing won't work. I assumed some maximum `shrink factor` (how many
|
||||
/// times rendered square will be smaller than MSDF size), and pick an arbitrary maximum glyph
|
||||
/// scale up.
|
||||
///
|
||||
/// The rest of parameters are the defaults taken from msdfgen library
|
||||
pub const MSDF_PARAMS: MsdfParameters = MsdfParameters {
|
||||
width: msdf::Texture::WIDTH,
|
||||
height: msdf::Texture::ONE_GLYPH_HEIGHT,
|
||||
edge_coloring_angle_threshold: 3.0,
|
||||
range: Self::MAX_MSDF_SHRINK_FACTOR * Self::MAX_MSDF_GLYPH_SCALE,
|
||||
max_scale: Self::MAX_MSDF_GLYPH_SCALE,
|
||||
edge_threshold: 1.001,
|
||||
overlap_support: true,
|
||||
};
|
||||
|
||||
/// Load new [`GlyphRenderInfo`] from msdf_sys font handle. This also extends the atlas with
|
||||
/// MSDF generated for this character.
|
||||
pub fn load(handle: &msdf_sys::OwnedFace, glyph_id: GlyphId, atlas: &msdf::Texture) -> Self {
|
||||
let params = Self::MSDF_PARAMS;
|
||||
let msdf = Msdf::generate_by_index(handle, glyph_id.0 as usize, ¶ms);
|
||||
let inversed_scale = Vector2::new(1.0 / msdf.scale.x, 1.0 / msdf.scale.y);
|
||||
let translation = msdf::convert_msdf_translation(&msdf);
|
||||
let glyph_id = atlas.rows() / msdf::Texture::ONE_GLYPH_HEIGHT;
|
||||
atlas.extend_with_raw_data(msdf.data.iter());
|
||||
GlyphRenderInfo {
|
||||
msdf_texture_glyph_id: glyph_id,
|
||||
offset: -translation,
|
||||
scale: Vector2(inversed_scale.x as f32, inversed_scale.y as f32),
|
||||
advance: msdf::x_distance_from_msdf_value(msdf.advance),
|
||||
}
|
||||
}
|
||||
}
|
2
lib/rust/ensogl/component/text/src/font/msdf/.gitignore
vendored
Normal file
2
lib/rust/ensogl/component/text/src/font/msdf/.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
# A thirdparty JS file, dowloaded during build by the build.rs script.
|
||||
msdfgen_wasm.js
|
27
lib/rust/ensogl/component/text/src/font/msdf/Cargo.toml
Normal file
27
lib/rust/ensogl/component/text/src/font/msdf/Cargo.toml
Normal file
@ -0,0 +1,27 @@
|
||||
[package]
|
||||
name = "ensogl-text-msdf"
|
||||
version = "0.1.0"
|
||||
authors = ["Enso Team <contact@enso.org>"]
|
||||
edition = "2021"
|
||||
|
||||
[lib]
|
||||
crate-type = ["cdylib", "rlib"]
|
||||
|
||||
[dependencies]
|
||||
enso-prelude = { path = "../../../../../../prelude" }
|
||||
enso-types = { path = "../../../../../../types" }
|
||||
failure = { version = "0.1.6" }
|
||||
js-sys = { version = "0.3" }
|
||||
nalgebra = { version = "0.26.1" }
|
||||
wasm-bindgen = { version = "0.2.78" }
|
||||
serde = { version = "1", features = ["rc"] }
|
||||
owned_ttf_parser = "0.15.1"
|
||||
|
||||
[dev-dependencies]
|
||||
wasm-bindgen-test = { version = "0.3.8" }
|
||||
futures = { version = "0.3.1" }
|
||||
ensogl-text-embedded-fonts = { path = "../../../src/font/embedded" }
|
||||
ensogl-text-font-family = { path = "../../../src/font/family" }
|
||||
|
||||
[build-dependencies]
|
||||
enso-build-utilities = { path = "../../../../../../../../build/build-utils" }
|
@ -11,7 +11,7 @@ mod msdfgen_wasm {
|
||||
|
||||
pub const PACKAGE: GithubRelease<&str> = GithubRelease {
|
||||
project_url: "https://github.com/enso-org/msdfgen-wasm",
|
||||
version: "v1.1",
|
||||
version: "v1.4",
|
||||
filename: "msdfgen_wasm.js",
|
||||
};
|
||||
|
||||
@ -28,9 +28,10 @@ mod msdfgen_wasm {
|
||||
PACKAGE.download(path::Path::new("."))
|
||||
}
|
||||
|
||||
const PATCH_LINE: &str = "; export { ccall, getValue, _msdfgen_getKerning,\
|
||||
_msdfgen_generateAutoframedMSDF, _msdfgen_result_getMSDFData,\
|
||||
_msdfgen_result_getAdvance, _msdfgen_result_getTranslation,\
|
||||
const PATCH_LINE: &str =
|
||||
"; export { ccall, getValue, _msdfgen_getKerning, _msdfgen_setVariationAxis,\
|
||||
_msdfgen_generateAutoframedMSDF, _msdfgen_generateAutoframedMSDFByIndex, \
|
||||
_msdfgen_result_getMSDFData, _msdfgen_result_getAdvance, _msdfgen_result_getTranslation,\
|
||||
_msdfgen_result_getScale, _msdfgen_freeResult, _msdfgen_freeFont,\
|
||||
addInitializationCb, isInitialized }";
|
||||
|
@ -28,6 +28,11 @@ extern "C" {
|
||||
#[wasm_bindgen(js_name = "_msdfgen_getKerning")]
|
||||
pub fn msdfgen_get_kerning(font_handle: JsValue, left_unicode: u32, right_unicode: u32) -> f64;
|
||||
|
||||
// Actually, this method returns bool, but Emscripten does not translate it to JavaScript
|
||||
// boolean type, so we read it here as usize. The 0 value means false, any other means true.
|
||||
#[wasm_bindgen(js_name = "_msdfgen_setVariationAxis")]
|
||||
pub fn msdfgen_set_variation_axis(font_handle: JsValue, name: u32, coordinate: f64) -> usize;
|
||||
|
||||
#[wasm_bindgen(js_name = "_msdfgen_generateAutoframedMSDF")]
|
||||
pub fn msdfgen_generate_msdf(
|
||||
width: usize,
|
||||
@ -41,6 +46,19 @@ extern "C" {
|
||||
overlap_support: bool,
|
||||
) -> JsValue;
|
||||
|
||||
#[wasm_bindgen(js_name = "_msdfgen_generateAutoframedMSDFByIndex")]
|
||||
pub fn msdfgen_generate_msdf_by_index(
|
||||
width: usize,
|
||||
height: usize,
|
||||
font_handle: JsValue,
|
||||
index: usize,
|
||||
edge_coloring_angle_threshold: f64,
|
||||
range: f64,
|
||||
max_scale: f64,
|
||||
edge_threshold: f64,
|
||||
overlap_support: bool,
|
||||
) -> JsValue;
|
||||
|
||||
#[wasm_bindgen(js_name = "_msdfgen_result_getMSDFData")]
|
||||
pub fn msdfgen_result_get_msdf_data(result_handle: JsValue) -> usize;
|
||||
|
@ -1,3 +1,5 @@
|
||||
//! Helpers for working with emscripten libraries.
|
||||
|
||||
use crate::prelude::*;
|
||||
|
||||
use crate::binding::emscripten_get_value_from_memory;
|
||||
@ -15,6 +17,7 @@ use wasm_bindgen::JsValue;
|
||||
/// The _emscirpten API_ is a set of functions that are put to library by emscripten SDK, the
|
||||
/// especially useful is a function reading value from given address in `msdfgen` library memory
|
||||
/// (we cannot do it directly, because each wasm module have separate address space)
|
||||
#[allow(missing_docs)]
|
||||
pub trait EmscriptenRepresentation: Sized {
|
||||
const EMSCRIPTEN_SIZE_IN_BYTES: usize;
|
||||
const EMSCRIPTEN_TYPE_NAME: &'static str;
|
||||
@ -56,7 +59,7 @@ impl EmscriptenRepresentation for f64 {
|
||||
pub struct ArrayMemoryView<F: EmscriptenRepresentation> {
|
||||
begin_address: usize,
|
||||
end_address: usize,
|
||||
type_marker: std::marker::PhantomData<F>,
|
||||
type_marker: PhantomData<F>,
|
||||
}
|
||||
|
||||
/// Iterator over values in `msdfgen` library memory
|
||||
@ -67,7 +70,7 @@ pub struct ArrayMemoryView<F: EmscriptenRepresentation> {
|
||||
pub struct ArrayMemoryViewIterator<'a, F: EmscriptenRepresentation> {
|
||||
next_read_address: usize,
|
||||
end_address: usize,
|
||||
view_lifetime: std::marker::PhantomData<&'a ArrayMemoryView<F>>,
|
||||
view_lifetime: PhantomData<&'a ArrayMemoryView<F>>,
|
||||
}
|
||||
|
||||
impl<F: EmscriptenRepresentation> ArrayMemoryView<F> {
|
||||
@ -77,17 +80,13 @@ impl<F: EmscriptenRepresentation> ArrayMemoryView<F> {
|
||||
ArrayMemoryView {
|
||||
begin_address: address,
|
||||
end_address: address + size_in_bytes,
|
||||
type_marker: std::marker::PhantomData,
|
||||
type_marker: PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
/// Create an empty view
|
||||
pub fn empty() -> ArrayMemoryView<F> {
|
||||
ArrayMemoryView {
|
||||
begin_address: 0,
|
||||
end_address: 0,
|
||||
type_marker: std::marker::PhantomData,
|
||||
}
|
||||
ArrayMemoryView { begin_address: 0, end_address: 0, type_marker: PhantomData }
|
||||
}
|
||||
|
||||
/// Iterator over elements
|
||||
@ -95,7 +94,7 @@ impl<F: EmscriptenRepresentation> ArrayMemoryView<F> {
|
||||
ArrayMemoryViewIterator {
|
||||
next_read_address: self.begin_address,
|
||||
end_address: self.end_address,
|
||||
view_lifetime: std::marker::PhantomData,
|
||||
view_lifetime: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
@ -1,21 +1,27 @@
|
||||
//! MSDF-gen libraries bindings and utilities.
|
||||
|
||||
// === Standard Linter Configuration ===
|
||||
#![deny(non_ascii_idents)]
|
||||
#![warn(unsafe_code)]
|
||||
// === Non-Standard Linter Configuration ===
|
||||
#![allow(missing_docs)]
|
||||
#![allow(clippy::option_map_unit_fn)]
|
||||
#![allow(clippy::precedence)]
|
||||
#![allow(dead_code)]
|
||||
#![deny(unconditional_recursion)]
|
||||
#![warn(missing_copy_implementations)]
|
||||
#![warn(missing_debug_implementations)]
|
||||
#![warn(missing_docs)]
|
||||
#![warn(trivial_casts)]
|
||||
#![warn(trivial_numeric_casts)]
|
||||
#![warn(unused_import_braces)]
|
||||
#![warn(unused_qualifications)]
|
||||
|
||||
|
||||
|
||||
mod binding;
|
||||
pub mod emscripten_data;
|
||||
pub use enso_prelude as prelude;
|
||||
|
||||
use crate::prelude::*;
|
||||
use binding::*;
|
||||
|
||||
use emscripten_data::ArrayMemoryView;
|
||||
use js_sys::Uint8Array;
|
||||
use owned_ttf_parser::Tag;
|
||||
use std::future::Future;
|
||||
use std::pin::Pin;
|
||||
use std::task::Context;
|
||||
@ -25,13 +31,42 @@ use wasm_bindgen::JsValue;
|
||||
|
||||
|
||||
|
||||
mod binding;
|
||||
pub mod emscripten_data;
|
||||
pub mod texture;
|
||||
|
||||
pub use texture::*;
|
||||
|
||||
/// Common types.
|
||||
pub mod prelude {
|
||||
pub use enso_prelude::*;
|
||||
pub use enso_types::*;
|
||||
}
|
||||
|
||||
|
||||
|
||||
// ==============
|
||||
// === Errors ===
|
||||
// ==============
|
||||
|
||||
#[allow(missing_docs)]
|
||||
#[derive(Clone, Debug, Fail, Eq, PartialEq)]
|
||||
pub enum SetVariationAxisError {
|
||||
#[fail(
|
||||
display = "Msdfgen `setVariationAxis` operation was not successfull for axis: {}.",
|
||||
name
|
||||
)]
|
||||
LibraryError { name: String },
|
||||
}
|
||||
|
||||
|
||||
|
||||
// ======================
|
||||
// === Initialization ===
|
||||
// ======================
|
||||
|
||||
/// Add initialization callback.
|
||||
///
|
||||
/// The callback passed as argument will be called once the msdfgen library is initialized.
|
||||
/// Add initialization callback. The callback passed as argument will be called once the msdfgen
|
||||
/// library is initialized.
|
||||
pub fn run_once_initialized<F>(callback: F)
|
||||
where F: 'static + FnOnce() {
|
||||
if is_emscripten_runtime_initialized() {
|
||||
@ -42,12 +77,12 @@ where F: 'static + FnOnce() {
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns future which returns once the msdfgen library is initialized.
|
||||
/// A future which resolves once the msdfgen library is initialized.
|
||||
pub fn initialized() -> impl Future<Output = ()> {
|
||||
MsdfgenJsInitialized()
|
||||
}
|
||||
|
||||
/// The future for running test after initialization
|
||||
/// A future for running test after initialization.
|
||||
#[derive(Debug)]
|
||||
struct MsdfgenJsInitialized();
|
||||
|
||||
@ -67,26 +102,29 @@ impl Future for MsdfgenJsInitialized {
|
||||
|
||||
|
||||
|
||||
// ============
|
||||
// === Font ===
|
||||
// ============
|
||||
// =================
|
||||
// === OwnedFace ===
|
||||
// =================
|
||||
|
||||
/// A font face loaded to JS memory and handled by the msdfgen library. The name uses the "owned"
|
||||
/// prefix in order to stay compatible with the `ttf-parser` library.
|
||||
#[allow(missing_docs)]
|
||||
#[derive(Debug)]
|
||||
pub struct Font {
|
||||
pub struct OwnedFace {
|
||||
pub handle: JsValue,
|
||||
}
|
||||
|
||||
impl Drop for Font {
|
||||
impl Drop for OwnedFace {
|
||||
fn drop(&mut self) {
|
||||
msdfgen_free_font(self.handle.clone())
|
||||
}
|
||||
}
|
||||
|
||||
impl Font {
|
||||
/// Loading font from memory
|
||||
impl OwnedFace {
|
||||
/// Load font from memory.
|
||||
///
|
||||
/// Loads font from a any format which freetype library can handle.
|
||||
/// See [https://www.freetype.org/freetype2/docs/index.html] for reference.
|
||||
/// Loads font from any format which freetype library can handle. See
|
||||
/// [https://www.freetype.org/freetype2/docs/index.html] for reference.
|
||||
pub fn load_from_memory(data: &[u8]) -> Self {
|
||||
let array_type_js = JsValue::from_str(ccall_types::ARRAY);
|
||||
let number_type_js = JsValue::from_str(ccall_types::NUMBER);
|
||||
@ -100,32 +138,37 @@ impl Font {
|
||||
let params = js_sys::Array::of2(&data_js, &data_size_js);
|
||||
|
||||
let handle = emscripten_call_function(function_name, return_type, param_types, params);
|
||||
Font { handle }
|
||||
OwnedFace { handle }
|
||||
}
|
||||
|
||||
pub fn retrieve_kerning(&self, left: char, right: char) -> f64 {
|
||||
let left_unicode = left as u32;
|
||||
let right_unicode = right as u32;
|
||||
msdfgen_get_kerning(self.handle.clone(), left_unicode, right_unicode)
|
||||
/// Set font's variation axis.
|
||||
pub fn set_variation_axis(
|
||||
&self,
|
||||
tag: Tag,
|
||||
coordinate: f64,
|
||||
) -> Result<(), SetVariationAxisError> {
|
||||
let ok = msdfgen_set_variation_axis(self.handle.clone(), tag.0, coordinate) != 0;
|
||||
ok.ok_or_else(|| SetVariationAxisError::LibraryError { name: tag.to_string() })
|
||||
}
|
||||
|
||||
pub fn mock_font() -> Font {
|
||||
/// Mocked version of this struct. Used for testing purposes.
|
||||
pub fn mock_font() -> OwnedFace {
|
||||
let handle = JsValue::from_f64(0.0);
|
||||
Font { handle }
|
||||
OwnedFace { handle }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
// =====================================================
|
||||
// === Mutlichannel signed distance field generation ===
|
||||
// =====================================================
|
||||
// ======================
|
||||
// === MsdfParameters ===
|
||||
// ======================
|
||||
|
||||
/// Parameters of MSDF generation
|
||||
/// Parameters of MSDF generation.
|
||||
///
|
||||
/// The structure gathering MSDF generation parameters meant to be same for all
|
||||
/// rendered glyphs
|
||||
/// The structure gathering MSDF generation parameters meant to be same for all rendered glyphs.
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
#[allow(missing_docs)]
|
||||
pub struct MsdfParameters {
|
||||
pub width: usize,
|
||||
pub height: usize,
|
||||
@ -136,6 +179,14 @@ pub struct MsdfParameters {
|
||||
pub overlap_support: bool,
|
||||
}
|
||||
|
||||
|
||||
|
||||
// ============
|
||||
// === Msdf ===
|
||||
// ============
|
||||
|
||||
/// Binding to the MSDF-gen library.
|
||||
#[allow(missing_docs)]
|
||||
#[derive(Debug)]
|
||||
pub struct Msdf {
|
||||
handle: JsValue,
|
||||
@ -152,13 +203,13 @@ impl Drop for Msdf {
|
||||
}
|
||||
|
||||
impl Msdf {
|
||||
/// Number of used color channels in the MSDF texture.
|
||||
pub const CHANNELS_COUNT: usize = 3;
|
||||
|
||||
/// Generate Mutlichannel Signed Distance Field (MSDF) for one glyph.
|
||||
///
|
||||
/// For more information about MSDF see
|
||||
/// [https://github.com/Chlumsky/msdfgen].
|
||||
pub fn generate(font: &Font, unicode: u32, params: &MsdfParameters) -> Msdf {
|
||||
/// For more information about MSDF see [https://github.com/Chlumsky/msdfgen].
|
||||
pub fn generate(font: &OwnedFace, unicode: u32, params: &MsdfParameters) -> Msdf {
|
||||
let handle = msdfgen_generate_msdf(
|
||||
params.width,
|
||||
params.height,
|
||||
@ -170,6 +221,28 @@ impl Msdf {
|
||||
params.edge_threshold,
|
||||
params.overlap_support,
|
||||
);
|
||||
Self::msdf_from_generation_result_handle(handle, params)
|
||||
}
|
||||
|
||||
/// Generate Mutlichannel Signed Distance Field (MSDF) for one glyph by its index in the font.
|
||||
///
|
||||
/// For more information about MSDF see [https://github.com/Chlumsky/msdfgen].
|
||||
pub fn generate_by_index(font: &OwnedFace, index: usize, params: &MsdfParameters) -> Msdf {
|
||||
let handle = msdfgen_generate_msdf_by_index(
|
||||
params.width,
|
||||
params.height,
|
||||
font.handle.clone(),
|
||||
index,
|
||||
params.edge_coloring_angle_threshold,
|
||||
params.range,
|
||||
params.max_scale,
|
||||
params.edge_threshold,
|
||||
params.overlap_support,
|
||||
);
|
||||
Self::msdf_from_generation_result_handle(handle, params)
|
||||
}
|
||||
|
||||
fn msdf_from_generation_result_handle(handle: JsValue, params: &MsdfParameters) -> Msdf {
|
||||
let advance = msdfgen_result_get_advance(handle.clone());
|
||||
let translation = Self::translation(&handle);
|
||||
let scale = Self::scale(&handle);
|
||||
@ -199,6 +272,7 @@ impl Msdf {
|
||||
nalgebra::Vector2::new(scale_x, scale_y)
|
||||
}
|
||||
|
||||
/// Mocked version of this struct. Used for testing purposes.
|
||||
pub fn mock_results() -> Msdf {
|
||||
Msdf {
|
||||
handle: JsValue::from_f64(0.0),
|
||||
@ -220,9 +294,7 @@ impl Msdf {
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
use ensogl_text_embedded_fonts::EmbeddedFonts;
|
||||
use ensogl_text_embedded_fonts_names::DejaVuSans;
|
||||
use ensogl_text_embedded_fonts_names::FontFamily;
|
||||
use ensogl_text_embedded_fonts::Embedded;
|
||||
use nalgebra::Vector2;
|
||||
use wasm_bindgen_test::wasm_bindgen_test;
|
||||
use wasm_bindgen_test::wasm_bindgen_test_configure;
|
||||
@ -233,9 +305,9 @@ mod tests {
|
||||
async fn generate_msdf_for_capital_a() {
|
||||
initialized().await;
|
||||
// given
|
||||
let font_base = EmbeddedFonts::create_and_fill();
|
||||
let font_name = DejaVuSans::mono_bold();
|
||||
let font = Font::load_from_memory(font_base.font_data_by_name.get(font_name).unwrap());
|
||||
let font_base = Embedded::init_and_load_embedded_fonts();
|
||||
let font_name = "DejaVuSansMono-Bold.ttf";
|
||||
let font = OwnedFace::load_from_memory(font_base.data.get(font_name).unwrap());
|
||||
let params = MsdfParameters {
|
||||
width: 32,
|
||||
height: 32,
|
||||
@ -257,6 +329,34 @@ mod tests {
|
||||
assert_eq!(19.265625, msdf.advance);
|
||||
}
|
||||
|
||||
#[wasm_bindgen_test(async)]
|
||||
async fn generate_msdf_for_capital_a_by_index() {
|
||||
initialized().await;
|
||||
// given
|
||||
let font_base = Embedded::init_and_load_embedded_fonts();
|
||||
let font_name = "DejaVuSansMono-Bold.ttf";
|
||||
let font = OwnedFace::load_from_memory(font_base.data.get(font_name).unwrap());
|
||||
let params = MsdfParameters {
|
||||
width: 32,
|
||||
height: 32,
|
||||
edge_coloring_angle_threshold: 3.0,
|
||||
range: 2.0,
|
||||
max_scale: 2.0,
|
||||
edge_threshold: 1.001,
|
||||
overlap_support: true,
|
||||
};
|
||||
// when
|
||||
let msdf = Msdf::generate_by_index(&font, 36, ¶ms);
|
||||
// then
|
||||
let data: Vec<f32> = msdf.data.iter().collect();
|
||||
assert_eq!(-0.9408906, data[0]); // Note [asserts]
|
||||
assert_eq!(0.2, data[10]);
|
||||
assert_eq!(-4.3035655, data[data.len() - 1]);
|
||||
assert_eq!(Vector2::new(3.03125, 1.0), msdf.translation);
|
||||
assert_eq!(Vector2::new(1.25, 1.25), msdf.scale);
|
||||
assert_eq!(19.265625, msdf.advance);
|
||||
}
|
||||
|
||||
/* Note [asserts]
|
||||
*
|
||||
* we're checking rust - js interface only, so there is no need to check
|
@ -2,20 +2,22 @@
|
||||
|
||||
use crate::prelude::*;
|
||||
|
||||
use ensogl_text_msdf_sys::Msdf;
|
||||
use crate::Msdf;
|
||||
|
||||
use serde;
|
||||
|
||||
|
||||
|
||||
// ====================
|
||||
// === MSDF Texture ===
|
||||
// ====================
|
||||
// ===============
|
||||
// === Texture ===
|
||||
// ===============
|
||||
|
||||
/// MSDF texture data for all loaded glyph of a font.
|
||||
///
|
||||
/// This structure keeps texture data in 8-bit-per-channel RGB format, which is ready to be passed
|
||||
/// to WebGL `texImage2D`. The texture contains MSDFs for all loaded glyph, organized in vertical
|
||||
/// column.
|
||||
#[derive(Clone, CloneRef, Debug, Default)]
|
||||
#[derive(Clone, CloneRef, Debug, Default, serde::Serialize, serde::Deserialize)]
|
||||
pub struct Texture {
|
||||
/// A plain data of this texture.
|
||||
data: Rc<RefCell<Vec<u8>>>,
|
||||
@ -143,7 +145,7 @@ mod test {
|
||||
|
||||
#[wasm_bindgen_test(async)]
|
||||
async fn msdf_translation_converting() {
|
||||
ensogl_text_msdf_sys::initialized().await;
|
||||
crate::initialized().await;
|
||||
let mut msdf = Msdf::mock_results();
|
||||
msdf.translation = Vector2::new(16.0, 4.0);
|
||||
|
129
lib/rust/ensogl/component/text/src/font/pen.rs
Normal file
129
lib/rust/ensogl/component/text/src/font/pen.rs
Normal file
@ -0,0 +1,129 @@
|
||||
//! The pen is a point on the text _baseline_ used to locate glyph. It moves along the _baseline_
|
||||
//! with each glyph rendered. For details, see
|
||||
//! [freetype documentation](https://www.freetype.org/freetype2/docs/glyphs/glyphs-3.html#section-1)
|
||||
|
||||
use crate::prelude::*;
|
||||
|
||||
use crate::font;
|
||||
|
||||
use super::Font;
|
||||
|
||||
|
||||
|
||||
// =====================
|
||||
// === AdvanceResult ===
|
||||
// =====================
|
||||
|
||||
/// Information about the char at the pen position.
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
#[allow(missing_docs)]
|
||||
pub struct AdvanceResult {
|
||||
pub char: Option<char>,
|
||||
pub offset: f32,
|
||||
}
|
||||
|
||||
|
||||
|
||||
// ================
|
||||
// === CharInfo ===
|
||||
// ================
|
||||
|
||||
/// Information about the char used to transform the pen.
|
||||
#[derive(Clone, Debug)]
|
||||
#[allow(missing_docs)]
|
||||
pub struct CharInfo {
|
||||
pub char: char,
|
||||
pub size: f32,
|
||||
non_variable_font_variations: font::NonVariableFaceHeader,
|
||||
variable_font_variations: font::VariationAxes,
|
||||
}
|
||||
|
||||
impl CharInfo {
|
||||
/// Constructor.
|
||||
pub fn new(
|
||||
char: char,
|
||||
size: f32,
|
||||
non_variable_font_variations: font::NonVariableFaceHeader,
|
||||
variable_font_variations: font::VariationAxes,
|
||||
) -> Self {
|
||||
Self { char, size, non_variable_font_variations, variable_font_variations }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
// ===========
|
||||
// === Pen ===
|
||||
// ===========
|
||||
|
||||
/// Pen iterates over chars producing the position for a given char.
|
||||
///
|
||||
/// The pen is a font-specific term (see
|
||||
/// [freetype documentation](https://www.freetype.org/freetype2/docs/glyphs/glyphs-3.html#section-1)
|
||||
/// for details).
|
||||
#[derive(Debug)]
|
||||
pub struct Pen {
|
||||
offset: f32,
|
||||
current_char: Option<CharInfo>,
|
||||
font: Font,
|
||||
}
|
||||
|
||||
impl Pen {
|
||||
/// Create a new pen iterator for a given font.
|
||||
pub fn new(font: &Font) -> Self {
|
||||
let offset = default();
|
||||
let current_char = default();
|
||||
let font = font.clone_ref();
|
||||
Self { offset, current_char, font }
|
||||
}
|
||||
|
||||
// FIXME[WD] all unwraps. https://www.pivotaltracker.com/story/show/182746060
|
||||
/// Advance the pen to the next position.
|
||||
pub fn advance(&mut self, next: Option<CharInfo>) -> AdvanceResult {
|
||||
let next_char = next.as_ref().map(|t| t.char);
|
||||
if let Some(current) = &self.current_char {
|
||||
let a = self
|
||||
.font
|
||||
.glyph_id_of_code_point(
|
||||
current.non_variable_font_variations,
|
||||
¤t.variable_font_variations,
|
||||
current.char,
|
||||
)
|
||||
.unwrap();
|
||||
let b = next.as_ref().map(|t| {
|
||||
self.font
|
||||
.glyph_id_of_code_point(
|
||||
t.non_variable_font_variations,
|
||||
&t.variable_font_variations,
|
||||
t.char,
|
||||
)
|
||||
.unwrap()
|
||||
});
|
||||
let kerning = b
|
||||
.map(|ch| {
|
||||
self.font.kerning(
|
||||
current.non_variable_font_variations,
|
||||
¤t.variable_font_variations,
|
||||
a,
|
||||
ch,
|
||||
)
|
||||
})
|
||||
.unwrap_or_default();
|
||||
let advance = self
|
||||
.font
|
||||
.glyph_info(
|
||||
current.non_variable_font_variations,
|
||||
¤t.variable_font_variations,
|
||||
a,
|
||||
)
|
||||
.unwrap()
|
||||
.advance
|
||||
+ kerning;
|
||||
let offset = advance * current.size;
|
||||
self.offset += offset;
|
||||
}
|
||||
self.current_char = next;
|
||||
let offset = self.offset;
|
||||
AdvanceResult { char: next_char, offset }
|
||||
}
|
||||
}
|
@ -28,7 +28,7 @@
|
||||
|
||||
pub mod buffer;
|
||||
pub mod component;
|
||||
pub mod typeface;
|
||||
pub mod font;
|
||||
|
||||
|
||||
|
||||
|
@ -1,11 +0,0 @@
|
||||
// === Non-Standard Linter Configuration ===
|
||||
#![allow(missing_docs)]
|
||||
|
||||
|
||||
// ==============
|
||||
// === Export ===
|
||||
// ==============
|
||||
|
||||
pub mod font;
|
||||
pub mod glyph;
|
||||
pub mod pen;
|
@ -1,421 +0,0 @@
|
||||
//! In this module we handle the fonts information required for rendering glyphs.
|
||||
|
||||
use crate::prelude::*;
|
||||
|
||||
use enso_shapely::shared;
|
||||
use ensogl_core::display::scene;
|
||||
use ensogl_core::display::Scene;
|
||||
use ensogl_text_embedded_fonts as embedded_fonts;
|
||||
use ensogl_text_embedded_fonts::EmbeddedFonts;
|
||||
use ensogl_text_embedded_fonts::Family;
|
||||
use ensogl_text_msdf_sys as msdf_sys;
|
||||
use msdf_sys::Msdf;
|
||||
use msdf_sys::MsdfParameters;
|
||||
use std::collections::hash_map::Entry;
|
||||
|
||||
|
||||
// ==============
|
||||
// === Export ===
|
||||
// ==============
|
||||
|
||||
pub mod msdf;
|
||||
|
||||
|
||||
|
||||
// =================
|
||||
// === Constants ===
|
||||
// =================
|
||||
|
||||
/// Default font the app will revert to if a desired font could not be loaded.
|
||||
pub const DEFAULT_FONT: &str = embedded_fonts::DefaultFamily::regular();
|
||||
|
||||
|
||||
|
||||
// =============
|
||||
// === Cache ===
|
||||
// =============
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Cache<K: Eq + Hash, V> {
|
||||
map: RefCell<HashMap<K, V>>,
|
||||
}
|
||||
|
||||
impl<K: Eq + Hash, V: Copy> Cache<K, V> {
|
||||
pub fn get_or_create<F>(&self, key: K, constructor: F) -> V
|
||||
where F: FnOnce() -> V {
|
||||
let mut map = self.map.borrow_mut();
|
||||
match map.entry(key) {
|
||||
Entry::Occupied(entry) => *entry.get(),
|
||||
Entry::Vacant(entry) => *entry.insert(constructor()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<K: Eq + Hash, V> Cache<K, V> {
|
||||
pub fn len(&self) -> usize {
|
||||
self.map.borrow().len()
|
||||
}
|
||||
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.map.borrow().is_empty()
|
||||
}
|
||||
|
||||
pub fn invalidate(&self, key: &K) {
|
||||
self.map.borrow_mut().remove(key);
|
||||
}
|
||||
}
|
||||
|
||||
impl<K: Eq + Hash, V> Default for Cache<K, V> {
|
||||
fn default() -> Self {
|
||||
Cache { map: default() }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
// ========================
|
||||
// === Font render info ===
|
||||
// ========================
|
||||
|
||||
/// Data used for rendering a single glyph.
|
||||
///
|
||||
/// Each distance and transformation values are expressed in normalized coordinates, where
|
||||
/// (0.0, 0.0) is initial pen position for an character, and `y` = 1.0 is _ascender_.
|
||||
///
|
||||
/// The `offset` and `scale` fields transforms the _base square_ for a character, such the glyph
|
||||
/// will be rendered correctly with assigned MSDF texture. The _base square_ corners are (0.0, 0.0),
|
||||
/// (1.0, 1.0).
|
||||
///
|
||||
/// For explanation of various font-rendering terms, see the
|
||||
/// [freetype documentation](https://www.freetype.org/freetype2/docs/glyphs/glyphs-3.html#section-1)
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub struct GlyphRenderInfo {
|
||||
/// An index of glyph in a msdf texture (counted from the top of column). For details, see
|
||||
/// msdf::Texture documentation.
|
||||
pub msdf_texture_glyph_id: usize,
|
||||
|
||||
/// A required offset of the _base square_. See structure documentation for details.
|
||||
pub offset: Vector2<f32>,
|
||||
|
||||
/// A required scale of the _base square_. See structure documentation for details.
|
||||
pub scale: Vector2<f32>,
|
||||
|
||||
/// Distance between two successive pen positions for specific glyph.
|
||||
pub advance: f32,
|
||||
}
|
||||
|
||||
impl GlyphRenderInfo {
|
||||
/// See `MSDF_PARAMS` docs.
|
||||
pub const MAX_MSDF_SHRINK_FACTOR: f64 = 4.0;
|
||||
|
||||
/// See `MSDF_PARAMS` docs.
|
||||
pub const MAX_MSDF_GLYPH_SCALE: f64 = 2.0;
|
||||
|
||||
/// Parameters used for MSDF generation.
|
||||
///
|
||||
/// The range was picked such way, that we avoid fitting range in one rendered pixel.
|
||||
/// Otherwise the antialiasing won't work. I assumed some maximum `shrink factor` (how many
|
||||
/// times rendered square will be smaller than MSDF size), and pick an arbitrary maximum glyph
|
||||
/// scale up.
|
||||
///
|
||||
/// The rest of parameters are the defaults taken from msdfgen library
|
||||
pub const MSDF_PARAMS: MsdfParameters = MsdfParameters {
|
||||
width: msdf::Texture::WIDTH,
|
||||
height: msdf::Texture::ONE_GLYPH_HEIGHT,
|
||||
edge_coloring_angle_threshold: 3.0,
|
||||
range: Self::MAX_MSDF_SHRINK_FACTOR * Self::MAX_MSDF_GLYPH_SCALE,
|
||||
max_scale: Self::MAX_MSDF_GLYPH_SCALE,
|
||||
edge_threshold: 1.001,
|
||||
overlap_support: true,
|
||||
};
|
||||
|
||||
/// Load new GlyphRenderInfo from msdf_sys font handle. This also extends the atlas with
|
||||
/// MSDF generated for this character.
|
||||
pub fn load(handle: &msdf_sys::Font, ch: char, atlas: &msdf::Texture) -> Self {
|
||||
let unicode = ch as u32;
|
||||
let params = Self::MSDF_PARAMS;
|
||||
let msdf = Msdf::generate(handle, unicode, ¶ms);
|
||||
let inversed_scale = Vector2::new(1.0 / msdf.scale.x, 1.0 / msdf.scale.y);
|
||||
let translation = msdf::convert_msdf_translation(&msdf);
|
||||
let glyph_id = atlas.rows() / msdf::Texture::ONE_GLYPH_HEIGHT;
|
||||
atlas.extend_with_raw_data(msdf.data.iter());
|
||||
GlyphRenderInfo {
|
||||
msdf_texture_glyph_id: glyph_id,
|
||||
offset: -translation,
|
||||
scale: Vector2(inversed_scale.x as f32, inversed_scale.y as f32),
|
||||
advance: msdf::x_distance_from_msdf_value(msdf.advance),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
// ============
|
||||
// === Font ===
|
||||
// ============
|
||||
|
||||
/// A single font data used for rendering.
|
||||
///
|
||||
/// The data for individual characters and kerning are load on demand.
|
||||
///
|
||||
/// Each distance and transformation values are expressed in normalized coordinates, where `y` = 0.0
|
||||
/// is _baseline_ and `y` = 1.0 is _ascender_. For explanation of various font-rendering terms, see
|
||||
/// [freetype documentation](https://www.freetype.org/freetype2/docs/glyphs/glyphs-3.html#section-1)
|
||||
|
||||
#[derive(Debug, Clone, CloneRef, Deref)]
|
||||
pub struct Font {
|
||||
rc: Rc<FontData>,
|
||||
}
|
||||
|
||||
impl From<FontData> for Font {
|
||||
fn from(t: FontData) -> Self {
|
||||
let rc = Rc::new(t);
|
||||
Self { rc }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
// ================
|
||||
// === FontData ===
|
||||
// ================
|
||||
|
||||
/// Internal representation of `Font`.
|
||||
#[derive(Debug)]
|
||||
#[allow(missing_docs)]
|
||||
pub struct FontData {
|
||||
pub name: String,
|
||||
msdf_font: msdf_sys::Font,
|
||||
atlas: msdf::Texture,
|
||||
glyphs: Cache<char, GlyphRenderInfo>,
|
||||
kerning: Cache<(char, char), f32>,
|
||||
}
|
||||
|
||||
impl Font {
|
||||
/// Constructor.
|
||||
pub fn from_msdf_font(name: String, msdf_font: msdf_sys::Font) -> Self {
|
||||
let atlas = default();
|
||||
let glyphs = default();
|
||||
let kerning = default();
|
||||
FontData { name, msdf_font, atlas, glyphs, kerning }.into()
|
||||
}
|
||||
|
||||
/// Constructor.
|
||||
pub fn from_raw_data(name: String, font_data: &[u8]) -> Self {
|
||||
Self::from_msdf_font(name, msdf_sys::Font::load_from_memory(font_data))
|
||||
}
|
||||
|
||||
/// Create render info for one of embedded fonts
|
||||
pub fn try_from_embedded(base: &EmbeddedFonts, name: &str) -> Option<Self> {
|
||||
let font_data_opt = base.font_data_by_name.get(name);
|
||||
font_data_opt.map(|data| Self::from_raw_data(name.to_string(), data))
|
||||
}
|
||||
|
||||
/// Get render info for one character, generating one if not found.
|
||||
pub fn glyph_info(&self, ch: char) -> GlyphRenderInfo {
|
||||
let handle = &self.msdf_font;
|
||||
self.glyphs.get_or_create(ch, move || GlyphRenderInfo::load(handle, ch, &self.atlas))
|
||||
}
|
||||
|
||||
/// Get kerning between two characters
|
||||
pub fn kerning(&self, left: char, right: char) -> f32 {
|
||||
self.kerning.get_or_create((left, right), || {
|
||||
let msdf_val = self.msdf_font.retrieve_kerning(left, right);
|
||||
msdf::x_distance_from_msdf_value(msdf_val)
|
||||
})
|
||||
}
|
||||
|
||||
/// A whole msdf texture bound for this font.
|
||||
pub fn with_borrowed_msdf_texture_data<F, R>(&self, operation: F) -> R
|
||||
where F: FnOnce(&[u8]) -> R {
|
||||
self.atlas.with_borrowed_data(operation)
|
||||
}
|
||||
|
||||
/// Get number of rows in msdf texture.
|
||||
pub fn msdf_texture_rows(&self) -> usize {
|
||||
self.atlas.rows()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub fn mock(name: impl Into<String>) -> Self {
|
||||
Self::from_msdf_font(name.into(), msdf_sys::Font::mock_font())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub fn mock_char_info(
|
||||
&self,
|
||||
ch: char,
|
||||
offset: Vector2<f32>,
|
||||
scale: Vector2<f32>,
|
||||
advance: f32,
|
||||
) -> GlyphRenderInfo {
|
||||
self.glyphs.invalidate(&ch);
|
||||
let data_size = msdf::Texture::ONE_GLYPH_SIZE;
|
||||
let msdf_data = (0..data_size).map(|_| 0.12345);
|
||||
let msdf_texture_glyph_id = self.msdf_texture_rows() / msdf::Texture::ONE_GLYPH_HEIGHT;
|
||||
|
||||
self.atlas.extend_with_raw_data(msdf_data);
|
||||
self.glyphs.get_or_create(ch, move || GlyphRenderInfo {
|
||||
offset,
|
||||
scale,
|
||||
advance,
|
||||
msdf_texture_glyph_id,
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub fn mock_kerning_info(&self, l: char, r: char, value: f32) {
|
||||
self.kerning.invalidate(&(l, r));
|
||||
self.kerning.get_or_create((l, r), || value);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
// ====================
|
||||
// === RegistryData ===
|
||||
// ====================
|
||||
|
||||
shared! { Registry
|
||||
/// Structure keeping all fonts loaded from different sources.
|
||||
#[derive(Debug)]
|
||||
pub struct RegistryData {
|
||||
embedded : EmbeddedFonts,
|
||||
fonts : HashMap<String,Font>,
|
||||
default : Font,
|
||||
}
|
||||
|
||||
impl {
|
||||
/// Load a font by name. The font can be loaded either from cache or from the embedded fonts
|
||||
/// registry if not used before. Returns None if the name is missing in both cache and embedded
|
||||
/// font list.
|
||||
pub fn try_load(&mut self, name:&str) -> Option<Font> {
|
||||
match self.fonts.entry(name.to_string()) {
|
||||
Entry::Occupied (entry) => Some(entry.get().clone_ref()),
|
||||
Entry::Vacant (entry) => Font::try_from_embedded(&self.embedded,name).map(|font| {
|
||||
entry.insert(font.clone_ref());
|
||||
font
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// Load a font by name. The font can be loaded either from cache or from the embedded fonts
|
||||
/// registry if not used before. Returns default font if the name is missing in both cache and
|
||||
/// embedded font list.
|
||||
pub fn load(&mut self, name:&str) -> Font {
|
||||
self.try_load(name).unwrap_or_else(|| self.default())
|
||||
}
|
||||
|
||||
/// Get the default font. It is often used in case the desired font could not be loaded.
|
||||
pub fn default(&self) -> Font {
|
||||
self.default.clone_ref()
|
||||
}
|
||||
}}
|
||||
|
||||
impl RegistryData {
|
||||
/// Create empty font `Registry` and load raw data of embedded fonts.
|
||||
pub fn init_and_load_default() -> RegistryData {
|
||||
let embedded = EmbeddedFonts::create_and_fill();
|
||||
let fonts = HashMap::new();
|
||||
let default_name = DEFAULT_FONT;
|
||||
let default = Font::try_from_embedded(&embedded, default_name)
|
||||
.unwrap_or_else(|| panic!("Cannot load default font {}.", default_name));
|
||||
Self { embedded, fonts, default }
|
||||
}
|
||||
}
|
||||
|
||||
impl Registry {
|
||||
/// Constructor.
|
||||
pub fn init_and_load_default() -> Registry {
|
||||
let rc = Rc::new(RefCell::new(RegistryData::init_and_load_default()));
|
||||
Self { rc }
|
||||
}
|
||||
}
|
||||
|
||||
impl scene::Extension for Registry {
|
||||
fn init(_scene: &Scene) -> Self {
|
||||
Self::init_and_load_default()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
// =============
|
||||
// === Tests ===
|
||||
// =============
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
use ensogl_text_embedded_fonts;
|
||||
use ensogl_text_embedded_fonts::EmbeddedFonts;
|
||||
use ensogl_text_embedded_fonts::Family;
|
||||
use wasm_bindgen_test::wasm_bindgen_test;
|
||||
use wasm_bindgen_test::wasm_bindgen_test_configure;
|
||||
|
||||
const TEST_FONT_NAME: &str = embedded_fonts::DefaultFamily::mono_bold();
|
||||
|
||||
fn create_test_font() -> Font {
|
||||
let embedded_fonts = EmbeddedFonts::create_and_fill();
|
||||
Font::try_from_embedded(&embedded_fonts, TEST_FONT_NAME).unwrap()
|
||||
}
|
||||
|
||||
wasm_bindgen_test_configure!(run_in_browser);
|
||||
|
||||
#[wasm_bindgen_test(async)]
|
||||
async fn empty_font_render_info() {
|
||||
ensogl_text_msdf_sys::initialized().await;
|
||||
let font_render_info = create_test_font();
|
||||
|
||||
assert_eq!(TEST_FONT_NAME, font_render_info.name);
|
||||
assert_eq!(0, font_render_info.atlas.with_borrowed_data(|t: &[u8]| t.len()));
|
||||
assert_eq!(0, font_render_info.glyphs.len());
|
||||
}
|
||||
|
||||
#[wasm_bindgen_test(async)]
|
||||
async fn loading_glyph_info() {
|
||||
ensogl_text_msdf_sys::initialized().await;
|
||||
let font_render_info = create_test_font();
|
||||
|
||||
font_render_info.glyph_info('A');
|
||||
font_render_info.glyph_info('B');
|
||||
|
||||
let chars = 2;
|
||||
let tex_width = msdf::Texture::WIDTH;
|
||||
let tex_height = msdf::Texture::ONE_GLYPH_HEIGHT * chars;
|
||||
let channels = Msdf::CHANNELS_COUNT;
|
||||
let tex_size = tex_width * tex_height * channels;
|
||||
|
||||
assert_eq!(tex_height, font_render_info.msdf_texture_rows());
|
||||
assert_eq!(tex_size, font_render_info.atlas.with_borrowed_data(|t| t.len()));
|
||||
assert_eq!(chars, font_render_info.glyphs.len());
|
||||
|
||||
let first_char = font_render_info.glyphs.get_or_create('A', || panic!("Expected value"));
|
||||
let second_char = font_render_info.glyphs.get_or_create('B', || panic!("Expected value"));
|
||||
|
||||
let first_index = 0;
|
||||
let second_index = 1;
|
||||
|
||||
assert_eq!(first_index, first_char.msdf_texture_glyph_id);
|
||||
assert_eq!(second_index, second_char.msdf_texture_glyph_id);
|
||||
}
|
||||
|
||||
#[wasm_bindgen_test(async)]
|
||||
async fn getting_or_creating_char() {
|
||||
ensogl_text_msdf_sys::initialized().await;
|
||||
let font_render_info = create_test_font();
|
||||
|
||||
{
|
||||
let char_info = font_render_info.glyph_info('A');
|
||||
assert_eq!(0, char_info.msdf_texture_glyph_id);
|
||||
}
|
||||
assert_eq!(1, font_render_info.glyphs.len());
|
||||
|
||||
{
|
||||
let char_info = font_render_info.glyph_info('A');
|
||||
assert_eq!(0, char_info.msdf_texture_glyph_id);
|
||||
}
|
||||
assert_eq!(1, font_render_info.glyphs.len());
|
||||
}
|
||||
}
|
@ -1,132 +0,0 @@
|
||||
//! The pen is a point on the text _baseline_ used to locate glyph. It moves along the _baseline_
|
||||
//! with each glyph rendered. For details, see
|
||||
//! [freetype documentation](https://www.freetype.org/freetype2/docs/glyphs/glyphs-3.html#section-1)
|
||||
|
||||
use crate::prelude::*;
|
||||
|
||||
use super::font::Font;
|
||||
|
||||
|
||||
|
||||
// =====================
|
||||
// === AdvanceResult ===
|
||||
// =====================
|
||||
|
||||
/// Information about the char at the pen position.
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
#[allow(missing_docs)]
|
||||
pub struct AdvanceResult {
|
||||
pub char: Option<char>,
|
||||
pub offset: f32,
|
||||
}
|
||||
|
||||
|
||||
|
||||
// ================
|
||||
// === CharInfo ===
|
||||
// ================
|
||||
|
||||
/// Information about the char used to transform the pen.
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
#[allow(missing_docs)]
|
||||
pub struct CharInfo {
|
||||
pub char: char,
|
||||
pub size: f32,
|
||||
}
|
||||
|
||||
impl CharInfo {
|
||||
pub fn new(char: char, size: f32) -> Self {
|
||||
Self { char, size }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
// ===========
|
||||
// === Pen ===
|
||||
// ===========
|
||||
|
||||
/// Pen iterates over chars producing the position for a given char.
|
||||
///
|
||||
/// The pen is a font-specific term (see
|
||||
/// [freetype documentation](https://www.freetype.org/freetype2/docs/glyphs/glyphs-3.html#section-1)
|
||||
/// for details).
|
||||
#[derive(Debug)]
|
||||
pub struct Pen {
|
||||
offset: f32,
|
||||
current_char: Option<CharInfo>,
|
||||
font: Font,
|
||||
}
|
||||
|
||||
impl Pen {
|
||||
/// Create a new pen iterator for a given font.
|
||||
pub fn new(font: &Font) -> Self {
|
||||
let offset = default();
|
||||
let current_char = default();
|
||||
let font = font.clone_ref();
|
||||
Self { offset, current_char, font }
|
||||
}
|
||||
|
||||
/// Advance the pen to the next position.
|
||||
pub fn advance(&mut self, next: Option<CharInfo>) -> AdvanceResult {
|
||||
let next_char = next.map(|t| t.char);
|
||||
if let Some(current) = self.current_char {
|
||||
let kerning =
|
||||
next_char.map(|ch| self.font.kerning(current.char, ch)).unwrap_or_default();
|
||||
let advance = self.font.glyph_info(current.char).advance + kerning;
|
||||
let offset = advance * current.size;
|
||||
self.offset += offset;
|
||||
}
|
||||
self.current_char = next;
|
||||
let offset = self.offset;
|
||||
AdvanceResult { char: next_char, offset }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
// =============
|
||||
// === Tests ===
|
||||
// =============
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
use crate::typeface::font::GlyphRenderInfo;
|
||||
|
||||
use wasm_bindgen_test::wasm_bindgen_test;
|
||||
|
||||
#[wasm_bindgen_test(async)]
|
||||
async fn moving_pen() {
|
||||
ensogl_text_msdf_sys::initialized().await;
|
||||
let font = Font::mock("Test font");
|
||||
mock_a_glyph_info(font.clone_ref());
|
||||
mock_w_glyph_info(font.clone_ref());
|
||||
font.mock_kerning_info('A', 'W', -0.16);
|
||||
font.mock_kerning_info('W', 'A', 0.0);
|
||||
|
||||
let mut pen = Pen::new(&font);
|
||||
let mut result = Vec::new();
|
||||
for chr in "AWA".chars() {
|
||||
let char_info = CharInfo::new(chr, 1.0);
|
||||
result.push(pen.advance(Some(char_info)).offset);
|
||||
}
|
||||
let expected = vec![0.0, 0.4, 1.1];
|
||||
assert_eq!(expected, result);
|
||||
}
|
||||
|
||||
fn mock_a_glyph_info(font: Font) -> GlyphRenderInfo {
|
||||
let advance = 0.56;
|
||||
let scale = Vector2::new(0.5, 0.8);
|
||||
let offset = Vector2::new(0.1, 0.2);
|
||||
font.mock_char_info('A', scale, offset, advance)
|
||||
}
|
||||
|
||||
fn mock_w_glyph_info(font: Font) -> GlyphRenderInfo {
|
||||
let advance = 0.7;
|
||||
let scale = Vector2::new(0.6, 0.9);
|
||||
let offset = Vector2::new(0.1, 0.2);
|
||||
font.mock_char_info('W', scale, offset, advance)
|
||||
}
|
||||
}
|
@ -27,8 +27,8 @@ enso-shapely = { path = "../../shapely" }
|
||||
enso-shortcuts = { path = "../../shortcuts" }
|
||||
enso-types = { path = "../../types" }
|
||||
enso-web = { path = "../../web" }
|
||||
ensogl-text-embedded-fonts = { path = "../component/text/embedded-fonts" }
|
||||
ensogl-text-msdf-sys = { path = "../component/text/msdf-sys" }
|
||||
ensogl-text-embedded-fonts = { path = "../component/text/src/font/embedded" }
|
||||
ensogl-text-msdf = { path = "../component/text/src/font/msdf" }
|
||||
bit_field = { version = "0.10.0" }
|
||||
console_error_panic_hook = { version = "0.1.6" }
|
||||
enum_dispatch = { version = "0.3.6" }
|
||||
|
@ -239,8 +239,7 @@ impl WorldData {
|
||||
}
|
||||
|
||||
fn init_environment(&self) {
|
||||
web::forward_panic_hook_to_console();
|
||||
web::set_stack_trace_limit();
|
||||
init_wasm();
|
||||
}
|
||||
|
||||
fn init_debug_hotkeys(&self) {
|
||||
|
@ -10,6 +10,6 @@ crate-type = ["cdylib", "rlib"]
|
||||
[dependencies]
|
||||
enso-frp = { path = "../../../frp" }
|
||||
ensogl-core = { path = "../../core" }
|
||||
ensogl-text-msdf-sys = { path = "../../component/text/msdf-sys" }
|
||||
ensogl-text-msdf = { path = "../../component/text/src/font/msdf" }
|
||||
enso-prelude = { path = "../../../prelude" }
|
||||
wasm-bindgen = { version = "0.2.78", features = ["nightly"] }
|
||||
|
@ -25,7 +25,7 @@ use wasm_bindgen::prelude::*;
|
||||
|
||||
use ensogl_core::application::Application;
|
||||
use ensogl_core::DEPRECATED_Animation;
|
||||
use ensogl_text_msdf_sys::run_once_initialized;
|
||||
use ensogl_text_msdf::run_once_initialized;
|
||||
use logger::TraceLogger as Logger;
|
||||
|
||||
|
||||
|
@ -289,8 +289,6 @@ macro_rules! examples {
|
||||
#[entry_point]
|
||||
#[allow(dead_code)]
|
||||
pub fn main() {
|
||||
web::forward_panic_hook_to_console();
|
||||
web::set_stack_trace_limit();
|
||||
let container = web::document.create_div_or_panic();
|
||||
container.set_attribute_or_warn("id", "examples");
|
||||
container.set_style_or_warn("display", "flex");
|
||||
|
@ -10,6 +10,6 @@ crate-type = ["cdylib", "rlib"]
|
||||
[dependencies]
|
||||
ensogl-core = { path = "../../core" }
|
||||
ensogl-text = { path = "../../component/text" }
|
||||
ensogl-text-embedded-fonts = { path = "../../component/text/embedded-fonts" }
|
||||
ensogl-text-msdf-sys = { path = "../../component/text/msdf-sys" }
|
||||
ensogl-text-embedded-fonts = { path = "../../component/text/src/font/embedded" }
|
||||
ensogl-text-msdf = { path = "../../component/text/src/font/msdf" }
|
||||
wasm-bindgen = { version = "0.2.78", features = ["nightly"] }
|
||||
|
@ -22,13 +22,11 @@
|
||||
|
||||
use ensogl_core::display::world::*;
|
||||
use ensogl_core::prelude::*;
|
||||
use ensogl_text::typeface::*;
|
||||
use wasm_bindgen::prelude::*;
|
||||
|
||||
use ensogl_core::data::color;
|
||||
use ensogl_text_embedded_fonts as embedded_fonts;
|
||||
use ensogl_text_embedded_fonts::Family;
|
||||
use ensogl_text_msdf_sys::run_once_initialized;
|
||||
use ensogl_text::font;
|
||||
use ensogl_text_msdf::run_once_initialized;
|
||||
|
||||
|
||||
|
||||
@ -44,12 +42,13 @@ pub fn main() {
|
||||
|
||||
fn init(world: &World) {
|
||||
let fonts = world.default_scene.extension::<font::Registry>();
|
||||
let font = fonts.load(embedded_fonts::DefaultFamily::regular());
|
||||
let glyph_system = glyph::System::new(&world.default_scene, font);
|
||||
let height = 32.0;
|
||||
let font = fonts.load("mplus1");
|
||||
let glyph_system = font::glyph::System::new(&world.default_scene, font);
|
||||
let height = 64.0;
|
||||
let color = color::Rgba::new(0.5, 0.0, 0.0, 1.0);
|
||||
let start_pos = Vector2(-300.0, -300.0);
|
||||
|
||||
|
||||
for (line_ind, line) in CHARS_TO_TEST.iter().enumerate() {
|
||||
for (char_ind, char) in line.chars().enumerate() {
|
||||
let glyph = glyph_system.new_glyph();
|
||||
@ -61,7 +60,7 @@ fn init(world: &World) {
|
||||
bold_glyph.set_char(char);
|
||||
bold_glyph.set_color(color);
|
||||
bold_glyph.set_font_size(height);
|
||||
bold_glyph.set_bold(true);
|
||||
bold_glyph.set_weight_bold();
|
||||
|
||||
let x = char_ind as f32 * (height + 4.0);
|
||||
let y = line_ind as f32 * (height * 2.0 + 8.0);
|
||||
|
@ -12,7 +12,7 @@ enso-frp = { path = "../../../frp" }
|
||||
ensogl-core = { path = "../../core" }
|
||||
ensogl-hardcoded-theme = { path = "../../app/theme/hardcoded" }
|
||||
ensogl-grid-view = { path = "../../component/grid-view" }
|
||||
ensogl-text-msdf-sys = { path = "../../component/text/msdf-sys" }
|
||||
ensogl-text-msdf = { path = "../../component/text/src/font/msdf" }
|
||||
enso-text = { path = "../../../text" }
|
||||
wasm-bindgen = { version = "0.2.78", features = ["nightly"] }
|
||||
itertools = "0.10.3"
|
||||
|
@ -24,7 +24,6 @@ use ensogl_core::prelude::*;
|
||||
use wasm_bindgen::prelude::*;
|
||||
|
||||
use enso_frp as frp;
|
||||
use enso_frp::web::forward_panic_hook_to_console;
|
||||
use ensogl_core::application::Application;
|
||||
use ensogl_core::data::color;
|
||||
use ensogl_core::display::navigation::navigator::Navigator;
|
||||
@ -33,7 +32,7 @@ use ensogl_grid_view as grid_view;
|
||||
use ensogl_grid_view::Col;
|
||||
use ensogl_grid_view::Row;
|
||||
use ensogl_hardcoded_theme as theme;
|
||||
use ensogl_text_msdf_sys::run_once_initialized;
|
||||
use ensogl_text_msdf::run_once_initialized;
|
||||
|
||||
|
||||
|
||||
@ -46,8 +45,6 @@ use ensogl_text_msdf_sys::run_once_initialized;
|
||||
#[allow(dead_code)]
|
||||
pub fn main() {
|
||||
run_once_initialized(|| {
|
||||
init_tracing(TRACE);
|
||||
forward_panic_hook_to_console();
|
||||
let app = Application::new("root");
|
||||
init(&app);
|
||||
mem::forget(app);
|
||||
|
@ -12,6 +12,6 @@ enso-frp = { path = "../../../frp" }
|
||||
ensogl-core = { path = "../../core" }
|
||||
ensogl-hardcoded-theme = { path = "../../app/theme/hardcoded" }
|
||||
ensogl-list-view = { path = "../../component/list-view" }
|
||||
ensogl-text-msdf-sys = { path = "../../component/text/msdf-sys" }
|
||||
ensogl-text-msdf = { path = "../../component/text/src/font/msdf" }
|
||||
enso-text = { path = "../../../text" }
|
||||
wasm-bindgen = { version = "0.2.78", features = ["nightly"] }
|
||||
|
@ -28,7 +28,7 @@ use ensogl_core::application::Application;
|
||||
use ensogl_core::display::object::ObjectOps;
|
||||
use ensogl_hardcoded_theme as theme;
|
||||
use ensogl_list_view as list_view;
|
||||
use ensogl_text_msdf_sys::run_once_initialized;
|
||||
use ensogl_text_msdf::run_once_initialized;
|
||||
use logger::TraceLogger as Logger;
|
||||
|
||||
|
||||
|
@ -10,5 +10,5 @@ crate-type = ["cdylib", "rlib"]
|
||||
[dependencies]
|
||||
enso-frp = { path = "../../../frp" }
|
||||
ensogl-core = { path = "../../core" }
|
||||
ensogl-text-msdf-sys = { path = "../../component/text/msdf-sys" }
|
||||
ensogl-text-msdf = { path = "../../component/text/src/font/msdf" }
|
||||
wasm-bindgen = { version = "0.2.78", features = ["nightly"] }
|
||||
|
@ -32,7 +32,7 @@ use ensogl_core::define_shape_system;
|
||||
use ensogl_core::display;
|
||||
use ensogl_core::display::navigation::navigator::Navigator;
|
||||
use ensogl_core::display::object::ObjectOps;
|
||||
use ensogl_text_msdf_sys::run_once_initialized;
|
||||
use ensogl_text_msdf::run_once_initialized;
|
||||
|
||||
|
||||
|
||||
|
@ -21,7 +21,7 @@ ensogl-sequence-diagram = { path = "../../component/sequence-diagram" }
|
||||
ensogl-tooltip = { path = "../../component/tooltip" }
|
||||
ensogl-hardcoded-theme = { path = "../../app/theme/hardcoded" }
|
||||
ensogl-text = { path = "../../component/text" }
|
||||
ensogl-text-msdf-sys = { path = "../../component/text/msdf-sys" }
|
||||
ensogl-text-msdf = { path = "../../component/text/src/font/msdf" }
|
||||
futures = "0.3"
|
||||
serde = "1"
|
||||
wasm-bindgen = { version = "0.2.58", features = ["nightly"] }
|
||||
|
@ -36,7 +36,6 @@ use ensogl_core::display::object::ObjectOps;
|
||||
use ensogl_core::display::style::theme;
|
||||
use ensogl_core::display::Scene;
|
||||
use ensogl_core::frp;
|
||||
use ensogl_core::system::web;
|
||||
use ensogl_flame_graph as flame_graph;
|
||||
use ensogl_flame_graph::COLOR_BLOCK_ACTIVE;
|
||||
use ensogl_flame_graph::COLOR_BLOCK_PAUSED;
|
||||
@ -67,9 +66,6 @@ const SHOW_BACKEND_MESSAGE_MARKS: bool = true;
|
||||
#[entry_point]
|
||||
#[allow(dead_code)]
|
||||
pub async fn main() {
|
||||
web::forward_panic_hook_to_console();
|
||||
web::set_stack_trace_limit();
|
||||
|
||||
let app = &Application::new("root");
|
||||
let world = &app.display;
|
||||
let scene = &world.default_scene;
|
||||
|
@ -17,7 +17,7 @@ ensogl-core = { path = "../../core" }
|
||||
ensogl-flame-graph = { path = "../../component/flame-graph" }
|
||||
ensogl-hardcoded-theme = { path = "../../app/theme/hardcoded" }
|
||||
ensogl-text = { path = "../../component/text" }
|
||||
ensogl-text-msdf-sys = { path = "../../component/text/msdf-sys" }
|
||||
ensogl-text-msdf = { path = "../../component/text/src/font/msdf" }
|
||||
ensogl-tooltip = { path = "../../component/tooltip" }
|
||||
futures = "0.3"
|
||||
wasm-bindgen = { version = "0.2.58", features = ["nightly"] }
|
||||
|
@ -11,5 +11,5 @@ crate-type = ["cdylib", "rlib"]
|
||||
ensogl-core = { path = "../../core" }
|
||||
ensogl-hardcoded-theme = { path = "../../app/theme/hardcoded" }
|
||||
ensogl-scroll-area = { path = "../../component/scroll-area" }
|
||||
ensogl-text-msdf-sys = { path = "../../component/text/msdf-sys" }
|
||||
ensogl-text-msdf = { path = "../../component/text/src/font/msdf" }
|
||||
wasm-bindgen = { version = "0.2.78", features = ["nightly"] }
|
||||
|
@ -30,7 +30,7 @@ use ensogl_core::display::navigation::navigator::Navigator;
|
||||
use ensogl_core::display::object::ObjectOps;
|
||||
use ensogl_hardcoded_theme as theme;
|
||||
use ensogl_scroll_area::ScrollArea;
|
||||
use ensogl_text_msdf_sys::run_once_initialized;
|
||||
use ensogl_text_msdf::run_once_initialized;
|
||||
|
||||
|
||||
|
||||
|
@ -10,6 +10,6 @@ crate-type = ["cdylib", "rlib"]
|
||||
[dependencies]
|
||||
ensogl-core = { path = "../../core" }
|
||||
ensogl-hardcoded-theme = { path = "../../app/theme/hardcoded" }
|
||||
ensogl-text-msdf-sys = { path = "../../component/text/msdf-sys" }
|
||||
ensogl-text-msdf = { path = "../../component/text/src/font/msdf" }
|
||||
ensogl-selector = { path = "../../component/selector" }
|
||||
wasm-bindgen = { version = "0.2.78", features = ["nightly"] }
|
||||
|
@ -29,7 +29,7 @@ use ensogl_core::display::object::ObjectOps;
|
||||
use ensogl_hardcoded_theme as theme;
|
||||
use ensogl_selector as selector;
|
||||
use ensogl_selector::Bounds;
|
||||
use ensogl_text_msdf_sys::run_once_initialized;
|
||||
use ensogl_text_msdf::run_once_initialized;
|
||||
|
||||
|
||||
|
||||
|
@ -11,6 +11,6 @@ crate-type = ["cdylib", "rlib"]
|
||||
ensogl-core = { path = "../../core" }
|
||||
ensogl-hardcoded-theme = { path = "../../app/theme/hardcoded" }
|
||||
ensogl-text = { path = "../../component/text" }
|
||||
ensogl-text-embedded-fonts = { path = "../../component/text/embedded-fonts" }
|
||||
ensogl-text-msdf-sys = { path = "../../component/text/msdf-sys" }
|
||||
ensogl-text-embedded-fonts = { path = "../../component/text/src/font/embedded" }
|
||||
ensogl-text-msdf = { path = "../../component/text/src/font/msdf" }
|
||||
wasm-bindgen = { version = "0.2.78", features = ["nightly"] }
|
||||
|
@ -30,9 +30,7 @@ use ensogl_core::display::navigation::navigator::Navigator;
|
||||
use ensogl_text::buffer;
|
||||
use ensogl_text::style;
|
||||
use ensogl_text::Area;
|
||||
use ensogl_text_embedded_fonts as embedded_fonts;
|
||||
use ensogl_text_embedded_fonts::Family;
|
||||
use ensogl_text_msdf_sys::run_once_initialized;
|
||||
use ensogl_text_msdf::run_once_initialized;
|
||||
|
||||
|
||||
|
||||
@ -54,9 +52,10 @@ fn init(app: Application) {
|
||||
let quote = "Et Eärello Endorenna utúlien.\nSinome maruvan ar Hildinyar tenn' Ambar-metta\n";
|
||||
let snowman = "\u{2603}";
|
||||
let zalgo = "Z̮̞̠͙͔ͅḀ̗̞͈̻̗Ḷ͙͎̯̹̞͓G̻O̭̗̮";
|
||||
let text = quote.to_string() + snowman + zalgo;
|
||||
let _text = quote.to_string() + snowman + zalgo;
|
||||
let text = "test".to_string();
|
||||
area.set_content(text.clone() + "\n" + text.as_str());
|
||||
area.set_font(embedded_fonts::DefaultFamily::regular());
|
||||
area.set_font("default");
|
||||
area.focus();
|
||||
area.hover();
|
||||
area.set_cursor_at_end();
|
||||
@ -79,7 +78,7 @@ fn init(app: Application) {
|
||||
let text = "red green blue";
|
||||
let colored_area = app.new_view::<Area>();
|
||||
app.display.default_scene.add_child(&colored_area);
|
||||
colored_area.set_font(embedded_fonts::DefaultFamily::regular());
|
||||
colored_area.set_font("DejaVuSans");
|
||||
colored_area.set_position_xy(Vector2::new(200.0, 200.0));
|
||||
|
||||
colored_area.set_default_color(color::Rgba::black());
|
||||
|
@ -1,5 +1,6 @@
|
||||
//! A module with Javascript IO bindings utilities.
|
||||
|
||||
use crate::prelude::*;
|
||||
use enso_web::prelude::*;
|
||||
|
||||
use crate as frp;
|
||||
|
@ -22,6 +22,7 @@ enso-prelude = { version = "^0.2.1", path = "../prelude" }
|
||||
enso-shapely = { version = "^0.2.0", path = "../shapely" }
|
||||
wasm-bindgen = { version = "0.2.78", features = ["nightly"] }
|
||||
js-sys = { version = "0.3.28" }
|
||||
ifmt = "0.3.3"
|
||||
|
||||
[dependencies.web-sys]
|
||||
version = "0.3.4"
|
||||
|
@ -131,6 +131,7 @@ use crate::processor::Processor;
|
||||
|
||||
use enso_shapely::CloneRef;
|
||||
|
||||
pub use ifmt::iformat;
|
||||
|
||||
|
||||
// ==============
|
||||
|
@ -14,7 +14,7 @@ macro_rules! log_template {
|
||||
};
|
||||
|
||||
($expand:ident, $level:path, $logger:expr, $msg:tt) => {
|
||||
$crate::LoggerOps::<$level>::log(&$logger,$level,||iformat!($msg))
|
||||
$crate::LoggerOps::<$level>::log(&$logger,$level,||$crate::iformat!($msg))
|
||||
};
|
||||
|
||||
($expand:ident, $level:path, $logger:expr, || $msg:expr) => {
|
||||
@ -34,7 +34,7 @@ macro_rules! log_template {
|
||||
};
|
||||
|
||||
($expand:ident, $level:path, $logger:expr, $msg:tt, || $($body:tt)*) => {
|
||||
$crate::log_template_group!($expand,$level,$logger,[||iformat!($msg)],||$($body)*)
|
||||
$crate::log_template_group!($expand,$level,$logger,[||$crate::iformat!($msg)],||$($body)*)
|
||||
};
|
||||
|
||||
($expand:ident, $level:path, $logger:expr, || $msg:expr, || $($body:tt)*) => {
|
||||
|
@ -1,8 +1,8 @@
|
||||
//! Java interface to [`enso_parser`].
|
||||
|
||||
// === Features ===
|
||||
// === Standard Linter Configuration ===
|
||||
#![deny(non_ascii_idents)]
|
||||
#![warn(unsafe_code)]
|
||||
// === Non-Standard Linter Configuration ===
|
||||
#![allow(clippy::option_map_unit_fn)]
|
||||
#![allow(clippy::precedence)]
|
||||
@ -38,6 +38,7 @@ use jni::JNIEnv;
|
||||
/// The input buffer contents MUST be valid UTF-8.
|
||||
/// The contents of the returned buffer MUST not be accessed after another call to `parseInput`, or
|
||||
/// a call to `freeState`.
|
||||
#[allow(unsafe_code)]
|
||||
#[no_mangle]
|
||||
pub extern "system" fn Java_org_enso_syntax2_Parser_parseInput(
|
||||
env: JNIEnv,
|
||||
@ -75,6 +76,7 @@ pub extern "system" fn Java_org_enso_syntax2_Parser_parseInput(
|
||||
///
|
||||
/// The input MUST have been returned by `allocState`, and MUST NOT have previously been passed to
|
||||
/// `freeState`.
|
||||
#[allow(unsafe_code)]
|
||||
#[no_mangle]
|
||||
pub extern "system" fn Java_org_enso_syntax2_Parser_getLastInputBase(
|
||||
_env: JNIEnv,
|
||||
@ -87,6 +89,7 @@ pub extern "system" fn Java_org_enso_syntax2_Parser_getLastInputBase(
|
||||
|
||||
/// Allocate a new parser state object. The returned value should be passed to `freeState` when no
|
||||
/// longer needed.
|
||||
#[allow(unsafe_code)]
|
||||
#[no_mangle]
|
||||
pub extern "system" fn Java_org_enso_syntax2_Parser_allocState(
|
||||
_env: JNIEnv,
|
||||
@ -101,6 +104,7 @@ pub extern "system" fn Java_org_enso_syntax2_Parser_allocState(
|
||||
///
|
||||
/// The input MUST have been returned by `allocState`, and MUST NOT have previously been passed to
|
||||
/// `freeState`.
|
||||
#[allow(unsafe_code)]
|
||||
#[no_mangle]
|
||||
pub extern "system" fn Java_org_enso_syntax2_Parser_freeState(
|
||||
_env: JNIEnv,
|
||||
|
@ -2,6 +2,7 @@
|
||||
|
||||
use crate::macros::pattern::*;
|
||||
use crate::macros::*;
|
||||
|
||||
use crate::syntax::operator;
|
||||
|
||||
|
||||
|
@ -33,7 +33,7 @@ use enso_parser::prelude::*;
|
||||
// =============
|
||||
|
||||
fn main() {
|
||||
init_tracing(TRACE);
|
||||
init_wasm();
|
||||
let ast = enso_parser::Parser::new().run("foo = 23");
|
||||
println!("\n\n==================\n\n");
|
||||
println!("{:#?}", ast);
|
||||
|
@ -1,9 +1,8 @@
|
||||
//! Code blocks.
|
||||
|
||||
|
||||
use crate::syntax::tree::*;
|
||||
|
||||
use crate::syntax::token;
|
||||
use crate::syntax::tree::*;
|
||||
|
||||
|
||||
|
||||
|
@ -17,7 +17,7 @@ crate-type = ["cdylib", "rlib"]
|
||||
|
||||
[dependencies]
|
||||
enso-reflect = { path = "../reflect" }
|
||||
enso-shapely = { version = "^0.2.0", path = "../shapely" }
|
||||
enso-shapely = { path = "../shapely" }
|
||||
anyhow = "1.0.37"
|
||||
assert_approx_eq = { version = "1.1.0" }
|
||||
backtrace = "0.3.53"
|
||||
@ -45,6 +45,7 @@ tracing-wasm = "0.2"
|
||||
wasm-bindgen = { version = "0.2.78", features = ["nightly"], optional = true }
|
||||
weak-table = "0.3.0"
|
||||
nalgebra = { version = "0.26.2", optional = true }
|
||||
enso-web = { path = "../web" }
|
||||
|
||||
[target.'cfg(target_arch = "wasm32")'.dependencies]
|
||||
wasm-bindgen = { version = "0.2.78", features = ["nightly"] }
|
||||
|
@ -18,7 +18,6 @@
|
||||
|
||||
#[cfg(feature = "futures")]
|
||||
pub mod channel;
|
||||
mod clone;
|
||||
mod collections;
|
||||
mod data;
|
||||
pub mod debug;
|
||||
@ -47,10 +46,11 @@ mod wrapper;
|
||||
#[cfg(feature = "serde")]
|
||||
pub use crate::serde::*;
|
||||
pub use crate::smallvec::*;
|
||||
pub use clone::*;
|
||||
pub use collections::*;
|
||||
pub use data::*;
|
||||
pub use debug::*;
|
||||
pub use enso_shapely::clone_ref::*;
|
||||
pub use enso_shapely::impl_clone_ref_as_clone;
|
||||
pub use fail::*;
|
||||
pub use leak::Leak;
|
||||
pub use leak::*;
|
||||
@ -125,18 +125,37 @@ pub const INFO: tracing::Level = tracing::Level::INFO;
|
||||
pub const DEBUG: tracing::Level = tracing::Level::DEBUG;
|
||||
pub const TRACE: tracing::Level = tracing::Level::TRACE;
|
||||
|
||||
|
||||
use std::sync::Once;
|
||||
|
||||
/// Tracing's `set_global_default` can be called only once. When running tests this can fail if
|
||||
/// not fenced.
|
||||
static TRACING_INIT_ONCE: Once = Once::new();
|
||||
|
||||
pub fn init_tracing(level: tracing::Level) {
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
let subscriber =
|
||||
tracing::fmt().compact().with_target(false).with_max_level(level).without_time().finish();
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
let subscriber = {
|
||||
use tracing_subscriber::layer::SubscriberExt;
|
||||
use tracing_wasm::*;
|
||||
let config = WASMLayerConfigBuilder::new().set_max_level(level).build();
|
||||
tracing::Registry::default().with(WASMLayer::new(config))
|
||||
};
|
||||
tracing::subscriber::set_global_default(subscriber).expect("Failed to initialize logger.");
|
||||
TRACING_INIT_ONCE.call_once(|| {
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
let subscriber = tracing::fmt()
|
||||
.compact()
|
||||
.with_target(false)
|
||||
.with_max_level(level)
|
||||
.without_time()
|
||||
.finish();
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
let subscriber = {
|
||||
use tracing_subscriber::layer::SubscriberExt;
|
||||
use tracing_wasm::*;
|
||||
let config = WASMLayerConfigBuilder::new().set_max_level(level).build();
|
||||
tracing::Registry::default().with(WASMLayer::new(config))
|
||||
};
|
||||
tracing::subscriber::set_global_default(subscriber).expect("Failed to initialize logger.");
|
||||
});
|
||||
}
|
||||
|
||||
pub fn init_wasm() {
|
||||
init_tracing(WARN);
|
||||
enso_web::forward_panic_hook_to_console();
|
||||
enso_web::set_stack_trace_limit();
|
||||
}
|
||||
|
||||
|
||||
@ -210,6 +229,12 @@ pub trait ToImpl: Sized {
|
||||
}
|
||||
impl<T> ToImpl for T {}
|
||||
|
||||
|
||||
|
||||
// ================
|
||||
// === Nalgebra ===
|
||||
// ================
|
||||
|
||||
#[cfg(feature = "nalgebra")]
|
||||
impl<T, R, C, S> TypeDisplay for nalgebra::Matrix<T, R, C, S>
|
||||
where
|
||||
|
@ -1,8 +1,8 @@
|
||||
//! This module defines several useful string variants, including copy-on-write and immutable
|
||||
//! implementations.
|
||||
|
||||
use crate::clone::*;
|
||||
use derive_more::*;
|
||||
use enso_shapely::clone_ref::*;
|
||||
use itertools::*;
|
||||
|
||||
use crate::impls;
|
||||
|
@ -24,6 +24,10 @@ paste = { version = "0.1" }
|
||||
derivative = { version = "2.2.0" }
|
||||
shrinkwraprs = { version = "0.3.0" }
|
||||
rustversion = { version = "1.0" }
|
||||
wasm-bindgen = { version = "0.2.78", features = ["nightly"] }
|
||||
|
||||
[dependencies.web-sys]
|
||||
version = "0.3.4"
|
||||
|
||||
[dev-dependencies]
|
||||
enso-prelude = { path = "../prelude" }
|
||||
|
@ -6,18 +6,38 @@ use crate::prelude::*;
|
||||
// === Entry Point ===
|
||||
// ===================
|
||||
|
||||
fn crate_name_to_fn_name(name: &str) -> String {
|
||||
fn crate_name_to_base_name(name: &str) -> String {
|
||||
let name = name.replace("debug-scene-", "");
|
||||
let name = name.replace("ensogl-example-", "");
|
||||
let name = name.replace("enso-example-", "");
|
||||
let name = name.replace("enso-", "");
|
||||
let name = name.replace("example-", "");
|
||||
let name = name.replace('-', "_");
|
||||
name.replace('-', "_")
|
||||
}
|
||||
|
||||
fn base_name_to_fn_name(name: &str) -> String {
|
||||
format!("entry_point_{}", name)
|
||||
}
|
||||
|
||||
pub fn derive(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
|
||||
let crate_name = std::env::var("CARGO_PKG_NAME").unwrap();
|
||||
pub fn derive(
|
||||
args: proc_macro::TokenStream,
|
||||
input: proc_macro::TokenStream,
|
||||
) -> proc_macro::TokenStream {
|
||||
let mut args_iter = args.into_iter();
|
||||
let fn_name_str = match args_iter.next() {
|
||||
None => {
|
||||
let crate_name = std::env::var("CARGO_PKG_NAME").unwrap();
|
||||
crate_name_to_base_name(&crate_name)
|
||||
}
|
||||
Some(token) => {
|
||||
if args_iter.next().is_some() {
|
||||
panic!("Expected maximum one argument, the entry point name. If missing, the crate name will be used.");
|
||||
}
|
||||
token.to_string()
|
||||
}
|
||||
};
|
||||
let fn_name_str = base_name_to_fn_name(&fn_name_str);
|
||||
let fn_name = quote::format_ident!("{}", fn_name_str);
|
||||
let decl = syn::parse_macro_input!(input as syn::Item);
|
||||
match decl {
|
||||
syn::Item::Fn(f) => {
|
||||
@ -25,7 +45,6 @@ pub fn derive(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
|
||||
if &name != "main" {
|
||||
panic!("The function should be named 'main'.");
|
||||
}
|
||||
let fn_name = quote::format_ident!("{}", crate_name_to_fn_name(&crate_name));
|
||||
let mut fn_sig = f.sig.clone();
|
||||
fn_sig.ident = fn_name;
|
||||
let attrs = &f.attrs;
|
||||
@ -33,7 +52,10 @@ pub fn derive(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
|
||||
let output = quote! {
|
||||
#(#attrs)*
|
||||
#[wasm_bindgen]
|
||||
pub #fn_sig #block
|
||||
pub #fn_sig {
|
||||
init_wasm();
|
||||
#block
|
||||
}
|
||||
};
|
||||
output.into()
|
||||
}
|
||||
|
@ -148,13 +148,15 @@ pub fn derive_for_each_variant(input: proc_macro::TokenStream) -> proc_macro::To
|
||||
}
|
||||
|
||||
/// Exposes the function as an application entry point. Entry points are alternative application
|
||||
/// running modes that you can access by adding `?entry=` to the end of the application URL.
|
||||
/// running modes that you can access by adding `?entry=` to the end of the application URL. If no
|
||||
/// explicit name is provided to this macro (as an argument), the crate name will be used as the
|
||||
/// entry point name.
|
||||
#[proc_macro_attribute]
|
||||
pub fn entry_point(
|
||||
_: proc_macro::TokenStream,
|
||||
args: proc_macro::TokenStream,
|
||||
item: proc_macro::TokenStream,
|
||||
) -> proc_macro::TokenStream {
|
||||
derive_entry_point::derive(item)
|
||||
derive_entry_point::derive(args, item)
|
||||
}
|
||||
|
||||
#[allow(missing_docs)]
|
||||
|
@ -1,13 +1,10 @@
|
||||
use crate::*;
|
||||
|
||||
|
||||
// ==============
|
||||
// === Export ===
|
||||
// ==============
|
||||
|
||||
pub use enso_shapely::entry_point;
|
||||
pub use enso_shapely::CloneRef;
|
||||
pub use enso_shapely::NoCloneBecauseOfCustomDrop;
|
||||
pub use crate::entry_point;
|
||||
pub use crate::CloneRef;
|
||||
pub use crate::NoCloneBecauseOfCustomDrop;
|
||||
|
||||
|
||||
|
||||
@ -90,11 +87,10 @@ impl_clone_ref_as_clone_no_from!(u32);
|
||||
impl_clone_ref_as_clone_no_from!(u64);
|
||||
impl_clone_ref_as_clone_no_from!(usize);
|
||||
impl_clone_ref_as_clone_no_from!(std::any::TypeId);
|
||||
impl_clone_ref_as_clone_no_from!([T] PhantomData<T>);
|
||||
impl_clone_ref_as_clone_no_from!([T:?Sized] Rc<T>);
|
||||
impl_clone_ref_as_clone_no_from!([T:?Sized] Weak<T>);
|
||||
impl_clone_ref_as_clone_no_from!([T] std::marker::PhantomData<T>);
|
||||
impl_clone_ref_as_clone_no_from!([T:?Sized] std::rc::Rc<T>);
|
||||
impl_clone_ref_as_clone_no_from!([T:?Sized] std::rc::Weak<T>);
|
||||
|
||||
#[cfg(feature = "wasm-bindgen")]
|
||||
impl_clone_ref_as_clone_no_from!(wasm_bindgen::JsValue);
|
||||
impl_clone_ref_as_clone_no_from!(web_sys::Element);
|
||||
impl_clone_ref_as_clone_no_from!(web_sys::HtmlDivElement);
|
@ -21,6 +21,7 @@
|
||||
// ==============
|
||||
|
||||
pub mod cartesian;
|
||||
pub mod clone_ref;
|
||||
pub mod generator;
|
||||
pub mod shared;
|
||||
pub mod singleton;
|
||||
|
@ -5,25 +5,19 @@
|
||||
use enso_prelude::*;
|
||||
use wasm_bindgen::prelude::*;
|
||||
|
||||
use enso_web as web;
|
||||
use enso_shortcuts as shortcuts;
|
||||
use enso_shortcuts::Registry;
|
||||
use enso_frp as frp;
|
||||
use frp::io::keyboard;
|
||||
use frp::io::keyboard::Keyboard;
|
||||
use enso_logger::AnyLogger;
|
||||
use enso_logger::WarningLogger as Logger;
|
||||
use enso_shortcuts as shortcuts;
|
||||
use enso_shortcuts::Registry;
|
||||
use enso_web as web;
|
||||
use frp::io::keyboard;
|
||||
use frp::io::keyboard::Keyboard;
|
||||
|
||||
|
||||
|
||||
#[wasm_bindgen]
|
||||
#[entry_point(shortcuts)]
|
||||
#[allow(dead_code)]
|
||||
pub fn entry_point_shortcuts() {
|
||||
web::forward_panic_hook_to_console();
|
||||
web::set_stack_trace_limit();
|
||||
main();
|
||||
}
|
||||
|
||||
pub fn main() {
|
||||
let shortcut_registry = shortcuts::AutomataRegistry::<String>::new();
|
||||
shortcut_registry.add(shortcuts::Press, "ctrl + a", "hello");
|
||||
|
@ -11,4 +11,4 @@ crate-type = ["rlib", "cdylib"]
|
||||
enso-prelude = { path = "../prelude" }
|
||||
enso-types = { path = "../types" }
|
||||
xi-rope = { version = "0.3.0" }
|
||||
serde = "1.0"
|
||||
serde = "1"
|
||||
|
@ -10,16 +10,16 @@ edition = "2021"
|
||||
default = ["console_error_panic_hook"]
|
||||
|
||||
[dependencies]
|
||||
enso-data-structures = { path = "../data-structures" }
|
||||
enso-debug-api = { path = "../debug-api" }
|
||||
enso-logger = { path = "../logger" }
|
||||
enso-prelude = { path = "../prelude", features = ["wasm-bindgen"] }
|
||||
console_error_panic_hook = { version = "0.1.6", optional = true }
|
||||
failure = { version = "0.1.5" }
|
||||
gloo-timers = { version = "0.2.1", features = ["futures"] }
|
||||
js-sys = { version = "0.3.28" }
|
||||
nalgebra = { version = "0.26.1" }
|
||||
wasm-bindgen = { version = "0.2.78", features = ["nightly"] }
|
||||
derivative = "2.2.0"
|
||||
tracing = "0.1"
|
||||
enso-shapely = { path = "../shapely" }
|
||||
|
||||
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
|
||||
async-std = { version = "1.5.0" }
|
||||
@ -53,6 +53,9 @@ features = [
|
||||
'DomRectReadOnly',
|
||||
'Location',
|
||||
'ReadableStream',
|
||||
'AddEventListenerOptions',
|
||||
'KeyboardEvent',
|
||||
'WheelEvent',
|
||||
]
|
||||
|
||||
[dev-dependencies]
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user