mirror of
https://github.com/enso-org/enso.git
synced 2024-08-17 00:40:34 +03:00
Use Enso Font (#7516)
Use the new Enso Font; also change the anti-aliasing logic to be based on device pixel ratio, rather than platform. This will improve the clarity of font rendering on Windows/Linux machines with high pixel densities. Design reference: ![image](https://github.com/enso-org/enso/assets/1047859/934ec9ac-52c3-4a81-a9f9-143378ecb658) Tested on various combinations of DPR/platform: OS X, `devicePixelRatio` = 2 (should look similar to how we were already rendering *mplus1* on OS X): <img width="1440" alt="Screenshot 2023-08-07 at 5 46 11 PM" src="https://github.com/enso-org/enso/assets/1047859/2fdf251a-ba5e-426f-b6c4-194347a9cee4"> Windows, `devicePixelRatio` = 1.25 (should look similar to how we were already rendering *mplus1* on this platform/DPR): ![image](https://github.com/enso-org/enso/assets/1047859/55c4a129-4fff-4a9b-8e55-51a5d206e659) Linux, `devicePixelRatio` = 1 (should look similar to how we were already rendering *mplus1* on this platform/DPR): ![image](https://github.com/enso-org/enso/assets/1047859/c5ac61f0-e3c5-43ca-8ee7-e1e04e84d35e) # Important Notes Style changes: - Use the Enso Font for code in Rust, replacing the DejaVu fonts. - Use the Enso Font in HTML: code in documentation, and error visualizations. - Change SpanWidgets from Bold to Extra Bold, to match the design. Implementation improvements: - The new font download is cached (and Github-authenticated); this should eliminate a "rate limit" build failure I've encountered in the past. - Clean up DocSection HTML rendering a bit. - Remove a CSS file that seems to have been superseded.
This commit is contained in:
parent
d15b3db0ac
commit
1dfdee5808
@ -2,12 +2,12 @@
|
||||
<component name="#ProjectRunConfigurationManager">
|
||||
<configuration
|
||||
default="false"
|
||||
name="clippy/ensogl-text-font-family [wasm, linux]"
|
||||
name="clippy/enso-font [wasm, linux]"
|
||||
type="CargoCommandRunConfiguration"
|
||||
factoryName="Cargo Command"
|
||||
folderName="🎨 EnsoGl component/text/src/font/family"
|
||||
folderName="📚 Lib font-family"
|
||||
>
|
||||
<option name="command" value="clippy --all-targets --target wasm32-unknown-unknown --target x86_64-unknown-linux-gnu --target-dir dist/intellij_wasm_linux --package ensogl-text-font-family" />
|
||||
<option name="command" value="clippy --all-targets --target wasm32-unknown-unknown --target x86_64-unknown-linux-gnu --target-dir dist/intellij_wasm_linux --package enso-font" />
|
||||
<option name="workingDirectory" value="file://$PROJECT_DIR$" />
|
||||
<option name="channel" value="DEFAULT" />
|
||||
<option name="requiredFeatures" value="true" />
|
@ -2,12 +2,12 @@
|
||||
<component name="#ProjectRunConfigurationManager">
|
||||
<configuration
|
||||
default="false"
|
||||
name="clippy/ensogl-text-font-family [wasm, macos]"
|
||||
name="clippy/enso-font [wasm, macos]"
|
||||
type="CargoCommandRunConfiguration"
|
||||
factoryName="Cargo Command"
|
||||
folderName="🎨 EnsoGl component/text/src/font/family"
|
||||
folderName="📚 Lib font-family"
|
||||
>
|
||||
<option name="command" value="clippy --all-targets --target wasm32-unknown-unknown --target aarch64-apple-darwin --target-dir dist/intellij_wasm_macos --package ensogl-text-font-family" />
|
||||
<option name="command" value="clippy --all-targets --target wasm32-unknown-unknown --target aarch64-apple-darwin --target-dir dist/intellij_wasm_macos --package enso-font" />
|
||||
<option name="workingDirectory" value="file://$PROJECT_DIR$" />
|
||||
<option name="channel" value="DEFAULT" />
|
||||
<option name="requiredFeatures" value="true" />
|
@ -2,12 +2,12 @@
|
||||
<component name="#ProjectRunConfigurationManager">
|
||||
<configuration
|
||||
default="false"
|
||||
name="clippy/ensogl-text-font-family [wasm, windows]"
|
||||
name="clippy/enso-font [wasm, windows]"
|
||||
type="CargoCommandRunConfiguration"
|
||||
factoryName="Cargo Command"
|
||||
folderName="🎨 EnsoGl component/text/src/font/family"
|
||||
folderName="📚 Lib font-family"
|
||||
>
|
||||
<option name="command" value="clippy --all-targets --target wasm32-unknown-unknown --target x86_64-pc-windows-msvc --target-dir dist/intellij_wasm_windows --package ensogl-text-font-family" />
|
||||
<option name="command" value="clippy --all-targets --target wasm32-unknown-unknown --target x86_64-pc-windows-msvc --target-dir dist/intellij_wasm_windows --package enso-font" />
|
||||
<option name="workingDirectory" value="file://$PROJECT_DIR$" />
|
||||
<option name="channel" value="DEFAULT" />
|
||||
<option name="requiredFeatures" value="true" />
|
@ -2,12 +2,12 @@
|
||||
<component name="#ProjectRunConfigurationManager">
|
||||
<configuration
|
||||
default="false"
|
||||
name="clippy/ensogl-text-font-family [wasm]"
|
||||
name="clippy/enso-font [wasm]"
|
||||
type="CargoCommandRunConfiguration"
|
||||
factoryName="Cargo Command"
|
||||
folderName="🎨 EnsoGl component/text/src/font/family"
|
||||
folderName="📚 Lib font-family"
|
||||
>
|
||||
<option name="command" value="clippy --all-targets --target wasm32-unknown-unknown --target-dir dist/intellij_wasm --package ensogl-text-font-family" />
|
||||
<option name="command" value="clippy --all-targets --target wasm32-unknown-unknown --target-dir dist/intellij_wasm --package enso-font" />
|
||||
<option name="workingDirectory" value="file://$PROJECT_DIR$" />
|
||||
<option name="channel" value="DEFAULT" />
|
||||
<option name="requiredFeatures" value="true" />
|
@ -2,12 +2,12 @@
|
||||
<component name="#ProjectRunConfigurationManager">
|
||||
<configuration
|
||||
default="false"
|
||||
name="doc-test/ensogl-text-font-family [native]"
|
||||
name="doc-test/enso-font [native]"
|
||||
type="CargoCommandRunConfiguration"
|
||||
factoryName="Cargo Command"
|
||||
folderName="🎨 EnsoGl component/text/src/font/family"
|
||||
folderName="📚 Lib font-family"
|
||||
>
|
||||
<option name="command" value="test --doc --target-dir dist/intellij_native --package ensogl-text-font-family" />
|
||||
<option name="command" value="test --doc --target-dir dist/intellij_native --package enso-font" />
|
||||
<option name="workingDirectory" value="file://$PROJECT_DIR$" />
|
||||
<option name="channel" value="DEFAULT" />
|
||||
<option name="requiredFeatures" value="true" />
|
@ -2,12 +2,12 @@
|
||||
<component name="#ProjectRunConfigurationManager">
|
||||
<configuration
|
||||
default="false"
|
||||
name="test/ensogl-text-font-family [native]"
|
||||
name="test/enso-font [native]"
|
||||
type="CargoCommandRunConfiguration"
|
||||
factoryName="Cargo Command"
|
||||
folderName="🎨 EnsoGl component/text/src/font/family"
|
||||
folderName="📚 Lib font-family"
|
||||
>
|
||||
<option name="command" value="test --all-targets --target-dir dist/intellij_native --package ensogl-text-font-family" />
|
||||
<option name="command" value="test --all-targets --target-dir dist/intellij_native --package enso-font" />
|
||||
<option name="workingDirectory" value="file://$PROJECT_DIR$" />
|
||||
<option name="channel" value="DEFAULT" />
|
||||
<option name="requiredFeatures" value="true" />
|
85
Cargo.lock
generated
85
Cargo.lock
generated
@ -1889,6 +1889,8 @@ dependencies = [
|
||||
"dirs",
|
||||
"enso-build-base",
|
||||
"enso-build-macros-lib",
|
||||
"enso-enso-font",
|
||||
"enso-font",
|
||||
"ensogl-pack",
|
||||
"filetime",
|
||||
"flate2",
|
||||
@ -2116,6 +2118,15 @@ dependencies = [
|
||||
"pretty_assertions",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "enso-enso-font"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"enso-font",
|
||||
"ide-ci",
|
||||
"owned_ttf_parser",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "enso-executor"
|
||||
version = "0.1.0"
|
||||
@ -2126,6 +2137,14 @@ dependencies = [
|
||||
"futures",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "enso-font"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"derive_more",
|
||||
"owned_ttf_parser",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "enso-formatter"
|
||||
version = "0.1.0"
|
||||
@ -3297,6 +3316,7 @@ dependencies = [
|
||||
"bincode 2.0.0-rc.2",
|
||||
"const_format",
|
||||
"enso-bitmap",
|
||||
"enso-font",
|
||||
"enso-frp",
|
||||
"enso-prelude",
|
||||
"enso-shapely",
|
||||
@ -3304,7 +3324,6 @@ dependencies = [
|
||||
"enso-types",
|
||||
"ensogl-core",
|
||||
"ensogl-text-embedded-fonts",
|
||||
"ensogl-text-font-family",
|
||||
"ensogl-text-msdf",
|
||||
"ordered-float",
|
||||
"owned_ttf_parser",
|
||||
@ -3322,21 +3341,13 @@ name = "ensogl-text-embedded-fonts"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"enso-build",
|
||||
"enso-build-utilities",
|
||||
"enso-enso-font",
|
||||
"enso-font",
|
||||
"enso-prelude",
|
||||
"ensogl-text-font-family",
|
||||
"ide-ci",
|
||||
"owned_ttf_parser",
|
||||
"rustybuzz",
|
||||
"tokio",
|
||||
"zip",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ensogl-text-font-family"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"derive_more",
|
||||
"owned_ttf_parser",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -3345,12 +3356,12 @@ version = "0.1.0"
|
||||
dependencies = [
|
||||
"enso-bitmap",
|
||||
"enso-build-utilities",
|
||||
"enso-font",
|
||||
"enso-prelude",
|
||||
"enso-profiler",
|
||||
"enso-types",
|
||||
"enso-web",
|
||||
"ensogl-text-embedded-fonts",
|
||||
"ensogl-text-font-family",
|
||||
"failure",
|
||||
"futures",
|
||||
"ide-ci",
|
||||
@ -3473,7 +3484,7 @@ checksum = "4e884668cd0c7480504233e951174ddc3b382f7c2666e3b7310b5c4e7b0c37f9"
|
||||
dependencies = [
|
||||
"cfg-if 1.0.0",
|
||||
"libc",
|
||||
"redox_syscall",
|
||||
"redox_syscall 0.2.16",
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
@ -4284,6 +4295,7 @@ dependencies = [
|
||||
"tempfile",
|
||||
"tokio",
|
||||
"tokio-stream",
|
||||
"tokio-tar",
|
||||
"tokio-util",
|
||||
"tracing",
|
||||
"tracing-subscriber",
|
||||
@ -4718,9 +4730,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.139"
|
||||
version = "0.2.147"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "201de327520df007757c1f0adce6e827fe8562fbc28bfd9c15571c66ca1f5f79"
|
||||
checksum = "b4668fb0ea861c1df094127ac5f1da3409a82116a4ba74fca2e58ef927159bb3"
|
||||
|
||||
[[package]]
|
||||
name = "libm"
|
||||
@ -5338,7 +5350,7 @@ checksum = "ba1ef8814b5c993410bb3adfad7a5ed269563e4a2f90c41f5d85be7fb47133bf"
|
||||
dependencies = [
|
||||
"cfg-if 1.0.0",
|
||||
"libc",
|
||||
"redox_syscall",
|
||||
"redox_syscall 0.2.16",
|
||||
"smallvec 1.10.0",
|
||||
"windows-sys",
|
||||
]
|
||||
@ -5864,6 +5876,15 @@ dependencies = [
|
||||
"bitflags 1.3.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "redox_syscall"
|
||||
version = "0.3.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "567664f262709473930a4bf9e51bf2ebf3348f2e748ccc50dea20646858f8f29"
|
||||
dependencies = [
|
||||
"bitflags 1.3.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "redox_users"
|
||||
version = "0.4.3"
|
||||
@ -5871,7 +5892,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b033d837a7cf162d7993aded9304e30a83213c648b6e389db233191f891e5c2b"
|
||||
dependencies = [
|
||||
"getrandom 0.2.8",
|
||||
"redox_syscall",
|
||||
"redox_syscall 0.2.16",
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
@ -6643,7 +6664,7 @@ checksum = "4b55807c0344e1e6c04d7c965f5289c39a8d94ae23ed5c0b57aabac549f871c6"
|
||||
dependencies = [
|
||||
"filetime",
|
||||
"libc",
|
||||
"xattr",
|
||||
"xattr 0.2.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -6655,7 +6676,7 @@ dependencies = [
|
||||
"cfg-if 1.0.0",
|
||||
"fastrand",
|
||||
"libc",
|
||||
"redox_syscall",
|
||||
"redox_syscall 0.2.16",
|
||||
"remove_dir_all",
|
||||
"winapi",
|
||||
]
|
||||
@ -6880,6 +6901,21 @@ dependencies = [
|
||||
"tokio",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tokio-tar"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9d5714c010ca3e5c27114c1cdeb9d14641ace49874aa5626d7149e47aedace75"
|
||||
dependencies = [
|
||||
"filetime",
|
||||
"futures-core",
|
||||
"libc",
|
||||
"redox_syscall 0.3.5",
|
||||
"tokio",
|
||||
"tokio-stream",
|
||||
"xattr 1.0.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tokio-tungstenite"
|
||||
version = "0.17.2"
|
||||
@ -7775,6 +7811,15 @@ dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "xattr"
|
||||
version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f4686009f71ff3e5c4dbcf1a282d0a44db3f021ba69350cd42086b3e5f1c6985"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "xi-rope"
|
||||
version = "0.3.0"
|
||||
|
@ -29,6 +29,10 @@
|
||||
padding-bottom: 4px;
|
||||
}
|
||||
|
||||
.enso-docs code {
|
||||
font-family: EnsoRegular;
|
||||
}
|
||||
|
||||
.enso-docs .unordered-list {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
@ -195,6 +199,7 @@ div.enso-docs .marked-icon-info {
|
||||
}
|
||||
|
||||
.enso-docs .example {
|
||||
font-family: EnsoRegular;
|
||||
white-space: pre;
|
||||
overflow-x: auto;
|
||||
margin: 0.05rem 0.1rem;
|
||||
|
@ -258,13 +258,10 @@ fn list_of_examples<'a>(examples: &'a Examples) -> Box<dyn Render + 'a> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Build an HTML code of the example from the documentation section. Engine already provides as
|
||||
/// with a preformatted HTML code, but we need to modify some tags in order to properly style it.
|
||||
fn example_from_doc_section(doc_section: &DocSection) -> String {
|
||||
fn example_from_doc_section(doc_section: &DocSection) -> &str {
|
||||
match doc_section {
|
||||
DocSection::Marked { mark: Mark::Example, body, .. } =>
|
||||
body.replace("<pre>", "<div class=\"example\">").replace("</pre>", "</div>"),
|
||||
_ => String::from("Invalid example"),
|
||||
DocSection::Marked { mark: Mark::Example, body, .. } => body,
|
||||
_ => "Invalid example",
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -185,7 +185,7 @@ impl Model {
|
||||
dom.dom().set_attribute_or_warn("class", "visualization scrollable");
|
||||
dom.dom().set_style_or_warn("overflow-x", "hidden");
|
||||
dom.dom().set_style_or_warn("overflow-y", "auto");
|
||||
dom.dom().set_style_or_warn("font-family", "DejaVuSansMonoBook");
|
||||
dom.dom().set_style_or_warn("font-family", "EnsoRegular");
|
||||
dom.dom().set_style_or_warn("font-size", "12px");
|
||||
dom.dom().set_style_or_warn("border-radius", "14px");
|
||||
dom.dom().set_style_or_warn("padding-left", &padding_text);
|
||||
|
@ -147,7 +147,7 @@ impl SpanWidget for Widget {
|
||||
|
||||
let ext = ctx.get_extension_or_default::<Extension>();
|
||||
let bold = ext.bold || is_placeholder;
|
||||
let text_weight = bold.then_some(text::Weight::Bold);
|
||||
let text_weight = bold.then_some(text::Weight::ExtraBold);
|
||||
|
||||
let input = &self.frp.public.input;
|
||||
input.content(content);
|
||||
|
@ -116,7 +116,6 @@ export function bundlerOptions(args: Arguments) {
|
||||
pathModule.resolve(THIS_PATH, 'src', 'index.html'),
|
||||
pathModule.resolve(THIS_PATH, 'src', 'run.js'),
|
||||
pathModule.resolve(THIS_PATH, 'src', 'style.css'),
|
||||
pathModule.resolve(THIS_PATH, 'src', 'docsStyle.css'),
|
||||
pathModule.resolve(THIS_PATH, 'src', 'serviceWorker.ts'),
|
||||
...wasmArtifacts.split(pathModule.delimiter),
|
||||
...fsSync
|
||||
|
@ -1,201 +0,0 @@
|
||||
@font-face {
|
||||
font-family: "Source Code Pro";
|
||||
font-style: normal;
|
||||
font-weight: 300;
|
||||
font-display: swap;
|
||||
src: url(https://fonts.gstatic.com/s/sourcecodepro/v14/HI_XiYsKILxRpg3hIP6sJ7fM7PqtlsnztA.ttf)
|
||||
format("truetype");
|
||||
}
|
||||
@font-face {
|
||||
font-family: "Source Code Pro";
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
font-display: swap;
|
||||
src: url(https://fonts.gstatic.com/s/sourcecodepro/v14/HI_SiYsKILxRpg3hIP6sJ7fM7PqVOg.ttf)
|
||||
format("truetype");
|
||||
}
|
||||
@font-face {
|
||||
font-family: "Source Code Pro";
|
||||
font-style: normal;
|
||||
font-weight: 500;
|
||||
font-display: swap;
|
||||
src: url(https://fonts.gstatic.com/s/sourcecodepro/v14/HI_XiYsKILxRpg3hIP6sJ7fM7PqtzsjztA.ttf)
|
||||
format("truetype");
|
||||
}
|
||||
@font-face {
|
||||
font-family: "Source Code Pro";
|
||||
font-style: normal;
|
||||
font-weight: 600;
|
||||
font-display: swap;
|
||||
src: url(https://fonts.gstatic.com/s/sourcecodepro/v14/HI_XiYsKILxRpg3hIP6sJ7fM7Pqt4s_ztA.ttf)
|
||||
format("truetype");
|
||||
}
|
||||
@font-face {
|
||||
font-family: "Source Code Pro";
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
font-display: swap;
|
||||
src: url(https://fonts.gstatic.com/s/sourcecodepro/v14/HI_XiYsKILxRpg3hIP6sJ7fM7Pqths7ztA.ttf)
|
||||
format("truetype");
|
||||
}
|
||||
.enso.docs {
|
||||
font-family: "M PLUS 1", DejaVuSansMonoBook, sans-serif;
|
||||
}
|
||||
.enso.docs .root {
|
||||
display: flex;
|
||||
margin-top: 4rem;
|
||||
}
|
||||
.enso.docs .main {
|
||||
max-width: 650px;
|
||||
min-width: 0;
|
||||
margin-top: -20px;
|
||||
}
|
||||
.enso.docs .doc-code-container {
|
||||
font-family: Source Code Pro, monospace;
|
||||
font-size: 14px;
|
||||
background-color: #e7ebef;
|
||||
border-radius: 12px;
|
||||
padding: 1px 5px 2px;
|
||||
box-decoration-break: clone;
|
||||
margin-top: 10px;
|
||||
-webkit-box-decoration-break: clone;
|
||||
}
|
||||
.enso.docs code {
|
||||
font-family: Source Code Pro, monospace;
|
||||
font-size: 14px;
|
||||
padding: 1px 0px 2px;
|
||||
margin-top: 10px;
|
||||
color: #4ea5fd;
|
||||
-webkit-box-decoration-break: clone;
|
||||
}
|
||||
.enso.docs .doc-copy-btn {
|
||||
margin-top: 1rem;
|
||||
background-color: #4876d4;
|
||||
border-radius: 12px;
|
||||
font-weight: 600;
|
||||
font-size: 15px;
|
||||
line-height: 2;
|
||||
padding: 0.25rem 1rem;
|
||||
color: #ffffff;
|
||||
vertical-align: text-top;
|
||||
}
|
||||
.enso.docs .tags {
|
||||
display: flex;
|
||||
font-size: 13px;
|
||||
}
|
||||
.enso.docs .tag {
|
||||
align-self: flex-end;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
.enso.docs ul {
|
||||
list-style-type: disc;
|
||||
margin-top: 0.75rem;
|
||||
margin-bottom: 0.75rem;
|
||||
padding-left: 1.25rem;
|
||||
}
|
||||
.enso.docs .toc-body {
|
||||
position: -webkit-sticky;
|
||||
position: sticky;
|
||||
top: 4rem;
|
||||
}
|
||||
.enso.docs .toc {
|
||||
display: none;
|
||||
width: 250px;
|
||||
}
|
||||
.light-theme .enso.docs {
|
||||
font-weight: 400;
|
||||
font-size: 16px;
|
||||
line-height: 1.4;
|
||||
color: #4b5864;
|
||||
}
|
||||
.light-theme .enso.docs .doc-title-container .doc-title-name {
|
||||
font-weight: 600;
|
||||
padding: 4px 10px 0 0;
|
||||
font-size: 20px;
|
||||
}
|
||||
.light-theme .enso.docs .content {
|
||||
font-weight: 500;
|
||||
font-size: 16px;
|
||||
line-height: 1.4;
|
||||
color: #4b5864;
|
||||
}
|
||||
.light-theme .enso.docs p {
|
||||
margin-top: 0.5rem;
|
||||
}
|
||||
.light-theme .enso.docs .summary {
|
||||
font-weight: 500;
|
||||
font-size: 16px;
|
||||
line-height: 1.4;
|
||||
color: #4b5864;
|
||||
}
|
||||
.light-theme .enso.docs h1 {
|
||||
display: flex;
|
||||
font-weight: 700;
|
||||
font-size: 40px;
|
||||
line-height: 1.25;
|
||||
color: #2a394a;
|
||||
}
|
||||
.light-theme .enso.docs h1 .tag {
|
||||
font-size: 16px;
|
||||
line-height: 1.4;
|
||||
}
|
||||
.light-theme .enso.docs h2 {
|
||||
font-weight: 700;
|
||||
font-size: 40px;
|
||||
line-height: 1.375;
|
||||
margin-top: 4rem;
|
||||
color: #2a394a;
|
||||
}
|
||||
.light-theme .enso.docs h3 {
|
||||
color: #2a394a;
|
||||
font-weight: 600;
|
||||
font-size: 22px;
|
||||
line-height: 1.25;
|
||||
}
|
||||
.light-theme .enso.docs .tag .added,
|
||||
.light-theme .enso.docs .tag .advanced,
|
||||
.light-theme .enso.docs .tag .deprecated,
|
||||
.light-theme .enso.docs .tag .modified,
|
||||
.light-theme .enso.docs .tag .private,
|
||||
.light-theme .enso.docs .tag .textonly,
|
||||
.light-theme .enso.docs .tag .unstable,
|
||||
.light-theme .enso.docs .tag .upcoming,
|
||||
.light-theme .enso.docs .tag .alias,
|
||||
.light-theme .enso.docs .tag .version {
|
||||
margin-left: 0;
|
||||
display: inline-table;
|
||||
border-radius: 16px;
|
||||
padding: 4px 10px;
|
||||
line-height: 1.35;
|
||||
box-decoration-break: clone;
|
||||
-webkit-box-decoration-break: clone;
|
||||
background-color: #e7e9eb;
|
||||
}
|
||||
.light-theme .enso.docs .tag .details {
|
||||
font-weight: 700;
|
||||
}
|
||||
.light-theme .enso.docs .important,
|
||||
.light-theme .enso.docs .info,
|
||||
.light-theme .enso.docs .example {
|
||||
background-color: #e7e9eb;
|
||||
border-radius: 12px;
|
||||
padding: 15px;
|
||||
}
|
||||
.light-theme .enso.docs .important .summary,
|
||||
.light-theme .enso.docs .info .summary,
|
||||
.light-theme .enso.docs .example .summary {
|
||||
font-weight: 500;
|
||||
font-size: 20px;
|
||||
line-height: 1.4;
|
||||
margin-bottom: 0.5rem;
|
||||
color: #4b5864;
|
||||
}
|
||||
@media (min-width: 768px) {
|
||||
.light-theme .enso.docs h1 {
|
||||
font-size: 48px;
|
||||
line-height: 1.25;
|
||||
}
|
||||
.enso.docs .toc {
|
||||
display: block;
|
||||
}
|
||||
}
|
@ -1,176 +0,0 @@
|
||||
@import "https://fonts.googleapis.com/css2?family=Source+Code+Pro:wght@300;400;500;600;700&display=swap";
|
||||
|
||||
.enso.docs {
|
||||
font-family: "M PLUS 1", DejaVuSansMonoBook, sans-serif;
|
||||
.root {
|
||||
display: flex;
|
||||
margin-top: 4rem;
|
||||
}
|
||||
.main {
|
||||
max-width: 650px;
|
||||
min-width: 0;
|
||||
margin-top: -20px;
|
||||
}
|
||||
.doc-code-container {
|
||||
font-family: Source Code Pro, monospace;
|
||||
font-size: 14px;
|
||||
background-color: #e7ebef;
|
||||
border-radius: 12px;
|
||||
padding: 1px 5px 2px;
|
||||
box-decoration-break: clone;
|
||||
margin-top: 10px;
|
||||
-webkit-box-decoration-break: clone;
|
||||
}
|
||||
code {
|
||||
font-family: Source Code Pro, monospace;
|
||||
font-size: 14px;
|
||||
padding: 1px 0px 2px;
|
||||
margin-top: 10px;
|
||||
color: #4ea5fd;
|
||||
-webkit-box-decoration-break: clone;
|
||||
}
|
||||
.doc-copy-btn {
|
||||
margin-top: 1rem;
|
||||
background-color: rgb(72 118 212);
|
||||
border-radius: 12px;
|
||||
font-weight: 600;
|
||||
font-size: 15px;
|
||||
line-height: 2;
|
||||
padding: 0.25rem 1rem;
|
||||
color: rgba(255, 255, 255, 1);
|
||||
vertical-align: text-top;
|
||||
}
|
||||
.tags {
|
||||
display: flex;
|
||||
font-size: 13px;
|
||||
}
|
||||
.tag {
|
||||
align-self: flex-end;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
ul {
|
||||
list-style-type: disc;
|
||||
margin-top: 0.75rem;
|
||||
margin-bottom: 0.75rem;
|
||||
padding-left: 1.25rem;
|
||||
}
|
||||
.toc-body {
|
||||
position: -webkit-sticky;
|
||||
position: sticky;
|
||||
top: 4rem;
|
||||
}
|
||||
.toc {
|
||||
display: none;
|
||||
width: 250px;
|
||||
}
|
||||
}
|
||||
|
||||
.light-theme {
|
||||
.enso.docs {
|
||||
font-weight: 400;
|
||||
font-size: 16px;
|
||||
line-height: 1.4;
|
||||
color: rgba(75, 88, 100, 1);
|
||||
.doc-title-container {
|
||||
display: flex;
|
||||
.doc-title-name {
|
||||
font-weight: 600;
|
||||
padding: 4px 10px 0 0;
|
||||
font-size: 20px;
|
||||
}
|
||||
}
|
||||
.content {
|
||||
font-weight: 500;
|
||||
font-size: 16px;
|
||||
line-height: 1.4;
|
||||
color: rgba(75, 88, 100, 1);
|
||||
}
|
||||
p {
|
||||
margin-top: 0.5rem;
|
||||
}
|
||||
.summary {
|
||||
font-weight: 500;
|
||||
font-size: 16px;
|
||||
line-height: 1.4;
|
||||
color: rgba(75, 88, 100, 1);
|
||||
}
|
||||
h1 {
|
||||
display: flex;
|
||||
font-weight: 700;
|
||||
font-size: 40px;
|
||||
line-height: 1.25;
|
||||
color: rgb(42 57 74);
|
||||
.tag {
|
||||
font-size: 16px;
|
||||
line-height: 1.4;
|
||||
}
|
||||
}
|
||||
h2 {
|
||||
font-weight: 700;
|
||||
font-size: 40px;
|
||||
line-height: 1.375;
|
||||
margin-top: 4rem;
|
||||
color: rgb(42 57 74);
|
||||
}
|
||||
h3 {
|
||||
color: rgb(42 57 74);
|
||||
font-weight: 600;
|
||||
font-size: 22px;
|
||||
line-height: 1.25;
|
||||
}
|
||||
.tag {
|
||||
.added,
|
||||
.advanced,
|
||||
.deprecated,
|
||||
.modified,
|
||||
.private,
|
||||
.textonly,
|
||||
.unstable,
|
||||
.upcoming,
|
||||
.alias,
|
||||
.version {
|
||||
margin-left: 0;
|
||||
display: inline-table;
|
||||
border-radius: 16px;
|
||||
padding: 4px 10px;
|
||||
line-height: 1.35;
|
||||
box-decoration-break: clone;
|
||||
-webkit-box-decoration-break: clone;
|
||||
background-color: #e7e9eb;
|
||||
}
|
||||
.details {
|
||||
font-weight: 700;
|
||||
}
|
||||
}
|
||||
.important,
|
||||
.info,
|
||||
.example {
|
||||
background-color: #e7e9eb;
|
||||
border-radius: 12px;
|
||||
padding: 15px;
|
||||
.summary {
|
||||
font-weight: 500;
|
||||
font-size: 20px;
|
||||
line-height: 1.4;
|
||||
margin-bottom: 0.5rem;
|
||||
color: #4b5864;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 768px) {
|
||||
.light-theme {
|
||||
.enso.docs {
|
||||
h1 {
|
||||
font-size: 48px;
|
||||
line-height: 1.25;
|
||||
}
|
||||
}
|
||||
}
|
||||
.enso.docs {
|
||||
.toc {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
}
|
@ -35,7 +35,8 @@
|
||||
<title>Enso</title>
|
||||
<link rel="stylesheet" href="./tailwind.css" />
|
||||
<link rel="stylesheet" href="./style.css" />
|
||||
<link rel="stylesheet" href="./docsStyle.css" />
|
||||
<!-- Generated by the build script based on the Enso Font package. -->
|
||||
<link rel="stylesheet" href="./ensoFont.css" />
|
||||
<script type="module" src="./index.js" defer></script>
|
||||
<script type="module" src="./run.js" defer></script>
|
||||
<script
|
||||
|
@ -48,12 +48,6 @@ export const DEPENDENCIES = [
|
||||
'https://cdn.jsdelivr.net/npm/ag-grid-community/dist/ag-grid-community.min.js',
|
||||
'https://cdn.jsdelivr.net/npm/ag-grid-community/styles/ag-grid.css',
|
||||
'https://cdn.jsdelivr.net/npm/ag-grid-community/styles/ag-theme-alpine.css',
|
||||
// app/ide-desktop/lib/content/src/docsStyle.css
|
||||
'https://fonts.gstatic.com/s/sourcecodepro/v14/HI_XiYsKILxRpg3hIP6sJ7fM7PqtlsnztA.ttf',
|
||||
'https://fonts.gstatic.com/s/sourcecodepro/v14/HI_SiYsKILxRpg3hIP6sJ7fM7PqVOg.ttf',
|
||||
'https://fonts.gstatic.com/s/sourcecodepro/v14/HI_XiYsKILxRpg3hIP6sJ7fM7PqtzsjztA.ttf',
|
||||
'https://fonts.gstatic.com/s/sourcecodepro/v14/HI_XiYsKILxRpg3hIP6sJ7fM7Pqt4s_ztA.ttf',
|
||||
'https://fonts.gstatic.com/s/sourcecodepro/v14/HI_XiYsKILxRpg3hIP6sJ7fM7Pqths7ztA.ttf',
|
||||
// app/ide-desktop/lib/dashboard/src/tailwind.css
|
||||
'https://fonts.googleapis.com/css2?family=M+PLUS+1:wght@500;700&display=swap',
|
||||
// Loaded by https://fonts.googleapis.com/css2?family=M+PLUS+1:wght@500;700&display=swap
|
||||
|
@ -33,6 +33,8 @@ handlebars = "4.3.5"
|
||||
heck = "0.4.0"
|
||||
humantime = "2.1.0"
|
||||
enso-build-base = { path = "../base" }
|
||||
enso-enso-font = { path = "../../lib/rust/enso-font" }
|
||||
enso-font = { path = "../../lib/rust/font" }
|
||||
ensogl-pack = { path = "../../lib/rust/ensogl/pack" }
|
||||
ide-ci = { path = "../ci_utils" }
|
||||
indexmap = "1.7.0"
|
||||
|
@ -26,6 +26,7 @@ use tracing::Span;
|
||||
// === Export ===
|
||||
// ==============
|
||||
|
||||
pub mod fonts;
|
||||
pub mod google_font;
|
||||
|
||||
|
||||
@ -169,8 +170,7 @@ impl<Output: AsRef<Path>> ContentEnvironment<TempDir, Output> {
|
||||
let installation = ide.install();
|
||||
let asset_dir = TempDir::new()?;
|
||||
let assets_download = download_js_assets(&asset_dir);
|
||||
let fonts_download =
|
||||
google_font::download_google_font(&ide.cache, &ide.octocrab, "mplus1", &asset_dir);
|
||||
let fonts_download = fonts::install_html_fonts(&ide.cache, &ide.octocrab, &asset_dir);
|
||||
let (wasm, _, _, _) =
|
||||
try_join4(wasm, installation, assets_download, fonts_download).await?;
|
||||
wasm.symlink_ensogl_dist(&ide.linked_dist)?;
|
||||
|
104
build/build/src/ide/web/fonts.rs
Normal file
104
build/build/src/ide/web/fonts.rs
Normal file
@ -0,0 +1,104 @@
|
||||
use crate::prelude::*;
|
||||
|
||||
use crate::ide::web::google_font;
|
||||
|
||||
use enso_enso_font::ttf;
|
||||
use enso_font::NonVariableFaceHeader;
|
||||
use ide_ci::cache::Cache;
|
||||
|
||||
|
||||
|
||||
// =========================
|
||||
// === HTML Font Support ===
|
||||
// =========================
|
||||
|
||||
pub async fn install_html_fonts(
|
||||
cache: &Cache,
|
||||
octocrab: &Octocrab,
|
||||
output_path: impl AsRef<Path>,
|
||||
) -> Result {
|
||||
let output_path = output_path.as_ref();
|
||||
google_font::download_google_font(cache, octocrab, "mplus1", output_path).await?;
|
||||
install_enso_font_for_html(cache, octocrab, output_path).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn install_enso_font_for_html(
|
||||
cache: &Cache,
|
||||
octocrab: &Octocrab,
|
||||
output_path: impl AsRef<Path>,
|
||||
) -> Result {
|
||||
let output_path = output_path.as_ref();
|
||||
let html_fonts: HashMap<_, _> = [
|
||||
(NonVariableFaceHeader { weight: ttf::Weight::Normal, ..default() }, "Regular"),
|
||||
(NonVariableFaceHeader { weight: ttf::Weight::ExtraBold, ..default() }, "Bold"),
|
||||
]
|
||||
.into_iter()
|
||||
.collect();
|
||||
let html_font_definitions = enso_enso_font::enso_font()
|
||||
.variations()
|
||||
.filter(|v| html_fonts.contains_key(&v.header))
|
||||
.collect();
|
||||
let get_font_files = async {
|
||||
let package = get_enso_font_package_(cache, octocrab).await?;
|
||||
enso_enso_font::extract_fonts(&html_font_definitions, package, output_path).await
|
||||
};
|
||||
let make_css_file = async {
|
||||
let mut css = String::new();
|
||||
let family = "Enso";
|
||||
let url = ".";
|
||||
for (header, variant) in html_fonts {
|
||||
use std::fmt::Write;
|
||||
let def = html_font_definitions.get(header);
|
||||
let def = def.ok_or_else(|| {
|
||||
anyhow!(
|
||||
"Required font not found in Enso Font package. \
|
||||
Expected a font matching: {header:?}."
|
||||
)
|
||||
})?;
|
||||
let file = &def.file;
|
||||
writeln!(&mut css, "@font-face {{")?;
|
||||
writeln!(&mut css, " font-family: '{family}{variant}';")?;
|
||||
writeln!(&mut css, " src: url('{url}/{file}');")?;
|
||||
writeln!(&mut css, " font-weight: normal;")?;
|
||||
writeln!(&mut css, " font-style: normal;")?;
|
||||
writeln!(&mut css, "}}")?;
|
||||
}
|
||||
let css_path = output_path.join("ensoFont.css");
|
||||
ide_ci::fs::tokio::write(css_path, css).await?;
|
||||
Ok(())
|
||||
};
|
||||
try_join!(get_font_files, make_css_file)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
|
||||
// =================
|
||||
// === Enso Font ===
|
||||
// =================
|
||||
|
||||
/// Download the Enso Font package, with caching and GitHub authentication.
|
||||
pub async fn get_enso_font_package() -> Result<Box<Path>> {
|
||||
let cache = Cache::new_default().await?;
|
||||
let octocrab = ide_ci::github::setup_octocrab().await?;
|
||||
get_enso_font_package_(&cache, &octocrab).await
|
||||
}
|
||||
|
||||
async fn get_enso_font_package_(cache: &Cache, octocrab: &Octocrab) -> Result<Box<Path>> {
|
||||
Ok(cache
|
||||
.get(ide_ci::cache::download::DownloadFile {
|
||||
client: octocrab.client.clone(),
|
||||
key: ide_ci::cache::download::Key {
|
||||
url: enso_enso_font::PACKAGE_URL.parse().unwrap(),
|
||||
additional_headers: reqwest::header::HeaderMap::from_iter([(
|
||||
reqwest::header::ACCEPT,
|
||||
reqwest::header::HeaderValue::from_static(
|
||||
mime::APPLICATION_OCTET_STREAM.as_ref(),
|
||||
),
|
||||
)]),
|
||||
},
|
||||
})
|
||||
.await?
|
||||
.into_boxed_path())
|
||||
}
|
@ -71,6 +71,7 @@ symlink = "0.1.0"
|
||||
syn = { workspace = true }
|
||||
sysinfo = "0.26.2"
|
||||
tar = "0.4.37"
|
||||
tokio-tar = "0.3.1"
|
||||
tempfile = "3.2.0"
|
||||
tokio = { workspace = true }
|
||||
tokio-stream = { workspace = true }
|
||||
|
@ -149,22 +149,22 @@ pub async fn extract_item(
|
||||
let item_path = item_path.as_ref().to_path_buf();
|
||||
let output_path = output_path.as_ref().to_path_buf();
|
||||
|
||||
let extract_task = match format {
|
||||
match format {
|
||||
Format::Zip => {
|
||||
let mut archive = zip::open(&archive_path)?;
|
||||
tokio::task::spawn_blocking(move || {
|
||||
zip::extract_subtree(&mut archive, item_path, output_path)
|
||||
})
|
||||
.instrument(Span::current())
|
||||
.await??;
|
||||
}
|
||||
Format::Tar(Some(Compression::Gzip)) => {
|
||||
let mut archive = tar::open_tar_gz(&archive_path)?;
|
||||
tokio::task::spawn_blocking(move || {
|
||||
tar::extract_subtree(&mut archive, item_path, output_path)
|
||||
})
|
||||
let archive = tar::Archive::open_tar_gz(&archive_path).await?;
|
||||
archive.extract_subtree(item_path, output_path).await?;
|
||||
}
|
||||
_ => todo!(),
|
||||
};
|
||||
extract_task.instrument(Span::current()).await??;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
@ -1,30 +1,74 @@
|
||||
use crate::prelude::*;
|
||||
|
||||
use flate2::read::GzDecoder;
|
||||
use std::fs::File;
|
||||
use tar::Archive;
|
||||
use async_compression::tokio::bufread::GzipDecoder;
|
||||
use tokio::io::AsyncRead;
|
||||
use tokio::io::BufReader;
|
||||
use tokio_tar::Archive as Tar;
|
||||
|
||||
|
||||
|
||||
pub fn open_tar_gz(path: impl AsRef<Path>) -> Result<Archive<GzDecoder<File>>> {
|
||||
let file = crate::fs::open(&path)?;
|
||||
let tar_stream = flate2::read::GzDecoder::new(file);
|
||||
Ok(tar::Archive::new(tar_stream))
|
||||
// ===============
|
||||
// === Archive ===
|
||||
// ===============
|
||||
|
||||
/// A `tar` archive.
|
||||
#[derive(Derivative)]
|
||||
#[derivative(Debug)]
|
||||
pub struct Archive {
|
||||
/// The path that the `file` originated from. This is stored for error reporting.
|
||||
path: Box<Path>,
|
||||
#[derivative(Debug = "ignore")]
|
||||
file: Tar<Box<dyn AsyncRead + Unpin + Send>>,
|
||||
}
|
||||
|
||||
pub fn extract_subtree<R: Read>(
|
||||
archive: &mut Archive<R>,
|
||||
prefix: impl AsRef<Path>,
|
||||
output: impl AsRef<Path>,
|
||||
) -> Result {
|
||||
for entry in archive.entries()? {
|
||||
let mut entry = entry?;
|
||||
let path_in_archive = entry.path()?;
|
||||
if let Ok(relative_path) = path_in_archive.strip_prefix(&prefix) {
|
||||
let output = output.as_ref().join(relative_path);
|
||||
trace!("Extracting {}", output.display());
|
||||
entry.unpack(output)?;
|
||||
}
|
||||
impl Archive {
|
||||
/// Open a gzip-compressed tar archive.
|
||||
pub async fn open_tar_gz(path: impl AsRef<Path>) -> Result<Self> {
|
||||
let file = crate::fs::tokio::open(&path).await?;
|
||||
let file = BufReader::new(file);
|
||||
let file = GzipDecoder::new(file);
|
||||
let file: Box<dyn AsyncRead + Unpin + Send> = Box::new(file);
|
||||
let file = Tar::new(file);
|
||||
let path = path.as_ref().to_owned().into_boxed_path();
|
||||
Ok(Self { path, file })
|
||||
}
|
||||
|
||||
/// The given function will be called with the path of each file within the archive. For each
|
||||
/// input path, if it returns a path the file will be extracted to the returned path.
|
||||
///
|
||||
/// IMPORTANT: If the function uses its input path to generate an output path, care must be
|
||||
/// taken that the output path is not in an unexpected location, especially if coming from an
|
||||
/// untrusted archive.
|
||||
#[context("Failed to extract files from archive: {}", self.path.display())]
|
||||
pub async fn extract_files(
|
||||
mut self,
|
||||
mut filter: impl FnMut(&Path) -> Option<PathBuf>,
|
||||
) -> Result {
|
||||
let mut entries = self.file.entries()?;
|
||||
while let Some(entry) = entries.next().await {
|
||||
let mut entry = entry?;
|
||||
let path_in_archive = entry.path()?;
|
||||
if let Some(output_path) = filter(&path_in_archive) {
|
||||
trace!("Extracting {}", output_path.display());
|
||||
entry.unpack(&output_path).await?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Extract all files from the specified subtree in the archive, placing them in the specified
|
||||
/// output directory.
|
||||
pub async fn extract_subtree(
|
||||
self,
|
||||
prefix: impl AsRef<Path>,
|
||||
output: impl AsRef<Path>,
|
||||
) -> Result {
|
||||
self.extract_files(|path_in_archive| {
|
||||
path_in_archive
|
||||
.strip_prefix(&prefix)
|
||||
.ok()
|
||||
.map(|relative_path| output.as_ref().join(relative_path))
|
||||
})
|
||||
.await
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
10
lib/rust/enso-font/Cargo.toml
Normal file
10
lib/rust/enso-font/Cargo.toml
Normal file
@ -0,0 +1,10 @@
|
||||
[package]
|
||||
name = "enso-enso-font"
|
||||
version = "0.1.0"
|
||||
authors = ["Enso Team <contact@enso.org>"]
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
enso-font = { path = "../font" }
|
||||
ide-ci = { path = "../../../build/ci_utils" }
|
||||
owned_ttf_parser = { workspace = true }
|
106
lib/rust/enso-font/src/lib.rs
Normal file
106
lib/rust/enso-font/src/lib.rs
Normal file
@ -0,0 +1,106 @@
|
||||
//! The Enso Font. This crate supports downloading and unpacking the font family, as well as
|
||||
//! constructing a reduced font family from a subset of the fonts.
|
||||
|
||||
// === Features ===
|
||||
#![feature(let_chains)]
|
||||
// === Standard Linter Configuration ===
|
||||
#![deny(non_ascii_idents)]
|
||||
#![warn(unsafe_code)]
|
||||
#![allow(clippy::bool_to_int_with_if)]
|
||||
#![allow(clippy::let_and_return)]
|
||||
// === Non-Standard Linter Configuration ===
|
||||
#![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)]
|
||||
|
||||
use ide_ci::prelude::*;
|
||||
|
||||
use enso_font::NonVariableDefinition;
|
||||
use enso_font::NonVariableFaceHeader;
|
||||
|
||||
|
||||
// ==============
|
||||
// === Export ===
|
||||
// ==============
|
||||
|
||||
pub use owned_ttf_parser as ttf;
|
||||
|
||||
|
||||
|
||||
// =================
|
||||
// === Constants ===
|
||||
// =================
|
||||
|
||||
/// The name of the Enso font family.
|
||||
pub const ENSO_FONT_FAMILY_NAME: &str = "enso";
|
||||
|
||||
const ENSO_FONT_FAMILY_FONTS: &[(&str, ttf::Weight)] = &[
|
||||
("Black", ttf::Weight::Black),
|
||||
("Bold", ttf::Weight::Bold),
|
||||
("ExtraBold", ttf::Weight::ExtraBold),
|
||||
("ExtraLight", ttf::Weight::ExtraLight),
|
||||
("Light", ttf::Weight::Light),
|
||||
("Medium", ttf::Weight::Medium),
|
||||
("Regular", ttf::Weight::Normal),
|
||||
("SemiBold", ttf::Weight::SemiBold),
|
||||
("Thin", ttf::Weight::Thin),
|
||||
];
|
||||
|
||||
/// The URL for the Enso Font package.
|
||||
pub const PACKAGE_URL: &str =
|
||||
"https://github.com/enso-org/font/releases/download/1.0/enso-font-1.0.tar.gz";
|
||||
const PACKAGE_FONTS_PREFIX: &str = "ttf";
|
||||
|
||||
/// Font features.
|
||||
pub mod feature {
|
||||
/// The flag identifying the ligature feature in this font.
|
||||
pub const LIGATURES: &str = "liga";
|
||||
}
|
||||
|
||||
|
||||
|
||||
// =================
|
||||
// === Enso Font ===
|
||||
// =================
|
||||
|
||||
/// Returns the Enso Font.
|
||||
pub fn enso_font() -> NonVariableDefinition {
|
||||
ENSO_FONT_FAMILY_FONTS
|
||||
.iter()
|
||||
.map(|(name, weight)| {
|
||||
let file = format!("Enso-{name}.ttf");
|
||||
let header = NonVariableFaceHeader {
|
||||
weight: *weight,
|
||||
width: ttf::Width::Normal,
|
||||
style: ttf::Style::Normal,
|
||||
};
|
||||
(header, file)
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// Extract the fonts from the given archive file, and write them in the given directory.
|
||||
#[context("Failed to extract fonts from archive: {}", package.as_ref().display())]
|
||||
pub async fn extract_fonts(
|
||||
fonts: &NonVariableDefinition,
|
||||
package: impl AsRef<Path>,
|
||||
out_dir: impl AsRef<Path>,
|
||||
) -> Result {
|
||||
let mut files_expected: HashSet<_> = fonts.files().collect();
|
||||
ide_ci::archive::tar::Archive::open_tar_gz(&package)
|
||||
.await?
|
||||
.extract_files(|path_in_archive| {
|
||||
path_in_archive
|
||||
.strip_prefix(PACKAGE_FONTS_PREFIX)
|
||||
.ok()
|
||||
.filter(|path| path.to_str().map_or(false, |path| files_expected.remove(path)))
|
||||
.map(|path| out_dir.as_ref().join(path))
|
||||
})
|
||||
.await?;
|
||||
ensure!(files_expected.is_empty(), "Required fonts not found in archive: {files_expected:?}.");
|
||||
Ok(())
|
||||
}
|
@ -19,8 +19,8 @@ use enso_prelude::*;
|
||||
use ensogl_core::prelude::*;
|
||||
|
||||
use enso_shapely::before_main;
|
||||
use ensogl_text::font::DEFAULT_CODE_FONT;
|
||||
use ensogl_text::font::DEFAULT_FONT;
|
||||
use ensogl_text::font::DEFAULT_FONT_MONO;
|
||||
|
||||
|
||||
|
||||
@ -666,7 +666,7 @@ define_themes! { [light:0, dark:1]
|
||||
text = Lcha(0.0,0.0,0.0,0.7), Lcha(1.0,0.0,0.0,0.7);
|
||||
text {
|
||||
selection = Lcha(0.7,0.0,0.125,0.7);
|
||||
font = DEFAULT_FONT_MONO;
|
||||
font = DEFAULT_CODE_FONT;
|
||||
size = 12.0;
|
||||
highlight_bold = 0.02;
|
||||
}
|
||||
|
@ -50,7 +50,7 @@ const PRELOAD_VARIATIONS: &[font::NonVariableFaceHeader] = &[
|
||||
];
|
||||
|
||||
/// The typefaces for which atlases should be pre-built.
|
||||
const PRELOAD_TYPEFACES: &[&str] = &[font::DEFAULT_FONT_MONO, font::DEFAULT_FONT];
|
||||
const PRELOAD_TYPEFACES: &[&str] = &[font::DEFAULT_CODE_FONT, font::DEFAULT_FONT];
|
||||
|
||||
/// Path within the asset directory to store the glyph atlas image.
|
||||
const ATLAS_FILE: &str = "atlas.ppm";
|
||||
@ -132,7 +132,7 @@ impl TryFrom<JsValue> for Atlas {
|
||||
|
||||
/// Generate MSDF data for a font.
|
||||
fn build_atlas(name: &str) -> anyhow::Result<Atlas> {
|
||||
let fonts = font::Embedded::new();
|
||||
let fonts = font::Embedded::default();
|
||||
let font = fonts.load_font(name.into()).ok_or_else(|| anyhow!("Failed to load font."))?;
|
||||
let font = match font {
|
||||
Font::NonVariable(font) => font,
|
||||
|
@ -9,6 +9,7 @@ crate-type = ["rlib", "cdylib"]
|
||||
|
||||
[dependencies]
|
||||
enso-bitmap = { path = "../../../bitmap" }
|
||||
enso-font = { path = "../../../font" }
|
||||
enso-frp = { path = "../../../frp" }
|
||||
enso-prelude = { path = "../../../prelude" }
|
||||
enso-shapely = { path = "../../../shapely" }
|
||||
@ -24,7 +25,6 @@ bincode = { workspace = true }
|
||||
serde = { version = "1", features = ["rc"] }
|
||||
serde_json = { workspace = true }
|
||||
ordered-float = { workspace = true }
|
||||
ensogl-text-font-family = { path = "src/font/family" }
|
||||
rustybuzz = { workspace = true }
|
||||
|
||||
[dev-dependencies]
|
||||
|
@ -7,12 +7,11 @@ use enso_text::unit::*;
|
||||
|
||||
use crate::buffer::formatting::Formatting;
|
||||
use crate::buffer::rope::formatted::FormattedRope;
|
||||
use crate::buffer::selection::Selection;
|
||||
|
||||
use enso_font::NonVariableFaceHeader;
|
||||
use enso_frp as frp;
|
||||
use enso_text::text;
|
||||
use enso_text::text::BoundsError;
|
||||
use ensogl_text_font_family::NonVariableFaceHeader;
|
||||
|
||||
|
||||
// ==============
|
||||
@ -34,6 +33,7 @@ pub mod traits {
|
||||
|
||||
pub use formatting::*;
|
||||
pub use movement::*;
|
||||
pub use selection::Selection;
|
||||
|
||||
pub use enso_text::index::*;
|
||||
pub use enso_text::unit::*;
|
||||
@ -124,7 +124,7 @@ impl<Metric: Default, Str: Default> Default for Change<Metric, Str> {
|
||||
// === FRP ===
|
||||
// ===========
|
||||
|
||||
ensogl_core::define_endpoints! {
|
||||
ensogl_core::define_endpoints_2! {
|
||||
Input {
|
||||
cursors_move (Transform),
|
||||
cursors_select (Transform),
|
||||
@ -189,7 +189,7 @@ impl Buffer {
|
||||
let frp = Frp::new();
|
||||
let network = &frp.network;
|
||||
let input = &frp.input;
|
||||
let output = &frp.output;
|
||||
let output = &frp.private.output;
|
||||
let model = model.into();
|
||||
let m = &model;
|
||||
|
||||
@ -200,11 +200,11 @@ impl Buffer {
|
||||
mod_on_delete_right <- input.delete_right.map(f_!(m.delete_right()));
|
||||
mod_on_delete_word_left <- input.delete_word_left.map(f_!(m.delete_word_left()));
|
||||
mod_on_delete_word_right <- input.delete_word_right.map(f_!(m.delete_word_right()));
|
||||
mod_on_delete <- any(mod_on_delete_left,mod_on_delete_right, mod_on_delete_word_left,
|
||||
mod_on_delete <- any(mod_on_delete_left, mod_on_delete_right, mod_on_delete_word_left,
|
||||
mod_on_delete_word_right);
|
||||
any_mod <- any(mod_on_insert, mod_on_paste, mod_on_delete);
|
||||
changed <- any_mod.map(|m| !m.changes.is_empty());
|
||||
output.source.text_change <+ any_mod.gate(&changed).map(|m| Rc::new(m.changes.clone()));
|
||||
output.text_change <+ any_mod.gate(&changed).map(|m| Rc::new(m.changes.clone()));
|
||||
|
||||
sel_on_move <- input.cursors_move.map(f!((t) m.moved_selection(*t,false)));
|
||||
sel_on_mod <- input.cursors_select.map(f!((t) m.moved_selection(*t,true)));
|
||||
@ -236,37 +236,37 @@ impl Buffer {
|
||||
eval input.mod_property (((range,value)) m.mod_property(range,*value));
|
||||
eval input.set_property_default ((prop) m.set_property_default(*prop));
|
||||
|
||||
output.source.selection_edit_mode <+ any_mod;
|
||||
output.source.selection_non_edit_mode <+ sel_on_undo;
|
||||
output.source.selection_non_edit_mode <+ sel_on_move;
|
||||
output.source.selection_non_edit_mode <+ sel_on_mod;
|
||||
output.source.selection_non_edit_mode <+ sel_on_clear;
|
||||
output.source.selection_non_edit_mode <+ sel_on_keep_last;
|
||||
output.source.selection_non_edit_mode <+ sel_on_keep_first;
|
||||
output.source.selection_non_edit_mode <+ sel_on_keep_newest;
|
||||
output.source.selection_non_edit_mode <+ sel_on_keep_oldest;
|
||||
output.source.selection_non_edit_mode <+ sel_on_keep_lst_cursor;
|
||||
output.source.selection_non_edit_mode <+ sel_on_keep_fst_cursor;
|
||||
output.source.selection_non_edit_mode <+ sel_on_keep_newest_cursor;
|
||||
output.source.selection_non_edit_mode <+ sel_on_keep_oldest_cursor;
|
||||
output.source.selection_non_edit_mode <+ sel_on_set_cursor;
|
||||
output.source.selection_non_edit_mode <+ sel_on_add_cursor;
|
||||
output.source.selection_non_edit_mode <+ sel_on_set_single_selection;
|
||||
output.source.selection_non_edit_mode <+ sel_on_set_newest_end;
|
||||
output.source.selection_non_edit_mode <+ sel_on_set_oldest_end;
|
||||
output.source.selection_non_edit_mode <+ sel_on_remove_all;
|
||||
output.selection_edit_mode <+ any_mod;
|
||||
output.selection_non_edit_mode <+ sel_on_undo;
|
||||
output.selection_non_edit_mode <+ sel_on_move;
|
||||
output.selection_non_edit_mode <+ sel_on_mod;
|
||||
output.selection_non_edit_mode <+ sel_on_clear;
|
||||
output.selection_non_edit_mode <+ sel_on_keep_last;
|
||||
output.selection_non_edit_mode <+ sel_on_keep_first;
|
||||
output.selection_non_edit_mode <+ sel_on_keep_newest;
|
||||
output.selection_non_edit_mode <+ sel_on_keep_oldest;
|
||||
output.selection_non_edit_mode <+ sel_on_keep_lst_cursor;
|
||||
output.selection_non_edit_mode <+ sel_on_keep_fst_cursor;
|
||||
output.selection_non_edit_mode <+ sel_on_keep_newest_cursor;
|
||||
output.selection_non_edit_mode <+ sel_on_keep_oldest_cursor;
|
||||
output.selection_non_edit_mode <+ sel_on_set_cursor;
|
||||
output.selection_non_edit_mode <+ sel_on_add_cursor;
|
||||
output.selection_non_edit_mode <+ sel_on_set_single_selection;
|
||||
output.selection_non_edit_mode <+ sel_on_set_newest_end;
|
||||
output.selection_non_edit_mode <+ sel_on_set_oldest_end;
|
||||
output.selection_non_edit_mode <+ sel_on_remove_all;
|
||||
|
||||
eval output.source.selection_edit_mode ((t) m.set_selection(&t.selection_group));
|
||||
eval output.source.selection_non_edit_mode ((t) m.set_selection(t));
|
||||
eval output.selection_edit_mode ((t) m.set_selection(&t.selection_group));
|
||||
eval output.selection_non_edit_mode ((t) m.set_selection(t));
|
||||
|
||||
// === Buffer Area Management ===
|
||||
|
||||
eval input.set_first_view_line ((line) m.set_first_view_line(*line));
|
||||
output.source.first_view_line <+ input.set_first_view_line;
|
||||
output.first_view_line <+ input.set_first_view_line;
|
||||
|
||||
new_first_view_line <- input.mod_first_view_line.map
|
||||
(f!((diff) m.mod_first_view_line(*diff)));
|
||||
output.source.first_view_line <+ new_first_view_line;
|
||||
output.first_view_line <+ new_first_view_line;
|
||||
}
|
||||
Self { model, frp }
|
||||
}
|
||||
|
@ -243,7 +243,7 @@ pub struct View {
|
||||
pub glyphs: VecIndexedBy<Glyph, Column>,
|
||||
/// Division points between glyphs. There is always the beginning division point (0.0). If
|
||||
/// there are any glyphs, this also contains the last division point, which is the glyph
|
||||
/// right hand side + `x_advance`, where `a_advance` is the space to the next glyph place.
|
||||
/// right hand side + `x_advance`, where `x_advance` is the space to the next glyph place.
|
||||
pub divs: NonEmptyVec<f32>,
|
||||
/// Centers between division points. Used for glyph selection with mouse cursor.
|
||||
pub centers: Vec<f32>,
|
||||
@ -344,9 +344,7 @@ impl View {
|
||||
/// Set the division points (offsets between letters). Also updates center points.
|
||||
#[cfg_attr(not(target_arch = "wasm32"), allow(dead_code))]
|
||||
pub fn set_divs(&mut self, divs: NonEmptyVec<f32>) {
|
||||
let div_iter = divs.iter();
|
||||
let div_iter_skipped = divs.iter().skip(1);
|
||||
self.centers = div_iter.zip(div_iter_skipped).map(|(t, s)| (t + s) / 2.0).collect();
|
||||
self.centers = divs.as_slice().array_windows().map(|[t, s]| (t + s) / 2.0).collect();
|
||||
self.divs = divs;
|
||||
}
|
||||
|
||||
|
@ -23,6 +23,7 @@ use crate::font::Font;
|
||||
use crate::font::GlyphId;
|
||||
use crate::font::GlyphRenderInfo;
|
||||
|
||||
use enso_font::NonVariableFaceHeader;
|
||||
use enso_frp as frp;
|
||||
use enso_frp::io::keyboard::Key;
|
||||
use enso_frp::stream::ValueProvider;
|
||||
@ -35,7 +36,6 @@ use ensogl_core::data::color;
|
||||
use ensogl_core::display;
|
||||
use ensogl_core::gui::cursor;
|
||||
use ensogl_core::system::web::clipboard;
|
||||
use ensogl_text_font_family::NonVariableFaceHeader;
|
||||
use owned_ttf_parser::AsFaceRef;
|
||||
|
||||
|
||||
@ -177,7 +177,8 @@ impl Text {
|
||||
#[profile(Debug)]
|
||||
pub fn new(app: &Application) -> Self {
|
||||
let frp = Frp::new();
|
||||
let data = TextModel::new(app, &frp);
|
||||
let scene = app.display.default_scene.clone_ref();
|
||||
let data = TextModel::new(scene, &frp);
|
||||
Self { data, frp }.init()
|
||||
}
|
||||
}
|
||||
@ -414,8 +415,7 @@ impl Text {
|
||||
let m = &self.data;
|
||||
let network = self.frp.network();
|
||||
let input = &self.frp.input;
|
||||
let scene = &m.app.display.default_scene;
|
||||
let mouse = &scene.mouse.frp_deprecated;
|
||||
let mouse = &m.scene.mouse.frp_deprecated;
|
||||
|
||||
let buf = &m.buffer.frp;
|
||||
|
||||
@ -423,10 +423,10 @@ impl Text {
|
||||
|
||||
// === Setting Cursors ===
|
||||
|
||||
loc_on_set <- input.set_cursor.map(f!([m](t) t.expand(&m)));
|
||||
loc_on_add <- input.add_cursor.map(f!([m](t) t.expand(&m)));
|
||||
loc_on_set <- input.set_cursor.map(f!([m](t) t.expand(&m.buffer)));
|
||||
loc_on_add <- input.add_cursor.map(f!([m](t) t.expand(&m.buffer)));
|
||||
shape_on_select <- input.select.map(
|
||||
f!([m]((s, e)) buffer::selection::Shape(s.expand(&m), e.expand(&m)))
|
||||
f!([m]((s, e)) buffer::selection::Shape(s.expand(&m.buffer), e.expand(&m.buffer)))
|
||||
);
|
||||
|
||||
mouse_on_set <- mouse.position.sample(&input.set_cursor_at_mouse_position);
|
||||
@ -435,12 +435,16 @@ impl Text {
|
||||
loc_on_mouse_add <- mouse_on_add.map(f!((p) m.screen_to_text_location(*p)));
|
||||
|
||||
loc_on_set_at_front <- input.set_cursor_at_text_start.constant(default());
|
||||
loc_on_set_at_end <- input.set_cursor_at_text_end.map(f_!(m.last_line_last_location()));
|
||||
loc_on_set_at_end <- input.set_cursor_at_text_end.map(
|
||||
f_!(m.buffer.last_line_last_location())
|
||||
);
|
||||
loc_on_add_at_front <- input.add_cursor_at_front.constant(default());
|
||||
loc_on_add_at_end <- input.add_cursor_at_end.map(f_!(m.last_line_last_location()));
|
||||
loc_on_add_at_end <- input.add_cursor_at_end.map(
|
||||
f_!(m.buffer.last_line_last_location())
|
||||
);
|
||||
|
||||
loc_on_set <- any(loc_on_set,loc_on_mouse_set,loc_on_set_at_front,loc_on_set_at_end);
|
||||
loc_on_add <- any(loc_on_add,loc_on_mouse_add,loc_on_add_at_front,loc_on_add_at_end);
|
||||
loc_on_set <- any(loc_on_set, loc_on_mouse_set, loc_on_set_at_front, loc_on_set_at_end);
|
||||
loc_on_add <- any(loc_on_add, loc_on_mouse_add, loc_on_add_at_front, loc_on_add_at_end);
|
||||
|
||||
buf.set_cursor <+ loc_on_set;
|
||||
buf.add_cursor <+ loc_on_add;
|
||||
@ -497,16 +501,14 @@ impl Text {
|
||||
/// Get current text location under the mouse cursor within this text area.
|
||||
pub fn location_at_mouse_position(&self) -> Location {
|
||||
let m = &self.data;
|
||||
let scene = &m.app.display.default_scene;
|
||||
let mouse = &scene.mouse.frp_deprecated;
|
||||
let mouse = &m.scene.mouse.frp_deprecated;
|
||||
let position = mouse.position.value();
|
||||
m.screen_to_text_location(position)
|
||||
}
|
||||
|
||||
fn init_selections(&self) {
|
||||
let m = &self.data;
|
||||
let scene = &m.app.display.default_scene;
|
||||
let mouse = &scene.mouse.frp_deprecated;
|
||||
let mouse = &m.scene.mouse.frp_deprecated;
|
||||
let network = self.frp.network();
|
||||
let input = &self.frp.input;
|
||||
|
||||
@ -519,9 +521,9 @@ impl Text {
|
||||
m.on_modified_selection(sels, None)
|
||||
);
|
||||
|
||||
selecting <- bool
|
||||
( &input.stop_newest_selection_end_follow_mouse
|
||||
, &input.start_newest_selection_end_follow_mouse
|
||||
selecting <- bool(
|
||||
&input.stop_newest_selection_end_follow_mouse,
|
||||
&input.start_newest_selection_end_follow_mouse
|
||||
);
|
||||
|
||||
sel_end_1 <- mouse.position.gate(&selecting);
|
||||
@ -548,8 +550,8 @@ impl Text {
|
||||
|
||||
eval_ copy_whole_lines (m.buffer.frp.cursors_select(Transform::Line));
|
||||
sels_on_copy_whole_lines <- copy_whole_lines.map(f_!(m.buffer.selections_contents()));
|
||||
text_chubks_to_copy <- any(&sels_on_copy_whole_lines, ©_regions_only);
|
||||
eval text_chubks_to_copy ((s) m.copy(s));
|
||||
text_chunks_to_copy <- any(&sels_on_copy_whole_lines, ©_regions_only);
|
||||
eval text_chunks_to_copy ((s) m.copy(s));
|
||||
|
||||
// === Cut ===
|
||||
|
||||
@ -574,7 +576,7 @@ impl Text {
|
||||
|
||||
fn init_edits(&self) {
|
||||
let m = &self.data;
|
||||
let scene = &m.app.display.default_scene;
|
||||
let scene = &m.scene;
|
||||
let network = self.frp.network();
|
||||
let input = &self.frp.input;
|
||||
let out = &self.frp.private.output;
|
||||
@ -649,11 +651,11 @@ impl Text {
|
||||
|
||||
// === Style ===
|
||||
|
||||
new_prop <- input.set_property.map(f!([m]((r, p)) (Rc::new(r.expand(&m)),*p)));
|
||||
new_prop <- input.set_property.map(f!([m]((r, p)) (Rc::new(r.expand(&m.buffer)),*p)));
|
||||
m.buffer.frp.set_property <+ new_prop;
|
||||
eval new_prop ([m](t) t.1.map(|p| m.set_property(&t.0, p)));
|
||||
|
||||
mod_prop <- input.mod_property.map(f!([m]((r, p)) (Rc::new(r.expand(&m)),*p)));
|
||||
mod_prop <- input.mod_property.map(f!([m]((r, p)) (Rc::new(r.expand(&m.buffer)),*p)));
|
||||
m.buffer.frp.mod_property <+ mod_prop;
|
||||
eval mod_prop ([m](t) t.1.map(|p| m.mod_property(&t.0, p)));
|
||||
}
|
||||
@ -703,11 +705,10 @@ pub struct TextModel {
|
||||
}
|
||||
|
||||
/// Internal representation of `Text`.
|
||||
#[derive(Debug, Deref, display::Object)]
|
||||
#[derive(Debug, display::Object)]
|
||||
pub struct TextModelData {
|
||||
#[deref]
|
||||
buffer: buffer::Buffer,
|
||||
app: Application,
|
||||
scene: display::Scene,
|
||||
frp: WeakFrp,
|
||||
display_object: display::object::Instance,
|
||||
glyph_system: RefCell<glyph::System>,
|
||||
@ -721,22 +722,17 @@ pub struct TextModelData {
|
||||
|
||||
impl TextModel {
|
||||
/// Constructor.
|
||||
pub fn new(app: &Application, frp: &Frp) -> Self {
|
||||
let app = app.clone_ref();
|
||||
let scene = &app.display.default_scene;
|
||||
fn new(scene: display::Scene, frp: &Frp) -> Self {
|
||||
let selection_map = default();
|
||||
let display_object = display::object::Instance::new_named("Text");
|
||||
let glyph_system = font::glyph::System::new(scene, font::DEFAULT_FONT_MONO);
|
||||
let glyph_system = font::glyph::System::new(&scene, font::DEFAULT_CODE_FONT);
|
||||
frp.private.output.glyph_system.emit(Some(glyph_system.clone()));
|
||||
let glyph_system = RefCell::new(glyph_system);
|
||||
let buffer = buffer::Buffer::new(buffer::BufferModel::new());
|
||||
|
||||
let default_size = buffer.formatting.font_size().default.value;
|
||||
let first_line = Self::new_line_helper(
|
||||
&app.display.default_scene.frp.frame_time,
|
||||
&display_object,
|
||||
default_size,
|
||||
);
|
||||
let first_line =
|
||||
Self::new_line_helper(&scene.frp.frame_time, &display_object, default_size);
|
||||
first_line.set_baseline((-default_size).round());
|
||||
first_line.skip_baseline_animation();
|
||||
|
||||
@ -747,7 +743,7 @@ impl TextModel {
|
||||
|
||||
let frp = frp.downgrade();
|
||||
let data = TextModelData {
|
||||
app,
|
||||
scene,
|
||||
frp,
|
||||
buffer,
|
||||
display_object,
|
||||
@ -793,7 +789,7 @@ impl TextModel {
|
||||
|
||||
fn new_line(&self) -> line::View {
|
||||
let line = Self::new_line_helper(
|
||||
&self.app.display.default_scene.frp.frame_time,
|
||||
&self.scene.frp.frame_time,
|
||||
&self.display_object,
|
||||
self.buffer.formatting.font_size().default.value,
|
||||
);
|
||||
@ -824,7 +820,7 @@ impl TextModel {
|
||||
let inv_object_matrix = self.transformation_matrix().try_inverse();
|
||||
let Some(inv_object_matrix) = inv_object_matrix else { return Vector2::zero() };
|
||||
|
||||
let shape = self.app.display.default_scene.frp.shape.value();
|
||||
let shape = self.scene.frp.shape.value();
|
||||
let clip_space_z = origin_clip_space.z;
|
||||
let clip_space_x = origin_clip_space.w * 2.0 * screen_pos.x / shape.width;
|
||||
let clip_space_y = origin_clip_space.w * 2.0 * screen_pos.y / shape.height;
|
||||
@ -954,8 +950,8 @@ impl TextModel {
|
||||
|
||||
/// Recompute the shape of the provided byte range.
|
||||
fn shape_range(&self, range: Range<Byte>) -> Vec<ShapedGlyphSet> {
|
||||
let line_style = self.sub_style(range.clone());
|
||||
let rope = self.rope.sub(range);
|
||||
let line_style = self.buffer.sub_style(range.clone());
|
||||
let rope = self.buffer.rope.sub(range);
|
||||
let content = rope.to_string();
|
||||
let glyph_system = self.glyph_system.borrow();
|
||||
let font = &glyph_system.font;
|
||||
@ -985,7 +981,8 @@ impl TextModel {
|
||||
let buzz_face = rustybuzz::Face::from_face(ttf_face.clone()).unwrap();
|
||||
let mut buffer = rustybuzz::UnicodeBuffer::new();
|
||||
buffer.push_str(&content[range.start.value..range.end.value]);
|
||||
let shaped = rustybuzz::shape(&buzz_face, &[], buffer);
|
||||
let features = font.feature_settings();
|
||||
let shaped = rustybuzz::shape(&buzz_face, features, buffer);
|
||||
let variable_variations = default();
|
||||
let glyphs = shaped
|
||||
.glyph_positions()
|
||||
@ -1037,12 +1034,14 @@ impl TextModel {
|
||||
/// Recompute the shape of the provided line index.
|
||||
#[profile(Debug)]
|
||||
pub fn shape_line(&self, line: Line) -> ShapedLine {
|
||||
let line_range = self.line_range_snapped(line);
|
||||
let line_range = self.buffer.line_range_snapped(line);
|
||||
let glyph_sets = self.shape_range(line_range.clone());
|
||||
match NonEmptyVec::try_from(glyph_sets) {
|
||||
Ok(glyph_sets) => ShapedLine::NonEmpty { glyph_sets },
|
||||
Err(_) => {
|
||||
if let Some(prev_grapheme_off) = self.rope.prev_grapheme_offset(line_range.start) {
|
||||
if let Some(prev_grapheme_off) =
|
||||
self.buffer.rope.prev_grapheme_offset(line_range.start)
|
||||
{
|
||||
let prev_char_range = prev_grapheme_off..line_range.start;
|
||||
let prev_glyph_sets = self.shape_range(prev_char_range);
|
||||
let last_glyph_set = prev_glyph_sets.into_iter().last();
|
||||
@ -1233,7 +1232,7 @@ impl TextModel {
|
||||
selection.set_width_and_flip_sides_if_needed(width, start_pos.x);
|
||||
selection
|
||||
} else {
|
||||
let frame_time = &self.app.display.default_scene.frp.frame_time;
|
||||
let frame_time = &self.scene.frp.frame_time;
|
||||
let selection = Selection::new(frame_time, do_edit);
|
||||
let network = selection.network();
|
||||
let out = &self.frp.private.output;
|
||||
@ -1262,11 +1261,7 @@ impl TextModel {
|
||||
|
||||
/// Constrain the selection to values fitting inside the current text buffer. This can be needed
|
||||
/// when using the API and providing invalid values.
|
||||
#[cfg_attr(not(target_arch = "wasm32"), allow(dead_code))]
|
||||
fn limit_selection_to_known_values(
|
||||
&self,
|
||||
selection: buffer::selection::Selection,
|
||||
) -> buffer::selection::Selection {
|
||||
fn limit_selection_to_known_values(&self, selection: buffer::Selection) -> buffer::Selection {
|
||||
let start_location = Location::from_in_context_snapped(&self.buffer, selection.start);
|
||||
let end_location = Location::from_in_context_snapped(&self.buffer, selection.end);
|
||||
let start = self.buffer.snap_location(start_location);
|
||||
@ -1730,9 +1725,7 @@ impl TextModel {
|
||||
|
||||
#[profile(Debug)]
|
||||
fn set_font(&self, font_name: &str) -> glyph::System {
|
||||
let app = &self.app;
|
||||
let scene = &app.display.default_scene;
|
||||
let glyph_system = font::glyph::System::new(scene, font_name);
|
||||
let glyph_system = font::glyph::System::new(&self.scene, font_name);
|
||||
self.glyph_system.replace(glyph_system.clone());
|
||||
// Remove old Glyph structures, as they still refer to the old Glyph System.
|
||||
self.take_lines();
|
||||
|
@ -3,16 +3,13 @@
|
||||
|
||||
use crate::prelude::*;
|
||||
|
||||
use enso_shapely::shared;
|
||||
use ensogl_core::display::scene;
|
||||
use ensogl_core::system::gpu;
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
use ensogl_core::system::gpu::texture;
|
||||
use ensogl_core::system::web::platform;
|
||||
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;
|
||||
|
||||
|
||||
@ -23,10 +20,9 @@ use ttf::AsFaceRef;
|
||||
pub mod glyph;
|
||||
pub mod glyph_render_info;
|
||||
|
||||
pub use ensogl_text_font_family as family;
|
||||
pub use enso_font as family;
|
||||
pub use family::Name;
|
||||
pub use family::NonVariableFaceHeader;
|
||||
pub use family::NonVariableFaceHeaderMatch;
|
||||
pub use glyph_render_info::GlyphRenderInfo;
|
||||
pub use ttf::GlyphId;
|
||||
pub use ttf::Style;
|
||||
@ -35,7 +31,6 @@ pub use ttf::Weight;
|
||||
pub use ttf::Width;
|
||||
|
||||
|
||||
|
||||
// =================
|
||||
// === Constants ===
|
||||
// =================
|
||||
@ -44,11 +39,11 @@ pub use ttf::Width;
|
||||
/// most web browsers (you cannot define `@font-face` in CSS for multiple faces of the same file).
|
||||
const TTF_FONT_FACE_INDEX: u32 = 0;
|
||||
|
||||
/// The name of the default proportional font family.
|
||||
/// The name of the default font family for general text.
|
||||
pub const DEFAULT_FONT: &str = "mplus1p";
|
||||
|
||||
/// The name of the default monospace font family.
|
||||
pub const DEFAULT_FONT_MONO: &str = "dejavusansmono";
|
||||
/// The name of the default font family for code.
|
||||
pub const DEFAULT_CODE_FONT: &str = "enso";
|
||||
|
||||
|
||||
|
||||
@ -270,7 +265,7 @@ pub trait Family {
|
||||
#[derive(Debug)]
|
||||
pub struct NonVariableFamily {
|
||||
pub definition: family::NonVariableDefinition,
|
||||
pub faces: Rc<RefCell<HashMap<NonVariableFaceHeader, Face>>>,
|
||||
pub faces: RefCell<HashMap<NonVariableFaceHeader, Face>>,
|
||||
}
|
||||
|
||||
/// A variable font family. Contains font family definition and the font face. The face is kept in
|
||||
@ -281,21 +276,21 @@ pub struct NonVariableFamily {
|
||||
#[derive(Debug)]
|
||||
pub struct VariableFamily {
|
||||
pub definition: family::VariableDefinition,
|
||||
pub face: Rc<RefCell<Option<Face>>>,
|
||||
pub face: 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>>>,
|
||||
pub last_axes: 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) = embedded.load_face(file_name) {
|
||||
self.faces.borrow_mut().insert(*header, face);
|
||||
fn load_all_faces(&mut self, embedded: &EmbeddedData) {
|
||||
for variation in self.definition.variations() {
|
||||
if let Some(face) = embedded.load_face(variation.file) {
|
||||
self.faces.get_mut().insert(variation.header, face);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -311,7 +306,7 @@ impl NonVariableFamily {
|
||||
let mut closest = None;
|
||||
let mut closest_distance = usize::MAX;
|
||||
for known_header in faces.keys() {
|
||||
let distance = known_header.similarity_distance(variation);
|
||||
let distance = similarity_distance(known_header, &variation);
|
||||
if distance < closest_distance {
|
||||
closest_distance = distance;
|
||||
closest = Some(NonVariableFaceHeaderMatch::closest(*known_header));
|
||||
@ -333,7 +328,7 @@ impl NonVariableFamily {
|
||||
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) {
|
||||
fn load_all_faces(&mut self, embedded: &EmbeddedData) {
|
||||
if let Some(face) = embedded.load_face(&self.definition.file_name) {
|
||||
// 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
|
||||
@ -341,7 +336,7 @@ impl VariableFamily {
|
||||
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);
|
||||
*self.face.get_mut() = Some(face);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -427,6 +422,79 @@ impl From<&family::NonVariableDefinition> for NonVariableFamily {
|
||||
|
||||
|
||||
|
||||
// ========================================
|
||||
// === NonVariableFaceHeader comparison ===
|
||||
// ========================================
|
||||
|
||||
/// Distance between two font variations. It is used to find the closest variations if the
|
||||
/// provided is not available.
|
||||
fn similarity_distance(this: &NonVariableFaceHeader, other: &NonVariableFaceHeader) -> usize {
|
||||
let width_weight = 10;
|
||||
let weight_weight = 100;
|
||||
let style_weight = 1;
|
||||
|
||||
let self_width = this.width.to_number() as usize;
|
||||
let self_weight = this.weight.to_number() as usize;
|
||||
let self_style: usize = match this.style {
|
||||
Style::Normal => 0,
|
||||
Style::Italic => 1,
|
||||
Style::Oblique => 2,
|
||||
};
|
||||
|
||||
let other_width = other.width.to_number() as usize;
|
||||
let other_weight = other.weight.to_number() as usize;
|
||||
let other_style: usize = match other.style {
|
||||
Style::Normal => 0,
|
||||
Style::Italic => 1,
|
||||
Style::Oblique => 2,
|
||||
};
|
||||
|
||||
let width = self_width.abs_diff(other_width) * width_weight;
|
||||
let weight = self_weight.abs_diff(other_weight) * weight_weight;
|
||||
let style = self_style.abs_diff(other_style) * style_weight;
|
||||
width + weight + style
|
||||
}
|
||||
|
||||
/// Indicates whether the provided variation was an exact match or a closest match was found.
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
||||
#[allow(missing_docs)]
|
||||
pub enum NonVariableFaceHeaderMatchType {
|
||||
Exact,
|
||||
Closest,
|
||||
}
|
||||
|
||||
/// Result of finding a closest font variation for a non-variable font family.
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
#[allow(missing_docs)]
|
||||
pub struct NonVariableFaceHeaderMatch {
|
||||
pub variations: NonVariableFaceHeader,
|
||||
pub match_type: NonVariableFaceHeaderMatchType,
|
||||
}
|
||||
|
||||
impl NonVariableFaceHeaderMatch {
|
||||
/// Constructor.
|
||||
pub fn exact(variations: NonVariableFaceHeader) -> Self {
|
||||
Self { variations, match_type: NonVariableFaceHeaderMatchType::Exact }
|
||||
}
|
||||
|
||||
/// Constructor.
|
||||
pub fn closest(variations: NonVariableFaceHeader) -> Self {
|
||||
Self { variations, match_type: NonVariableFaceHeaderMatchType::Closest }
|
||||
}
|
||||
|
||||
/// Checks whether the match was exact.
|
||||
pub fn was_exact(&self) -> bool {
|
||||
self.match_type == NonVariableFaceHeaderMatchType::Exact
|
||||
}
|
||||
|
||||
/// Checks whether the match was closest.
|
||||
pub fn was_closest(&self) -> bool {
|
||||
self.match_type == NonVariableFaceHeaderMatchType::Closest
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
// ============
|
||||
// === Font ===
|
||||
// ============
|
||||
@ -562,6 +630,14 @@ impl Font {
|
||||
Font::Variable(font) => font.family.with_borrowed_face(&default(), f),
|
||||
}
|
||||
}
|
||||
|
||||
/// Return the base set of feature settings to be used when rendering this font in EnsoGL.
|
||||
pub fn feature_settings(&self) -> &[rustybuzz::Feature] {
|
||||
match self {
|
||||
Font::NonVariable(font) => &font.features,
|
||||
Font::Variable(font) => &font.features,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -583,10 +659,11 @@ pub struct FontTemplate<F: Family> {
|
||||
#[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>>,
|
||||
pub name: Name,
|
||||
pub family: F,
|
||||
pub features: Vec<rustybuzz::Feature>,
|
||||
pub atlas: msdf::Texture,
|
||||
pub cache: RefCell<HashMap<F::Variations, FontDataCache>>,
|
||||
}
|
||||
|
||||
/// A cache for common glyph properties, used to layout glyphs.
|
||||
@ -605,11 +682,11 @@ impl<F: Family> From<FontTemplateData<F>> for FontTemplate<F> {
|
||||
|
||||
impl<F: Family> FontTemplate<F> {
|
||||
/// Constructor.
|
||||
pub fn new(name: Name, family: impl Into<F>) -> Self {
|
||||
pub fn new(name: Name, family: impl Into<F>, features: Vec<rustybuzz::Feature>) -> Self {
|
||||
let atlas = default();
|
||||
let cache = default();
|
||||
let family = family.into();
|
||||
let data = FontTemplateData { name, family, atlas, cache };
|
||||
let data = FontTemplateData { name, family, features, atlas, cache };
|
||||
Self { rc: Rc::new(data) }
|
||||
}
|
||||
|
||||
@ -865,7 +942,7 @@ type AtlasTexture = gpu::Texture<texture::GpuOnly, texture::Rgb, u8>;
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
type AtlasTexture = f32;
|
||||
|
||||
/// A font with an associated GPU-stored data.
|
||||
/// A font with associated GPU-stored data.
|
||||
#[allow(missing_docs)]
|
||||
#[derive(Clone, CloneRef, Debug, Deref)]
|
||||
pub struct FontWithGpuData {
|
||||
@ -878,10 +955,9 @@ pub struct FontWithGpuData {
|
||||
}
|
||||
|
||||
impl FontWithGpuData {
|
||||
fn new(font: Font, hinting: Hinting, scene: &scene::Scene) -> Self {
|
||||
fn new(font: Font, hinting: Hinting, context: &Context) -> Self {
|
||||
let Hinting { opacity_increase, opacity_exponent } = hinting;
|
||||
let context = get_context(scene);
|
||||
let texture = get_texture(&context);
|
||||
let texture = get_texture(context);
|
||||
let atlas = gpu::Uniform::new(texture);
|
||||
let opacity_increase = gpu::Uniform::new(opacity_increase);
|
||||
let opacity_exponent = gpu::Uniform::new(opacity_exponent);
|
||||
@ -895,6 +971,67 @@ impl FontWithGpuData {
|
||||
// === Registry ===
|
||||
// ================
|
||||
|
||||
/// Stores all loaded fonts.
|
||||
#[derive(Debug, Clone)]
|
||||
#[derive(CloneRef)]
|
||||
pub struct Registry {
|
||||
fonts: Rc<HashMap<Name, FontWithGpuData>>,
|
||||
}
|
||||
|
||||
impl Registry {
|
||||
/// Load the default font. See the docs of [`load`] to learn more.
|
||||
pub fn load_default(&self) -> FontWithGpuData {
|
||||
self.try_load(DEFAULT_FONT).expect("Default font not found.")
|
||||
}
|
||||
|
||||
/// Load a font by name. Returns the default font if a font is not found for the name.
|
||||
pub fn load(&self, name: impl Into<Name>) -> FontWithGpuData {
|
||||
let name = name.into();
|
||||
self.try_load(&name).unwrap_or_else(|| {
|
||||
warn!("Font '{name}' not found. Loading the default font.");
|
||||
self.load_default()
|
||||
})
|
||||
}
|
||||
|
||||
/// Load a font by name. Returns [`None`] if a font is not found for the name.
|
||||
pub fn try_load(&self, name: impl Into<Name>) -> Option<FontWithGpuData> {
|
||||
let name = name.into();
|
||||
self.fonts.get(&name).cloned()
|
||||
}
|
||||
|
||||
fn from_fonts(fonts: impl IntoIterator<Item = (Name, Font)>) -> Self {
|
||||
let scene = scene();
|
||||
let scene_shape = scene.shape().value();
|
||||
let context = get_context(&scene);
|
||||
let fonts = fonts
|
||||
.into_iter()
|
||||
.map(|(name, font)| {
|
||||
debug!("Loading font: {:?}", name);
|
||||
let hinting = Hinting::for_font(&name, scene_shape);
|
||||
(name, FontWithGpuData::new(font, hinting, &context))
|
||||
})
|
||||
.collect();
|
||||
let fonts = Rc::new(fonts);
|
||||
Self { fonts }
|
||||
}
|
||||
}
|
||||
|
||||
impl scene::Extension for Registry {
|
||||
fn init(_scene: &scene::Scene) -> Self {
|
||||
Self::default()
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Registry {
|
||||
fn default() -> Self {
|
||||
let fonts = Embedded::default().into_fonts();
|
||||
Self::from_fonts(fonts)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// === Context helpers ===
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
#[derive(Clone, Copy, CloneRef, Debug, Default)]
|
||||
/// Mocked version of WebGL context.
|
||||
@ -924,68 +1061,6 @@ fn get_texture(context: &Context) -> AtlasTexture {
|
||||
}
|
||||
|
||||
|
||||
shared! { Registry
|
||||
/// Structure keeping all fonts loaded from different sources.
|
||||
#[derive(Debug)]
|
||||
pub struct RegistryData {
|
||||
embedded: Embedded,
|
||||
fonts: HashMap<Name, FontWithGpuData>,
|
||||
}
|
||||
|
||||
impl {
|
||||
/// Load the default font. See the docs of [`load`] to learn more.
|
||||
pub fn load_default(&mut self) -> FontWithGpuData {
|
||||
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>) -> FontWithGpuData {
|
||||
let name = name.into();
|
||||
self.try_load(&name).unwrap_or_else(|| {
|
||||
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<FontWithGpuData> {
|
||||
let name = name.into();
|
||||
match self.fonts.entry(name.clone()) {
|
||||
Entry::Occupied (entry) => Some(entry.get().clone_ref()),
|
||||
Entry::Vacant (entry) => {
|
||||
debug!("Loading font: {:?}", name);
|
||||
let hinting = Hinting::for_font(&name);
|
||||
let font = self.embedded.load_font(name)?;
|
||||
let font = FontWithGpuData::new(font, hinting, &scene());
|
||||
entry.insert(font.clone_ref());
|
||||
Some(font)
|
||||
}
|
||||
}
|
||||
}
|
||||
}}
|
||||
|
||||
impl Registry {
|
||||
/// Constructor.
|
||||
pub fn init_and_load_embedded_fonts() -> Registry {
|
||||
let embedded = Embedded::new();
|
||||
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()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
// ===============
|
||||
// === Hinting ===
|
||||
@ -1002,17 +1077,14 @@ struct Hinting {
|
||||
}
|
||||
|
||||
impl Hinting {
|
||||
fn for_font(font_name: &str) -> Self {
|
||||
let platform = platform::current();
|
||||
// The optimal hinting values must be found by testing. The [`text_are`] debug scene
|
||||
fn for_font(font_name: &str, shape: scene::Shape) -> Self {
|
||||
let pixel_ratio = shape.pixel_ratio;
|
||||
// The optimal hinting values must be found by testing. The [`text_area`] debug scene
|
||||
// supports trying different values at runtime.
|
||||
match (platform, font_name) {
|
||||
(Some(platform::Platform::MacOS), "mplus1p") =>
|
||||
match font_name {
|
||||
"mplus1p" | "enso" if pixel_ratio >= 2.0 =>
|
||||
Self { opacity_increase: 0.4, opacity_exponent: 4.0 },
|
||||
(Some(platform::Platform::Windows), "mplus1p") =>
|
||||
Self { opacity_increase: 0.3, opacity_exponent: 3.0 },
|
||||
(Some(platform::Platform::Linux), "mplus1p") =>
|
||||
Self { opacity_increase: 0.3, opacity_exponent: 3.0 },
|
||||
"mplus1p" | "enso" => Self { opacity_increase: 0.3, opacity_exponent: 3.0 },
|
||||
_ => Self::default(),
|
||||
}
|
||||
}
|
||||
@ -1031,52 +1103,115 @@ impl Default for Hinting {
|
||||
// =========================
|
||||
|
||||
/// A registry of font data built-in to the application.
|
||||
#[derive(Debug, Default)]
|
||||
#[derive(Debug)]
|
||||
pub struct Embedded {
|
||||
definitions: HashMap<Name, family::Definition>,
|
||||
data: HashMap<&'static str, &'static [u8]>,
|
||||
definitions: HashMap<Name, family::FontFamily>,
|
||||
data: EmbeddedData,
|
||||
features: HashMap<Name, Vec<rustybuzz::Feature>>,
|
||||
}
|
||||
|
||||
impl Embedded {
|
||||
/// Load the registry.
|
||||
pub fn new() -> Self {
|
||||
let fonts = ensogl_text_embedded_fonts::Embedded::init_and_load_embedded_fonts();
|
||||
let ensogl_text_embedded_fonts::Embedded { definitions, data } = fonts;
|
||||
Self { definitions, data }
|
||||
}
|
||||
|
||||
/// Load a font from the registry.
|
||||
pub fn load_font(&self, name: Name) -> Option<Font> {
|
||||
self.definitions.get(&name).map(|definition| match definition {
|
||||
family::Definition::NonVariable(definition) => {
|
||||
let family = NonVariableFamily::from(definition);
|
||||
let features = self.features.get(&name).cloned().unwrap_or_default();
|
||||
let definition = self.definitions.get(&name);
|
||||
definition.map(|def| self.data.load_font(name.clone(), def, features))
|
||||
}
|
||||
|
||||
/// Load and return all fonts from the registry.
|
||||
pub fn into_fonts(self) -> impl Iterator<Item = (Name, Font)> {
|
||||
let Self { definitions, data, mut features } = self;
|
||||
definitions.into_iter().map(move |(name, definition)| {
|
||||
let features = features.remove(&name).unwrap_or_default();
|
||||
(name.clone(), data.load_font(name, &definition, features))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Embedded {
|
||||
fn default() -> Self {
|
||||
let ensogl_text_embedded_fonts::Embedded { definitions, data, features } = default();
|
||||
Self { definitions, data: EmbeddedData { data }, features }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// === Embedded data ===
|
||||
|
||||
/// Font files compiled into the application.
|
||||
#[derive(Debug)]
|
||||
pub struct EmbeddedData {
|
||||
data: HashMap<&'static str, &'static [u8]>,
|
||||
}
|
||||
|
||||
impl EmbeddedData {
|
||||
fn load_font(
|
||||
&self,
|
||||
name: Name,
|
||||
definition: &family::FontFamily,
|
||||
features: Vec<rustybuzz::Feature>,
|
||||
) -> Font {
|
||||
match definition {
|
||||
family::FontFamily::NonVariable(definition) => {
|
||||
let mut family = NonVariableFamily::from(definition);
|
||||
family.load_all_faces(self);
|
||||
let cache = PREBUILT_ATLASES.with_borrow_mut(|atlases| atlases.get(&name).cloned());
|
||||
let font = NonVariableFont::new(name, family);
|
||||
let font = NonVariableFont::new(name, family, features);
|
||||
if let Some(cache) = cache {
|
||||
font.load_cache(&cache)
|
||||
.unwrap_or_else(|e| error!("Failed to load cached font data: {e}."));
|
||||
}
|
||||
font.into()
|
||||
}
|
||||
family::Definition::Variable(definition) => {
|
||||
let family = VariableFamily::from(definition);
|
||||
family::FontFamily::Variable(definition) => {
|
||||
let mut family = VariableFamily::from(definition);
|
||||
family.load_all_faces(self);
|
||||
VariableFont::new(name, family).into()
|
||||
VariableFont::new(name, family, features).into()
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// Load the font face from memory. Corrupted faces will be reported.
|
||||
fn load_face(&self, name: &str) -> Option<Face> {
|
||||
let result = self.load_face_internal(name);
|
||||
let result = self.try_load_face(name);
|
||||
result.map_err(|err| error!("Error parsing font: {}", err)).ok()
|
||||
}
|
||||
|
||||
fn load_face_internal(&self, name: &str) -> anyhow::Result<Face> {
|
||||
fn try_load_face(&self, name: &str) -> anyhow::Result<Face> {
|
||||
let data = self.data.get(name).ok_or_else(|| anyhow!("Font '{}' not found", name))?;
|
||||
let ttf = ttf::OwnedFace::from_vec((**data).into(), TTF_FONT_FACE_INDEX)?;
|
||||
let msdf = msdf::OwnedFace::load_from_memory(data)?;
|
||||
Ok(Face { msdf, ttf })
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
// =============
|
||||
// === Tests ===
|
||||
// =============
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_enso_font_ligatures_disabled() {
|
||||
let registry: HashMap<_, _> = Embedded::default().into_fonts().collect();
|
||||
let font = registry.get(&"enso".into()).unwrap();
|
||||
font.with_borrowed_face(NonVariableFaceHeader::default(), |face| {
|
||||
let face = face.ttf.as_face_ref().clone();
|
||||
let face = rustybuzz::Face::from_face(face).unwrap();
|
||||
let features = font.feature_settings();
|
||||
// If the font's ligatures are used, these two characters will correspond to one
|
||||
// glyph. `ensogl_text` doesn't support ligatures, so its shaped length must be the
|
||||
// same as its character count.
|
||||
let test_str = "fi";
|
||||
let mut buffer = rustybuzz::UnicodeBuffer::new();
|
||||
buffer.push_str(test_str);
|
||||
let shaped = rustybuzz::shape(&face, features, buffer);
|
||||
assert_eq!(shaped.len(), test_str.chars().count());
|
||||
})
|
||||
.unwrap();
|
||||
}
|
||||
}
|
||||
|
@ -7,15 +7,15 @@ edition = "2021"
|
||||
[lib]
|
||||
crate-type = ["cdylib", "rlib"]
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
[dependencies]
|
||||
enso-prelude = { path = "../../../../../../prelude" }
|
||||
ensogl-text-font-family = { path = "../../font/family" }
|
||||
enso-font = { path = "../../../../../../font" }
|
||||
rustybuzz = { workspace = true }
|
||||
|
||||
[build-dependencies]
|
||||
enso-enso-font = { path = "../../../../../../enso-font" }
|
||||
enso-font = { path = "../../../../../../font" }
|
||||
ide-ci = { path = "../../../../../../../../build/ci_utils" }
|
||||
enso-build = { path = "../../../../../../../../build/build" }
|
||||
enso-build-utilities = { path = "../../../../../../../../build/deprecated/build-utils" }
|
||||
tokio = { workspace = true }
|
||||
zip = { version = "0.6.2", default-features = false, features = ["deflate"] }
|
||||
owned_ttf_parser = { workspace = true }
|
||||
tokio = { workspace = true }
|
||||
|
@ -2,9 +2,12 @@
|
||||
|
||||
// === Features ===
|
||||
#![feature(const_trait_impl)]
|
||||
#![feature(let_chains)]
|
||||
|
||||
use ide_ci::prelude::*;
|
||||
|
||||
use enso_font::NonVariableDefinition;
|
||||
use enso_font::NonVariableFaceHeader;
|
||||
use ide_ci::log::setup_logging;
|
||||
use owned_ttf_parser::AsFaceRef;
|
||||
use owned_ttf_parser::OwnedFace;
|
||||
@ -12,6 +15,15 @@ use std::fmt::Write as FmtWrite;
|
||||
|
||||
|
||||
|
||||
// =================
|
||||
// === Constants ===
|
||||
// =================
|
||||
|
||||
/// The name of the Rust source file that will be generated from the downloaded font data.
|
||||
const GENERATED_SOURCE_FILE_NAME: &str = "embedded_fonts_data.rs";
|
||||
|
||||
|
||||
|
||||
// =============
|
||||
// === Utils ===
|
||||
// =============
|
||||
@ -34,39 +46,67 @@ macro_rules! ln {
|
||||
pub struct CodeGenerator {
|
||||
embeds: String,
|
||||
definitions: String,
|
||||
features: 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}\"));");
|
||||
ln!(1, &mut self.embeds, "fonts.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});");
|
||||
let value = format!("family::FontFamily::Variable({family_def})");
|
||||
ln!(1, &mut self.definitions, "families.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 add_non_variable_font_definition(&mut self, family_name: &str, def: &NonVariableDefinition) {
|
||||
ln!(1, &mut self.definitions, "families.insert(\"{family_name}\".into(),");
|
||||
let fam_def = "family::FontFamily::NonVariable";
|
||||
ln!(2, &mut self.definitions, "{}(family::NonVariableDefinition::from_iter([", fam_def);
|
||||
for variation in def.variations() {
|
||||
let file_name = &variation.file;
|
||||
let header = &variation.header;
|
||||
ln!(3, &mut self.definitions, "(");
|
||||
ln!(4, &mut self.definitions, "family::NonVariableFaceHeader::new(");
|
||||
ln!(5, &mut self.definitions, "family::Width::{:?},", &header.width);
|
||||
ln!(5, &mut self.definitions, "family::Weight::{:?},", &header.weight);
|
||||
ln!(5, &mut self.definitions, "family::Style::{:?},", &header.style);
|
||||
ln!(4, &mut self.definitions, "),");
|
||||
ln!(4, &mut self.definitions, "\"{file_name}\".to_string(),");
|
||||
ln!(3, &mut self.definitions, "),");
|
||||
}
|
||||
ln!(2, &mut self.definitions, "]))");
|
||||
ln!(1, &mut self.definitions, ");");
|
||||
}
|
||||
|
||||
fn add_font_features(&mut self, family: &str, features: &[&str]) {
|
||||
ln!(1, &mut self.features, "features.insert(\"{family}\".into(), {features:?}.to_vec());");
|
||||
}
|
||||
|
||||
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();");
|
||||
ln!(1, body, "let mut fonts = HashMap::<&'static str, &'static [u8]>::new();");
|
||||
write!(body, "{}", self.embeds).ok();
|
||||
ln!(1, body, "map");
|
||||
ln!(1, body, "fonts");
|
||||
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();");
|
||||
ln!(0, body, "-> HashMap<family::Name, family::FontFamily> {{");
|
||||
ln!(1, body, "let mut families = HashMap::new();");
|
||||
write!(body, "{}", self.definitions).ok();
|
||||
ln!(1, body, "map");
|
||||
ln!(1, body, "families");
|
||||
ln!(0, body, "}}");
|
||||
ln!(0, body, "/// Feature settings to be used when rendering embedded families.");
|
||||
ln!(0, body, "pub fn embedded_family_features()");
|
||||
ln!(0, body, "-> HashMap<family::Name, Vec<&'static str>> {{");
|
||||
ln!(1, body, "let mut features = HashMap::new();");
|
||||
write!(body, "{}", self.features).ok();
|
||||
ln!(1, body, "features");
|
||||
ln!(0, body, "}}");
|
||||
body
|
||||
}
|
||||
@ -74,58 +114,23 @@ impl CodeGenerator {
|
||||
|
||||
|
||||
|
||||
// ===================
|
||||
// === DejaVu Font ===
|
||||
// ===================
|
||||
// =================
|
||||
// === Enso Font ===
|
||||
// =================
|
||||
|
||||
mod deja_vu {
|
||||
use super::*;
|
||||
|
||||
use crate::CodeGenerator;
|
||||
use enso_build_utilities::GithubRelease;
|
||||
|
||||
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";
|
||||
|
||||
const FILE_NAMES: [&str; 4] =
|
||||
["DejaVuSans.ttf", "DejaVuSans-Bold.ttf", "DejaVuSansMono.ttf", "DejaVuSansMono-Bold.ttf"];
|
||||
|
||||
pub fn extract_all_fonts(package_path: &Path) -> Result {
|
||||
let archive_file = ide_ci::fs::open(package_path)?;
|
||||
let mut archive = zip::ZipArchive::new(archive_file).unwrap();
|
||||
for file_name in FILE_NAMES {
|
||||
let font_in_package_path = format!("{PACKAGE_FONTS_PREFIX}/{file_name}");
|
||||
let mut input_stream = archive.by_name(&font_in_package_path).with_context(|| {
|
||||
format!(
|
||||
"Cannot find font file {} in the package {}",
|
||||
file_name,
|
||||
package_path.display()
|
||||
)
|
||||
})?;
|
||||
let output_path = package_path.with_file_name(file_name);
|
||||
let mut output_stream = ide_ci::fs::create(&output_path)?;
|
||||
std::io::copy(&mut input_stream, &mut output_stream).with_context(|| {
|
||||
format!("Cannot extract font file {} to {}", file_name, output_path.display())
|
||||
})?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn download_and_extract_all_fonts(out_dir: &Path) -> Result {
|
||||
let package_path = ide_ci::io::download_to_dir(PACKAGE.url()?, out_dir).await?;
|
||||
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);
|
||||
}
|
||||
pub async fn load_enso_font(out_dir: impl AsRef<Path>, code_gen: &mut CodeGenerator) -> Result {
|
||||
let family_name = enso_enso_font::ENSO_FONT_FAMILY_NAME;
|
||||
let font_family = enso_enso_font::enso_font();
|
||||
let package = enso_build::ide::web::fonts::get_enso_font_package().await?;
|
||||
enso_enso_font::extract_fonts(&font_family, package, &out_dir).await?;
|
||||
code_gen.add_non_variable_font_definition(family_name, &font_family);
|
||||
for file in font_family.files() {
|
||||
code_gen.add_font_data(file);
|
||||
}
|
||||
// The Enso Font has ligatures, but `ensogl_text` does not support them.
|
||||
let disable_ligatures = format!("-{}", enso_enso_font::feature::LIGATURES);
|
||||
code_gen.add_font_features(family_name, &[&disable_ligatures]);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
@ -146,6 +151,17 @@ mod google_fonts {
|
||||
face: OwnedFace,
|
||||
}
|
||||
|
||||
impl FaceDefinition {
|
||||
fn header(&self) -> NonVariableFaceHeader {
|
||||
let face = self.face.as_face_ref();
|
||||
NonVariableFaceHeader {
|
||||
width: face.width(),
|
||||
weight: face.weight(),
|
||||
style: face.style(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A description of downloaded file.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct DownloadedFile {
|
||||
@ -194,23 +210,8 @@ mod google_fonts {
|
||||
let err4 = "see: https://developer.mozilla.org/en-US/docs/Web/CSS/@font-face";
|
||||
bail!("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);
|
||||
let variations = font_faces.into_iter().map(|def| (def.header(), def.file_name));
|
||||
buffer.add_non_variable_font_definition(family_name, &variations.collect());
|
||||
};
|
||||
Ok(())
|
||||
}
|
||||
@ -226,16 +227,14 @@ async fn main() -> Result {
|
||||
println!("cargo:rerun-if-changed=build.rs");
|
||||
setup_logging()?;
|
||||
let out_dir = ide_ci::programs::cargo::build_env::OUT_DIR.get()?;
|
||||
deja_vu::download_and_extract_all_fonts(&out_dir).await?;
|
||||
|
||||
let mut code_gen = CodeGenerator::default();
|
||||
google_fonts::load(&out_dir, &mut code_gen, "mplus1").await?;
|
||||
|
||||
google_fonts::load(&out_dir, &mut code_gen, "mplus1p").await?;
|
||||
|
||||
deja_vu::add_entries_to_fill_map_rs(&mut code_gen);
|
||||
load_enso_font(&out_dir, &mut code_gen).await?;
|
||||
|
||||
let body = code_gen.body();
|
||||
let out_path = out_dir.join("embedded_fonts_data.rs");
|
||||
let out_path = out_dir.join(GENERATED_SOURCE_FILE_NAME);
|
||||
ide_ci::fs::tokio::write(&out_path, body).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
@ -20,7 +20,7 @@
|
||||
|
||||
use enso_prelude::*;
|
||||
|
||||
use ensogl_text_font_family as family;
|
||||
use enso_font as family;
|
||||
|
||||
|
||||
|
||||
@ -45,16 +45,23 @@ include!(concat!(env!("OUT_DIR"), "/embedded_fonts_data.rs"));
|
||||
#[allow(missing_docs)]
|
||||
#[derive(Clone)]
|
||||
pub struct Embedded {
|
||||
pub definitions: HashMap<family::Name, family::Definition>,
|
||||
pub definitions: HashMap<family::Name, family::FontFamily>,
|
||||
pub data: HashMap<&'static str, &'static [u8]>,
|
||||
pub features: HashMap<family::Name, Vec<rustybuzz::Feature>>,
|
||||
}
|
||||
|
||||
impl Embedded {
|
||||
/// Construct and load all the embedded fonts to memory.
|
||||
pub fn init_and_load_embedded_fonts() -> Self {
|
||||
impl Default for Embedded {
|
||||
fn default() -> Self {
|
||||
let data = embedded_fonts_data();
|
||||
let definitions = embedded_family_definitions_ext();
|
||||
Self { data, definitions }
|
||||
let definitions = embedded_family_definitions();
|
||||
let features = embedded_family_features()
|
||||
.into_iter()
|
||||
.map(|(family, feats)| {
|
||||
// Safe to `unwrap` because the input is compile-time constant.
|
||||
(family, feats.into_iter().map(|feat| feat.parse().unwrap()).collect())
|
||||
})
|
||||
.collect();
|
||||
Self { data, definitions, features }
|
||||
}
|
||||
}
|
||||
|
||||
@ -76,62 +83,10 @@ mod test {
|
||||
|
||||
#[test]
|
||||
fn loading_embedded_fonts() {
|
||||
let fonts = Embedded::init_and_load_embedded_fonts();
|
||||
let example_font = fonts.data.get("DejaVuSans.ttf").unwrap();
|
||||
|
||||
let fonts = Embedded::default();
|
||||
let example_font = fonts.data.get("Enso-Regular.ttf").unwrap();
|
||||
assert_eq!(0x00, example_font[0]);
|
||||
assert_eq!(0x01, example_font[1]);
|
||||
assert_eq!(0x1d, example_font[example_font.len() - 1]);
|
||||
assert_eq!(0x00, 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);
|
||||
map.insert("dejavusansmono".into(), dejavusansmono);
|
||||
map
|
||||
}
|
||||
|
@ -24,7 +24,7 @@ enso-web = { path = "../../../../../../web" }
|
||||
wasm-bindgen-test = { workspace = true }
|
||||
futures = { workspace = true }
|
||||
ensogl-text-embedded-fonts = { path = "../../../src/font/embedded" }
|
||||
ensogl-text-font-family = { path = "../../../src/font/family" }
|
||||
enso-font = { path = "../../../../../../font" }
|
||||
|
||||
[build-dependencies]
|
||||
ide-ci = { path = "../../../../../../../../build/ci_utils" }
|
||||
|
@ -323,65 +323,74 @@ mod tests {
|
||||
|
||||
wasm_bindgen_test_configure!(run_in_browser);
|
||||
|
||||
const TEST_PARAMETERS: MsdfParameters = 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,
|
||||
};
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
struct OutputValueChecks {
|
||||
word0: f32,
|
||||
word10: f32,
|
||||
last_word: f32,
|
||||
translation: Vector2<f64>,
|
||||
scale: Vector2<f64>,
|
||||
advance: f64,
|
||||
}
|
||||
|
||||
/// Check some values to test Rust-JS interface.
|
||||
fn check_outputs(msdf: &Msdf, data: &[f32]) {
|
||||
let computed = OutputValueChecks {
|
||||
word0: data[0],
|
||||
word10: data[10],
|
||||
last_word: data[data.len() - 1],
|
||||
translation: msdf.translation,
|
||||
scale: msdf.scale,
|
||||
advance: msdf.advance,
|
||||
};
|
||||
let expected = OutputValueChecks {
|
||||
word0: 0.053712357,
|
||||
word10: 0.125,
|
||||
last_word: -3.9244988,
|
||||
translation: Vector2::new(1.2421875, 1.0),
|
||||
scale: Vector2::new(2.0, 2.0),
|
||||
advance: 10.921875,
|
||||
};
|
||||
assert_eq!(computed, expected);
|
||||
}
|
||||
|
||||
#[wasm_bindgen_test(async)]
|
||||
async fn generate_msdf_for_capital_a() {
|
||||
initialized().await;
|
||||
// given
|
||||
let font_base = Embedded::init_and_load_embedded_fonts();
|
||||
let font_name = "DejaVuSansMono-Bold.ttf";
|
||||
let font_base = Embedded::default();
|
||||
let font_name = "Enso-Regular.ttf";
|
||||
let font = OwnedFace::load_from_memory(font_base.data.get(font_name).unwrap()).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,
|
||||
};
|
||||
let params = TEST_PARAMETERS;
|
||||
// when
|
||||
let msdf = Msdf::generate(&font, 'A' as u32, ¶ms);
|
||||
// then
|
||||
let data: Vec<f32> = msdf.data.iter().collect();
|
||||
assert_eq!(-0.9408906, data[0]); // Note [asserts]
|
||||
assert_eq!(0.2, data[10]);
|
||||
assert_eq!(-4.3035655, data[data.len() - 1]);
|
||||
assert_eq!(Vector2::new(3.03125, 1.0), msdf.translation);
|
||||
assert_eq!(Vector2::new(1.25, 1.25), msdf.scale);
|
||||
assert_eq!(19.265625, msdf.advance);
|
||||
let data = msdf.data.iter().collect_vec();
|
||||
check_outputs(&msdf, &data);
|
||||
}
|
||||
|
||||
#[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_base = Embedded::default();
|
||||
let font_name = "Enso-Regular.ttf";
|
||||
let font = OwnedFace::load_from_memory(font_base.data.get(font_name).unwrap()).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,
|
||||
};
|
||||
let params = TEST_PARAMETERS;
|
||||
// when
|
||||
let msdf = Msdf::generate_by_index(&font, 36, ¶ms);
|
||||
let msdf = Msdf::generate_by_index(&font, 2, ¶ms);
|
||||
// then
|
||||
let data: Vec<f32> = msdf.data.iter().collect();
|
||||
assert_eq!(-0.9408906, data[0]); // Note [asserts]
|
||||
assert_eq!(0.2, data[10]);
|
||||
assert_eq!(-4.3035655, data[data.len() - 1]);
|
||||
assert_eq!(Vector2::new(3.03125, 1.0), msdf.translation);
|
||||
assert_eq!(Vector2::new(1.25, 1.25), msdf.scale);
|
||||
assert_eq!(19.265625, msdf.advance);
|
||||
let data = msdf.data.iter().collect_vec();
|
||||
check_outputs(&msdf, &data);
|
||||
}
|
||||
|
||||
/* Note [asserts]
|
||||
*
|
||||
* we're checking rust - js interface only, so there is no need to check
|
||||
* all values
|
||||
*/
|
||||
}
|
||||
|
@ -227,7 +227,7 @@ fn init_debug_hotkeys(scene: &Scene, area: &Rc<RefCell<Option<Text>>>, div: &web
|
||||
let scene = scene.clone_ref();
|
||||
let area = area.clone_ref();
|
||||
let div = div.clone();
|
||||
let mut fonts_cycle = ["dejavusans", "dejavusansmono", "mplus1p"].iter().cycle();
|
||||
let mut fonts_cycle = ["enso", "mplus1p"].iter().cycle();
|
||||
let closure: Closure<dyn FnMut(JsValue)> = Closure::new(move |val: JsValue| {
|
||||
let event = val.unchecked_into::<web::KeyboardEvent>();
|
||||
if event.ctrl_key() {
|
||||
|
@ -1,5 +1,5 @@
|
||||
[package]
|
||||
name = "ensogl-text-font-family"
|
||||
name = "enso-font"
|
||||
version = "0.1.0"
|
||||
authors = ["Enso Team <contact@enso.org>"]
|
||||
edition = "2021"
|
@ -10,31 +10,12 @@
|
||||
//! 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(associated_type_defaults)]
|
||||
#![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)]
|
||||
#![allow(clippy::bool_to_int_with_if)]
|
||||
#![allow(clippy::let_and_return)]
|
||||
// === 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)]
|
||||
@ -46,7 +27,7 @@
|
||||
|
||||
use derive_more::Deref;
|
||||
use derive_more::Display;
|
||||
use std::collections::HashMap;
|
||||
|
||||
|
||||
|
||||
// ==============
|
||||
@ -99,15 +80,15 @@ impl From<String> for Name {
|
||||
|
||||
|
||||
|
||||
// ==================
|
||||
// === Definition ===
|
||||
// ==================
|
||||
// ===================
|
||||
// === Font Family ===
|
||||
// ===================
|
||||
|
||||
/// 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 {
|
||||
pub enum FontFamily {
|
||||
Variable(VariableDefinition),
|
||||
NonVariable(NonVariableDefinition),
|
||||
}
|
||||
@ -147,28 +128,67 @@ impl VariableDefinition {
|
||||
#[allow(missing_docs)]
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub struct NonVariableDefinition {
|
||||
pub map: HashMap<NonVariableFaceHeader, String>,
|
||||
variations: std::collections::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()
|
||||
let mut weights: Vec<_> = self.variations().map(|var| var.header.weight).collect();
|
||||
weights.sort_unstable_by_key(|w| w.to_number());
|
||||
weights.dedup();
|
||||
weights
|
||||
}
|
||||
|
||||
/// Return an iterator over the filenames associated with fonts in this family.
|
||||
pub fn files(&self) -> impl Iterator<Item = &str> {
|
||||
self.variations().map(|v| v.file)
|
||||
}
|
||||
|
||||
/// Return an iterator over the fonts in this family.
|
||||
pub fn variations(&self) -> impl Iterator<Item = NonVariableVariation> {
|
||||
self.variations
|
||||
.iter()
|
||||
.map(|(header, file)| NonVariableVariation { header: *header, file: file.as_str() })
|
||||
}
|
||||
|
||||
/// Return the font in this family that exactly matches the given parameters, if any.
|
||||
pub fn get(&self, header: NonVariableFaceHeader) -> Option<NonVariableVariation> {
|
||||
self.variations.get(&header).map(|file| NonVariableVariation { header, file })
|
||||
}
|
||||
}
|
||||
|
||||
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())
|
||||
let map = iter.into_iter().collect();
|
||||
Self { variations: map }
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> FromIterator<NonVariableVariation<'a>> for NonVariableDefinition {
|
||||
fn from_iter<T>(iter: T) -> Self
|
||||
where T: IntoIterator<Item = NonVariableVariation<'a>> {
|
||||
let map = iter.into_iter().map(|v| (v.header, v.file.to_owned())).collect();
|
||||
Self { variations: map }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
// ============================
|
||||
// === NonVariableVariation ===
|
||||
// ============================
|
||||
|
||||
/// The parameters of a font, and the filename where it should be found.
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub struct NonVariableVariation<'a> {
|
||||
/// The parameters of the font.
|
||||
pub header: NonVariableFaceHeader,
|
||||
/// The filename for the font.
|
||||
pub file: &'a str,
|
||||
}
|
||||
|
||||
|
||||
|
||||
// =============================
|
||||
@ -190,77 +210,4 @@ impl NonVariableFaceHeader {
|
||||
pub const fn new(width: Width, weight: Weight, style: Style) -> Self {
|
||||
Self { width, weight, style }
|
||||
}
|
||||
|
||||
/// Distance between two font variations. It is used to find the closest variations if the
|
||||
/// provided is not available.
|
||||
pub fn similarity_distance(&self, other: NonVariableFaceHeader) -> usize {
|
||||
let width_weight = 10;
|
||||
let weight_weight = 100;
|
||||
let style_weight = 1;
|
||||
|
||||
let self_width = self.width.to_number() as usize;
|
||||
let self_weight = self.weight.to_number() as usize;
|
||||
let self_style: usize = match self.style {
|
||||
Style::Normal => 0,
|
||||
Style::Italic => 1,
|
||||
Style::Oblique => 2,
|
||||
};
|
||||
|
||||
let other_width = other.width.to_number() as usize;
|
||||
let other_weight = other.weight.to_number() as usize;
|
||||
let other_style: usize = match other.style {
|
||||
Style::Normal => 0,
|
||||
Style::Italic => 1,
|
||||
Style::Oblique => 2,
|
||||
};
|
||||
|
||||
let width = self_width.abs_diff(other_width) * width_weight;
|
||||
let weight = self_weight.abs_diff(other_weight) * weight_weight;
|
||||
let style = self_style.abs_diff(other_style) * style_weight;
|
||||
width + weight + style
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
// ==================================
|
||||
// === NonVariableFaceHeaderMatch ===
|
||||
// ==================================
|
||||
|
||||
/// Indicates whether the provided variation was an exact match or a closest match was found.
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
||||
#[allow(missing_docs)]
|
||||
pub enum NonVariableFaceHeaderMatchType {
|
||||
Exact,
|
||||
Closest,
|
||||
}
|
||||
|
||||
/// Result of finding a closest font variation for a non-variable font family.
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
#[allow(missing_docs)]
|
||||
pub struct NonVariableFaceHeaderMatch {
|
||||
pub variations: NonVariableFaceHeader,
|
||||
pub match_type: NonVariableFaceHeaderMatchType,
|
||||
}
|
||||
|
||||
impl NonVariableFaceHeaderMatch {
|
||||
/// Constructor.
|
||||
pub fn exact(variations: NonVariableFaceHeader) -> Self {
|
||||
Self { variations, match_type: NonVariableFaceHeaderMatchType::Exact }
|
||||
}
|
||||
|
||||
/// Constructor.
|
||||
pub fn closest(variations: NonVariableFaceHeader) -> Self {
|
||||
Self { variations, match_type: NonVariableFaceHeaderMatchType::Closest }
|
||||
}
|
||||
|
||||
/// Checks whether the match was exact.
|
||||
pub fn was_exact(&self) -> bool {
|
||||
self.match_type == NonVariableFaceHeaderMatchType::Exact
|
||||
}
|
||||
|
||||
/// Checks whether the match was closest.
|
||||
pub fn was_closest(&self) -> bool {
|
||||
self.match_type == NonVariableFaceHeaderMatchType::Closest
|
||||
}
|
||||
}
|
@ -214,7 +214,7 @@ impl<L> TokenConsumer<L> for DocSectionCollector {
|
||||
}
|
||||
|
||||
fn start_raw(&mut self) {
|
||||
self.current_body.push_str("<pre>");
|
||||
self.current_body.push_str("<div class=\"example\">");
|
||||
}
|
||||
|
||||
fn start_quote(&mut self) {
|
||||
@ -252,7 +252,7 @@ impl<L> TokenConsumer<L> for DocSectionCollector {
|
||||
self.current_list.push(self.current_body.drain(..).collect());
|
||||
}
|
||||
ScopeType::Paragraph => (),
|
||||
ScopeType::Raw => self.current_body.push_str("</pre>"),
|
||||
ScopeType::Raw => self.current_body.push_str("</div>"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -754,7 +754,7 @@ mod tests {
|
||||
Marked {
|
||||
mark: Example,
|
||||
header: Some("Example".into()),
|
||||
body: "<p>Parse the text \"20220216\" into an integer number.<pre>\nInteger.parse \"20220216\"</pre>".into()
|
||||
body: "<p>Parse the text \"20220216\" into an integer number.<div class=\"example\">\nInteger.parse \"20220216\"</div>".into()
|
||||
}].to_vec();
|
||||
assert_eq!(res, expected);
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user