mirror of
https://github.com/emilk/egui.git
synced 2026-08-30 13:20:05 -04:00
Replace ab_glyph with Skrifa + vello_cpu; enable font hinting (#7694)
<!-- Please read the "Making a PR" section of [`CONTRIBUTING.md`](https://github.com/emilk/egui/blob/main/CONTRIBUTING.md) before opening a Pull Request! * Keep your PR:s small and focused. * The PR title is what ends up in the changelog, so make it descriptive! * If applicable, add a screenshot or gif. * If it is a non-trivial addition, consider adding a demo for it to `egui_demo_lib`, or a new example. * Do NOT open PR:s from your `master` branch, as that makes it hard for maintainers to test and add commits to your PR. * Remember to run `cargo fmt` and `cargo clippy`. * Open the PR as a draft until you have self-reviewed it and run `./scripts/check.sh`. * When you have addressed a PR comment, mark it as resolved. Please be patient! I will review your PR, but my time is limited! --> * Closes N/A * [x] I have followed the instructions in the PR template I'll probably come back to this and clean it up a bit. This PR reimplements ab_glyph's functionality on top of Skrifa, a somewhat lower-level font API that's being used in Chrome now. Skrifa doesn't perform rasterization itself, so I'm using [vello_cpu](https://github.com/linebender/vello) from the Linebender project for rasterization. It's still in its early days, but I believe it's already quite fast. It also supports color and gradient fills, so color emoji support will be easier. Skrifa also supports font hinting, which should make text look a bit nicer / less blurry. Here's the current ab_glyph rendering: <img width="1592" height="1068" alt="image" src="https://github.com/user-attachments/assets/2385b66e-23f8-4c6e-b8c2-ea90e0eea4e4" /> Here's Skrifa *without* hinting--it looks almost identical, but there are some subpixel differences, probably due to rasterizer behavior: <img width="1592" height="1068" alt="image" src="https://github.com/user-attachments/assets/a815f3e9-65ac-4940-bc00-571177bef53d" /> Here's Skrifa *with* hinting: <img width="1592" height="1068" alt="image" src="https://github.com/user-attachments/assets/d6cc0669-3537-4377-bba9-ed5ef09664db" /> Hinting does make the horizontal strokes look a bit bolder, which makes me wonder once again about increasing the font weight from "light" to "regular". --------- Co-authored-by: Emil Ernerfeldt <emil.ernerfeldt@gmail.com>
This commit is contained in:
@@ -20,7 +20,7 @@ use crate::{
|
||||
ModifierNames, Modifiers, NumExt as _, Order, Painter, RawInput, Response, RichText,
|
||||
SafeAreaInsets, ScrollArea, Sense, Style, TextStyle, TextureHandle, TextureOptions, Ui,
|
||||
ViewportBuilder, ViewportCommand, ViewportId, ViewportIdMap, ViewportIdPair, ViewportIdSet,
|
||||
ViewportOutput, Widget as _, WidgetRect, WidgetText,
|
||||
ViewportOutput, Visuals, Widget as _, WidgetRect, WidgetText,
|
||||
animation_manager::AnimationManager,
|
||||
containers::{self, area::AreaState},
|
||||
data::output::PlatformOutput,
|
||||
@@ -34,8 +34,7 @@ use crate::{
|
||||
os::OperatingSystem,
|
||||
output::FullOutput,
|
||||
pass_state::PassState,
|
||||
plugin,
|
||||
plugin::TypedPluginHandle,
|
||||
plugin::{self, TypedPluginHandle},
|
||||
resize, response, scroll_area,
|
||||
util::IdTypeMap,
|
||||
viewport::ViewportClass,
|
||||
@@ -564,7 +563,10 @@ impl ContextImpl {
|
||||
log::trace!("Adding new fonts");
|
||||
}
|
||||
|
||||
let text_alpha_from_coverage = self.memory.options.style().visuals.text_alpha_from_coverage;
|
||||
let Visuals {
|
||||
mut text_options, ..
|
||||
} = self.memory.options.style().visuals;
|
||||
text_options.max_texture_side = max_texture_side;
|
||||
|
||||
let mut is_new = false;
|
||||
|
||||
@@ -573,16 +575,12 @@ impl ContextImpl {
|
||||
|
||||
is_new = true;
|
||||
profiling::scope!("Fonts::new");
|
||||
Fonts::new(
|
||||
max_texture_side,
|
||||
text_alpha_from_coverage,
|
||||
self.font_definitions.clone(),
|
||||
)
|
||||
Fonts::new(text_options, self.font_definitions.clone())
|
||||
});
|
||||
|
||||
{
|
||||
profiling::scope!("Fonts::begin_pass");
|
||||
fonts.begin_pass(max_texture_side, text_alpha_from_coverage);
|
||||
fonts.begin_pass(text_options);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2006,15 +2004,12 @@ impl Context {
|
||||
pub fn set_fonts(&self, font_definitions: FontDefinitions) {
|
||||
profiling::function_scope!();
|
||||
|
||||
let mut update_fonts = true;
|
||||
|
||||
self.read(|ctx| {
|
||||
if let Some(current_fonts) = ctx.fonts.as_ref() {
|
||||
// NOTE: this comparison is expensive since it checks TTF data for equality
|
||||
if current_fonts.definitions() == &font_definitions {
|
||||
update_fonts = false; // no need to update
|
||||
}
|
||||
}
|
||||
let update_fonts = self.read(|ctx| {
|
||||
// NOTE: this comparison is expensive since it checks TTF data for equality
|
||||
// TODO(valadaptive): add_font only checks the *names* for equality. Change this?
|
||||
ctx.fonts
|
||||
.as_ref()
|
||||
.is_none_or(|fonts| fonts.definitions() != &font_definitions)
|
||||
});
|
||||
|
||||
if update_fonts {
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
#![allow(clippy::if_same_then_else)]
|
||||
|
||||
use emath::Align;
|
||||
use epaint::{AlphaFromCoverage, CornerRadius, Shadow, Stroke, text::FontTweak};
|
||||
use epaint::{AlphaFromCoverage, CornerRadius, Shadow, Stroke, TextOptions, text::FontTweak};
|
||||
use std::{collections::BTreeMap, ops::RangeInclusive, sync::Arc};
|
||||
|
||||
use crate::{
|
||||
@@ -948,8 +948,11 @@ pub struct Visuals {
|
||||
/// this is more to provide a convenient summary of the rest of the settings.
|
||||
pub dark_mode: bool,
|
||||
|
||||
/// ADVANCED: Controls how we render text.
|
||||
pub text_alpha_from_coverage: AlphaFromCoverage,
|
||||
/// Controls how we render text.
|
||||
///
|
||||
/// The [`TextOptions::max_texture_side`] is ignored and overruled by
|
||||
/// [`crate::RawInput::max_texture_side`].
|
||||
pub text_options: TextOptions,
|
||||
|
||||
/// Override default text color for all text.
|
||||
///
|
||||
@@ -1407,7 +1410,10 @@ impl Visuals {
|
||||
pub fn dark() -> Self {
|
||||
Self {
|
||||
dark_mode: true,
|
||||
text_alpha_from_coverage: AlphaFromCoverage::DARK_MODE_DEFAULT,
|
||||
text_options: TextOptions {
|
||||
alpha_from_coverage: AlphaFromCoverage::DARK_MODE_DEFAULT,
|
||||
..Default::default()
|
||||
},
|
||||
override_text_color: None,
|
||||
weak_text_alpha: 0.6,
|
||||
weak_text_color: None,
|
||||
@@ -1470,7 +1476,10 @@ impl Visuals {
|
||||
pub fn light() -> Self {
|
||||
Self {
|
||||
dark_mode: false,
|
||||
text_alpha_from_coverage: AlphaFromCoverage::LIGHT_MODE_DEFAULT,
|
||||
text_options: TextOptions {
|
||||
alpha_from_coverage: AlphaFromCoverage::LIGHT_MODE_DEFAULT,
|
||||
..Default::default()
|
||||
},
|
||||
widgets: Widgets::light(),
|
||||
selection: Selection::light(),
|
||||
hyperlink_color: Color32::from_rgb(0, 155, 255),
|
||||
@@ -2107,7 +2116,7 @@ impl Visuals {
|
||||
pub fn ui(&mut self, ui: &mut crate::Ui) {
|
||||
let Self {
|
||||
dark_mode,
|
||||
text_alpha_from_coverage,
|
||||
text_options,
|
||||
override_text_color: _,
|
||||
weak_text_alpha,
|
||||
weak_text_color,
|
||||
@@ -2207,7 +2216,7 @@ impl Visuals {
|
||||
});
|
||||
});
|
||||
|
||||
ui.collapsing("Text color", |ui| {
|
||||
ui.collapsing("Text rendering", |ui| {
|
||||
fn ui_text_color(ui: &mut Ui, color: &mut Color32, label: impl Into<RichText>) {
|
||||
ui.label(label.into().color(*color));
|
||||
ui.color_edit_button_srgba(color);
|
||||
@@ -2259,7 +2268,15 @@ impl Visuals {
|
||||
|
||||
ui.add_space(4.0);
|
||||
|
||||
text_alpha_from_coverage_ui(ui, text_alpha_from_coverage);
|
||||
let TextOptions {
|
||||
max_texture_side: _,
|
||||
alpha_from_coverage,
|
||||
font_hinting,
|
||||
} = text_options;
|
||||
|
||||
text_alpha_from_coverage_ui(ui, alpha_from_coverage);
|
||||
|
||||
ui.checkbox(font_hinting, "Enable font hinting");
|
||||
});
|
||||
|
||||
ui.collapsing("Text cursor", |ui| {
|
||||
@@ -2370,9 +2387,9 @@ impl Visuals {
|
||||
}
|
||||
}
|
||||
|
||||
fn text_alpha_from_coverage_ui(ui: &mut Ui, text_alpha_from_coverage: &mut AlphaFromCoverage) {
|
||||
fn text_alpha_from_coverage_ui(ui: &mut Ui, alpha_from_coverage: &mut AlphaFromCoverage) {
|
||||
let mut dark_mode_special =
|
||||
*text_alpha_from_coverage == AlphaFromCoverage::TwoCoverageMinusCoverageSq;
|
||||
*alpha_from_coverage == AlphaFromCoverage::TwoCoverageMinusCoverageSq;
|
||||
|
||||
ui.horizontal(|ui| {
|
||||
ui.label("Text rendering:");
|
||||
@@ -2380,9 +2397,9 @@ fn text_alpha_from_coverage_ui(ui: &mut Ui, text_alpha_from_coverage: &mut Alpha
|
||||
ui.checkbox(&mut dark_mode_special, "Dark-mode special");
|
||||
|
||||
if dark_mode_special {
|
||||
*text_alpha_from_coverage = AlphaFromCoverage::TwoCoverageMinusCoverageSq;
|
||||
*alpha_from_coverage = AlphaFromCoverage::DARK_MODE_DEFAULT;
|
||||
} else {
|
||||
let mut gamma = match text_alpha_from_coverage {
|
||||
let mut gamma = match alpha_from_coverage {
|
||||
AlphaFromCoverage::Linear => 1.0,
|
||||
AlphaFromCoverage::Gamma(gamma) => *gamma,
|
||||
AlphaFromCoverage::TwoCoverageMinusCoverageSq => 0.5, // approximately the same
|
||||
@@ -2396,9 +2413,9 @@ fn text_alpha_from_coverage_ui(ui: &mut Ui, text_alpha_from_coverage: &mut Alpha
|
||||
);
|
||||
|
||||
if gamma == 1.0 {
|
||||
*text_alpha_from_coverage = AlphaFromCoverage::Linear;
|
||||
*alpha_from_coverage = AlphaFromCoverage::Linear;
|
||||
} else {
|
||||
*text_alpha_from_coverage = AlphaFromCoverage::Gamma(gamma);
|
||||
*alpha_from_coverage = AlphaFromCoverage::Gamma(gamma);
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -2812,6 +2829,7 @@ impl Widget for &mut FontTweak {
|
||||
scale,
|
||||
y_offset_factor,
|
||||
y_offset,
|
||||
hinting_override,
|
||||
} = self;
|
||||
|
||||
ui.label("Scale");
|
||||
@@ -2827,6 +2845,19 @@ impl Widget for &mut FontTweak {
|
||||
ui.add(DragValue::new(y_offset).speed(-0.02));
|
||||
ui.end_row();
|
||||
|
||||
ui.label("hinting_override");
|
||||
ComboBox::from_id_salt("hinting_override")
|
||||
.selected_text(match hinting_override {
|
||||
None => "None",
|
||||
Some(true) => "Enable",
|
||||
Some(false) => "Disable",
|
||||
})
|
||||
.show_ui(ui, |ui| {
|
||||
ui.selectable_value(hinting_override, None, "None");
|
||||
ui.selectable_value(hinting_override, Some(true), "Enable");
|
||||
ui.selectable_value(hinting_override, Some(false), "Disable");
|
||||
});
|
||||
|
||||
if ui.button("Reset").clicked() {
|
||||
*self = Default::default();
|
||||
}
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:784cbcdfd8deaf61e7b663f9416d67724e6a6a189a20ba3351908aa5c5f2deff
|
||||
size 336159
|
||||
oid sha256:7051c34854469652d2d953f3110ebcf6fd60f8ee9a2b0c134d9f7255ef180ce5
|
||||
size 335353
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:4cdde1dda0e64f584c769c72f5910a7035e6a4a86a074b590e88365f12570109
|
||||
size 94062
|
||||
oid sha256:49823cfa4dfba54e54d0122f2bbb246c1daad2ca3ba15071c1ca44eeb3662855
|
||||
size 92791
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:824d941ea538fd44fc374f5df1893eee2309004c0ee5e69a97f1c84a74b2b423
|
||||
size 182128
|
||||
oid sha256:1b65b6b1a3afe41337b8fe537525677284e49bd90be29cddb837787162ee452a
|
||||
size 169596
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:44ea7ac8c8e22eb51fbcb63f00c8510de0e6ae126d19ab44c5d708d979b5362b
|
||||
size 100345
|
||||
oid sha256:9c3af0c37a6997abe549dd28450c41d3d18bbc99d9577997d493566fbb7f9277
|
||||
size 96709
|
||||
|
||||
@@ -161,15 +161,11 @@ pub fn criterion_benchmark(c: &mut Criterion) {
|
||||
|
||||
{
|
||||
let pixels_per_point = 1.0;
|
||||
let max_texture_side = 8 * 1024;
|
||||
let wrap_width = 512.0;
|
||||
let font_id = egui::FontId::default();
|
||||
let text_color = egui::Color32::WHITE;
|
||||
let mut fonts = egui::epaint::text::Fonts::new(
|
||||
max_texture_side,
|
||||
egui::epaint::AlphaFromCoverage::default(),
|
||||
egui::FontDefinitions::default(),
|
||||
);
|
||||
let mut fonts =
|
||||
egui::epaint::text::Fonts::new(Default::default(), egui::FontDefinitions::default());
|
||||
{
|
||||
c.bench_function("text_layout_uncached", |b| {
|
||||
b.iter(|| {
|
||||
@@ -209,7 +205,7 @@ pub fn criterion_benchmark(c: &mut Criterion) {
|
||||
|
||||
let mut rng = rand::rng();
|
||||
b.iter(|| {
|
||||
fonts.begin_pass(max_texture_side, egui::epaint::AlphaFromCoverage::default());
|
||||
fonts.begin_pass(egui::epaint::TextOptions::default());
|
||||
|
||||
// Delete a random character, simulating a user making an edit in a long file:
|
||||
let mut new_string = string.clone();
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:30929184fab7e7d5975243d86bcab79cd9f7a0c5d57dd9ae827464ff6570be7b
|
||||
size 31795
|
||||
oid sha256:0c6f6847df5b3bfdcb020c1f897a57ffe0971e9de1e6977b19d3909730e1b9a5
|
||||
size 30957
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:cb944eca56724f6a2106ea8db2043dc94c0ea40bdd4cdeb0e520790f97cc9598
|
||||
size 27049
|
||||
oid sha256:43ef176837f05d1795eddda2fee344e935ff6d53edc26548c97eea191d4c6ca2
|
||||
size 25839
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:5e4a6476a2bb8980a9207868b77a253c65c0ba8433f843bb17e622856695b720
|
||||
size 27686
|
||||
oid sha256:55b899e115bbb7a17e0e40216479f8fb3a343deddf929e4af6af137a3bf6d4b8
|
||||
size 25632
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:5c1951b99908326b3f05ebb72aa4d02d0f297bdd925f38ded09041fae45400c1
|
||||
size 85217
|
||||
oid sha256:8ed04e25b2e961f44e2911a037c93729b0cb4de82b2e51cab145abbcb7b4efea
|
||||
size 74543
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:4c303fa620a2c7bc491a0ac1f9afdf9601b352e0e5163526c5f8732edf6bd6b3
|
||||
size 63404
|
||||
oid sha256:1bc9711ea98472bae9267190a91d3240146f4ce9a0caf0cf462d322581ec96aa
|
||||
size 60508
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:0df751bac5947c9bf6f82d075cf5670a562742b80d6c512bcd642da5ed449d26
|
||||
size 25975
|
||||
oid sha256:111caa91ae0658acc17a7dd49582b780ae706ba4ac7812d2a63e09a18a0be967
|
||||
size 25585
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:0e26e87f2909414b614278a1cf0b485cda425aceb5419906426615dccdcbef2b
|
||||
size 20877
|
||||
oid sha256:7e34217e8a006721bc4525277b96cf99618e3275626aa054b97a8cb4c7c963ab
|
||||
size 19937
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:20e3050bd41c7b9d225feb71f3bea3fdd1b8f749f77c4d140b5e560f53eb32b7
|
||||
size 10731
|
||||
oid sha256:e62836d9afa18cf4e486fe2819e652bf5df160026dc258201db0b99a75bdf7f1
|
||||
size 10125
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:1227636b03a7d35db3482b19f6059ec7aaf03ca795edadd5338056be6f6a8f7f
|
||||
size 126724
|
||||
oid sha256:c504102299780498c6b3e463e627588653446caa10bdebc4366900d0e46b649c
|
||||
size 112349
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:c01e96bf0aab24dbcfc05f2a6dcb0ffcddff69ec2474797de4fbce0a0670a8cd
|
||||
size 24964
|
||||
oid sha256:d44e5abe3f64d5f72bbf7519b6ead816adf1f8ad22711e8ef8f34f9574223d4b
|
||||
size 24152
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:ec357eafd145194f99c36a53a149a8b331fd691c5088df43ee96282b84bc81a4
|
||||
size 99439
|
||||
oid sha256:38d3d349f3c31b6e5a5a04984d290e2e36441b2ced7ac2060b6c2311715068d9
|
||||
size 96806
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:b5c95085e3c78b3fa1cc39ebd032834bd5ab5a80c3a2cad482d8a5bcbad004b9
|
||||
size 18064
|
||||
oid sha256:1f949b6ce193d360e9624b9e29e21413021828237de944c594beb5c4c3fb60e1
|
||||
size 17320
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:28a69a52c07344576f2b5335497151e4e923b838dfaec9791402949ffb099c12
|
||||
size 116116
|
||||
oid sha256:a61c10399c3d48d2cbbb4c6cb3beeaf5b448d4a4eb56f6709ad22e2a67b6cff0
|
||||
size 111994
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:e1378d865af3df02e12a0c4bc087620a4e9ef0029221db3180cdd2fd34f69d7f
|
||||
size 24832
|
||||
oid sha256:17027c0e50ca6f3543897420cab3d94156c514160e7c4db4ecc5db470e801ee0
|
||||
size 24014
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:bb3f7b5f790830b46d1410c2bbb5e19c6beb403f8fe979eb8d250fba4f89be3e
|
||||
size 51670
|
||||
oid sha256:1525cf27432b6d50a2dac99f400550f5018ddde8f62f77364c536455529e494b
|
||||
size 49780
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:70b00222e6c63f97bfd8c7a179c15cfaba93f8f2566702d4b03997f4714fe6cb
|
||||
size 22609
|
||||
oid sha256:2777a8d4d64983512d42074129d1c9d3bde2e43ec9f3b3b929a2df5320eb01e6
|
||||
size 21650
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:ad26106a86a6236f0db1c51bed754b370530813e9bb6e36c1be2948820fbef25
|
||||
size 47827
|
||||
oid sha256:88a10a92d0fd0104c7883434b5e49f424f7100ab5013a456216c6bf5bb1e4076
|
||||
size 45904
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:afa66ba8daca5c00f9c49b1d9173ad8f5e826247d3a9369d7e7c360cbdfcb72e
|
||||
size 22928
|
||||
oid sha256:48867e0418b2002c5e3f6fae69e6a30d0ef69b3d2dc98a1f7ee175064596450f
|
||||
size 21879
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:170cee9d72a4ab59aa2faf1b77aff4a9eee64f3380aa3f1b256340d88b1dabc2
|
||||
size 66525
|
||||
oid sha256:08b787f4e579746d87338b669d5754f8fecfdd1cb7cf23f6f3dcd1e483054dcb
|
||||
size 63759
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:90ab689d8a5034f5cab2ae2b44a8054d6dd815b3d295bee040c5bfcdf4564dee
|
||||
size 33063
|
||||
oid sha256:2ce9633fe06a9bd63d6219f0f7764fa5459a5441a35f385234aa5051495ad48a
|
||||
size 32357
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:bf7f0a76424a959ede7afbb0eaf777638038cc6fe208ef710d9d82638d68b4d0
|
||||
size 37848
|
||||
oid sha256:dc89d49518a6a41c346cf9146657474f5b8898fa0f53e2aedc9c350c62865c41
|
||||
size 36721
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:2d2370972781f15a1d602deca28bca38f1c077152801870edf2112650b8b1349
|
||||
size 17708
|
||||
oid sha256:b040b83a49b599d0833ca3ce53ba974e0672e6a2eb1e711fc87e3a59718a7d89
|
||||
size 17003
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:f7a7d0e2618b852b5966073438c95cb62901d5410c1473639920b0b0bf2ec59b
|
||||
size 256913
|
||||
oid sha256:9da1fc5172d2d20ac44048f34b1cead35eb32a9bffebf8d9c031686880c767b7
|
||||
size 247070
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:f8b937a8a63de6fedcd0f9748b1d04cd863331a297bec78906885a0107def32a
|
||||
size 61242
|
||||
oid sha256:24088f20928106f51c38197b4c5d61d1c0d32371ca94018dc0f8b4f9e43700d0
|
||||
size 53228
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:a11b0aeb8b8a7ff3acd54b99869af03cd04cc2edf13afc713ce924c52d39262d
|
||||
size 24826
|
||||
oid sha256:ecfa78eda551ae362b65118b82054ec640ebd80ddcaf1eee5fbec002bba2bcde
|
||||
size 24722
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:2855bd95ab33b5232edada1f65684bbba2748025b6b64eb9ac68a5f2d10ad4bd
|
||||
size 34491
|
||||
oid sha256:dbbd302b6dcd22b89567747ad791f1d05ead01292fb7146ef8ab04e99e2a6c97
|
||||
size 31886
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:be599ae66323140bba4a7d63546acbf84340b57e2d82d4736bf3fe590040319d
|
||||
size 23623
|
||||
oid sha256:c9d4f953f7cffc647da604e32caa8b122aec6e940b9af38756c4f8746d1a1b31
|
||||
size 22691
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:01c3cb5e8972e0cab5325328f93af8f51b35a0d61016e74969eab0f7ddea1e02
|
||||
size 176973
|
||||
oid sha256:e2fa6340b31dfb2cf9b31b88391555357d84ec4bd062cb1039838d078af07c2f
|
||||
size 170465
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:f2f6cedc262259d52c1fbf4283d99b4b62ec732e8688b1e2799a2581425e0564
|
||||
size 120342
|
||||
oid sha256:d1a2c686b37d7d70d09a0236ce83e4c6eed6c72f8e3cc02331a99cc376115234
|
||||
size 116410
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:142f65cee971f82a4917734c4f49ae233aa9a873028dca8c807d2825672bf2b2
|
||||
size 26657
|
||||
oid sha256:d3d99f7790cfe1eb9ff2e2f010756781bb5911cc8102c2d38ee3e82c04f8f944
|
||||
size 25450
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:72f4c6fe4f5ec243506152027e1150f3069caf98511ceef92b8fea4f6a1563d5
|
||||
size 77614
|
||||
oid sha256:ab7d1620779aa75f6596d9f84707533f7f6b04bb9c51d8dfea10cfb7abdb7b73
|
||||
size 73525
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:cc24f146adf0282cfb51723b56c76eceb92f2988fc67bbeefd16b93950505922
|
||||
size 70110
|
||||
oid sha256:af2373c1ff32cc520f64e14a29a0c386b14df7dba04f6c281c20b537016b98dc
|
||||
size 68101
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:77aeaa1dcd391a571cb38732686e0b85b2d727975c02507a114d4e932f2c351b
|
||||
size 65562
|
||||
oid sha256:9b6eedb91e29999e0300707f88a44ffa941c72639a89383a507ed7e8c5d80731
|
||||
size 59421
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:5f964939ed1b3904706592915ca4fbbb951855ac88b466c51b835cd1c7467fb0
|
||||
size 21501
|
||||
oid sha256:c050180f968ac82287212f6f12eb242dd1266fd920f249cc6d48d8c9bfa1abe6
|
||||
size 20814
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:415b1ce17dd6df7ca7a86fed92750c2ef811ff64720a447ae3ca6be10090666e
|
||||
size 64624
|
||||
oid sha256:c105e9267e81541d11e73a10fcb92c80ae8daeba6b9c586800b07264d5143071
|
||||
size 61536
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:0eaf717bf0083737c4186ac39e7baf98f42fffb36b49434a6658eff1430a0ac6
|
||||
size 13187
|
||||
oid sha256:185b62db2f890b05a3fb9029dae8e4452a37e3caae5c7b993c9995aefa078eff
|
||||
size 12813
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:611a2d6c793a85eebe807b2ddd4446cc0bc21e4284343dd756e64f0232fb6815
|
||||
size 35991
|
||||
oid sha256:7425b60e0064dfd9e341fe55e059cbb6a372d526433cb3b1c234a105f16fd247
|
||||
size 34520
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:b85a2af24c3361a0008fd0996e8d7244dc3e289646ec7233e8bad39a586c871c
|
||||
size 44512
|
||||
oid sha256:4731c35a62a88533940c12a06cf8d31479c59a538ef69109b3a655e2d7ef3e43
|
||||
size 42109
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:67b709c116f56fba7e4e9f182018e84f46f6c6dd33a51f9d0524125dc2056b8c
|
||||
size 12950
|
||||
oid sha256:89074b8dab103a419bc3dac743da4d8c47f435fa55b98d8aab71f6c9fb4d39de
|
||||
size 12370
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:4b0a70c0d66306edbbc6f77d03ea624aa68b846656811d4cc7d76d28572d177b
|
||||
size 30723
|
||||
oid sha256:968c478d986fc71d8655492b19e833ca07bc0ab85899dc04022bc7cf1dcf782f
|
||||
size 29319
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:137ba69ac73a5e9aacc6bf3bbd589e8640b41c50ccfb49edcda4e2d6efed6c09
|
||||
size 13384
|
||||
oid sha256:7bd7b54ff60859e4d4793000bef3adbec4c071063bec6bfdbde62516c4fc3478
|
||||
size 12959
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:2f798c666ad21d3f9bb57826e30f2f6ef044543bc05af8c185e0e63c8297e824
|
||||
size 33181
|
||||
oid sha256:61e59f8360c567e20bf03b401362de7bb0f87716f13e817cc8da3df742ab68bf
|
||||
size 31869
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:44fc7d745b478fe937fa7c131871a00b26712a0317aaa027a088782533be6136
|
||||
size 7125
|
||||
oid sha256:7389e319d9153af313cc113d97b57d462da00feb0d5f99da211552af3ac7e18a
|
||||
size 6704
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:abfa00ef9385d380bbd188d6254f92d6839a94f368100e75a2780337438f969f
|
||||
size 11068
|
||||
oid sha256:d4480dd34ed36c6bdbc2084843dd136448b3934c22b3df3e40314ba6324b5b39
|
||||
size 10306
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:5e5c9015e2005429ba83a407ed1f7d4dfbf30624f666152e82079c6ed3b3cda5
|
||||
size 17238
|
||||
oid sha256:624bfa884431c35cc5d852b96653f13da17e60f8545471f9fb1c3bb85b40ffc8
|
||||
size 16555
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:3319a8bca1213fc3a2dd91ead155be1e25045bc614701250bc961848cfc42176
|
||||
size 7327
|
||||
oid sha256:bf665389ef43524e097a7ae4eec0aa01bb788bdbb306144f20f9133f74a64b2c
|
||||
size 6941
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:5172aa12f07b4abf4bb217b8952b4cf5cf61b688455751964a1b54433d8c05b1
|
||||
size 11709
|
||||
oid sha256:a2480d0f49a929993de70612572b321457b2507c149a25112064cfc27840e6ee
|
||||
size 11005
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:5e3eedd952d4416af73179451c0c90bcb76635c9c3c94d37f42bdd228ddbdd03
|
||||
size 18802
|
||||
oid sha256:1e0013b499934f47370a3a20b3d3a19f8a8c6db360752a35a3fb1d676d122263
|
||||
size 18068
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:60a44771c6bc9236593717236f9eca40bcb4723bac7567493cab4326f003eba3
|
||||
size 48693
|
||||
oid sha256:0054283b203602742d9819ba275c44ea40211ba18bf56fc66dca4fca766184d3
|
||||
size 47076
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:e7bc441559ff2d8723cf344113ce5ff8158e41179e4c93abcacbe7b1b13b3723
|
||||
size 48998
|
||||
oid sha256:6a40e3e7314cb32398797665b2bebb237f1a9cc79e306df9c29f7f04faa3a435
|
||||
size 47715
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:3e092be54efaeb700a63d9b679894647159f39a0d3062692ac7056e98242cbee
|
||||
size 45364
|
||||
oid sha256:b1ddff4f50b9a245d270cc13a0ebb9ac71d3590b83d401afb10ab107439cf235
|
||||
size 43893
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:88930779ac199e42fcc9ee25f29bd120478c129807713218370b617905340087
|
||||
size 45366
|
||||
oid sha256:50dea7c459cf291e6c0b3166354a7e622af6682842ec36f295d532df8c064b38
|
||||
size 43984
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:f9980486c36a0242f3b043a172c411d4fc9573543d3cd7c1c43bf020c18868a9
|
||||
size 619816
|
||||
oid sha256:db510af76578693c85ce78ca91224758a56f7bbf33db3221c9a4edca08b06600
|
||||
size 590547
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:040e2e486ae4773a084da99513a53c620e8e2bba215183ec26c6e489381d6254
|
||||
size 823086
|
||||
oid sha256:cae2b789e8afff23b7545d42a530e6c972d28736bad2bdacbc69f0e7065f85cc
|
||||
size 740660
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:439a5f942a5f05b9c09685ef90be94c150a21a68d1d235af22372b9b6a7b7389
|
||||
size 1035734
|
||||
oid sha256:09d9f567ec371d60881b525ddb462d9135552db97af5921a6eb02aba40e40616
|
||||
size 971544
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:3a3a9aa8383abfe4580be2cc9987f8123aeabf36bf8ec06029a9af64b9500ec9
|
||||
size 1206157
|
||||
oid sha256:3c383dd89fda6094704027074a72085591339a276d60502626d78e8e527b2e10
|
||||
size 1076719
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:fedd5546e36a89121c0bb0a780b0bbe081611c2c04013064872801181503fb83
|
||||
size 1231599
|
||||
oid sha256:0b4559541cf3259496c760a26f8d83e82179cb7e4576333682c5af49ee4a35a7
|
||||
size 1125331
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:69a7040336fc92c6d7b158283aabbc5817980c2fa84a1120b788516cf437b985
|
||||
size 1461979
|
||||
oid sha256:67c8412a1e8fdbfd88f8573797fbf6fbd89c6ce783a074a8e90f7d8d9e67dd57
|
||||
size 1366351
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:1a32d361afa20fc8c20122a89b01fe14b45849da42663991e589579f70fb8577
|
||||
size 46790
|
||||
oid sha256:a2b7b54a1af0f5cd31bd64f0506e3035dd423314ce3389e61730fa160434fbf3
|
||||
size 45074
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:c8d55205cf4225123da33895ed45eb186e5e57184ef5928400a4bae3ab6092be
|
||||
size 88548
|
||||
oid sha256:7b66a0be67ff2d684a54c2321123521b3ad06dfe5ebffd50e89260d77efcfcc4
|
||||
size 86833
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:77ab9e2e18c788f8cdbec171269afe4d0a90c52abeda7063cae3766dcaa5e93b
|
||||
size 120612
|
||||
oid sha256:19320291c99a23429b114a59de4636689e281e1e68766abe2aa1e56562128e50
|
||||
size 118919
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:36622a2f934503a7b60ded2f44b002e37eedde22d548dcf5a209f54c19548665
|
||||
size 53064
|
||||
oid sha256:5edf089c00715f1456fe7838e85aadcfc42b6216a3fd95b48d9c21fc8d700cba
|
||||
size 51371
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:4adeb7a77a0d0fe85097fcd190a99b49858dce11bde601d30335afcb6cc3d5f6
|
||||
size 56276
|
||||
oid sha256:6cd1a10639dcb323bdc3b2c43e0c35665184fc809731ced90088ee9edb9de845
|
||||
size 54577
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:f45249f7cc90433a64856905c727571c4ef20468e2c7d3ac2029e18a0477932d
|
||||
size 56743
|
||||
oid sha256:87e34024f701dc93f4026213ac7eb468a2cd6d3393eb0dbec382bf58007f8e61
|
||||
size 55042
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:d437f68c521f3e627a1b50d46605e8b0b343c5fc3716d9669e8c4436c8992b6f
|
||||
size 37602
|
||||
oid sha256:d7940ff56796efb27bec66b632ff33aa2ad390c4962a711bf520aee341f035a4
|
||||
size 35968
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:08ba98437403a08cca825ed8e288c9f088a46d9a1081f0b0a4ed7fbb9b49bb85
|
||||
size 37640
|
||||
oid sha256:b7bbd16c8aad444f0d11aacf87cf2292d494cc80a1ca46e7e8db86ca3041d35a
|
||||
size 35931
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:14f253fedc94985ff1431f1016d901d747e1f9948531cc6350f6615649f29056
|
||||
size 4862
|
||||
oid sha256:0475c5ac04ab8f79b79d43cfdb985f05b61dbe90e81f898a6dc216c308a28841
|
||||
size 4707
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:e1ed0e40d08b2b9ea978a07a4b7bf282c9e2cba8c52896f12210f396241e1b78
|
||||
size 66859
|
||||
oid sha256:bbdc4199dee2ae853b8a240cd84528482dc6762233bd0d1249f2daa296b49487
|
||||
size 64172
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:28f9862dd6f16b99523f5880bf90346fd9455d0d44e7bdd530d523e0b2ef2d2c
|
||||
size 158892
|
||||
oid sha256:f6d38b6b47839d0e4eae530d203c83971fba8a41c9caa3d5b5d89ee7ed582613
|
||||
size 150090
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:98c40c99a237f8388d82259fba4388d7b1759fe7b4bf657d326532934135c934
|
||||
size 61119
|
||||
oid sha256:c0635f1564d6c9707efa68003fb8c9b6eb00408aa8f24c972e33c6c79fed5bdf
|
||||
size 59354
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:90d36311ce5b1dcf81cab22113620a3362f255059d1f52c6794c8249f960549e
|
||||
size 152215
|
||||
oid sha256:4288ee4a0d2229d59c31538179cdda50035a3849f69b400127e1618efe30cdc1
|
||||
size 145224
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:f297609dd69fd479377eaea7cd304b717e0523a650dbf76e19c6d1f1c5af1343
|
||||
size 4518
|
||||
oid sha256:3ca39801faddae7191ed054029263e8eca488d16e1fcbb40fed482d39fc89e8e
|
||||
size 4520
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:42911cbb500fa49170aac0da8e4167641c5d7c9724a6accd4d400258fc74e2d7
|
||||
size 8061
|
||||
oid sha256:bafe5d7129cd2137b8f7bc9662b894d959b7042c436443f835ecd421a0d9c33f
|
||||
size 8019
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:de4a197f82befde31b6966f902567c35cef96c7d541fd65b4207c693ab12bace
|
||||
size 2036
|
||||
oid sha256:cd2ff48cae729b3f957b1630bef23e94fb2176982c06ec3f123c1a0892fc536d
|
||||
size 1959
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:94ba2e648c981bf4afbd9b9d01eef0708f7067be6e4cefbdfacc13aa219c6289
|
||||
size 11253
|
||||
oid sha256:27e4950f17ab7f68f7e001ca3a4f3fc18943103f4745c87715dcf6c097e92a57
|
||||
size 11131
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:436999f511dce318f29172f0b7e2007e1f0fedae58f5e0e85e19f1d8e0bee361
|
||||
size 22273
|
||||
oid sha256:449cc473469dc80af81fe20e394dd90e67b4ae9c2033ebb7029726274d77d50c
|
||||
size 21644
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:28435faf5c8c6d880cd50d52050c9f4cd6b992d0c621f01ca28fb5502eed16a1
|
||||
size 29863
|
||||
oid sha256:1cbfc767ed169cbddb3c90c2b455daefd85925501e7e33c7a25a34a72fc13eac
|
||||
size 28512
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:f9a364b4b8c4ad3e78a80b0c6825d9de28c0e0d2e18dcfcd0ff18652ca86c859
|
||||
size 34750
|
||||
oid sha256:f4397b3d86bb5fe76d661cdb109ef71d43e771093bd9267b74722660d312ec7d
|
||||
size 33253
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:2fa5cb5b96232d729f89be8cc92263715fe7197e72343b71e57e53a359afe181
|
||||
size 19881
|
||||
oid sha256:e8038005841dbf272375388b224dcc9fc1177b5c113d3e6f6dbc2265c88c7e60
|
||||
size 19704
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:87c76a9d07174e4e24ad3d08585c1df7bf3628bdc8f183d11beb6f9e14c4b2ec
|
||||
size 2325
|
||||
oid sha256:fad8b83e553ffa6bfbc4d47b955f2180859048c3789dda99b640e27665d216c7
|
||||
size 2244
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:f038c7fdf31f35107ec6e29edc0895049160ccbe98d1577c16ae082605b58d52
|
||||
size 2207
|
||||
oid sha256:ad75a0e568e04c20d0e3b823c7e4906c39dcd0a69a086d8e30714a9e4530d031
|
||||
size 2128
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:4d94d4c3300d406fd1d93ddd90a9a46f99eac81a98a84f4d97a20fe4ef492e5d
|
||||
size 5674
|
||||
oid sha256:4216258893fae554f0ab8b3a76ef0905cacb62c70af47fa811ff6f3d99f9f3ab
|
||||
size 5619
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:460cb2e9c91d139334c797829fd82fa77d593e9e58531e57a6b649c7e8fad228
|
||||
size 7405
|
||||
oid sha256:eb8737af84c3d3b0c054b7e2a8bcb04685243d84cb13b72a1372dc40dbfd14fb
|
||||
size 7267
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:996985b155bd579cc4769d8cde5aa7e87c20ed909176da6b52dddeeb78a1cfba
|
||||
size 8290
|
||||
oid sha256:f1651bb1b9bbaa3c65ecd07c39c57527f4beb4c607581a5b2596a49dcf4c5db3
|
||||
size 7996
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:025942c144891b8862bf931385824e0484e60f4e7766f5d4401511c72ff20756
|
||||
size 2975
|
||||
oid sha256:21a92c29e27ef0fdec273ea2d94a2b3e74cdf380ec77f4783daeb008bd51db6d
|
||||
size 2767
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:025942c144891b8862bf931385824e0484e60f4e7766f5d4401511c72ff20756
|
||||
size 2975
|
||||
oid sha256:21a92c29e27ef0fdec273ea2d94a2b3e74cdf380ec77f4783daeb008bd51db6d
|
||||
size 2767
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:e269ede9c0784d00c153d51a13566d9c8f0d61ce11565997691fa63be06ec889
|
||||
size 5075
|
||||
oid sha256:f9ca5f8081d677b8bff47813c4eb94319ca03855e780aed834ecc2f3d905a22c
|
||||
size 4852
|
||||
|
||||
@@ -61,12 +61,14 @@ _override_unity = []
|
||||
emath.workspace = true
|
||||
ecolor.workspace = true
|
||||
|
||||
ab_glyph.workspace = true
|
||||
ahash.workspace = true
|
||||
log.workspace = true
|
||||
nohash-hasher.workspace = true
|
||||
parking_lot.workspace = true # Using parking_lot over std::sync::Mutex gives 50% speedups in some real-world scenarios.
|
||||
profiling = { workspace = true }
|
||||
profiling.workspace = true
|
||||
self_cell.workspace = true
|
||||
skrifa.workspace = true
|
||||
vello_cpu.workspace = true
|
||||
|
||||
#! ### Optional dependencies
|
||||
bytemuck = { workspace = true, optional = true, features = ["derive"] }
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
use criterion::{Criterion, criterion_group, criterion_main};
|
||||
|
||||
use epaint::{
|
||||
AlphaFromCoverage, ClippedShape, Color32, Mesh, PathStroke, Pos2, Rect, Shape, Stroke,
|
||||
TessellationOptions, Tessellator, TextureAtlas, Vec2, pos2, tessellator::Path,
|
||||
ClippedShape, Color32, Mesh, PathStroke, Pos2, Rect, Shape, Stroke, TessellationOptions,
|
||||
Tessellator, TextureAtlas, Vec2, pos2, tessellator::Path,
|
||||
};
|
||||
|
||||
use std::hint::black_box;
|
||||
@@ -68,7 +68,7 @@ fn tessellate_circles(c: &mut Criterion) {
|
||||
let pixels_per_point = 2.0;
|
||||
let options = TessellationOptions::default();
|
||||
|
||||
let atlas = TextureAtlas::new([4096, 256], AlphaFromCoverage::default());
|
||||
let atlas = TextureAtlas::new([4096, 256], Default::default());
|
||||
let font_tex_size = atlas.size();
|
||||
let prepared_discs = atlas.prepared_discs();
|
||||
|
||||
|
||||
@@ -62,7 +62,7 @@ pub use self::{
|
||||
stats::PaintStats,
|
||||
stroke::{PathStroke, Stroke, StrokeKind},
|
||||
tessellator::{TessellationOptions, Tessellator},
|
||||
text::{FontFamily, FontId, Fonts, FontsView, Galley},
|
||||
text::{FontFamily, FontId, Fonts, FontsView, Galley, TextOptions},
|
||||
texture_atlas::TextureAtlas,
|
||||
texture_handle::TextureHandle,
|
||||
textures::TextureManager,
|
||||
|
||||
@@ -185,11 +185,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn text_bounding_box_under_rotation() {
|
||||
let mut fonts = Fonts::new(
|
||||
1024,
|
||||
AlphaFromCoverage::default(),
|
||||
FontDefinitions::default(),
|
||||
);
|
||||
let mut fonts = Fonts::new(TextOptions::default(), FontDefinitions::default());
|
||||
let font = FontId::monospace(12.0);
|
||||
|
||||
let mut t = crate::Shape::text(
|
||||
|
||||
@@ -1,13 +1,20 @@
|
||||
#![allow(clippy::mem_forget)]
|
||||
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
use ab_glyph::{Font as _, OutlinedGlyph, PxScale};
|
||||
use emath::{GuiRounding as _, OrderedFloat, Vec2, vec2};
|
||||
use self_cell::self_cell;
|
||||
use skrifa::{
|
||||
MetadataProvider as _,
|
||||
raw::{TableProvider as _, tables::kern::SubtableKind},
|
||||
};
|
||||
use vello_cpu::{color, kurbo};
|
||||
|
||||
use crate::{
|
||||
TextureAtlas,
|
||||
TextOptions, TextureAtlas,
|
||||
text::{
|
||||
FontTweak,
|
||||
fonts::{CachedFamily, FontFaceKey},
|
||||
fonts::{Blob, CachedFamily, FontFaceKey},
|
||||
},
|
||||
};
|
||||
|
||||
@@ -43,9 +50,9 @@ pub struct GlyphInfo {
|
||||
/// Doesn't need to be unique.
|
||||
///
|
||||
/// Is `None` for a special "invisible" glyph.
|
||||
pub(crate) id: Option<ab_glyph::GlyphId>,
|
||||
pub(crate) id: Option<skrifa::GlyphId>,
|
||||
|
||||
/// In [`ab_glyph`]s "unscaled" coordinate system.
|
||||
/// In [`skrifa`]s "unscaled" coordinate system.
|
||||
pub advance_width_unscaled: OrderedFloat<f32>,
|
||||
}
|
||||
|
||||
@@ -123,8 +130,8 @@ pub struct GlyphAllocation {
|
||||
/// Used for pair-kerning.
|
||||
///
|
||||
/// Doesn't need to be unique.
|
||||
/// Use `ab_glyph::GlyphId(0)` if you just want to have an id, and don't care.
|
||||
pub(crate) id: ab_glyph::GlyphId,
|
||||
/// Use [`skrifa::GlyphId::NOTDEF`] if you just want to have an id, and don't care.
|
||||
pub(crate) id: skrifa::GlyphId,
|
||||
|
||||
/// Unit: screen pixels.
|
||||
pub advance_width_px: f32,
|
||||
@@ -139,7 +146,7 @@ struct GlyphCacheKey(u64);
|
||||
impl nohash_hasher::IsEnabled for GlyphCacheKey {}
|
||||
|
||||
impl GlyphCacheKey {
|
||||
fn new(glyph_id: ab_glyph::GlyphId, metrics: &ScaledMetrics, bin: SubpixelBin) -> Self {
|
||||
fn new(glyph_id: skrifa::GlyphId, metrics: &ScaledMetrics, bin: SubpixelBin) -> Self {
|
||||
let ScaledMetrics {
|
||||
pixels_per_point,
|
||||
px_scale_factor,
|
||||
@@ -164,41 +171,229 @@ impl GlyphCacheKey {
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
struct DependentFontData<'a> {
|
||||
skrifa: skrifa::FontRef<'a>,
|
||||
charmap: skrifa::charmap::Charmap<'a>,
|
||||
outline_glyphs: skrifa::outline::OutlineGlyphCollection<'a>,
|
||||
metrics: skrifa::metrics::Metrics,
|
||||
glyph_metrics: skrifa::metrics::GlyphMetrics<'a>,
|
||||
hinting_instance: Option<skrifa::outline::HintingInstance>,
|
||||
}
|
||||
|
||||
self_cell! {
|
||||
struct FontCell {
|
||||
owner: Blob,
|
||||
|
||||
#[covariant]
|
||||
dependent: DependentFontData,
|
||||
}
|
||||
}
|
||||
|
||||
impl FontCell {
|
||||
fn px_scale_factor(&self, scale: f32) -> f32 {
|
||||
let units_per_em = self.borrow_dependent().metrics.units_per_em as f32;
|
||||
scale / units_per_em
|
||||
}
|
||||
|
||||
fn allocate_glyph_uncached(
|
||||
&mut self,
|
||||
atlas: &mut TextureAtlas,
|
||||
metrics: &ScaledMetrics,
|
||||
glyph_info: &GlyphInfo,
|
||||
bin: SubpixelBin,
|
||||
) -> Option<GlyphAllocation> {
|
||||
let glyph_id = glyph_info.id?;
|
||||
|
||||
debug_assert!(
|
||||
glyph_id != skrifa::GlyphId::NOTDEF,
|
||||
"Can't allocate glyph for id 0"
|
||||
);
|
||||
|
||||
let mut path = kurbo::BezPath::new();
|
||||
let mut pen = VelloPen {
|
||||
path: &mut path,
|
||||
x_offset: bin.as_float() as f64,
|
||||
};
|
||||
|
||||
self.with_dependent_mut(|_, font_data| {
|
||||
let outline = font_data.outline_glyphs.get(glyph_id)?;
|
||||
|
||||
if let Some(hinting_instance) = &mut font_data.hinting_instance {
|
||||
let size = skrifa::instance::Size::new(metrics.scale);
|
||||
if hinting_instance.size() != size {
|
||||
hinting_instance
|
||||
.reconfigure(
|
||||
&font_data.outline_glyphs,
|
||||
size,
|
||||
skrifa::instance::LocationRef::default(),
|
||||
skrifa::outline::Target::Smooth {
|
||||
mode: skrifa::outline::SmoothMode::Normal,
|
||||
symmetric_rendering: true,
|
||||
preserve_linear_metrics: true,
|
||||
},
|
||||
)
|
||||
.ok()?;
|
||||
}
|
||||
let draw_settings = skrifa::outline::DrawSettings::hinted(hinting_instance, false);
|
||||
outline.draw(draw_settings, &mut pen).ok()?;
|
||||
} else {
|
||||
let draw_settings = skrifa::outline::DrawSettings::unhinted(
|
||||
skrifa::instance::Size::new(metrics.scale),
|
||||
skrifa::instance::LocationRef::default(),
|
||||
);
|
||||
outline.draw(draw_settings, &mut pen).ok()?;
|
||||
}
|
||||
|
||||
Some(())
|
||||
})?;
|
||||
|
||||
let bounds = path.control_box().expand();
|
||||
let width = bounds.width() as u16;
|
||||
let height = bounds.height() as u16;
|
||||
|
||||
let mut ctx = vello_cpu::RenderContext::new(width, height);
|
||||
ctx.set_transform(kurbo::Affine::translate((-bounds.x0, -bounds.y0)));
|
||||
ctx.set_paint(color::OpaqueColor::<color::Srgb>::WHITE);
|
||||
ctx.fill_path(&path);
|
||||
let mut dest = vello_cpu::Pixmap::new(width, height);
|
||||
ctx.render_to_pixmap(&mut dest);
|
||||
let uv_rect = if width == 0 || height == 0 {
|
||||
UvRect::default()
|
||||
} else {
|
||||
let glyph_pos = {
|
||||
let alpha_from_coverage = atlas.options().alpha_from_coverage;
|
||||
let (glyph_pos, image) = atlas.allocate((width as usize, height as usize));
|
||||
let pixels = dest.data_as_u8_slice();
|
||||
for y in 0..height as usize {
|
||||
for x in 0..width as usize {
|
||||
image[(x + glyph_pos.0, y + glyph_pos.1)] = alpha_from_coverage
|
||||
.color_from_coverage(
|
||||
pixels[((y * width as usize) + x) * 4 + 3] as f32 / 255.0,
|
||||
);
|
||||
}
|
||||
}
|
||||
glyph_pos
|
||||
};
|
||||
let offset_in_pixels = vec2(bounds.x0 as f32, bounds.y0 as f32);
|
||||
let offset =
|
||||
offset_in_pixels / metrics.pixels_per_point + metrics.y_offset_in_points * Vec2::Y;
|
||||
UvRect {
|
||||
offset,
|
||||
size: vec2(width as f32, height as f32) / metrics.pixels_per_point,
|
||||
min: [glyph_pos.0 as u16, glyph_pos.1 as u16],
|
||||
max: [
|
||||
(glyph_pos.0 + width as usize) as u16,
|
||||
(glyph_pos.1 + height as usize) as u16,
|
||||
],
|
||||
}
|
||||
};
|
||||
|
||||
Some(GlyphAllocation {
|
||||
id: glyph_id,
|
||||
advance_width_px: glyph_info.advance_width_unscaled.0 * metrics.px_scale_factor,
|
||||
uv_rect,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
struct VelloPen<'a> {
|
||||
path: &'a mut kurbo::BezPath,
|
||||
x_offset: f64,
|
||||
}
|
||||
|
||||
impl skrifa::outline::OutlinePen for VelloPen<'_> {
|
||||
fn move_to(&mut self, x: f32, y: f32) {
|
||||
self.path.move_to((x as f64 + self.x_offset, -y as f64));
|
||||
}
|
||||
|
||||
fn line_to(&mut self, x: f32, y: f32) {
|
||||
self.path.line_to((x as f64 + self.x_offset, -y as f64));
|
||||
}
|
||||
|
||||
fn quad_to(&mut self, cx0: f32, cy0: f32, x: f32, y: f32) {
|
||||
self.path.quad_to(
|
||||
(cx0 as f64 + self.x_offset, -cy0 as f64),
|
||||
(x as f64 + self.x_offset, -y as f64),
|
||||
);
|
||||
}
|
||||
|
||||
fn curve_to(&mut self, cx0: f32, cy0: f32, cx1: f32, cy1: f32, x: f32, y: f32) {
|
||||
self.path.curve_to(
|
||||
(cx0 as f64 + self.x_offset, -cy0 as f64),
|
||||
(cx1 as f64 + self.x_offset, -cy1 as f64),
|
||||
(x as f64 + self.x_offset, -y as f64),
|
||||
);
|
||||
}
|
||||
|
||||
fn close(&mut self) {
|
||||
self.path.close_path();
|
||||
}
|
||||
}
|
||||
|
||||
/// A specific font face.
|
||||
/// The interface uses points as the unit for everything.
|
||||
pub struct FontImpl {
|
||||
pub struct FontFace {
|
||||
name: String,
|
||||
ab_glyph_font: ab_glyph::FontArc,
|
||||
font: FontCell,
|
||||
tweak: FontTweak,
|
||||
glyph_info_cache: ahash::HashMap<char, GlyphInfo>,
|
||||
glyph_alloc_cache: ahash::HashMap<GlyphCacheKey, GlyphAllocation>,
|
||||
}
|
||||
|
||||
trait FontExt {
|
||||
fn px_scale_factor(&self, scale: f32) -> f32;
|
||||
}
|
||||
impl FontFace {
|
||||
pub fn new(
|
||||
options: TextOptions,
|
||||
name: String,
|
||||
font_data: Blob,
|
||||
index: u32,
|
||||
tweak: FontTweak,
|
||||
) -> Result<Self, Box<dyn std::error::Error>> {
|
||||
let font = FontCell::try_new(font_data, |font_data| {
|
||||
let skrifa_font =
|
||||
skrifa::FontRef::from_index(AsRef::<[u8]>::as_ref(font_data.as_ref()), index)?;
|
||||
|
||||
impl<T> FontExt for T
|
||||
where
|
||||
T: ab_glyph::Font,
|
||||
{
|
||||
fn px_scale_factor(&self, scale: f32) -> f32 {
|
||||
let units_per_em = self.units_per_em().unwrap_or_else(|| {
|
||||
panic!("The font unit size exceeds the expected range (16..=16384)")
|
||||
});
|
||||
scale / units_per_em
|
||||
}
|
||||
}
|
||||
let charmap = skrifa_font.charmap();
|
||||
let glyphs = skrifa_font.outline_glyphs();
|
||||
let metrics = skrifa_font.metrics(
|
||||
skrifa::instance::Size::unscaled(),
|
||||
skrifa::instance::LocationRef::default(),
|
||||
);
|
||||
let glyph_metrics = skrifa_font.glyph_metrics(
|
||||
skrifa::instance::Size::unscaled(),
|
||||
skrifa::instance::LocationRef::default(),
|
||||
);
|
||||
|
||||
impl FontImpl {
|
||||
pub fn new(name: String, ab_glyph_font: ab_glyph::FontArc, tweak: FontTweak) -> Self {
|
||||
Self {
|
||||
let hinting_enabled = tweak.hinting_override.unwrap_or(options.font_hinting);
|
||||
let hinting_instance = hinting_enabled
|
||||
.then(|| {
|
||||
// It doesn't really matter what we put here for options. Since the size is `unscaled()`, we will
|
||||
// always reconfigure this hinting instance with the real options when rendering for the first time.
|
||||
skrifa::outline::HintingInstance::new(
|
||||
&glyphs,
|
||||
skrifa::instance::Size::unscaled(),
|
||||
skrifa::instance::LocationRef::default(),
|
||||
skrifa::outline::Target::default(),
|
||||
)
|
||||
.ok()
|
||||
})
|
||||
.flatten();
|
||||
|
||||
Ok::<DependentFontData<'_>, Box<dyn std::error::Error>>(DependentFontData {
|
||||
skrifa: skrifa_font,
|
||||
charmap,
|
||||
outline_glyphs: glyphs,
|
||||
metrics,
|
||||
glyph_metrics,
|
||||
hinting_instance,
|
||||
})
|
||||
})?;
|
||||
Ok(Self {
|
||||
name,
|
||||
ab_glyph_font,
|
||||
font,
|
||||
tweak,
|
||||
glyph_info_cache: Default::default(),
|
||||
glyph_alloc_cache: Default::default(),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// Code points that will always be replaced by the replacement character.
|
||||
@@ -223,10 +418,11 @@ impl FontImpl {
|
||||
|
||||
/// An un-ordered iterator over all supported characters.
|
||||
fn characters(&self) -> impl Iterator<Item = char> + '_ {
|
||||
self.ab_glyph_font
|
||||
.codepoint_ids()
|
||||
.map(|(_, chr)| chr)
|
||||
.filter(|&chr| !self.ignore_character(chr))
|
||||
self.font
|
||||
.borrow_dependent()
|
||||
.charmap
|
||||
.mappings()
|
||||
.filter_map(|(chr, _)| char::from_u32(chr).filter(|c| !self.ignore_character(*c)))
|
||||
}
|
||||
|
||||
/// `\n` will result in `None`
|
||||
@@ -258,7 +454,7 @@ impl FontImpl {
|
||||
// https://en.wikipedia.org/wiki/Thin_space
|
||||
|
||||
if let Some(space) = self.glyph_info(' ') {
|
||||
let em = self.ab_glyph_font.units_per_em().unwrap_or(1.0);
|
||||
let em = self.font.borrow_dependent().metrics.units_per_em as f32;
|
||||
let advance_width = f32::min(em / 6.0, space.advance_width_unscaled.0 * 0.5); // TODO(emilk): make configurable
|
||||
let glyph_info = GlyphInfo {
|
||||
advance_width_unscaled: advance_width.into(),
|
||||
@@ -275,52 +471,68 @@ impl FontImpl {
|
||||
return Some(glyph_info);
|
||||
}
|
||||
|
||||
// Add new character:
|
||||
let glyph_id = self.ab_glyph_font.glyph_id(c);
|
||||
let font_data = self.font.borrow_dependent();
|
||||
|
||||
if glyph_id.0 == 0 {
|
||||
None // unsupported character
|
||||
} else {
|
||||
let glyph_info = GlyphInfo {
|
||||
id: Some(glyph_id),
|
||||
advance_width_unscaled: self.ab_glyph_font.h_advance_unscaled(glyph_id).into(),
|
||||
};
|
||||
self.glyph_info_cache.insert(c, glyph_info);
|
||||
Some(glyph_info)
|
||||
}
|
||||
// Add new character:
|
||||
let glyph_id = font_data
|
||||
.charmap
|
||||
.map(c)
|
||||
.filter(|id| *id != skrifa::GlyphId::NOTDEF)?;
|
||||
|
||||
let glyph_info = GlyphInfo {
|
||||
id: Some(glyph_id),
|
||||
advance_width_unscaled: font_data
|
||||
.glyph_metrics
|
||||
.advance_width(glyph_id)
|
||||
.unwrap_or_default()
|
||||
.into(),
|
||||
};
|
||||
self.glyph_info_cache.insert(c, glyph_info);
|
||||
Some(glyph_info)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(super) fn pair_kerning_pixels(
|
||||
&self,
|
||||
metrics: &ScaledMetrics,
|
||||
last_glyph_id: ab_glyph::GlyphId,
|
||||
glyph_id: ab_glyph::GlyphId,
|
||||
last_glyph_id: skrifa::GlyphId,
|
||||
glyph_id: skrifa::GlyphId,
|
||||
) -> f32 {
|
||||
self.ab_glyph_font.kern_unscaled(last_glyph_id, glyph_id) * metrics.px_scale_factor
|
||||
let skrifa_font = &self.font.borrow_dependent().skrifa;
|
||||
let Ok(kern) = skrifa_font.kern() else {
|
||||
return 0.0;
|
||||
};
|
||||
kern.subtables()
|
||||
.find_map(|st| match st.ok()?.kind().ok()? {
|
||||
SubtableKind::Format0(table_ref) => table_ref.kerning(last_glyph_id, glyph_id),
|
||||
SubtableKind::Format1(_) => None,
|
||||
SubtableKind::Format2(subtable2) => subtable2.kerning(last_glyph_id, glyph_id),
|
||||
SubtableKind::Format3(table_ref) => table_ref.kerning(last_glyph_id, glyph_id),
|
||||
})
|
||||
.unwrap_or_default() as f32
|
||||
* metrics.px_scale_factor
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn pair_kerning(
|
||||
&self,
|
||||
metrics: &ScaledMetrics,
|
||||
last_glyph_id: ab_glyph::GlyphId,
|
||||
glyph_id: ab_glyph::GlyphId,
|
||||
last_glyph_id: skrifa::GlyphId,
|
||||
glyph_id: skrifa::GlyphId,
|
||||
) -> f32 {
|
||||
self.pair_kerning_pixels(metrics, last_glyph_id, glyph_id) / metrics.pixels_per_point
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn scaled_metrics(&self, pixels_per_point: f32, font_size: f32) -> ScaledMetrics {
|
||||
let pt_scale_factor = self
|
||||
.ab_glyph_font
|
||||
.px_scale_factor(font_size * self.tweak.scale);
|
||||
let ascent = (self.ab_glyph_font.ascent_unscaled() * pt_scale_factor).round_ui();
|
||||
let descent = (self.ab_glyph_font.descent_unscaled() * pt_scale_factor).round_ui();
|
||||
let line_gap = (self.ab_glyph_font.line_gap_unscaled() * pt_scale_factor).round_ui();
|
||||
let pt_scale_factor = self.font.px_scale_factor(font_size * self.tweak.scale);
|
||||
let font_data = self.font.borrow_dependent();
|
||||
let ascent = (font_data.metrics.ascent * pt_scale_factor).round_ui();
|
||||
let descent = (font_data.metrics.descent * pt_scale_factor).round_ui();
|
||||
let line_gap = (font_data.metrics.leading * pt_scale_factor).round_ui();
|
||||
|
||||
let scale = font_size * self.tweak.scale * pixels_per_point;
|
||||
let px_scale_factor = self.ab_glyph_font.px_scale_factor(scale);
|
||||
let px_scale_factor = self.font.px_scale_factor(scale);
|
||||
|
||||
let y_offset_in_points = ((font_size * self.tweak.scale * self.tweak.y_offset_factor)
|
||||
+ self.tweak.y_offset)
|
||||
@@ -329,6 +541,7 @@ impl FontImpl {
|
||||
ScaledMetrics {
|
||||
pixels_per_point,
|
||||
px_scale_factor,
|
||||
scale,
|
||||
y_offset_in_points,
|
||||
ascent,
|
||||
row_height: ascent - descent + line_gap,
|
||||
@@ -370,77 +583,20 @@ impl FontImpl {
|
||||
std::collections::hash_map::Entry::Vacant(entry) => entry,
|
||||
};
|
||||
|
||||
debug_assert!(glyph_id.0 != 0, "Can't allocate glyph for id 0");
|
||||
let allocation = self
|
||||
.font
|
||||
.allocate_glyph_uncached(atlas, metrics, &glyph_info, bin)
|
||||
.unwrap_or_default();
|
||||
|
||||
let uv_rect = self.ab_glyph_font.outline(glyph_id).map(|outline| {
|
||||
let glyph = ab_glyph::Glyph {
|
||||
id: glyph_id,
|
||||
// We bypass ab-glyph's scaling method because it uses the wrong scale
|
||||
// (https://github.com/alexheretic/ab-glyph/issues/15), and this field is never accessed when
|
||||
// rasterizing. We can just put anything here.
|
||||
scale: PxScale::from(0.0),
|
||||
position: ab_glyph::Point {
|
||||
x: bin.as_float(),
|
||||
y: 0.0,
|
||||
},
|
||||
};
|
||||
let outlined = OutlinedGlyph::new(
|
||||
glyph,
|
||||
outline,
|
||||
ab_glyph::PxScaleFactor {
|
||||
horizontal: metrics.px_scale_factor,
|
||||
vertical: metrics.px_scale_factor,
|
||||
},
|
||||
);
|
||||
let bb = outlined.px_bounds();
|
||||
let glyph_width = bb.width() as usize;
|
||||
let glyph_height = bb.height() as usize;
|
||||
if glyph_width == 0 || glyph_height == 0 {
|
||||
UvRect::default()
|
||||
} else {
|
||||
let glyph_pos = {
|
||||
let text_alpha_from_coverage = atlas.text_alpha_from_coverage;
|
||||
let (glyph_pos, image) = atlas.allocate((glyph_width, glyph_height));
|
||||
outlined.draw(|x, y, v| {
|
||||
if 0.0 < v {
|
||||
let px = glyph_pos.0 + x as usize;
|
||||
let py = glyph_pos.1 + y as usize;
|
||||
image[(px, py)] = text_alpha_from_coverage.color_from_coverage(v);
|
||||
}
|
||||
});
|
||||
glyph_pos
|
||||
};
|
||||
|
||||
let offset_in_pixels = vec2(bb.min.x, bb.min.y);
|
||||
let offset = offset_in_pixels / metrics.pixels_per_point
|
||||
+ metrics.y_offset_in_points * Vec2::Y;
|
||||
UvRect {
|
||||
offset,
|
||||
size: vec2(glyph_width as f32, glyph_height as f32) / metrics.pixels_per_point,
|
||||
min: [glyph_pos.0 as u16, glyph_pos.1 as u16],
|
||||
max: [
|
||||
(glyph_pos.0 + glyph_width) as u16,
|
||||
(glyph_pos.1 + glyph_height) as u16,
|
||||
],
|
||||
}
|
||||
}
|
||||
});
|
||||
let uv_rect = uv_rect.unwrap_or_default();
|
||||
|
||||
let allocation = GlyphAllocation {
|
||||
id: glyph_id,
|
||||
advance_width_px,
|
||||
uv_rect,
|
||||
};
|
||||
entry.insert(allocation);
|
||||
(allocation, h_pos_round)
|
||||
}
|
||||
}
|
||||
|
||||
// TODO(emilk): rename?
|
||||
/// Wrapper over multiple [`FontImpl`] (e.g. a primary + fallbacks for emojis)
|
||||
/// Wrapper over multiple [`FontFace`] (e.g. a primary + fallbacks for emojis)
|
||||
pub struct Font<'a> {
|
||||
pub(super) fonts_by_id: &'a mut nohash_hasher::IntMap<FontFaceKey, FontImpl>,
|
||||
pub(super) fonts_by_id: &'a mut nohash_hasher::IntMap<FontFaceKey, FontFace>,
|
||||
pub(super) cached_family: &'a mut CachedFamily,
|
||||
pub(super) atlas: &'a mut TextureAtlas,
|
||||
}
|
||||
@@ -471,7 +627,7 @@ impl Font<'_> {
|
||||
.fonts
|
||||
.first()
|
||||
.and_then(|key| self.fonts_by_id.get(key))
|
||||
.map(|font_impl| font_impl.scaled_metrics(pixels_per_point, font_size))
|
||||
.map(|font_face| font_face.scaled_metrics(pixels_per_point, font_size))
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
@@ -479,7 +635,7 @@ impl Font<'_> {
|
||||
pub fn glyph_width(&mut self, c: char, font_size: f32) -> f32 {
|
||||
let (key, glyph_info) = self.glyph_info(c);
|
||||
if let Some(font) = &self.fonts_by_id.get(&key) {
|
||||
glyph_info.advance_width_unscaled.0 * font.ab_glyph_font.px_scale_factor(font_size)
|
||||
glyph_info.advance_width_unscaled.0 * font.font.px_scale_factor(font_size)
|
||||
} else {
|
||||
0.0
|
||||
}
|
||||
@@ -524,7 +680,10 @@ pub struct ScaledMetrics {
|
||||
/// Translates "unscaled" units to physical (screen) pixels.
|
||||
pub px_scale_factor: f32,
|
||||
|
||||
/// Vertical offset, in UI points.
|
||||
/// Absolute scale in screen pixels, for skrifa.
|
||||
pub scale: f32,
|
||||
|
||||
/// Vertical offset, in UI points (not screen-space).
|
||||
pub y_offset_in_points: f32,
|
||||
|
||||
/// This is the distance from the top to the baseline.
|
||||
@@ -540,7 +699,7 @@ pub struct ScaledMetrics {
|
||||
|
||||
/// Code points that will always be invisible (zero width).
|
||||
///
|
||||
/// See also [`FontImpl::ignore_character`].
|
||||
/// See also [`FontFace::ignore_character`].
|
||||
#[inline]
|
||||
fn invisible_char(c: char) -> bool {
|
||||
if c == '\r' {
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
use std::{
|
||||
borrow::Cow,
|
||||
collections::BTreeMap,
|
||||
sync::{
|
||||
Arc,
|
||||
@@ -7,10 +8,10 @@ use std::{
|
||||
};
|
||||
|
||||
use crate::{
|
||||
AlphaFromCoverage, TextureAtlas,
|
||||
TextureAtlas,
|
||||
text::{
|
||||
Galley, LayoutJob, LayoutSection,
|
||||
font::{Font, FontImpl, GlyphInfo},
|
||||
Galley, LayoutJob, LayoutSection, TextOptions,
|
||||
font::{Font, FontFace, GlyphInfo},
|
||||
},
|
||||
};
|
||||
use emath::{NumExt as _, OrderedFloat};
|
||||
@@ -116,7 +117,7 @@ impl std::fmt::Display for FontFamily {
|
||||
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
|
||||
pub struct FontData {
|
||||
/// The content of a `.ttf` or `.otf` file.
|
||||
pub font: std::borrow::Cow<'static, [u8]>,
|
||||
pub font: Cow<'static, [u8]>,
|
||||
|
||||
/// Which font face in the file to use.
|
||||
/// When in doubt, use `0`.
|
||||
@@ -129,7 +130,7 @@ pub struct FontData {
|
||||
impl FontData {
|
||||
pub fn from_static(font: &'static [u8]) -> Self {
|
||||
Self {
|
||||
font: std::borrow::Cow::Borrowed(font),
|
||||
font: Cow::Borrowed(font),
|
||||
index: 0,
|
||||
tweak: Default::default(),
|
||||
}
|
||||
@@ -137,7 +138,7 @@ impl FontData {
|
||||
|
||||
pub fn from_owned(font: Vec<u8>) -> Self {
|
||||
Self {
|
||||
font: std::borrow::Cow::Owned(font),
|
||||
font: Cow::Owned(font),
|
||||
index: 0,
|
||||
tweak: Default::default(),
|
||||
}
|
||||
@@ -184,6 +185,11 @@ pub struct FontTweak {
|
||||
///
|
||||
/// Example value: `2.0`.
|
||||
pub y_offset: f32,
|
||||
|
||||
/// Override the global font hinting setting for this specific font.
|
||||
///
|
||||
/// `None` means use the global setting.
|
||||
pub hinting_override: Option<bool>,
|
||||
}
|
||||
|
||||
impl Default for FontTweak {
|
||||
@@ -192,24 +198,20 @@ impl Default for FontTweak {
|
||||
scale: 1.0,
|
||||
y_offset_factor: 0.0,
|
||||
y_offset: 0.0,
|
||||
hinting_override: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
fn ab_glyph_font_from_font_data(name: &str, data: &FontData) -> ab_glyph::FontArc {
|
||||
match &data.font {
|
||||
std::borrow::Cow::Borrowed(bytes) => {
|
||||
ab_glyph::FontRef::try_from_slice_and_index(bytes, data.index)
|
||||
.map(ab_glyph::FontArc::from)
|
||||
}
|
||||
std::borrow::Cow::Owned(bytes) => {
|
||||
ab_glyph::FontVec::try_from_vec_and_index(bytes.clone(), data.index)
|
||||
.map(ab_glyph::FontArc::from)
|
||||
}
|
||||
pub type Blob = Arc<dyn AsRef<[u8]> + Send + Sync>;
|
||||
|
||||
fn blob_from_font_data(data: &FontData) -> Blob {
|
||||
match data.clone().font {
|
||||
Cow::Borrowed(bytes) => Arc::new(bytes) as Blob,
|
||||
Cow::Owned(bytes) => Arc::new(bytes) as Blob,
|
||||
}
|
||||
.unwrap_or_else(|err| panic!("Error parsing {name:?} TTF/OTF font file: {err}"))
|
||||
}
|
||||
|
||||
/// Describes the font data and the sizes to use.
|
||||
@@ -438,7 +440,7 @@ pub(super) struct CachedFamily {
|
||||
impl CachedFamily {
|
||||
fn new(
|
||||
fonts: Vec<FontFaceKey>,
|
||||
fonts_by_id: &mut nohash_hasher::IntMap<FontFaceKey, FontImpl>,
|
||||
fonts_by_id: &mut nohash_hasher::IntMap<FontFaceKey, FontFace>,
|
||||
) -> Self {
|
||||
if fonts.is_empty() {
|
||||
return Self {
|
||||
@@ -476,11 +478,11 @@ impl CachedFamily {
|
||||
pub(crate) fn glyph_info_no_cache_or_fallback(
|
||||
&mut self,
|
||||
c: char,
|
||||
fonts_by_id: &mut nohash_hasher::IntMap<FontFaceKey, FontImpl>,
|
||||
fonts_by_id: &mut nohash_hasher::IntMap<FontFaceKey, FontFace>,
|
||||
) -> Option<(FontFaceKey, GlyphInfo)> {
|
||||
for font_key in &self.fonts {
|
||||
let font_impl = fonts_by_id.get_mut(font_key).expect("Nonexistent font ID");
|
||||
if let Some(glyph_info) = font_impl.glyph_info(c) {
|
||||
let font_face = fonts_by_id.get_mut(font_key).expect("Nonexistent font ID");
|
||||
if let Some(glyph_info) = font_face.glyph_info(c) {
|
||||
self.glyph_info_cache.insert(c, (*font_key, glyph_info));
|
||||
return Some((*font_key, glyph_info));
|
||||
}
|
||||
@@ -508,43 +510,29 @@ pub struct Fonts {
|
||||
impl Fonts {
|
||||
/// Create a new [`Fonts`] for text layout.
|
||||
/// This call is expensive, so only create one [`Fonts`] and then reuse it.
|
||||
///
|
||||
/// * `max_texture_side`: largest supported texture size (one side).
|
||||
pub fn new(
|
||||
max_texture_side: usize,
|
||||
text_alpha_from_coverage: AlphaFromCoverage,
|
||||
definitions: FontDefinitions,
|
||||
) -> Self {
|
||||
pub fn new(options: TextOptions, definitions: FontDefinitions) -> Self {
|
||||
Self {
|
||||
fonts: FontsImpl::new(max_texture_side, text_alpha_from_coverage, definitions),
|
||||
fonts: FontsImpl::new(options, definitions),
|
||||
galley_cache: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Call at the start of each frame with the latest known
|
||||
/// `pixels_per_point`, `max_texture_side`, and `text_alpha_from_coverage`.
|
||||
/// Call at the start of each frame with the latest known [`TextOptions`].
|
||||
///
|
||||
/// Call after painting the previous frame, but before using [`Fonts`] for the new frame.
|
||||
///
|
||||
/// This function will react to changes in `pixels_per_point`, `max_texture_side`, and `text_alpha_from_coverage`,
|
||||
/// This function will react to changes in [`TextOptions`],
|
||||
/// as well as notice when the font atlas is getting full, and handle that.
|
||||
pub fn begin_pass(
|
||||
&mut self,
|
||||
max_texture_side: usize,
|
||||
text_alpha_from_coverage: AlphaFromCoverage,
|
||||
) {
|
||||
let max_texture_side_changed = self.fonts.max_texture_side != max_texture_side;
|
||||
let text_alpha_from_coverage_changed =
|
||||
self.fonts.atlas.text_alpha_from_coverage != text_alpha_from_coverage;
|
||||
pub fn begin_pass(&mut self, options: TextOptions) {
|
||||
let text_options_changed = self.fonts.options() != &options;
|
||||
let font_atlas_almost_full = self.fonts.atlas.fill_ratio() > 0.8;
|
||||
let needs_recreate =
|
||||
max_texture_side_changed || text_alpha_from_coverage_changed || font_atlas_almost_full;
|
||||
let needs_recreate = text_options_changed || font_atlas_almost_full;
|
||||
|
||||
if needs_recreate {
|
||||
let definitions = self.fonts.definitions.clone();
|
||||
|
||||
*self = Self {
|
||||
fonts: FontsImpl::new(max_texture_side, text_alpha_from_coverage, definitions),
|
||||
fonts: FontsImpl::new(options, definitions),
|
||||
galley_cache: Default::default(),
|
||||
};
|
||||
}
|
||||
@@ -558,8 +546,8 @@ impl Fonts {
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn max_texture_side(&self) -> usize {
|
||||
self.fonts.max_texture_side
|
||||
pub fn options(&self) -> &TextOptions {
|
||||
self.texture_atlas().options()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
@@ -628,8 +616,8 @@ pub struct FontsView<'a> {
|
||||
|
||||
impl FontsView<'_> {
|
||||
#[inline]
|
||||
pub fn max_texture_side(&self) -> usize {
|
||||
self.fonts.max_texture_side
|
||||
pub fn options(&self) -> &TextOptions {
|
||||
self.fonts.options()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
@@ -671,6 +659,7 @@ impl FontsView<'_> {
|
||||
/// Height of one row of text in points.
|
||||
///
|
||||
/// Returns a value rounded to [`emath::GUI_ROUNDING`].
|
||||
#[inline]
|
||||
pub fn row_height(&mut self, font_id: &FontId) -> f32 {
|
||||
self.fonts
|
||||
.font(&font_id.family)
|
||||
@@ -716,6 +705,7 @@ impl FontsView<'_> {
|
||||
/// Will wrap text at the given width and line break at `\n`.
|
||||
///
|
||||
/// The implementation uses memoization so repeated calls are cheap.
|
||||
#[inline]
|
||||
pub fn layout(
|
||||
&mut self,
|
||||
text: String,
|
||||
@@ -730,6 +720,7 @@ impl FontsView<'_> {
|
||||
/// Will line break at `\n`.
|
||||
///
|
||||
/// The implementation uses memoization so repeated calls are cheap.
|
||||
#[inline]
|
||||
pub fn layout_no_wrap(
|
||||
&mut self,
|
||||
text: String,
|
||||
@@ -743,6 +734,7 @@ impl FontsView<'_> {
|
||||
/// Like [`Self::layout`], made for when you want to pick a color for the text later.
|
||||
///
|
||||
/// The implementation uses memoization so repeated calls are cheap.
|
||||
#[inline]
|
||||
pub fn layout_delayed_color(
|
||||
&mut self,
|
||||
text: String,
|
||||
@@ -759,10 +751,9 @@ impl FontsView<'_> {
|
||||
///
|
||||
/// Required in order to paint text.
|
||||
pub struct FontsImpl {
|
||||
max_texture_side: usize,
|
||||
definitions: FontDefinitions,
|
||||
atlas: TextureAtlas,
|
||||
fonts_by_id: nohash_hasher::IntMap<FontFaceKey, FontImpl>,
|
||||
fonts_by_id: nohash_hasher::IntMap<FontFaceKey, FontFace>,
|
||||
fonts_by_name: ahash::HashMap<String, FontFaceKey>,
|
||||
family_cache: ahash::HashMap<FontFamily, CachedFamily>,
|
||||
}
|
||||
@@ -770,36 +761,36 @@ pub struct FontsImpl {
|
||||
impl FontsImpl {
|
||||
/// Create a new [`FontsImpl`] for text layout.
|
||||
/// This call is expensive, so only create one [`FontsImpl`] and then reuse it.
|
||||
pub fn new(
|
||||
max_texture_side: usize,
|
||||
text_alpha_from_coverage: AlphaFromCoverage,
|
||||
definitions: FontDefinitions,
|
||||
) -> Self {
|
||||
let texture_width = max_texture_side.at_most(16 * 1024);
|
||||
pub fn new(options: TextOptions, definitions: FontDefinitions) -> Self {
|
||||
let texture_width = options.max_texture_side.at_most(16 * 1024);
|
||||
let initial_height = 32; // Keep initial font atlas small, so it is fast to upload to GPU. This will expand as needed anyways.
|
||||
let atlas = TextureAtlas::new([texture_width, initial_height], text_alpha_from_coverage);
|
||||
let atlas = TextureAtlas::new([texture_width, initial_height], options);
|
||||
|
||||
let mut fonts_by_id: nohash_hasher::IntMap<FontFaceKey, FontImpl> = Default::default();
|
||||
let mut font_impls: ahash::HashMap<String, FontFaceKey> = Default::default();
|
||||
let mut fonts_by_id: nohash_hasher::IntMap<FontFaceKey, FontFace> = Default::default();
|
||||
let mut fonts_by_name: ahash::HashMap<String, FontFaceKey> = Default::default();
|
||||
for (name, font_data) in &definitions.font_data {
|
||||
let tweak = font_data.tweak;
|
||||
let ab_glyph = ab_glyph_font_from_font_data(name, font_data);
|
||||
let font_impl = FontImpl::new(name.clone(), ab_glyph, tweak);
|
||||
let blob = blob_from_font_data(font_data);
|
||||
let font_face = FontFace::new(options, name.clone(), blob, font_data.index, tweak)
|
||||
.unwrap_or_else(|err| panic!("Error parsing {name:?} TTF/OTF font file: {err}"));
|
||||
let key = FontFaceKey::new();
|
||||
fonts_by_id.insert(key, font_impl);
|
||||
font_impls.insert(name.clone(), key);
|
||||
fonts_by_id.insert(key, font_face);
|
||||
fonts_by_name.insert(name.clone(), key);
|
||||
}
|
||||
|
||||
Self {
|
||||
max_texture_side,
|
||||
definitions,
|
||||
atlas,
|
||||
fonts_by_id,
|
||||
fonts_by_name: font_impls,
|
||||
fonts_by_name,
|
||||
family_cache: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn options(&self) -> &TextOptions {
|
||||
self.atlas.options()
|
||||
}
|
||||
|
||||
/// Get the right font implementation from [`FontFamily`].
|
||||
pub fn font(&mut self, family: &FontFamily) -> Font<'_> {
|
||||
let cached_family = self.family_cache.entry(family.clone()).or_insert_with(|| {
|
||||
@@ -1192,12 +1183,7 @@ mod tests {
|
||||
#[test]
|
||||
fn test_split_paragraphs() {
|
||||
for pixels_per_point in [1.0, 2.0_f32.sqrt(), 2.0] {
|
||||
let max_texture_side = 4096;
|
||||
let mut fonts = FontsImpl::new(
|
||||
max_texture_side,
|
||||
AlphaFromCoverage::default(),
|
||||
FontDefinitions::default(),
|
||||
);
|
||||
let mut fonts = FontsImpl::new(TextOptions::default(), FontDefinitions::default());
|
||||
|
||||
for halign in [Align::Min, Align::Center, Align::Max] {
|
||||
for justify in [false, true] {
|
||||
@@ -1255,11 +1241,7 @@ mod tests {
|
||||
let rounded_output_to_gui = [false, true];
|
||||
|
||||
for pixels_per_point in pixels_per_point {
|
||||
let mut fonts = FontsImpl::new(
|
||||
1024,
|
||||
AlphaFromCoverage::default(),
|
||||
FontDefinitions::default(),
|
||||
);
|
||||
let mut fonts = FontsImpl::new(TextOptions::default(), FontDefinitions::default());
|
||||
|
||||
for &max_width in &max_widths {
|
||||
for round_output_to_gui in rounded_output_to_gui {
|
||||
@@ -1306,7 +1288,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_fallback_glyph_width() {
|
||||
let mut fonts = Fonts::new(1024, AlphaFromCoverage::default(), FontDefinitions::empty());
|
||||
let mut fonts = Fonts::new(TextOptions::default(), FontDefinitions::empty());
|
||||
let mut view = fonts.with_pixels_per_point(1.0);
|
||||
|
||||
let width = view.glyph_width(&FontId::new(12.0, FontFamily::Proportional), ' ');
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user