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:
Kaz Wesley 2023-08-17 11:36:12 -07:00 committed by GitHub
parent d15b3db0ac
commit 1dfdee5808
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
41 changed files with 940 additions and 973 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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
View File

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

View File

@ -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;

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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)?;

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

View File

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

View File

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

View File

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

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

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

View File

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

View File

@ -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,

View File

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

View File

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

View File

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

View File

@ -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, &copy_regions_only);
eval text_chubks_to_copy ((s) m.copy(s));
text_chunks_to_copy <- any(&sels_on_copy_whole_lines, &copy_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();

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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, &params);
// then
let data: Vec<f32> = msdf.data.iter().collect();
assert_eq!(-0.9408906, data[0]); // Note [asserts]
assert_eq!(0.2, data[10]);
assert_eq!(-4.3035655, data[data.len() - 1]);
assert_eq!(Vector2::new(3.03125, 1.0), msdf.translation);
assert_eq!(Vector2::new(1.25, 1.25), msdf.scale);
assert_eq!(19.265625, msdf.advance);
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, &params);
let msdf = Msdf::generate_by_index(&font, 2, &params);
// then
let data: Vec<f32> = msdf.data.iter().collect();
assert_eq!(-0.9408906, data[0]); // Note [asserts]
assert_eq!(0.2, data[10]);
assert_eq!(-4.3035655, data[data.len() - 1]);
assert_eq!(Vector2::new(3.03125, 1.0), msdf.translation);
assert_eq!(Vector2::new(1.25, 1.25), msdf.scale);
assert_eq!(19.265625, msdf.advance);
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
*/
}

View File

@ -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() {

View File

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

View File

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

View File

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

View File

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