Better fonts support. (#3616)

This commit is contained in:
Wojciech Daniło 2022-08-27 00:25:34 +02:00 committed by GitHub
parent e6e4692692
commit 4b96b4887c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
106 changed files with 2427 additions and 1443 deletions

122
Cargo.lock generated
View File

@ -818,6 +818,24 @@ dependencies = [
"serde", "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]] [[package]]
name = "bit_field" name = "bit_field"
version = "0.10.1" version = "0.10.1"
@ -1604,7 +1622,7 @@ dependencies = [
"ensogl-list-view", "ensogl-list-view",
"ensogl-scroll-area", "ensogl-scroll-area",
"ensogl-selector", "ensogl-selector",
"ensogl-text-msdf-sys", "ensogl-text-msdf",
"ide-view-component-group", "ide-view-component-group",
"wasm-bindgen", "wasm-bindgen",
] ]
@ -1619,7 +1637,7 @@ dependencies = [
"ensogl-hardcoded-theme", "ensogl-hardcoded-theme",
"ensogl-list-view", "ensogl-list-view",
"ensogl-selector", "ensogl-selector",
"ensogl-text-msdf-sys", "ensogl-text-msdf",
"ide-view-component-group", "ide-view-component-group",
"ide-view-component-list-panel", "ide-view-component-list-panel",
"js-sys", "js-sys",
@ -1644,7 +1662,7 @@ dependencies = [
"enso-frp", "enso-frp",
"ensogl", "ensogl",
"ensogl-hardcoded-theme", "ensogl-hardcoded-theme",
"ensogl-text-msdf-sys", "ensogl-text-msdf",
"ide-view", "ide-view",
"parser", "parser",
"span-tree", "span-tree",
@ -1659,7 +1677,7 @@ dependencies = [
"enso-frp", "enso-frp",
"ensogl", "ensogl",
"ensogl-hardcoded-theme", "ensogl-hardcoded-theme",
"ensogl-text-msdf-sys", "ensogl-text-msdf",
"ide-view", "ide-view",
"js-sys", "js-sys",
"nalgebra 0.26.2", "nalgebra 0.26.2",
@ -1945,6 +1963,7 @@ version = "0.1.0"
dependencies = [ dependencies = [
"path-clean", "path-clean",
"reqwest 0.10.10", "reqwest 0.10.10",
"serde",
] ]
[[package]] [[package]]
@ -2073,7 +2092,7 @@ dependencies = [
"ensogl-drop-manager", "ensogl-drop-manager",
"ensogl-examples", "ensogl-examples",
"ensogl-hardcoded-theme", "ensogl-hardcoded-theme",
"ensogl-text-msdf-sys", "ensogl-text-msdf",
"failure", "failure",
"flo_stream", "flo_stream",
"futures 0.3.21", "futures 0.3.21",
@ -2122,6 +2141,7 @@ version = "0.3.1"
dependencies = [ dependencies = [
"enso-prelude", "enso-prelude",
"enso-shapely", "enso-shapely",
"ifmt",
"js-sys", "js-sys",
"wasm-bindgen", "wasm-bindgen",
"web-sys", "web-sys",
@ -2141,7 +2161,7 @@ dependencies = [
name = "enso-metamodel" name = "enso-metamodel"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"bincode", "bincode 1.3.3",
"derivative", "derivative",
"derive_more", "derive_more",
] ]
@ -2150,7 +2170,7 @@ dependencies = [
name = "enso-metamodel-lexpr" name = "enso-metamodel-lexpr"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"bincode", "bincode 1.3.3",
"derivative", "derivative",
"enso-metamodel", "enso-metamodel",
"enso-reflect", "enso-reflect",
@ -2169,7 +2189,7 @@ dependencies = [
name = "enso-parser" name = "enso-parser"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"bincode", "bincode 1.3.3",
"enso-data-structures", "enso-data-structures",
"enso-metamodel", "enso-metamodel",
"enso-metamodel-lexpr", "enso-metamodel-lexpr",
@ -2200,7 +2220,7 @@ dependencies = [
name = "enso-parser-jni" name = "enso-parser-jni"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"bincode", "bincode 1.3.3",
"enso-parser", "enso-parser",
"enso-prelude", "enso-prelude",
"jni", "jni",
@ -2231,6 +2251,7 @@ dependencies = [
"enclose", "enclose",
"enso-reflect", "enso-reflect",
"enso-shapely", "enso-shapely",
"enso-web",
"failure", "failure",
"futures 0.3.21", "futures 0.3.21",
"ifmt", "ifmt",
@ -2337,7 +2358,9 @@ dependencies = [
"paste 0.1.18", "paste 0.1.18",
"rustversion", "rustversion",
"shrinkwraprs 0.3.0", "shrinkwraprs 0.3.0",
"wasm-bindgen",
"wasm-bindgen-test", "wasm-bindgen-test",
"web-sys",
] ]
[[package]] [[package]]
@ -2398,14 +2421,14 @@ version = "0.1.0"
dependencies = [ dependencies = [
"async-std", "async-std",
"console_error_panic_hook", "console_error_panic_hook",
"enso-data-structures", "derivative",
"enso-debug-api", "enso-debug-api",
"enso-logger", "enso-shapely",
"enso-prelude",
"failure", "failure",
"gloo-timers", "gloo-timers",
"js-sys", "js-sys",
"nalgebra 0.26.2", "nalgebra 0.26.2",
"tracing",
"wasm-bindgen", "wasm-bindgen",
"wasm-bindgen-test", "wasm-bindgen-test",
"web-sys", "web-sys",
@ -2470,7 +2493,7 @@ dependencies = [
"enso-types", "enso-types",
"enso-web", "enso-web",
"ensogl-text-embedded-fonts", "ensogl-text-embedded-fonts",
"ensogl-text-msdf-sys", "ensogl-text-msdf",
"enum_dispatch", "enum_dispatch",
"failure", "failure",
"itertools 0.10.3", "itertools 0.10.3",
@ -2531,7 +2554,7 @@ dependencies = [
"enso-frp", "enso-frp",
"enso-prelude", "enso-prelude",
"ensogl-core", "ensogl-core",
"ensogl-text-msdf-sys", "ensogl-text-msdf",
"wasm-bindgen", "wasm-bindgen",
] ]
@ -2592,7 +2615,7 @@ dependencies = [
"ensogl-core", "ensogl-core",
"ensogl-text", "ensogl-text",
"ensogl-text-embedded-fonts", "ensogl-text-embedded-fonts",
"ensogl-text-msdf-sys", "ensogl-text-msdf",
"wasm-bindgen", "wasm-bindgen",
] ]
@ -2605,7 +2628,7 @@ dependencies = [
"ensogl-core", "ensogl-core",
"ensogl-grid-view", "ensogl-grid-view",
"ensogl-hardcoded-theme", "ensogl-hardcoded-theme",
"ensogl-text-msdf-sys", "ensogl-text-msdf",
"itertools 0.10.3", "itertools 0.10.3",
"wasm-bindgen", "wasm-bindgen",
] ]
@ -2619,7 +2642,7 @@ dependencies = [
"ensogl-core", "ensogl-core",
"ensogl-hardcoded-theme", "ensogl-hardcoded-theme",
"ensogl-list-view", "ensogl-list-view",
"ensogl-text-msdf-sys", "ensogl-text-msdf",
"wasm-bindgen", "wasm-bindgen",
] ]
@ -2629,7 +2652,7 @@ version = "0.1.0"
dependencies = [ dependencies = [
"enso-frp", "enso-frp",
"ensogl-core", "ensogl-core",
"ensogl-text-msdf-sys", "ensogl-text-msdf",
"wasm-bindgen", "wasm-bindgen",
] ]
@ -2649,7 +2672,7 @@ dependencies = [
"ensogl-hardcoded-theme", "ensogl-hardcoded-theme",
"ensogl-sequence-diagram", "ensogl-sequence-diagram",
"ensogl-text", "ensogl-text",
"ensogl-text-msdf-sys", "ensogl-text-msdf",
"ensogl-tooltip", "ensogl-tooltip",
"futures 0.3.21", "futures 0.3.21",
"qstring", "qstring",
@ -2673,7 +2696,7 @@ dependencies = [
"ensogl-flame-graph", "ensogl-flame-graph",
"ensogl-hardcoded-theme", "ensogl-hardcoded-theme",
"ensogl-text", "ensogl-text",
"ensogl-text-msdf-sys", "ensogl-text-msdf",
"ensogl-tooltip", "ensogl-tooltip",
"futures 0.3.21", "futures 0.3.21",
"wasm-bindgen", "wasm-bindgen",
@ -2688,7 +2711,7 @@ dependencies = [
"ensogl-core", "ensogl-core",
"ensogl-hardcoded-theme", "ensogl-hardcoded-theme",
"ensogl-scroll-area", "ensogl-scroll-area",
"ensogl-text-msdf-sys", "ensogl-text-msdf",
"wasm-bindgen", "wasm-bindgen",
] ]
@ -2708,7 +2731,7 @@ dependencies = [
"ensogl-core", "ensogl-core",
"ensogl-hardcoded-theme", "ensogl-hardcoded-theme",
"ensogl-selector", "ensogl-selector",
"ensogl-text-msdf-sys", "ensogl-text-msdf",
"wasm-bindgen", "wasm-bindgen",
] ]
@ -2738,7 +2761,7 @@ dependencies = [
"ensogl-hardcoded-theme", "ensogl-hardcoded-theme",
"ensogl-text", "ensogl-text",
"ensogl-text-embedded-fonts", "ensogl-text-embedded-fonts",
"ensogl-text-msdf-sys", "ensogl-text-msdf",
"wasm-bindgen", "wasm-bindgen",
] ]
@ -2819,7 +2842,6 @@ name = "ensogl-hardcoded-theme"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"ensogl-core", "ensogl-core",
"ensogl-text-embedded-fonts",
] ]
[[package]] [[package]]
@ -2907,6 +2929,7 @@ dependencies = [
name = "ensogl-text" name = "ensogl-text"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"bincode 2.0.0-rc.1",
"const_format", "const_format",
"enso-frp", "enso-frp",
"enso-prelude", "enso-prelude",
@ -2915,7 +2938,11 @@ dependencies = [
"enso-types", "enso-types",
"ensogl-core", "ensogl-core",
"ensogl-text-embedded-fonts", "ensogl-text-embedded-fonts",
"ensogl-text-msdf-sys", "ensogl-text-font-family",
"ensogl-text-msdf",
"ordered-float",
"owned_ttf_parser",
"serde",
"wasm-bindgen-test", "wasm-bindgen-test",
"xi-rope", "xi-rope",
] ]
@ -2926,25 +2953,34 @@ version = "0.1.0"
dependencies = [ dependencies = [
"enso-build-utilities", "enso-build-utilities",
"enso-prelude", "enso-prelude",
"ensogl-text-embedded-fonts-names", "ensogl-text-font-family",
"owned_ttf_parser",
"zip 0.5.13", "zip 0.5.13",
] ]
[[package]] [[package]]
name = "ensogl-text-embedded-fonts-names" name = "ensogl-text-font-family"
version = "0.1.0" version = "0.1.0"
dependencies = [
"enso-prelude",
"owned_ttf_parser",
]
[[package]] [[package]]
name = "ensogl-text-msdf-sys" name = "ensogl-text-msdf"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"enso-build-utilities", "enso-build-utilities",
"enso-prelude", "enso-prelude",
"enso-types",
"ensogl-text-embedded-fonts", "ensogl-text-embedded-fonts",
"ensogl-text-embedded-fonts-names", "ensogl-text-font-family",
"failure",
"futures 0.3.21", "futures 0.3.21",
"js-sys", "js-sys",
"nalgebra 0.26.2", "nalgebra 0.26.2",
"owned_ttf_parser",
"serde",
"wasm-bindgen", "wasm-bindgen",
"wasm-bindgen-test", "wasm-bindgen-test",
] ]
@ -3827,7 +3863,7 @@ dependencies = [
"anyhow", "anyhow",
"async-compression", "async-compression",
"async-trait", "async-trait",
"bincode", "bincode 1.3.3",
"byte-unit", "byte-unit",
"bytes 1.1.0", "bytes 1.1.0",
"cached 0.34.0", "cached 0.34.0",
@ -3919,7 +3955,7 @@ dependencies = [
"ensogl-gui-component", "ensogl-gui-component",
"ensogl-hardcoded-theme", "ensogl-hardcoded-theme",
"ensogl-text", "ensogl-text",
"ensogl-text-msdf-sys", "ensogl-text-msdf",
"ide-view-component-browser", "ide-view-component-browser",
"ide-view-graph-editor", "ide-view-graph-editor",
"js-sys", "js-sys",
@ -4002,7 +4038,7 @@ dependencies = [
"ensogl-component", "ensogl-component",
"ensogl-drop-manager", "ensogl-drop-manager",
"ensogl-hardcoded-theme", "ensogl-hardcoded-theme",
"ensogl-text-msdf-sys", "ensogl-text-msdf",
"failure", "failure",
"js-sys", "js-sys",
"nalgebra 0.26.2", "nalgebra 0.26.2",
@ -5002,6 +5038,15 @@ dependencies = [
"syn", "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]] [[package]]
name = "parking" name = "parking"
version = "2.0.0" version = "2.0.0"
@ -5782,6 +5827,7 @@ dependencies = [
"percent-encoding 2.1.0", "percent-encoding 2.1.0",
"pin-project-lite 0.2.9", "pin-project-lite 0.2.9",
"serde", "serde",
"serde_json",
"serde_urlencoded", "serde_urlencoded",
"tokio 0.2.25", "tokio 0.2.25",
"tokio-tls 0.3.1", "tokio-tls 0.3.1",
@ -7109,6 +7155,12 @@ version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "59547bce71d9c38b83d9c0e92b6066c4253371f15005def0c30d9657f50c7642" checksum = "59547bce71d9c38b83d9c0e92b6066c4253371f15005def0c30d9657f50c7642"
[[package]]
name = "ttf-parser"
version = "0.15.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7b3e06c9b9d80ed6b745c7159c40b311ad2916abb34a49e9be2653b90db0d8dd"
[[package]] [[package]]
name = "typeable" name = "typeable"
version = "0.1.2" version = "0.1.2"
@ -7290,6 +7342,12 @@ version = "0.9.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
[[package]]
name = "virtue"
version = "0.0.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "757cfbfe0d17ee6f22fe97e536d463047d451b47cf9d11e2b7d1398b0ef274dd"
[[package]] [[package]]
name = "void" name = "void"
version = "1.0.2" version = "1.0.2"

View File

@ -25,7 +25,7 @@ enso-web = { path = "../../lib/rust/web" }
ensogl = { path = "../../lib/rust/ensogl" } ensogl = { path = "../../lib/rust/ensogl" }
ensogl-examples = { path = "../../lib/rust/ensogl/example" } ensogl-examples = { path = "../../lib/rust/ensogl/example" }
ensogl-component = { path = "../../lib/rust/ensogl/component" } 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-hardcoded-theme = { path = "../../lib/rust/ensogl/app/theme/hardcoded" }
ensogl-drop-manager = { path = "../../lib/rust/ensogl/component/drop-manager" } ensogl-drop-manager = { path = "../../lib/rust/ensogl/component/drop-manager" }
fuzzly = { path = "../../lib/rust/fuzzly" } fuzzly = { path = "../../lib/rust/fuzzly" }

View File

@ -32,7 +32,7 @@ wasm-bindgen-test = { version = "0.3.8" }
enso-web = { path = "../../../../lib/rust/web" } enso-web = { path = "../../../../lib/rust/web" }
[build-dependencies] [build-dependencies]
enso-build-utilities = { path = "../../../../lib/rust/build-utils" } enso-build-utilities = { path = "../../../../build/build-utils" }
bytes = { version = "0.5.4" } bytes = { version = "0.5.4" }
flatc-rust = { version = "0.1.2" } flatc-rust = { version = "0.1.2" }
futures = { version = "0.3.1" } futures = { version = "0.3.1" }

View File

@ -31,7 +31,7 @@ wasm-bindgen = { version = "0.2.78" }
wasm-bindgen-test = { version = "0.3.8" } wasm-bindgen-test = { version = "0.3.8" }
[build-dependencies] [build-dependencies]
enso-build-utilities = { path = "../../../../lib/rust/build-utils" } enso-build-utilities = { path = "../../../../build/build-utils" }
bytes = { version = "0.5.4" } bytes = { version = "0.5.4" }
futures = { version = "0.3.1" } futures = { version = "0.3.1" }
reqwest = { version = "0.10.1" } reqwest = { version = "0.10.1" }

View File

@ -2,12 +2,12 @@
#![deny(non_ascii_idents)] #![deny(non_ascii_idents)]
#![warn(unsafe_code)] #![warn(unsafe_code)]
use enso_prelude::*;
use wasm_bindgen::prelude::*;
use ast::crumbs::PatternMatchCrumb::*; use ast::crumbs::PatternMatchCrumb::*;
use ast::crumbs::*; use ast::crumbs::*;
use enso_prelude::*;
use enso_text::traits::*; use enso_text::traits::*;
use span_tree::*; use span_tree::*;
use wasm_bindgen::prelude::*;
use enso_web as web; use enso_web as web;
use span_tree::builder::Builder; use span_tree::builder::Builder;
@ -17,14 +17,8 @@ use uuid::Uuid;
#[wasm_bindgen] #[entry_point(span_tree)]
#[allow(dead_code)] #[allow(dead_code)]
pub fn entry_point_span_tree() {
web::forward_panic_hook_to_console();
web::set_stack_trace_limit();
main();
}
pub fn main() { pub fn main() {
let pattern_cr = vec![Seq { right: false }, Or, Or, Build]; let pattern_cr = vec![Seq { right: false }, Or, Or, Build];
let val = ast::crumbs::SegmentMatchCrumb::Body { val: pattern_cr }; let val = ast::crumbs::SegmentMatchCrumb::Body { val: pattern_cr };

View File

@ -24,7 +24,6 @@ use flo_stream::Subscriber;
use parser::Parser; use parser::Parser;
// ============== // ==============
// === Export === // === Export ===
// ============== // ==============

View File

@ -61,7 +61,7 @@ impl Initializer {
pub async fn start(self) -> Result<Ide, FailedIde> { pub async fn start(self) -> Result<Ide, FailedIde> {
info!(self.logger, "Starting IDE with the following config: {self.config:?}"); 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()); let ensogl_app = ensogl::application::Application::new(self.config.dom_parent_id());
register_views(&ensogl_app); register_views(&ensogl_app);
let view = ensogl_app.new_view::<ide_view::root::View>(); let view = ensogl_app.new_view::<ide_view::root::View>();

View File

@ -138,11 +138,10 @@ mod profile_workflow;
// =================== // ===================
/// IDE startup function. /// IDE startup function.
#[entry_point(ide)]
#[profile(Objective)] #[profile(Objective)]
#[wasm_bindgen]
#[allow(dead_code)] #[allow(dead_code)]
pub fn entry_point_ide() { pub fn main() {
init_tracing(WARN);
// Logging of build information. // Logging of build information.
#[cfg(debug_assertions)] #[cfg(debug_assertions)]
let debug_mode = true; let debug_mode = true;
@ -154,7 +153,7 @@ pub fn entry_point_ide() {
analytics::AnonymousData(debug_mode), analytics::AnonymousData(debug_mode),
); );
let config = 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 executor = crate::ide::initializer::setup_global_executor();
let initializer = crate::ide::initializer::Initializer::new(config); let initializer = crate::ide::initializer::Initializer::new(config);
executor::global::spawn(async move { executor::global::spawn(async move {

View File

@ -4,7 +4,6 @@ use crate::integration_test::prelude::*;
use wasm_bindgen::prelude::*; use wasm_bindgen::prelude::*;
use enso_debug_api as debug_api; 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. /// Startup function for running and profiling a test workflow.
#[wasm_bindgen] #[entry_point(profile)]
#[allow(dead_code)] // Used from JavaScript. #[allow(dead_code)] // Used from JavaScript.
pub async fn entry_point_profile() { pub async fn main() {
web::forward_panic_hook_to_console();
// Run selected workflow. // Run selected workflow.
let need_workflow = "`profile` entry point requires --workflow argument. \ let need_workflow = "`profile` entry point requires --workflow argument. \
Try --workflow=help to see a list of options."; Try --workflow=help to see a list of options.";

View File

@ -180,11 +180,11 @@ impl Model {
pub fn new(socket: web_sys::WebSocket, logger: Logger) -> Model { pub fn new(socket: web_sys::WebSocket, logger: Logger) -> Model {
socket.set_binary_type(BinaryType::Arraybuffer); socket.set_binary_type(BinaryType::Arraybuffer);
Model { Model {
on_close: Slot::new(&socket, &logger), on_close: Slot::new(&socket),
on_message: Slot::new(&socket, &logger), on_message: Slot::new(&socket),
on_open: Slot::new(&socket, &logger), on_open: Slot::new(&socket),
on_error: Slot::new(&socket, &logger), on_error: Slot::new(&socket),
on_close_internal: Slot::new(&socket, &logger), on_close_internal: Slot::new(&socket),
auto_reconnect: true, auto_reconnect: true,
logger, logger,
socket, socket,

View File

@ -19,7 +19,7 @@ ensogl = { path = "../../../lib/rust/ensogl" }
ensogl-component = { path = "../../../lib/rust/ensogl/component" } ensogl-component = { path = "../../../lib/rust/ensogl/component" }
ensogl-gui-component = { path = "../../../lib/rust/ensogl/component/gui" } ensogl-gui-component = { path = "../../../lib/rust/ensogl/component/gui" }
ensogl-text = { path = "../../../lib/rust/ensogl/component/text" } 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" } ensogl-hardcoded-theme = { path = "../../../lib/rust/ensogl/app/theme/hardcoded" }
ide-view-component-browser = { path = "component-browser" } ide-view-component-browser = { path = "component-browser" }
ide-view-graph-editor = { path = "graph-editor" } ide-view-graph-editor = { path = "graph-editor" }

View File

@ -15,6 +15,6 @@ ensogl-selector = { path = "../../../../../lib/rust/ensogl/component/selector" }
ensogl-hardcoded-theme = { path = "../../../../../lib/rust/ensogl/app/theme/hardcoded" } ensogl-hardcoded-theme = { path = "../../../../../lib/rust/ensogl/app/theme/hardcoded" }
ensogl-scroll-area = { path = "../../../../../lib/rust/ensogl/component/scroll-area" } ensogl-scroll-area = { path = "../../../../../lib/rust/ensogl/component/scroll-area" }
ensogl-list-view = { path = "../../../../../lib/rust/ensogl/component/list-view" } 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" } ide-view-component-group = { path = "../../component-browser/component-group" }
wasm-bindgen = { version = "0.2.78", features = ["nightly"] } wasm-bindgen = { version = "0.2.78", features = ["nightly"] }

View File

@ -27,7 +27,7 @@ use ensogl_list_view as list_view;
use ensogl_list_view::entry::GlyphHighlightedLabelModel; use ensogl_list_view::entry::GlyphHighlightedLabelModel;
use ensogl_scroll_area::ScrollArea; use ensogl_scroll_area::ScrollArea;
use ensogl_selector as selector; 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 as component_group;
use ide_view_component_group::entry; use ide_view_component_group::entry;
use ide_view_component_group::icon; use ide_view_component_group::icon;

View File

@ -13,7 +13,7 @@ ensogl-core = { path = "../../../../../lib/rust/ensogl/core" }
ensogl-hardcoded-theme = { path = "../../../../../lib/rust/ensogl/app/theme/hardcoded" } ensogl-hardcoded-theme = { path = "../../../../../lib/rust/ensogl/app/theme/hardcoded" }
ensogl-list-view = { path = "../../../../../lib/rust/ensogl/component/list-view" } ensogl-list-view = { path = "../../../../../lib/rust/ensogl/component/list-view" }
ensogl-selector = { path = "../../../../../lib/rust/ensogl/component/selector" } 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-group = { path = "../../component-browser/component-group" }
ide-view-component-list-panel = { path = "../../component-browser/searcher-list-panel" } ide-view-component-list-panel = { path = "../../component-browser/searcher-list-panel" }
wasm-bindgen = { version = "0.2.78", features = ["nightly"] } wasm-bindgen = { version = "0.2.78", features = ["nightly"] }

View File

@ -195,8 +195,7 @@ fn init_local_scope_section(searcher_list_panel: &ComponentBrowserPanel) {
#[entry_point] #[entry_point]
#[allow(dead_code)] #[allow(dead_code)]
pub fn main() { pub fn main() {
init_tracing(WARN); ensogl_text_msdf::run_once_initialized(|| {
ensogl_text_msdf_sys::run_once_initialized(|| {
let app = &Application::new("root"); let app = &Application::new("root");
theme::builtin::light::register(&app); theme::builtin::light::register(&app);
theme::builtin::light::enable(&app); theme::builtin::light::enable(&app);

View File

@ -12,7 +12,7 @@ ast = { path = "../../../language/ast/impl" }
enso-frp = { path = "../../../../../lib/rust/frp" } enso-frp = { path = "../../../../../lib/rust/frp" }
ensogl = { path = "../../../../../lib/rust/ensogl" } ensogl = { path = "../../../../../lib/rust/ensogl" }
ensogl-hardcoded-theme = { path = "../../../../../lib/rust/ensogl/app/theme/hardcoded" } 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 = "../.." } ide-view = { path = "../.." }
parser = { path = "../../../language/parser" } parser = { path = "../../../language/parser" }
span-tree = { path = "../../../language/span-tree" } span-tree = { path = "../../../language/span-tree" }

View File

@ -26,7 +26,7 @@ use ensogl::display::shape::StyleWatch;
use ensogl::gui::text; use ensogl::gui::text;
use ensogl::system::web; use ensogl::system::web;
use ensogl_hardcoded_theme as theme; 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;
use ide_view::graph_editor::component::node::vcs; use ide_view::graph_editor::component::node::vcs;
use ide_view::graph_editor::component::node::Expression; use ide_view::graph_editor::component::node::Expression;

View File

@ -11,7 +11,7 @@ crate-type = ["cdylib", "rlib"]
enso-frp = { path = "../../../../../lib/rust/frp" } enso-frp = { path = "../../../../../lib/rust/frp" }
ensogl = { path = "../../../../../lib/rust/ensogl" } ensogl = { path = "../../../../../lib/rust/ensogl" }
ensogl-hardcoded-theme = { path = "../../../../../lib/rust/ensogl/app/theme/hardcoded" } 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 = "../.." } ide-view = { path = "../.." }
js-sys = { version = "0.3.28" } js-sys = { version = "0.3.28" }
nalgebra = { version = "0.26.1" } nalgebra = { version = "0.26.1" }

View File

@ -19,7 +19,7 @@ use ensogl::animation;
use ensogl::application::Application; use ensogl::application::Application;
use ensogl::display::navigation::navigator::Navigator; use ensogl::display::navigation::navigator::Navigator;
use ensogl::system::web; 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;
use ide_view::graph_editor::component::visualization::Data; use ide_view::graph_editor::component::visualization::Data;
use ide_view::graph_editor::component::visualization::Registry; use ide_view::graph_editor::component::visualization::Registry;

View File

@ -21,7 +21,7 @@ enso-shapely = { path = "../../../../lib/rust/shapely" }
enso-text = { version = "0.1.0", path = "../../../../lib/rust/text" } enso-text = { version = "0.1.0", path = "../../../../lib/rust/text" }
ensogl = { version = "0.1.0", path = "../../../../lib/rust/ensogl" } ensogl = { version = "0.1.0", path = "../../../../lib/rust/ensogl" }
ensogl-component = { path = "../../../../lib/rust/ensogl/component" } 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-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" } ensogl-drop-manager = { version = "0.1.0", path = "../../../../lib/rust/ensogl/component/drop-manager" }
failure = { version = "0.1.8" } failure = { version = "0.1.8" }

View File

@ -755,117 +755,64 @@ function ok(value: any) {
} }
class Config { class Config {
public entry: string public entry: string = undefined
public project: string public project: string = undefined
public project_manager: string public project_manager: string = undefined
public language_server_rpc: string public language_server_rpc: string = undefined
public language_server_data: string public language_server_data: string = undefined
public namespace: string public namespace: string = undefined
public platform: string public platform: string = undefined
public frame: boolean public frame: boolean = false
public theme: string public theme: string = undefined
public dark_theme: boolean public dark_theme: boolean = false
public high_contrast: boolean public high_contrast: boolean = false
public use_loader: boolean public use_loader: boolean = true
public wasm_url: string public wasm_url: string = '/assets/ide.wasm'
public wasm_glue_url: string public wasm_glue_url: string = '/assets/wasm_imports.js'
public node_labels: boolean public node_labels: boolean = false
public crash_report_host: string public crash_report_host: string = defaultLogServerHost
public data_gathering: boolean public data_gathering: boolean = true
public is_in_cloud: boolean public is_in_cloud: boolean = false
public verbose: boolean public verbose: boolean = false
public authentication_enabled: boolean public authentication_enabled: boolean = true
public email: string public email: string = undefined
public application_config_url: 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' 'https://raw.githubusercontent.com/enso-org/ide/develop/config.json'
config.skip_min_version_check = Versions.isDevVersion() public test_workflow: string = undefined
config.preferred_engine_version = Versions.ideVersion public skip_min_version_check: boolean = Versions.isDevVersion()
config.enable_new_component_browser = false public preferred_engine_version: SemVer = Versions.ideVersion
return config public enable_new_component_browser: boolean = false
}
updateFromObject(other: any) { updateFromObject(other: any) {
if (!ok(other)) { if (!ok(other)) {
return return
} }
this.entry = ok(other.entry) ? tryAsString(other.entry) : this.entry for (let key of Object.keys(this)) {
this.project = ok(other.project) ? tryAsString(other.project) : this.project let self: any = this
this.project_manager = ok(other.project_manager) let otherVal = other[key]
? tryAsString(other.project_manager) let selfVal = self[key]
: this.project_manager if (ok(otherVal)) {
this.language_server_rpc = ok(other.language_server_rpc) if (typeof selfVal === 'boolean') {
? tryAsString(other.language_server_rpc) let val = tryAsBoolean(otherVal)
: this.language_server_rpc if (val === null) {
this.language_server_data = ok(other.language_server_data) console.error(
? tryAsString(other.language_server_data) `Invalid value for ${key}: ${otherVal}. Expected boolean. Reverting to the default value of `
: 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 { } else {
return null 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 return value
} }
function tryAsBoolean(value: any): boolean { function tryAsBoolean(value: any): boolean | null {
value = parseBooleanOrLeaveAsIs(value) value = parseBooleanOrLeaveAsIs(value)
assert(typeof value == 'boolean') return typeof value == 'boolean' ? value : null
return value
} }
function tryAsString(value: any): string { function tryAsString(value: any): string {
@ -963,12 +909,12 @@ API.main = async function (inputConfig: any) {
// @ts-ignore // @ts-ignore
const urlConfig = Object.fromEntries(urlParams.entries()) const urlConfig = Object.fromEntries(urlParams.entries())
const config = Config.default() const config = new Config()
config.updateFromObject(inputConfig) config.updateFromObject(inputConfig)
config.updateFromObject(urlConfig) config.updateFromObject(urlConfig)
if (await checkMinSupportedVersion(config)) { if (await checkMinSupportedVersion(config)) {
if (config.authentication_enabled) { if (config.authentication_enabled && !config.entry) {
new FirebaseAuthentication(function (user: any) { new FirebaseAuthentication(function (user: any) {
config.email = user.email config.email = user.email
runEntryPoint(config) runEntryPoint(config)

View File

@ -1,6 +1,6 @@
# Options intended to be common for all developers. # Options intended to be common for all developers.
wasm-size-limit: 5.29 MiB wasm-size-limit: 14.44 MiB
required-versions: required-versions:
cargo-watch: ^8.1.1 cargo-watch: ^8.1.1

View File

@ -6,7 +6,8 @@ edition = "2021"
[dependencies] [dependencies]
path-clean = "0.1.0" path-clean = "0.1.0"
serde = { version = "1.0", features = ["derive"] }
[dependencies.reqwest] [dependencies.reqwest]
version = "0.10.6" version = "0.10.6"
features = ['blocking'] features = ["blocking", "json"]

View 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")
}

View File

@ -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")
}

View File

@ -8,5 +8,4 @@ edition = "2021"
crate-type = ["rlib", "cdylib"] crate-type = ["rlib", "cdylib"]
[dependencies] [dependencies]
ensogl-core = { version = "0.1.0", path = "../../../core" } ensogl-core = { path = "../../../core" }
ensogl-text-embedded-fonts = { version = "0.1.0", path = "../../../../../../lib/rust/ensogl/component/text/embedded-fonts" }

View File

@ -14,8 +14,6 @@
#![warn(missing_debug_implementations)] #![warn(missing_debug_implementations)]
use ensogl_core::prelude::ImString; 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_size = 16.0, 16.0;
section_heading_offset = 50.0, 50.0; section_heading_offset = 50.0, 50.0;
section_heading_text_offset = 13.0, 13.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_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); 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 { component_group {
header { header {
text { text {
font = DefaultFontFamily::bold(), DefaultFontFamily::bold(); font = "default", "default";
size = 12.0, 12.0; size = 12.0, 12.0;
color_intensity = 1.0, 1.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); 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(); selected_color = Rgba::white(), Rgba::white();
text { text {
font = DefaultFontFamily::regular(), DefaultFontFamily::regular(); font = "default", "default";
size = 12.0, 12.0; size = 12.0, 12.0;
color = Rgba(0.4,0.4,0.4,1.0), Rgba(0.4,0.4,0.4,1.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; 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 = Lcha(0.0,0.0,0.0,0.7) , Lcha(1.0,0.0,0.0,0.7);
text { text {
selection = Lcha(0.7,0.0,0.125,0.7) , Lcha(0.7,0.0,0.125,0.7); 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; size = 12.0, 12.0;
highlight_bold = 0.02, 0.02; highlight_bold = 0.02, 0.02;
} }
@ -636,7 +634,7 @@ define_themes! { [light:0, dark:1]
text { text {
offset = 00.0, 00.0; offset = 00.0, 00.0;
size = 12.0, 12.0; size = 12.0, 12.0;
font = "DejaVuSans", "DejaVuSans"; font = "default", "default";
} }
padding_outer = 20.0, 20.0; padding_outer = 20.0, 20.0;
padding_inner_x = 10.0, 10.0; padding_inner_x = 10.0, 10.0;

View File

@ -3,10 +3,10 @@
use crate::prelude::*; use crate::prelude::*;
use crate::header; use crate::header;
use crate::header::WeakLayers;
use crate::selectable; use crate::selectable;
use crate::Entry; use crate::Entry;
use crate::header::WeakLayers;
use enso_frp as frp; use enso_frp as frp;
use ensogl_core::application::command::FrpNetworkProvider; use ensogl_core::application::command::FrpNetworkProvider;
use ensogl_core::application::Application; use ensogl_core::application::Application;
@ -15,6 +15,7 @@ use ensogl_core::display::scene::Layer;
use ensogl_scroll_area::ScrollArea; use ensogl_scroll_area::ScrollArea;
// ================ // ================
// === GridView === // === GridView ===
// ================ // ================

View File

@ -44,7 +44,7 @@ impl Default for EntryParams {
bg_margin: 0.0, bg_margin: 0.0,
hover_color: color::Rgba(0.9, 0.9, 0.9, 1.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), 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_offset: 7.0,
text_size: text::Size(14.0), text_size: text::Size(14.0),
text_color: color::Rgba(0.0, 0.0, 0.0, 1.0), text_color: color::Rgba(0.0, 0.0, 0.0, 1.0),

View File

@ -14,10 +14,15 @@ enso-shapely = { path = "../../../shapely" }
enso-text = { path = "../../../text" } enso-text = { path = "../../../text" }
enso-types = { path = "../../../types" } enso-types = { path = "../../../types" }
ensogl-core = { path = "../../core" } ensogl-core = { path = "../../core" }
ensogl-text-embedded-fonts = { path = "embedded-fonts" } ensogl-text-embedded-fonts = { path = "src/font/embedded" }
ensogl-text-msdf-sys = { path = "msdf-sys" } ensogl-text-msdf = { path = "src/font/msdf" }
const_format = "0.2.22" const_format = "0.2.22"
xi-rope = { version = "0.3.0" } 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] [dev-dependencies]
wasm-bindgen-test = { version = "0.3.8" } wasm-bindgen-test = { version = "0.3.8" }

View File

@ -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();
}

View File

@ -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"]

View File

@ -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"
}
}

View File

@ -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]);
}
}

View File

@ -1,2 +0,0 @@
# A thirdparty downloaded js
msdfgen_wasm.js

View File

@ -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" }

View File

@ -12,12 +12,12 @@ use crate::buffer::Transform;
use crate::component::selection; use crate::component::selection;
use crate::component::Selection; use crate::component::Selection;
#[cfg(target_arch = "wasm32")] #[cfg(target_arch = "wasm32")]
use crate::typeface; use crate::font;
#[cfg(target_arch = "wasm32")] #[cfg(target_arch = "wasm32")]
use crate::typeface::glyph; use crate::font::glyph;
use crate::typeface::glyph::Glyph; use crate::font::glyph::Glyph;
#[cfg(target_arch = "wasm32")] #[cfg(target_arch = "wasm32")]
use crate::typeface::pen; use crate::font::pen;
use enso_frp as frp; use enso_frp as frp;
use enso_frp::io::keyboard::Key; use enso_frp::io::keyboard::Key;
@ -29,10 +29,6 @@ use ensogl_core::display;
use ensogl_core::gui::cursor; use ensogl_core::gui::cursor;
use ensogl_core::system::web::clipboard; use ensogl_core::system::web::clipboard;
use ensogl_core::DEPRECATED_Animation; 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; use std::ops::Not;
@ -270,7 +266,7 @@ ensogl_core::define_endpoints! {
set_default_color (color::Rgba), set_default_color (color::Rgba),
set_selection_color (color::Rgb), set_selection_color (color::Rgb),
set_default_text_size (style::Size), 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 /// 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, /// 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); let display_object = display::object::Instance::new(&logger);
#[cfg(target_arch = "wasm32")] #[cfg(target_arch = "wasm32")]
let glyph_system = { let glyph_system = {
let fonts = scene.extension::<typeface::font::Registry>(); let fonts = scene.extension::<font::Registry>();
let font = fonts.load(embedded_fonts::DefaultFamily::mono()); let font = fonts.load(font::DEFAULT_FONT_MONO);
let glyph_system = typeface::glyph::System::new(&scene, font); let glyph_system = font::glyph::System::new(&scene, font);
display_object.add_child(&glyph_system); display_object.add_child(&glyph_system);
Rc::new(RefCell::new(glyph_system)) Rc::new(RefCell::new(glyph_system))
}; };
@ -824,7 +820,14 @@ impl AreaModel {
let next = iter.next(); let next = iter.next();
let style = line_style.next().unwrap_or_default(); let style = line_style.next().unwrap_or_default();
let chr_size = style.size.raw; 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); let info = pen.advance(char_info);
cursor_map.get(&column).for_each(|id| { cursor_map.get(&column).for_each(|id| {
@ -843,14 +846,25 @@ impl AreaModel {
Some((glyph, chr)) => { Some((glyph, chr)) => {
let chr_bytes: Bytes = chr.len_utf8().into(); let chr_bytes: Bytes = chr.len_utf8().into();
line_style.drop(chr_bytes - 1.bytes()); 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_offset = glyph_info.offset.scale(chr_size);
let glyph_x = info.offset + glyph_offset.x; let glyph_x = info.offset + glyph_offset.x;
let glyph_y = glyph_offset.y; let glyph_y = glyph_offset.y;
glyph.set_position_xy(Vector2(glyph_x, glyph_y)); glyph.set_position_xy(Vector2(glyph_x, glyph_y));
glyph.set_char(chr); glyph.set_char(chr);
glyph.set_color(style.color); 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_sdf_bold(style.sdf_bold.raw);
glyph.set_font_size(chr_size); glyph.set_font_size(chr_size);
match &last_cursor { match &last_cursor {
@ -878,6 +892,7 @@ impl AreaModel {
line.to_string() 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 /// 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 ("…") /// using the current font at `font_size`. Return the truncated string with an ellipsis ("…")
/// character appended, or `content` if not truncated. /// character appended, or `content` if not truncated.
@ -888,33 +903,34 @@ impl AreaModel {
fn line_truncated_with_ellipsis( fn line_truncated_with_ellipsis(
&self, &self,
line: &str, line: &str,
font_size: style::Size, _font_size: style::Size,
max_width_px: f32, _max_width_px: f32,
) -> String { ) -> String {
const ELLIPSIS: char = '\u{2026}'; // const ELLIPSIS: char = '\u{2026}';
let mut pen = pen::Pen::new(&self.glyph_system.borrow().font); // let mut pen = pen::Pen::new(&self.glyph_system.borrow().font);
let mut truncation_point = 0.bytes(); // let mut truncation_point = 0.bytes();
let truncate = line.char_indices().any(|(i, ch)| { // let truncate = line.char_indices().any(|(i, ch)| {
let char_info = pen::CharInfo::new(ch, font_size.raw); // let char_info = pen::CharInfo::new(ch, font_size.raw);
let pen_info = pen.advance(Some(char_info)); // let pen_info = pen.advance(Some(char_info));
let next_width = pen_info.offset + char_info.size; // let next_width = pen_info.offset + char_info.size;
if next_width > max_width_px { // if next_width > max_width_px {
return true; // return true;
} // }
let width_of_ellipsis = pen::CharInfo::new(ELLIPSIS, font_size.raw).size; // let width_of_ellipsis = pen::CharInfo::new(ELLIPSIS, font_size.raw).size;
let char_length: Bytes = ch.len_utf8().into(); // let char_length: Bytes = ch.len_utf8().into();
if next_width + width_of_ellipsis <= max_width_px { // if next_width + width_of_ellipsis <= max_width_px {
truncation_point = Bytes::from(i) + char_length; // truncation_point = Bytes::from(i) + char_length;
} // }
false // false
}); // });
if truncate { // if truncate {
let truncated_content = line[..truncation_point.as_usize()].to_string(); // let truncated_content = line[..truncation_point.as_usize()].to_string();
truncated_content + String::from(ELLIPSIS).as_str() // truncated_content + String::from(ELLIPSIS).as_str()
} else { // } else {
// line.to_string()
// }
line.to_string() line.to_string()
} }
}
/// Truncate trailing characters on every line of `text` that exceeds `max_width_px` when /// Truncate trailing characters on every line of `text` that exceeds `max_width_px` when
/// rendered using the current font at `font_size`. Return `text` with every truncated /// rendered using the current font at `font_size`. Return `text` with every truncated
@ -1028,9 +1044,9 @@ impl AreaModel {
fn set_font(&self, font_name: &str) { fn set_font(&self, font_name: &str) {
let app = &self.app; let app = &self.app;
let scene = &app.display.default_scene; 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 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); self.display_object.add_child(&glyph_system);
let old_glyph_system = self.glyph_system.replace(glyph_system); let old_glyph_system = self.glyph_system.replace(glyph_system);
self.display_object.remove_child(&old_glyph_system); self.display_object.remove_child(&old_glyph_system);

View 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 files 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 files 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 files 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 files 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 files 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()
}
}

View File

@ -9,10 +9,11 @@ crate-type = ["cdylib", "rlib"]
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies] [dependencies]
enso-prelude = { path = "../../../../prelude" } enso-prelude = { path = "../../../../../../prelude" }
ensogl-text-embedded-fonts-names = { path = "names" } ensogl-text-font-family = { path = "../../font/family" }
[build-dependencies] [build-dependencies]
enso-build-utilities = { path = "../../../../build-utils" } enso-build-utilities = { path = "../../../../../../../../build/build-utils" }
ensogl-text-embedded-fonts-names = { path = "names" } ensogl-text-font-family = { path = "../../font/family" }
zip = { version = "0.5" } zip = { version = "0.5" }
owned_ttf_parser = "0.15.1"

View 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();
}

View 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
}

View 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"]

View 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 }
}
}

View File

@ -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) { highp float median(highp vec3 v) {
return max(min(v.x, v.y), min(max(v.x, v.y), v.z)); 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() { 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 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 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; return font_size_px * fatting;
} }

View File

@ -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) { highp float median(highp vec3 v) {
return max(min(v.x, v.y), min(max(v.x, v.y), v.z)); 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() { 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 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 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; return font_size_px * fatting;
} }

View File

@ -4,9 +4,9 @@
use crate::prelude::*; use crate::prelude::*;
use ensogl_core::display::world::*; 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::data::color::Rgba;
use ensogl_core::display; use ensogl_core::display;
use ensogl_core::display::layout::Alignment; use ensogl_core::display::layout::Alignment;
@ -17,20 +17,7 @@ use ensogl_core::system::gpu;
use ensogl_core::system::gpu::texture; use ensogl_core::system::gpu::texture;
use font::Font; use font::Font;
use font::GlyphRenderInfo; use font::GlyphRenderInfo;
use owned_ttf_parser::GlyphId;
// =================
// === 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");
}
@ -44,79 +31,166 @@ pub type Texture = gpu::Texture<texture::GpuOnly, texture::Rgb, u8>;
/// A glyph rendered on screen. /// A glyph rendered on screen.
/// ///
/// The underlying sprite's size is automatically adjusted depending on char and font size set. /// 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 { pub struct Glyph {
sprite: Sprite, data: Rc<GlyphData>,
context: Context, }
font: Font,
font_size: Attribute<f32>, /// Internal structure of [`Glyph`].
color: Attribute<Vector4<f32>>, #[allow(missing_docs)]
style: Attribute<i32>, #[derive(Debug)]
sdf_bold: Attribute<f32>, pub struct GlyphData {
atlas_index: Attribute<f32>, pub glyph_id: Cell<GlyphId>,
atlas: Uniform<Texture>, pub sprite: Sprite,
char: Rc<Cell<char>>, 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 { 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 { pub fn color(&self) -> Rgba {
self.color.get().into() self.color.get().into()
} }
/// Color setter.
pub fn set_color(&self, color: impl Into<Rgba>) { pub fn set_color(&self, color: impl Into<Rgba>) {
self.color.set(color.into().into()) self.color.set(color.into().into())
} }
pub fn is_bold(&self) -> bool { /// SDF-based glyph thickness adjustment. Values greater than 0 make the glyph thicker, while
self.style.get() & style_flag::BOLD != 0 /// values lower than 0 makes it thinner.
}
pub fn set_bold(&self, value: bool) {
self.style.modify(|v| if value { *v |= style_flag::BOLD } else { *v &= !style_flag::BOLD });
}
pub fn sdf_bold(&self) -> f32 { pub fn sdf_bold(&self) -> f32 {
self.sdf_bold.get() self.sdf_bold.get()
} }
/// SDF-based glyph thickness getter.
pub fn set_sdf_bold(&self, value: f32) { pub fn set_sdf_bold(&self, value: f32) {
self.sdf_bold.set(value); self.sdf_bold.set(value);
} }
/// Size getter.
pub fn font_size(&self) -> f32 { pub fn font_size(&self) -> f32 {
self.font_size.get() self.font_size.get()
} }
/// Size setter.
pub fn set_font_size(&self, size: f32) { pub fn set_font_size(&self, size: f32) {
self.font_size.set(size); self.font_size.set(size);
let glyph_info = self.font.glyph_info(self.char.get()); let opt_glyph_info = self.font.glyph_info(
self.sprite.size.set(glyph_info.scale.scale(size)); 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. /// Change the displayed character.
pub fn set_char(&self, ch: char) { pub fn set_char(&self, ch: char) {
self.char.set(ch); let opt_glyph_id =
let glyph_info = self.font.glyph_info(ch); self.font.glyph_id_of_code_point(self.properties.get(), &self.variations.borrow(), ch);
self.atlas_index.set(glyph_info.msdf_texture_glyph_id as f32); if let Some(glyph_id) = opt_glyph_id {
self.update_msdf_texture(); self.set_glyph_id(glyph_id)
let font_size = self.font_size(); }
self.sprite.size.set(glyph_info.scale.scale(font_size)); // FIXME[WD]: display not found char. https://www.pivotaltracker.com/story/show/182746060
} }
// FIXME: How does it work? Replace with better checking. /// Change the displayed character.
fn update_msdf_texture(&self) { pub fn set_glyph_id(&self, glyph_id: GlyphId) {
let texture_changed = self.atlas.with_content(|texture| { self.glyph_id.set(glyph_id);
texture.storage().height != self.font.msdf_texture_rows() as i32 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 { if texture_changed {
let width = font::msdf::Texture::WIDTH as i32; let cpu_tex_width = font::msdf::Texture::WIDTH as i32;
let height = self.font.msdf_texture_rows() as i32; let texture = Texture::new(&self.context, (cpu_tex_width, cpu_tex_height));
let texture = Texture::new(&self.context, (width, height));
self.font.with_borrowed_msdf_texture_data(|data| texture.reload_with_content(data)); self.font.with_borrowed_msdf_texture_data(|data| texture.reload_with_content(data));
self.atlas.set(texture); 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 { impl display::Object for Glyph {
@ -141,7 +215,6 @@ pub struct System {
pub font: Font, pub font: Font,
font_size: Buffer<f32>, font_size: Buffer<f32>,
color: Buffer<Vector4<f32>>, color: Buffer<Vector4<f32>>,
style: Buffer<i32>,
sdf_bold: Buffer<f32>, sdf_bold: Buffer<f32>,
atlas_index: Buffer<f32>, atlas_index: Buffer<f32>,
atlas: Uniform<Texture>, atlas: Uniform<Texture>,
@ -174,7 +247,6 @@ impl System {
atlas: symbol.variables().add_or_panic("atlas", texture), atlas: symbol.variables().add_or_panic("atlas", texture),
font_size: mesh.instance_scope().add_buffer("font_size"), font_size: mesh.instance_scope().add_buffer("font_size"),
color: mesh.instance_scope().add_buffer("color"), color: mesh.instance_scope().add_buffer("color"),
style: mesh.instance_scope().add_buffer("style"),
sdf_bold: mesh.instance_scope().add_buffer("sdf_bold"), sdf_bold: mesh.instance_scope().add_buffer("sdf_bold"),
atlas_index: mesh.instance_scope().add_buffer("atlas_index"), atlas_index: mesh.instance_scope().add_buffer("atlas_index"),
} }
@ -188,15 +260,30 @@ impl System {
let instance_id = sprite.instance_id; let instance_id = sprite.instance_id;
let font_size = self.font_size.at(instance_id); let font_size = self.font_size.at(instance_id);
let color = self.color.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 sdf_bold = self.sdf_bold.at(instance_id);
let atlas_index = self.atlas_index.at(instance_id); let atlas_index = self.atlas_index.at(instance_id);
let font = self.font.clone_ref(); let font = self.font.clone_ref();
let atlas = self.atlas.clone(); 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)); color.set(Vector4::new(0.0, 0.0, 0.0, 0.0));
atlas_index.set(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. /// Get underlying sprite system.
@ -214,10 +301,9 @@ impl display::Object for System {
// === Material === // === Material ===
#[cfg(target_os = "macos")] #[cfg(target_os = "macos")]
const FUNCTIONS: &str = const FUNCTIONS: &str = include_str!("glsl/glyph_mac.glsl");
concatcp!(style_flag::GLSL_DEFINITIONS, include_str!("glsl/glyph_mac.glsl"));
#[cfg(not(target_os = "macos"))] #[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);"; 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("msdf_range", GlyphRenderInfo::MSDF_PARAMS.range as f32);
material.add_input("font_size", 10.0); material.add_input("font_size", 10.0);
material.add_input("color", Vector4::new(0.0, 0.0, 0.0, 1.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); 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 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 // FIXME outputs as the number of attachments to framebuffer. We should manage this more

View 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, &params);
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),
}
}
}

View File

@ -0,0 +1,2 @@
# A thirdparty JS file, dowloaded during build by the build.rs script.
msdfgen_wasm.js

View 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" }

View File

@ -11,7 +11,7 @@ mod msdfgen_wasm {
pub const PACKAGE: GithubRelease<&str> = GithubRelease { pub const PACKAGE: GithubRelease<&str> = GithubRelease {
project_url: "https://github.com/enso-org/msdfgen-wasm", project_url: "https://github.com/enso-org/msdfgen-wasm",
version: "v1.1", version: "v1.4",
filename: "msdfgen_wasm.js", filename: "msdfgen_wasm.js",
}; };
@ -28,9 +28,10 @@ mod msdfgen_wasm {
PACKAGE.download(path::Path::new(".")) PACKAGE.download(path::Path::new("."))
} }
const PATCH_LINE: &str = "; export { ccall, getValue, _msdfgen_getKerning,\ const PATCH_LINE: &str =
_msdfgen_generateAutoframedMSDF, _msdfgen_result_getMSDFData,\ "; export { ccall, getValue, _msdfgen_getKerning, _msdfgen_setVariationAxis,\
_msdfgen_result_getAdvance, _msdfgen_result_getTranslation,\ _msdfgen_generateAutoframedMSDF, _msdfgen_generateAutoframedMSDFByIndex, \
_msdfgen_result_getMSDFData, _msdfgen_result_getAdvance, _msdfgen_result_getTranslation,\
_msdfgen_result_getScale, _msdfgen_freeResult, _msdfgen_freeFont,\ _msdfgen_result_getScale, _msdfgen_freeResult, _msdfgen_freeFont,\
addInitializationCb, isInitialized }"; addInitializationCb, isInitialized }";

View File

@ -28,6 +28,11 @@ extern "C" {
#[wasm_bindgen(js_name = "_msdfgen_getKerning")] #[wasm_bindgen(js_name = "_msdfgen_getKerning")]
pub fn msdfgen_get_kerning(font_handle: JsValue, left_unicode: u32, right_unicode: u32) -> f64; 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")] #[wasm_bindgen(js_name = "_msdfgen_generateAutoframedMSDF")]
pub fn msdfgen_generate_msdf( pub fn msdfgen_generate_msdf(
width: usize, width: usize,
@ -41,6 +46,19 @@ extern "C" {
overlap_support: bool, overlap_support: bool,
) -> JsValue; ) -> 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")] #[wasm_bindgen(js_name = "_msdfgen_result_getMSDFData")]
pub fn msdfgen_result_get_msdf_data(result_handle: JsValue) -> usize; pub fn msdfgen_result_get_msdf_data(result_handle: JsValue) -> usize;

View File

@ -1,3 +1,5 @@
//! Helpers for working with emscripten libraries.
use crate::prelude::*; use crate::prelude::*;
use crate::binding::emscripten_get_value_from_memory; 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 /// 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 /// 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) /// (we cannot do it directly, because each wasm module have separate address space)
#[allow(missing_docs)]
pub trait EmscriptenRepresentation: Sized { pub trait EmscriptenRepresentation: Sized {
const EMSCRIPTEN_SIZE_IN_BYTES: usize; const EMSCRIPTEN_SIZE_IN_BYTES: usize;
const EMSCRIPTEN_TYPE_NAME: &'static str; const EMSCRIPTEN_TYPE_NAME: &'static str;
@ -56,7 +59,7 @@ impl EmscriptenRepresentation for f64 {
pub struct ArrayMemoryView<F: EmscriptenRepresentation> { pub struct ArrayMemoryView<F: EmscriptenRepresentation> {
begin_address: usize, begin_address: usize,
end_address: usize, end_address: usize,
type_marker: std::marker::PhantomData<F>, type_marker: PhantomData<F>,
} }
/// Iterator over values in `msdfgen` library memory /// Iterator over values in `msdfgen` library memory
@ -67,7 +70,7 @@ pub struct ArrayMemoryView<F: EmscriptenRepresentation> {
pub struct ArrayMemoryViewIterator<'a, F: EmscriptenRepresentation> { pub struct ArrayMemoryViewIterator<'a, F: EmscriptenRepresentation> {
next_read_address: usize, next_read_address: usize,
end_address: usize, end_address: usize,
view_lifetime: std::marker::PhantomData<&'a ArrayMemoryView<F>>, view_lifetime: PhantomData<&'a ArrayMemoryView<F>>,
} }
impl<F: EmscriptenRepresentation> ArrayMemoryView<F> { impl<F: EmscriptenRepresentation> ArrayMemoryView<F> {
@ -77,17 +80,13 @@ impl<F: EmscriptenRepresentation> ArrayMemoryView<F> {
ArrayMemoryView { ArrayMemoryView {
begin_address: address, begin_address: address,
end_address: address + size_in_bytes, end_address: address + size_in_bytes,
type_marker: std::marker::PhantomData, type_marker: PhantomData,
} }
} }
/// Create an empty view /// Create an empty view
pub fn empty() -> ArrayMemoryView<F> { pub fn empty() -> ArrayMemoryView<F> {
ArrayMemoryView { ArrayMemoryView { begin_address: 0, end_address: 0, type_marker: PhantomData }
begin_address: 0,
end_address: 0,
type_marker: std::marker::PhantomData,
}
} }
/// Iterator over elements /// Iterator over elements
@ -95,7 +94,7 @@ impl<F: EmscriptenRepresentation> ArrayMemoryView<F> {
ArrayMemoryViewIterator { ArrayMemoryViewIterator {
next_read_address: self.begin_address, next_read_address: self.begin_address,
end_address: self.end_address, end_address: self.end_address,
view_lifetime: std::marker::PhantomData, view_lifetime: PhantomData,
} }
} }
} }

View File

@ -1,21 +1,27 @@
//! MSDF-gen libraries bindings and utilities.
// === Standard Linter Configuration === // === Standard Linter Configuration ===
#![deny(non_ascii_idents)] #![deny(non_ascii_idents)]
#![warn(unsafe_code)] #![warn(unsafe_code)]
// === Non-Standard Linter Configuration === // === 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_copy_implementations)]
#![warn(missing_debug_implementations)] #![warn(missing_debug_implementations)]
#![warn(missing_docs)]
#![warn(trivial_casts)]
#![warn(trivial_numeric_casts)]
#![warn(unused_import_braces)]
#![warn(unused_qualifications)]
use crate::prelude::*;
mod binding;
pub mod emscripten_data;
pub use enso_prelude as prelude;
use binding::*; use binding::*;
use emscripten_data::ArrayMemoryView; use emscripten_data::ArrayMemoryView;
use js_sys::Uint8Array; use js_sys::Uint8Array;
use owned_ttf_parser::Tag;
use std::future::Future; use std::future::Future;
use std::pin::Pin; use std::pin::Pin;
use std::task::Context; 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 === // === Initialization ===
// ====================== // ======================
/// Add initialization callback. /// Add initialization callback. The callback passed as argument will be called once the msdfgen
/// /// library is initialized.
/// The callback passed as argument will be called once the msdfgen library is initialized.
pub fn run_once_initialized<F>(callback: F) pub fn run_once_initialized<F>(callback: F)
where F: 'static + FnOnce() { where F: 'static + FnOnce() {
if is_emscripten_runtime_initialized() { 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 = ()> { pub fn initialized() -> impl Future<Output = ()> {
MsdfgenJsInitialized() MsdfgenJsInitialized()
} }
/// The future for running test after initialization /// A future for running test after initialization.
#[derive(Debug)] #[derive(Debug)]
struct MsdfgenJsInitialized(); 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)] #[derive(Debug)]
pub struct Font { pub struct OwnedFace {
pub handle: JsValue, pub handle: JsValue,
} }
impl Drop for Font { impl Drop for OwnedFace {
fn drop(&mut self) { fn drop(&mut self) {
msdfgen_free_font(self.handle.clone()) msdfgen_free_font(self.handle.clone())
} }
} }
impl Font { impl OwnedFace {
/// Loading font from memory /// Load font from memory.
/// ///
/// Loads font from a any format which freetype library can handle. /// Loads font from any format which freetype library can handle. See
/// See [https://www.freetype.org/freetype2/docs/index.html] for reference. /// [https://www.freetype.org/freetype2/docs/index.html] for reference.
pub fn load_from_memory(data: &[u8]) -> Self { pub fn load_from_memory(data: &[u8]) -> Self {
let array_type_js = JsValue::from_str(ccall_types::ARRAY); let array_type_js = JsValue::from_str(ccall_types::ARRAY);
let number_type_js = JsValue::from_str(ccall_types::NUMBER); 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 params = js_sys::Array::of2(&data_js, &data_size_js);
let handle = emscripten_call_function(function_name, return_type, param_types, params); 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 { /// Set font's variation axis.
let left_unicode = left as u32; pub fn set_variation_axis(
let right_unicode = right as u32; &self,
msdfgen_get_kerning(self.handle.clone(), left_unicode, right_unicode) 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); 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 /// The structure gathering MSDF generation parameters meant to be same for all rendered glyphs.
/// rendered glyphs
#[derive(Clone, Copy, Debug)] #[derive(Clone, Copy, Debug)]
#[allow(missing_docs)]
pub struct MsdfParameters { pub struct MsdfParameters {
pub width: usize, pub width: usize,
pub height: usize, pub height: usize,
@ -136,6 +179,14 @@ pub struct MsdfParameters {
pub overlap_support: bool, pub overlap_support: bool,
} }
// ============
// === Msdf ===
// ============
/// Binding to the MSDF-gen library.
#[allow(missing_docs)]
#[derive(Debug)] #[derive(Debug)]
pub struct Msdf { pub struct Msdf {
handle: JsValue, handle: JsValue,
@ -152,13 +203,13 @@ impl Drop for Msdf {
} }
impl Msdf { impl Msdf {
/// Number of used color channels in the MSDF texture.
pub const CHANNELS_COUNT: usize = 3; pub const CHANNELS_COUNT: usize = 3;
/// Generate Mutlichannel Signed Distance Field (MSDF) for one glyph. /// Generate Mutlichannel Signed Distance Field (MSDF) for one glyph.
/// ///
/// For more information about MSDF see /// For more information about MSDF see [https://github.com/Chlumsky/msdfgen].
/// [https://github.com/Chlumsky/msdfgen]. pub fn generate(font: &OwnedFace, unicode: u32, params: &MsdfParameters) -> Msdf {
pub fn generate(font: &Font, unicode: u32, params: &MsdfParameters) -> Msdf {
let handle = msdfgen_generate_msdf( let handle = msdfgen_generate_msdf(
params.width, params.width,
params.height, params.height,
@ -170,6 +221,28 @@ impl Msdf {
params.edge_threshold, params.edge_threshold,
params.overlap_support, 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 advance = msdfgen_result_get_advance(handle.clone());
let translation = Self::translation(&handle); let translation = Self::translation(&handle);
let scale = Self::scale(&handle); let scale = Self::scale(&handle);
@ -199,6 +272,7 @@ impl Msdf {
nalgebra::Vector2::new(scale_x, scale_y) nalgebra::Vector2::new(scale_x, scale_y)
} }
/// Mocked version of this struct. Used for testing purposes.
pub fn mock_results() -> Msdf { pub fn mock_results() -> Msdf {
Msdf { Msdf {
handle: JsValue::from_f64(0.0), handle: JsValue::from_f64(0.0),
@ -220,9 +294,7 @@ impl Msdf {
mod tests { mod tests {
use super::*; use super::*;
use ensogl_text_embedded_fonts::EmbeddedFonts; use ensogl_text_embedded_fonts::Embedded;
use ensogl_text_embedded_fonts_names::DejaVuSans;
use ensogl_text_embedded_fonts_names::FontFamily;
use nalgebra::Vector2; use nalgebra::Vector2;
use wasm_bindgen_test::wasm_bindgen_test; use wasm_bindgen_test::wasm_bindgen_test;
use wasm_bindgen_test::wasm_bindgen_test_configure; use wasm_bindgen_test::wasm_bindgen_test_configure;
@ -233,9 +305,9 @@ mod tests {
async fn generate_msdf_for_capital_a() { async fn generate_msdf_for_capital_a() {
initialized().await; initialized().await;
// given // given
let font_base = EmbeddedFonts::create_and_fill(); let font_base = Embedded::init_and_load_embedded_fonts();
let font_name = DejaVuSans::mono_bold(); let font_name = "DejaVuSansMono-Bold.ttf";
let font = Font::load_from_memory(font_base.font_data_by_name.get(font_name).unwrap()); let font = OwnedFace::load_from_memory(font_base.data.get(font_name).unwrap());
let params = MsdfParameters { let params = MsdfParameters {
width: 32, width: 32,
height: 32, height: 32,
@ -257,6 +329,34 @@ mod tests {
assert_eq!(19.265625, msdf.advance); 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, &params);
// 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] /* Note [asserts]
* *
* we're checking rust - js interface only, so there is no need to check * we're checking rust - js interface only, so there is no need to check

View File

@ -2,20 +2,22 @@
use crate::prelude::*; 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. /// 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 /// 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 /// to WebGL `texImage2D`. The texture contains MSDFs for all loaded glyph, organized in vertical
/// column. /// column.
#[derive(Clone, CloneRef, Debug, Default)] #[derive(Clone, CloneRef, Debug, Default, serde::Serialize, serde::Deserialize)]
pub struct Texture { pub struct Texture {
/// A plain data of this texture. /// A plain data of this texture.
data: Rc<RefCell<Vec<u8>>>, data: Rc<RefCell<Vec<u8>>>,
@ -143,7 +145,7 @@ mod test {
#[wasm_bindgen_test(async)] #[wasm_bindgen_test(async)]
async fn msdf_translation_converting() { async fn msdf_translation_converting() {
ensogl_text_msdf_sys::initialized().await; crate::initialized().await;
let mut msdf = Msdf::mock_results(); let mut msdf = Msdf::mock_results();
msdf.translation = Vector2::new(16.0, 4.0); msdf.translation = Vector2::new(16.0, 4.0);

View 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,
&current.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,
&current.variable_font_variations,
a,
ch,
)
})
.unwrap_or_default();
let advance = self
.font
.glyph_info(
current.non_variable_font_variations,
&current.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 }
}
}

View File

@ -28,7 +28,7 @@
pub mod buffer; pub mod buffer;
pub mod component; pub mod component;
pub mod typeface; pub mod font;

View File

@ -1,11 +0,0 @@
// === Non-Standard Linter Configuration ===
#![allow(missing_docs)]
// ==============
// === Export ===
// ==============
pub mod font;
pub mod glyph;
pub mod pen;

View File

@ -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, &params);
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());
}
}

View File

@ -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)
}
}

View File

@ -27,8 +27,8 @@ enso-shapely = { path = "../../shapely" }
enso-shortcuts = { path = "../../shortcuts" } enso-shortcuts = { path = "../../shortcuts" }
enso-types = { path = "../../types" } enso-types = { path = "../../types" }
enso-web = { path = "../../web" } enso-web = { path = "../../web" }
ensogl-text-embedded-fonts = { path = "../component/text/embedded-fonts" } ensogl-text-embedded-fonts = { path = "../component/text/src/font/embedded" }
ensogl-text-msdf-sys = { path = "../component/text/msdf-sys" } ensogl-text-msdf = { path = "../component/text/src/font/msdf" }
bit_field = { version = "0.10.0" } bit_field = { version = "0.10.0" }
console_error_panic_hook = { version = "0.1.6" } console_error_panic_hook = { version = "0.1.6" }
enum_dispatch = { version = "0.3.6" } enum_dispatch = { version = "0.3.6" }

View File

@ -239,8 +239,7 @@ impl WorldData {
} }
fn init_environment(&self) { fn init_environment(&self) {
web::forward_panic_hook_to_console(); init_wasm();
web::set_stack_trace_limit();
} }
fn init_debug_hotkeys(&self) { fn init_debug_hotkeys(&self) {

View File

@ -10,6 +10,6 @@ crate-type = ["cdylib", "rlib"]
[dependencies] [dependencies]
enso-frp = { path = "../../../frp" } enso-frp = { path = "../../../frp" }
ensogl-core = { path = "../../core" } 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" } enso-prelude = { path = "../../../prelude" }
wasm-bindgen = { version = "0.2.78", features = ["nightly"] } wasm-bindgen = { version = "0.2.78", features = ["nightly"] }

View File

@ -25,7 +25,7 @@ use wasm_bindgen::prelude::*;
use ensogl_core::application::Application; use ensogl_core::application::Application;
use ensogl_core::DEPRECATED_Animation; 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; use logger::TraceLogger as Logger;

View File

@ -289,8 +289,6 @@ macro_rules! examples {
#[entry_point] #[entry_point]
#[allow(dead_code)] #[allow(dead_code)]
pub fn main() { pub fn main() {
web::forward_panic_hook_to_console();
web::set_stack_trace_limit();
let container = web::document.create_div_or_panic(); let container = web::document.create_div_or_panic();
container.set_attribute_or_warn("id", "examples"); container.set_attribute_or_warn("id", "examples");
container.set_style_or_warn("display", "flex"); container.set_style_or_warn("display", "flex");

View File

@ -10,6 +10,6 @@ crate-type = ["cdylib", "rlib"]
[dependencies] [dependencies]
ensogl-core = { path = "../../core" } ensogl-core = { path = "../../core" }
ensogl-text = { path = "../../component/text" } ensogl-text = { path = "../../component/text" }
ensogl-text-embedded-fonts = { path = "../../component/text/embedded-fonts" } ensogl-text-embedded-fonts = { path = "../../component/text/src/font/embedded" }
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"] } wasm-bindgen = { version = "0.2.78", features = ["nightly"] }

View File

@ -22,13 +22,11 @@
use ensogl_core::display::world::*; use ensogl_core::display::world::*;
use ensogl_core::prelude::*; use ensogl_core::prelude::*;
use ensogl_text::typeface::*;
use wasm_bindgen::prelude::*; use wasm_bindgen::prelude::*;
use ensogl_core::data::color; use ensogl_core::data::color;
use ensogl_text_embedded_fonts as embedded_fonts; use ensogl_text::font;
use ensogl_text_embedded_fonts::Family; use ensogl_text_msdf::run_once_initialized;
use ensogl_text_msdf_sys::run_once_initialized;
@ -44,12 +42,13 @@ pub fn main() {
fn init(world: &World) { fn init(world: &World) {
let fonts = world.default_scene.extension::<font::Registry>(); let fonts = world.default_scene.extension::<font::Registry>();
let font = fonts.load(embedded_fonts::DefaultFamily::regular()); let font = fonts.load("mplus1");
let glyph_system = glyph::System::new(&world.default_scene, font); let glyph_system = font::glyph::System::new(&world.default_scene, font);
let height = 32.0; let height = 64.0;
let color = color::Rgba::new(0.5, 0.0, 0.0, 1.0); let color = color::Rgba::new(0.5, 0.0, 0.0, 1.0);
let start_pos = Vector2(-300.0, -300.0); let start_pos = Vector2(-300.0, -300.0);
for (line_ind, line) in CHARS_TO_TEST.iter().enumerate() { for (line_ind, line) in CHARS_TO_TEST.iter().enumerate() {
for (char_ind, char) in line.chars().enumerate() { for (char_ind, char) in line.chars().enumerate() {
let glyph = glyph_system.new_glyph(); let glyph = glyph_system.new_glyph();
@ -61,7 +60,7 @@ fn init(world: &World) {
bold_glyph.set_char(char); bold_glyph.set_char(char);
bold_glyph.set_color(color); bold_glyph.set_color(color);
bold_glyph.set_font_size(height); 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 x = char_ind as f32 * (height + 4.0);
let y = line_ind as f32 * (height * 2.0 + 8.0); let y = line_ind as f32 * (height * 2.0 + 8.0);

View File

@ -12,7 +12,7 @@ enso-frp = { path = "../../../frp" }
ensogl-core = { path = "../../core" } ensogl-core = { path = "../../core" }
ensogl-hardcoded-theme = { path = "../../app/theme/hardcoded" } ensogl-hardcoded-theme = { path = "../../app/theme/hardcoded" }
ensogl-grid-view = { path = "../../component/grid-view" } 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" } enso-text = { path = "../../../text" }
wasm-bindgen = { version = "0.2.78", features = ["nightly"] } wasm-bindgen = { version = "0.2.78", features = ["nightly"] }
itertools = "0.10.3" itertools = "0.10.3"

View File

@ -24,7 +24,6 @@ use ensogl_core::prelude::*;
use wasm_bindgen::prelude::*; use wasm_bindgen::prelude::*;
use enso_frp as frp; use enso_frp as frp;
use enso_frp::web::forward_panic_hook_to_console;
use ensogl_core::application::Application; use ensogl_core::application::Application;
use ensogl_core::data::color; use ensogl_core::data::color;
use ensogl_core::display::navigation::navigator::Navigator; 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::Col;
use ensogl_grid_view::Row; use ensogl_grid_view::Row;
use ensogl_hardcoded_theme as theme; 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)] #[allow(dead_code)]
pub fn main() { pub fn main() {
run_once_initialized(|| { run_once_initialized(|| {
init_tracing(TRACE);
forward_panic_hook_to_console();
let app = Application::new("root"); let app = Application::new("root");
init(&app); init(&app);
mem::forget(app); mem::forget(app);

View File

@ -12,6 +12,6 @@ enso-frp = { path = "../../../frp" }
ensogl-core = { path = "../../core" } ensogl-core = { path = "../../core" }
ensogl-hardcoded-theme = { path = "../../app/theme/hardcoded" } ensogl-hardcoded-theme = { path = "../../app/theme/hardcoded" }
ensogl-list-view = { path = "../../component/list-view" } 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" } enso-text = { path = "../../../text" }
wasm-bindgen = { version = "0.2.78", features = ["nightly"] } wasm-bindgen = { version = "0.2.78", features = ["nightly"] }

View File

@ -28,7 +28,7 @@ use ensogl_core::application::Application;
use ensogl_core::display::object::ObjectOps; use ensogl_core::display::object::ObjectOps;
use ensogl_hardcoded_theme as theme; use ensogl_hardcoded_theme as theme;
use ensogl_list_view as list_view; 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; use logger::TraceLogger as Logger;

View File

@ -10,5 +10,5 @@ crate-type = ["cdylib", "rlib"]
[dependencies] [dependencies]
enso-frp = { path = "../../../frp" } enso-frp = { path = "../../../frp" }
ensogl-core = { path = "../../core" } 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"] } wasm-bindgen = { version = "0.2.78", features = ["nightly"] }

View File

@ -32,7 +32,7 @@ use ensogl_core::define_shape_system;
use ensogl_core::display; use ensogl_core::display;
use ensogl_core::display::navigation::navigator::Navigator; use ensogl_core::display::navigation::navigator::Navigator;
use ensogl_core::display::object::ObjectOps; use ensogl_core::display::object::ObjectOps;
use ensogl_text_msdf_sys::run_once_initialized; use ensogl_text_msdf::run_once_initialized;

View File

@ -21,7 +21,7 @@ ensogl-sequence-diagram = { path = "../../component/sequence-diagram" }
ensogl-tooltip = { path = "../../component/tooltip" } ensogl-tooltip = { path = "../../component/tooltip" }
ensogl-hardcoded-theme = { path = "../../app/theme/hardcoded" } ensogl-hardcoded-theme = { path = "../../app/theme/hardcoded" }
ensogl-text = { path = "../../component/text" } 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" futures = "0.3"
serde = "1" serde = "1"
wasm-bindgen = { version = "0.2.58", features = ["nightly"] } wasm-bindgen = { version = "0.2.58", features = ["nightly"] }

View File

@ -36,7 +36,6 @@ use ensogl_core::display::object::ObjectOps;
use ensogl_core::display::style::theme; use ensogl_core::display::style::theme;
use ensogl_core::display::Scene; use ensogl_core::display::Scene;
use ensogl_core::frp; use ensogl_core::frp;
use ensogl_core::system::web;
use ensogl_flame_graph as flame_graph; use ensogl_flame_graph as flame_graph;
use ensogl_flame_graph::COLOR_BLOCK_ACTIVE; use ensogl_flame_graph::COLOR_BLOCK_ACTIVE;
use ensogl_flame_graph::COLOR_BLOCK_PAUSED; use ensogl_flame_graph::COLOR_BLOCK_PAUSED;
@ -67,9 +66,6 @@ const SHOW_BACKEND_MESSAGE_MARKS: bool = true;
#[entry_point] #[entry_point]
#[allow(dead_code)] #[allow(dead_code)]
pub async fn main() { pub async fn main() {
web::forward_panic_hook_to_console();
web::set_stack_trace_limit();
let app = &Application::new("root"); let app = &Application::new("root");
let world = &app.display; let world = &app.display;
let scene = &world.default_scene; let scene = &world.default_scene;

View File

@ -17,7 +17,7 @@ ensogl-core = { path = "../../core" }
ensogl-flame-graph = { path = "../../component/flame-graph" } ensogl-flame-graph = { path = "../../component/flame-graph" }
ensogl-hardcoded-theme = { path = "../../app/theme/hardcoded" } ensogl-hardcoded-theme = { path = "../../app/theme/hardcoded" }
ensogl-text = { path = "../../component/text" } 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" } ensogl-tooltip = { path = "../../component/tooltip" }
futures = "0.3" futures = "0.3"
wasm-bindgen = { version = "0.2.58", features = ["nightly"] } wasm-bindgen = { version = "0.2.58", features = ["nightly"] }

View File

@ -11,5 +11,5 @@ crate-type = ["cdylib", "rlib"]
ensogl-core = { path = "../../core" } ensogl-core = { path = "../../core" }
ensogl-hardcoded-theme = { path = "../../app/theme/hardcoded" } ensogl-hardcoded-theme = { path = "../../app/theme/hardcoded" }
ensogl-scroll-area = { path = "../../component/scroll-area" } 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"] } wasm-bindgen = { version = "0.2.78", features = ["nightly"] }

View File

@ -30,7 +30,7 @@ use ensogl_core::display::navigation::navigator::Navigator;
use ensogl_core::display::object::ObjectOps; use ensogl_core::display::object::ObjectOps;
use ensogl_hardcoded_theme as theme; use ensogl_hardcoded_theme as theme;
use ensogl_scroll_area::ScrollArea; use ensogl_scroll_area::ScrollArea;
use ensogl_text_msdf_sys::run_once_initialized; use ensogl_text_msdf::run_once_initialized;

View File

@ -10,6 +10,6 @@ crate-type = ["cdylib", "rlib"]
[dependencies] [dependencies]
ensogl-core = { path = "../../core" } ensogl-core = { path = "../../core" }
ensogl-hardcoded-theme = { path = "../../app/theme/hardcoded" } 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" } ensogl-selector = { path = "../../component/selector" }
wasm-bindgen = { version = "0.2.78", features = ["nightly"] } wasm-bindgen = { version = "0.2.78", features = ["nightly"] }

View File

@ -29,7 +29,7 @@ use ensogl_core::display::object::ObjectOps;
use ensogl_hardcoded_theme as theme; use ensogl_hardcoded_theme as theme;
use ensogl_selector as selector; use ensogl_selector as selector;
use ensogl_selector::Bounds; use ensogl_selector::Bounds;
use ensogl_text_msdf_sys::run_once_initialized; use ensogl_text_msdf::run_once_initialized;

View File

@ -11,6 +11,6 @@ crate-type = ["cdylib", "rlib"]
ensogl-core = { path = "../../core" } ensogl-core = { path = "../../core" }
ensogl-hardcoded-theme = { path = "../../app/theme/hardcoded" } ensogl-hardcoded-theme = { path = "../../app/theme/hardcoded" }
ensogl-text = { path = "../../component/text" } ensogl-text = { path = "../../component/text" }
ensogl-text-embedded-fonts = { path = "../../component/text/embedded-fonts" } ensogl-text-embedded-fonts = { path = "../../component/text/src/font/embedded" }
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"] } wasm-bindgen = { version = "0.2.78", features = ["nightly"] }

View File

@ -30,9 +30,7 @@ use ensogl_core::display::navigation::navigator::Navigator;
use ensogl_text::buffer; use ensogl_text::buffer;
use ensogl_text::style; use ensogl_text::style;
use ensogl_text::Area; use ensogl_text::Area;
use ensogl_text_embedded_fonts as embedded_fonts; use ensogl_text_msdf::run_once_initialized;
use ensogl_text_embedded_fonts::Family;
use ensogl_text_msdf_sys::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 quote = "Et Eärello Endorenna utúlien.\nSinome maruvan ar Hildinyar tenn' Ambar-metta\n";
let snowman = "\u{2603}"; let snowman = "\u{2603}";
let zalgo = "Z̮̞̠͙͔ͅḀ̗̞͈̻̗Ḷ͙͎̯̹̞͓G̻O̭̗̮"; 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_content(text.clone() + "\n" + text.as_str());
area.set_font(embedded_fonts::DefaultFamily::regular()); area.set_font("default");
area.focus(); area.focus();
area.hover(); area.hover();
area.set_cursor_at_end(); area.set_cursor_at_end();
@ -79,7 +78,7 @@ fn init(app: Application) {
let text = "red green blue"; let text = "red green blue";
let colored_area = app.new_view::<Area>(); let colored_area = app.new_view::<Area>();
app.display.default_scene.add_child(&colored_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_position_xy(Vector2::new(200.0, 200.0));
colored_area.set_default_color(color::Rgba::black()); colored_area.set_default_color(color::Rgba::black());

View File

@ -1,5 +1,6 @@
//! A module with Javascript IO bindings utilities. //! A module with Javascript IO bindings utilities.
use crate::prelude::*;
use enso_web::prelude::*; use enso_web::prelude::*;
use crate as frp; use crate as frp;

View File

@ -22,6 +22,7 @@ enso-prelude = { version = "^0.2.1", path = "../prelude" }
enso-shapely = { version = "^0.2.0", path = "../shapely" } enso-shapely = { version = "^0.2.0", path = "../shapely" }
wasm-bindgen = { version = "0.2.78", features = ["nightly"] } wasm-bindgen = { version = "0.2.78", features = ["nightly"] }
js-sys = { version = "0.3.28" } js-sys = { version = "0.3.28" }
ifmt = "0.3.3"
[dependencies.web-sys] [dependencies.web-sys]
version = "0.3.4" version = "0.3.4"

View File

@ -131,6 +131,7 @@ use crate::processor::Processor;
use enso_shapely::CloneRef; use enso_shapely::CloneRef;
pub use ifmt::iformat;
// ============== // ==============

View File

@ -14,7 +14,7 @@ macro_rules! log_template {
}; };
($expand:ident, $level:path, $logger:expr, $msg:tt) => { ($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) => { ($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)*) => { ($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)*) => { ($expand:ident, $level:path, $logger:expr, || $msg:expr, || $($body:tt)*) => {

View File

@ -1,8 +1,8 @@
//! Java interface to [`enso_parser`]. //! Java interface to [`enso_parser`].
// === Features ===
// === Standard Linter Configuration === // === Standard Linter Configuration ===
#![deny(non_ascii_idents)] #![deny(non_ascii_idents)]
#![warn(unsafe_code)]
// === Non-Standard Linter Configuration === // === Non-Standard Linter Configuration ===
#![allow(clippy::option_map_unit_fn)] #![allow(clippy::option_map_unit_fn)]
#![allow(clippy::precedence)] #![allow(clippy::precedence)]
@ -38,6 +38,7 @@ use jni::JNIEnv;
/// The input buffer contents MUST be valid UTF-8. /// 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 /// The contents of the returned buffer MUST not be accessed after another call to `parseInput`, or
/// a call to `freeState`. /// a call to `freeState`.
#[allow(unsafe_code)]
#[no_mangle] #[no_mangle]
pub extern "system" fn Java_org_enso_syntax2_Parser_parseInput( pub extern "system" fn Java_org_enso_syntax2_Parser_parseInput(
env: JNIEnv, 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 /// The input MUST have been returned by `allocState`, and MUST NOT have previously been passed to
/// `freeState`. /// `freeState`.
#[allow(unsafe_code)]
#[no_mangle] #[no_mangle]
pub extern "system" fn Java_org_enso_syntax2_Parser_getLastInputBase( pub extern "system" fn Java_org_enso_syntax2_Parser_getLastInputBase(
_env: JNIEnv, _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 /// Allocate a new parser state object. The returned value should be passed to `freeState` when no
/// longer needed. /// longer needed.
#[allow(unsafe_code)]
#[no_mangle] #[no_mangle]
pub extern "system" fn Java_org_enso_syntax2_Parser_allocState( pub extern "system" fn Java_org_enso_syntax2_Parser_allocState(
_env: JNIEnv, _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 /// The input MUST have been returned by `allocState`, and MUST NOT have previously been passed to
/// `freeState`. /// `freeState`.
#[allow(unsafe_code)]
#[no_mangle] #[no_mangle]
pub extern "system" fn Java_org_enso_syntax2_Parser_freeState( pub extern "system" fn Java_org_enso_syntax2_Parser_freeState(
_env: JNIEnv, _env: JNIEnv,

View File

@ -2,6 +2,7 @@
use crate::macros::pattern::*; use crate::macros::pattern::*;
use crate::macros::*; use crate::macros::*;
use crate::syntax::operator; use crate::syntax::operator;

View File

@ -33,7 +33,7 @@ use enso_parser::prelude::*;
// ============= // =============
fn main() { fn main() {
init_tracing(TRACE); init_wasm();
let ast = enso_parser::Parser::new().run("foo = 23"); let ast = enso_parser::Parser::new().run("foo = 23");
println!("\n\n==================\n\n"); println!("\n\n==================\n\n");
println!("{:#?}", ast); println!("{:#?}", ast);

View File

@ -1,9 +1,8 @@
//! Code blocks. //! Code blocks.
use crate::syntax::tree::*;
use crate::syntax::token; use crate::syntax::token;
use crate::syntax::tree::*;

View File

@ -17,7 +17,7 @@ crate-type = ["cdylib", "rlib"]
[dependencies] [dependencies]
enso-reflect = { path = "../reflect" } enso-reflect = { path = "../reflect" }
enso-shapely = { version = "^0.2.0", path = "../shapely" } enso-shapely = { path = "../shapely" }
anyhow = "1.0.37" anyhow = "1.0.37"
assert_approx_eq = { version = "1.1.0" } assert_approx_eq = { version = "1.1.0" }
backtrace = "0.3.53" backtrace = "0.3.53"
@ -45,6 +45,7 @@ tracing-wasm = "0.2"
wasm-bindgen = { version = "0.2.78", features = ["nightly"], optional = true } wasm-bindgen = { version = "0.2.78", features = ["nightly"], optional = true }
weak-table = "0.3.0" weak-table = "0.3.0"
nalgebra = { version = "0.26.2", optional = true } nalgebra = { version = "0.26.2", optional = true }
enso-web = { path = "../web" }
[target.'cfg(target_arch = "wasm32")'.dependencies] [target.'cfg(target_arch = "wasm32")'.dependencies]
wasm-bindgen = { version = "0.2.78", features = ["nightly"] } wasm-bindgen = { version = "0.2.78", features = ["nightly"] }

View File

@ -18,7 +18,6 @@
#[cfg(feature = "futures")] #[cfg(feature = "futures")]
pub mod channel; pub mod channel;
mod clone;
mod collections; mod collections;
mod data; mod data;
pub mod debug; pub mod debug;
@ -47,10 +46,11 @@ mod wrapper;
#[cfg(feature = "serde")] #[cfg(feature = "serde")]
pub use crate::serde::*; pub use crate::serde::*;
pub use crate::smallvec::*; pub use crate::smallvec::*;
pub use clone::*;
pub use collections::*; pub use collections::*;
pub use data::*; pub use data::*;
pub use debug::*; pub use debug::*;
pub use enso_shapely::clone_ref::*;
pub use enso_shapely::impl_clone_ref_as_clone;
pub use fail::*; pub use fail::*;
pub use leak::Leak; pub use leak::Leak;
pub use leak::*; pub use leak::*;
@ -125,10 +125,22 @@ pub const INFO: tracing::Level = tracing::Level::INFO;
pub const DEBUG: tracing::Level = tracing::Level::DEBUG; pub const DEBUG: tracing::Level = tracing::Level::DEBUG;
pub const TRACE: tracing::Level = tracing::Level::TRACE; 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) { pub fn init_tracing(level: tracing::Level) {
TRACING_INIT_ONCE.call_once(|| {
#[cfg(not(target_arch = "wasm32"))] #[cfg(not(target_arch = "wasm32"))]
let subscriber = let subscriber = tracing::fmt()
tracing::fmt().compact().with_target(false).with_max_level(level).without_time().finish(); .compact()
.with_target(false)
.with_max_level(level)
.without_time()
.finish();
#[cfg(target_arch = "wasm32")] #[cfg(target_arch = "wasm32")]
let subscriber = { let subscriber = {
use tracing_subscriber::layer::SubscriberExt; use tracing_subscriber::layer::SubscriberExt;
@ -137,6 +149,13 @@ pub fn init_tracing(level: tracing::Level) {
tracing::Registry::default().with(WASMLayer::new(config)) tracing::Registry::default().with(WASMLayer::new(config))
}; };
tracing::subscriber::set_global_default(subscriber).expect("Failed to initialize logger."); 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 {} impl<T> ToImpl for T {}
// ================
// === Nalgebra ===
// ================
#[cfg(feature = "nalgebra")] #[cfg(feature = "nalgebra")]
impl<T, R, C, S> TypeDisplay for nalgebra::Matrix<T, R, C, S> impl<T, R, C, S> TypeDisplay for nalgebra::Matrix<T, R, C, S>
where where

View File

@ -1,8 +1,8 @@
//! This module defines several useful string variants, including copy-on-write and immutable //! This module defines several useful string variants, including copy-on-write and immutable
//! implementations. //! implementations.
use crate::clone::*;
use derive_more::*; use derive_more::*;
use enso_shapely::clone_ref::*;
use itertools::*; use itertools::*;
use crate::impls; use crate::impls;

View File

@ -24,6 +24,10 @@ paste = { version = "0.1" }
derivative = { version = "2.2.0" } derivative = { version = "2.2.0" }
shrinkwraprs = { version = "0.3.0" } shrinkwraprs = { version = "0.3.0" }
rustversion = { version = "1.0" } rustversion = { version = "1.0" }
wasm-bindgen = { version = "0.2.78", features = ["nightly"] }
[dependencies.web-sys]
version = "0.3.4"
[dev-dependencies] [dev-dependencies]
enso-prelude = { path = "../prelude" } enso-prelude = { path = "../prelude" }

View File

@ -6,18 +6,38 @@ use crate::prelude::*;
// === Entry Point === // === 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("debug-scene-", "");
let name = name.replace("ensogl-example-", ""); let name = name.replace("ensogl-example-", "");
let name = name.replace("enso-example-", ""); let name = name.replace("enso-example-", "");
let name = name.replace("enso-", ""); let name = name.replace("enso-", "");
let name = name.replace("example-", ""); let name = name.replace("example-", "");
let name = name.replace('-', "_"); name.replace('-', "_")
}
fn base_name_to_fn_name(name: &str) -> String {
format!("entry_point_{}", name) format!("entry_point_{}", name)
} }
pub fn derive(input: proc_macro::TokenStream) -> proc_macro::TokenStream { 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(); 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); let decl = syn::parse_macro_input!(input as syn::Item);
match decl { match decl {
syn::Item::Fn(f) => { syn::Item::Fn(f) => {
@ -25,7 +45,6 @@ pub fn derive(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
if &name != "main" { if &name != "main" {
panic!("The function should be named '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(); let mut fn_sig = f.sig.clone();
fn_sig.ident = fn_name; fn_sig.ident = fn_name;
let attrs = &f.attrs; let attrs = &f.attrs;
@ -33,7 +52,10 @@ pub fn derive(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
let output = quote! { let output = quote! {
#(#attrs)* #(#attrs)*
#[wasm_bindgen] #[wasm_bindgen]
pub #fn_sig #block pub #fn_sig {
init_wasm();
#block
}
}; };
output.into() output.into()
} }

View File

@ -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 /// 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] #[proc_macro_attribute]
pub fn entry_point( pub fn entry_point(
_: proc_macro::TokenStream, args: proc_macro::TokenStream,
item: proc_macro::TokenStream, item: proc_macro::TokenStream,
) -> proc_macro::TokenStream { ) -> proc_macro::TokenStream {
derive_entry_point::derive(item) derive_entry_point::derive(args, item)
} }
#[allow(missing_docs)] #[allow(missing_docs)]

View File

@ -1,13 +1,10 @@
use crate::*;
// ============== // ==============
// === Export === // === Export ===
// ============== // ==============
pub use enso_shapely::entry_point; pub use crate::entry_point;
pub use enso_shapely::CloneRef; pub use crate::CloneRef;
pub use enso_shapely::NoCloneBecauseOfCustomDrop; 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!(u64);
impl_clone_ref_as_clone_no_from!(usize); 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!(std::any::TypeId);
impl_clone_ref_as_clone_no_from!([T] PhantomData<T>); impl_clone_ref_as_clone_no_from!([T] std::marker::PhantomData<T>);
impl_clone_ref_as_clone_no_from!([T:?Sized] Rc<T>); impl_clone_ref_as_clone_no_from!([T:?Sized] std::rc::Rc<T>);
impl_clone_ref_as_clone_no_from!([T:?Sized] Weak<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!(wasm_bindgen::JsValue);
impl_clone_ref_as_clone_no_from!(web_sys::Element); impl_clone_ref_as_clone_no_from!(web_sys::Element);
impl_clone_ref_as_clone_no_from!(web_sys::HtmlDivElement); impl_clone_ref_as_clone_no_from!(web_sys::HtmlDivElement);

View File

@ -21,6 +21,7 @@
// ============== // ==============
pub mod cartesian; pub mod cartesian;
pub mod clone_ref;
pub mod generator; pub mod generator;
pub mod shared; pub mod shared;
pub mod singleton; pub mod singleton;

View File

@ -5,25 +5,19 @@
use enso_prelude::*; use enso_prelude::*;
use wasm_bindgen::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 enso_frp as frp;
use frp::io::keyboard;
use frp::io::keyboard::Keyboard;
use enso_logger::AnyLogger; use enso_logger::AnyLogger;
use enso_logger::WarningLogger as Logger; 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)] #[allow(dead_code)]
pub fn entry_point_shortcuts() {
web::forward_panic_hook_to_console();
web::set_stack_trace_limit();
main();
}
pub fn main() { pub fn main() {
let shortcut_registry = shortcuts::AutomataRegistry::<String>::new(); let shortcut_registry = shortcuts::AutomataRegistry::<String>::new();
shortcut_registry.add(shortcuts::Press, "ctrl + a", "hello"); shortcut_registry.add(shortcuts::Press, "ctrl + a", "hello");

View File

@ -11,4 +11,4 @@ crate-type = ["rlib", "cdylib"]
enso-prelude = { path = "../prelude" } enso-prelude = { path = "../prelude" }
enso-types = { path = "../types" } enso-types = { path = "../types" }
xi-rope = { version = "0.3.0" } xi-rope = { version = "0.3.0" }
serde = "1.0" serde = "1"

View File

@ -10,16 +10,16 @@ edition = "2021"
default = ["console_error_panic_hook"] default = ["console_error_panic_hook"]
[dependencies] [dependencies]
enso-data-structures = { path = "../data-structures" }
enso-debug-api = { path = "../debug-api" } 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 } console_error_panic_hook = { version = "0.1.6", optional = true }
failure = { version = "0.1.5" } failure = { version = "0.1.5" }
gloo-timers = { version = "0.2.1", features = ["futures"] } gloo-timers = { version = "0.2.1", features = ["futures"] }
js-sys = { version = "0.3.28" } js-sys = { version = "0.3.28" }
nalgebra = { version = "0.26.1" } nalgebra = { version = "0.26.1" }
wasm-bindgen = { version = "0.2.78", features = ["nightly"] } 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] [target.'cfg(not(target_arch = "wasm32"))'.dependencies]
async-std = { version = "1.5.0" } async-std = { version = "1.5.0" }
@ -53,6 +53,9 @@ features = [
'DomRectReadOnly', 'DomRectReadOnly',
'Location', 'Location',
'ReadableStream', 'ReadableStream',
'AddEventListenerOptions',
'KeyboardEvent',
'WheelEvent',
] ]
[dev-dependencies] [dev-dependencies]

Some files were not shown because too many files have changed in this diff Show More