mirror of
https://github.com/emilk/egui.git
synced 2026-08-29 04:40:03 -04:00
Add font variations API (#7859)
<!-- 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 This was mostly from last month, but I never got around to submitting it. This PR adds font variation coordinates to the `TextFormat` struct, and uses them when rendering text. The coordinates are stored in a `SmallVec`; I've chosen to store up to 2 inline, which makes it take up 24 bytes (the minimum possible for a `SmallVec`). The variation axis tags are stored as the `font_types::Tag` type, which I've chosen to re-export from `epaint::text`. The variation coordinates are resolved to a `skrifa::Location` during font rendering/scaling, and are cached in the same way as all the other scaled metrics. I've renamed the `ScaledMetrics` struct to `StyledMetrics`, since it now also contains the resolved variation coordinates. I haven't benchmarked the performance of text layout with variation coordinates, but the existing text layout performance is unchanged. I've replaced the API for manually overriding a font's weight (https://github.com/emilk/egui/pull/7790) with an API for manually overriding any variation coordinates via `FontTweak`. This should support the same use case as #7790 while being substantially more flexible. I have *not* yet added any higher-level API for mapping style attributes (weight, width, slant, etc) to variation coordinates or to different font faces within a single family. That's a pretty huge can of worms, and it'd involve rethinking the split between `FontId` and `TextFormat` (and whether `FontId` is so big that we should provide a way to reuse it). This API is intentionally pretty low-level for now. Likewise, I've intentionally not used variation coordinates when computing a font's row height. I can't think of any fonts that change their vertical metrics depending on variation axes, so this should be fine for now. --------- Co-authored-by: Emil Ernerfeldt <emil.ernerfeldt@gmail.com>
This commit is contained in:
@@ -9,6 +9,7 @@ isse = "isse" # part of @IsseW username
|
|||||||
tye = "tye" # part of @tye-exe username
|
tye = "tye" # part of @tye-exe username
|
||||||
ro = "ro" # read-only, also part of the username @Phen-Ro
|
ro = "ro" # read-only, also part of the username @Phen-Ro
|
||||||
typ = "typ" # Often used because `type` is a keyword in Rust
|
typ = "typ" # Often used because `type` is a keyword in Rust
|
||||||
|
wdth = "wdth" # The `wdth` tag is used in variable fonts
|
||||||
|
|
||||||
# I mistype these so often
|
# I mistype these so often
|
||||||
tesalator = "tessellator"
|
tesalator = "tessellator"
|
||||||
|
|||||||
@@ -1567,6 +1567,7 @@ dependencies = [
|
|||||||
"ecolor",
|
"ecolor",
|
||||||
"emath",
|
"emath",
|
||||||
"epaint_default_fonts",
|
"epaint_default_fonts",
|
||||||
|
"font-types",
|
||||||
"log",
|
"log",
|
||||||
"mimalloc",
|
"mimalloc",
|
||||||
"nohash-hasher",
|
"nohash-hasher",
|
||||||
@@ -1577,6 +1578,7 @@ dependencies = [
|
|||||||
"serde",
|
"serde",
|
||||||
"similar-asserts",
|
"similar-asserts",
|
||||||
"skrifa",
|
"skrifa",
|
||||||
|
"smallvec",
|
||||||
"vello_cpu",
|
"vello_cpu",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -1741,6 +1743,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "b1e4d2d0cf79d38430cc9dc9aadec84774bff2e1ba30ae2bf6c16cfce9385a23"
|
checksum = "b1e4d2d0cf79d38430cc9dc9aadec84774bff2e1ba30ae2bf6c16cfce9385a23"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bytemuck",
|
"bytemuck",
|
||||||
|
"serde",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -4136,6 +4139,9 @@ name = "smallvec"
|
|||||||
version = "1.15.1"
|
version = "1.15.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03"
|
checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03"
|
||||||
|
dependencies = [
|
||||||
|
"serde",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "smithay-client-toolkit"
|
name = "smithay-client-toolkit"
|
||||||
|
|||||||
@@ -90,6 +90,7 @@ document-features = "0.2.11"
|
|||||||
ehttp = { version = "0.6.0", default-features = false }
|
ehttp = { version = "0.6.0", default-features = false }
|
||||||
enum-map = "2.7.3"
|
enum-map = "2.7.3"
|
||||||
env_logger = { version = "0.11.8", default-features = false }
|
env_logger = { version = "0.11.8", default-features = false }
|
||||||
|
font-types = { version = "0.11.0", default-features = false, features = ["std"] }
|
||||||
glow = "0.16.0"
|
glow = "0.16.0"
|
||||||
glutin = { version = "0.32.3", default-features = false }
|
glutin = { version = "0.32.3", default-features = false }
|
||||||
glutin-winit = { version = "0.5.0", default-features = false }
|
glutin-winit = { version = "0.5.0", default-features = false }
|
||||||
|
|||||||
@@ -3262,7 +3262,7 @@ impl Context {
|
|||||||
|
|
||||||
for (name, data) in &mut font_definitions.font_data {
|
for (name, data) in &mut font_definitions.font_data {
|
||||||
ui.collapsing(name, |ui| {
|
ui.collapsing(name, |ui| {
|
||||||
let mut tweak = data.tweak;
|
let mut tweak = data.tweak.clone();
|
||||||
if tweak.ui(ui).changed() {
|
if tweak.ui(ui).changed() {
|
||||||
Arc::make_mut(data).tweak = tweak;
|
Arc::make_mut(data).tweak = tweak;
|
||||||
changed = true;
|
changed = true;
|
||||||
|
|||||||
@@ -1,7 +1,11 @@
|
|||||||
//! egui theme (spacing, colors, etc).
|
//! egui theme (spacing, colors, etc).
|
||||||
|
|
||||||
use emath::Align;
|
use emath::Align;
|
||||||
use epaint::{AlphaFromCoverage, CornerRadius, Shadow, Stroke, TextOptions, text::FontTweak};
|
use epaint::{
|
||||||
|
AlphaFromCoverage, CornerRadius, Shadow, Stroke, TextOptions,
|
||||||
|
mutex::Mutex,
|
||||||
|
text::{FontTweak, Tag},
|
||||||
|
};
|
||||||
use std::{collections::BTreeMap, ops::RangeInclusive, sync::Arc};
|
use std::{collections::BTreeMap, ops::RangeInclusive, sync::Arc};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
@@ -2837,7 +2841,7 @@ impl Widget for &mut crate::Frame {
|
|||||||
|
|
||||||
impl Widget for &mut FontTweak {
|
impl Widget for &mut FontTweak {
|
||||||
fn ui(self, ui: &mut Ui) -> Response {
|
fn ui(self, ui: &mut Ui) -> Response {
|
||||||
let original: FontTweak = *self;
|
let original: FontTweak = self.clone();
|
||||||
|
|
||||||
let mut response = Grid::new("font_tweak")
|
let mut response = Grid::new("font_tweak")
|
||||||
.num_columns(2)
|
.num_columns(2)
|
||||||
@@ -2847,6 +2851,7 @@ impl Widget for &mut FontTweak {
|
|||||||
y_offset_factor,
|
y_offset_factor,
|
||||||
y_offset,
|
y_offset,
|
||||||
hinting_override,
|
hinting_override,
|
||||||
|
coords,
|
||||||
} = self;
|
} = self;
|
||||||
|
|
||||||
ui.label("Scale");
|
ui.label("Scale");
|
||||||
@@ -2874,6 +2879,50 @@ impl Widget for &mut FontTweak {
|
|||||||
ui.selectable_value(hinting_override, Some(true), "Enable");
|
ui.selectable_value(hinting_override, Some(true), "Enable");
|
||||||
ui.selectable_value(hinting_override, Some(false), "Disable");
|
ui.selectable_value(hinting_override, Some(false), "Disable");
|
||||||
});
|
});
|
||||||
|
ui.end_row();
|
||||||
|
|
||||||
|
ui.label("coords");
|
||||||
|
ui.end_row();
|
||||||
|
let mut to_remove = None;
|
||||||
|
for (i, (tag, value)) in coords.as_mut().iter_mut().enumerate() {
|
||||||
|
let tag_text = ui.ctx().data_mut(|data| {
|
||||||
|
let tag = *tag;
|
||||||
|
Arc::clone(data.get_temp_mut_or_insert_with(ui.id().with(i), move || {
|
||||||
|
Arc::new(Mutex::new(tag.to_string()))
|
||||||
|
}))
|
||||||
|
});
|
||||||
|
|
||||||
|
let tag_text = &mut *tag_text.lock();
|
||||||
|
let response = ui.text_edit_singleline(tag_text);
|
||||||
|
if response.changed()
|
||||||
|
&& let Ok(new_tag) = Tag::new_checked(tag_text.as_bytes())
|
||||||
|
{
|
||||||
|
*tag = new_tag;
|
||||||
|
}
|
||||||
|
// Reset stale text when not actively editing
|
||||||
|
// (e.g. after an item was removed and indices shifted)
|
||||||
|
if !response.has_focus()
|
||||||
|
&& Tag::new_checked(tag_text.as_bytes()).ok() != Some(*tag)
|
||||||
|
{
|
||||||
|
*tag_text = tag.to_string();
|
||||||
|
}
|
||||||
|
|
||||||
|
ui.add(DragValue::new(value));
|
||||||
|
if ui.small_button("🗑").clicked() {
|
||||||
|
to_remove = Some(i);
|
||||||
|
}
|
||||||
|
ui.end_row();
|
||||||
|
}
|
||||||
|
if let Some(i) = to_remove {
|
||||||
|
coords.remove(i);
|
||||||
|
}
|
||||||
|
if ui.button("Add coord").clicked() {
|
||||||
|
coords.push(b"wght", 0.0);
|
||||||
|
}
|
||||||
|
if ui.button("Clear coords").clicked() {
|
||||||
|
coords.clear();
|
||||||
|
}
|
||||||
|
ui.end_row();
|
||||||
|
|
||||||
if ui.button("Reset").clicked() {
|
if ui.button("Reset").clicked() {
|
||||||
*self = Default::default();
|
*self = Default::default();
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
use emath::GuiRounding as _;
|
use emath::GuiRounding as _;
|
||||||
use epaint::text::TextFormat;
|
use epaint::text::{IntoTag, TextFormat, VariationCoords};
|
||||||
use std::fmt::Formatter;
|
use std::fmt::Formatter;
|
||||||
use std::{borrow::Cow, sync::Arc};
|
use std::{borrow::Cow, sync::Arc};
|
||||||
|
|
||||||
@@ -34,6 +34,7 @@ pub struct RichText {
|
|||||||
background_color: Color32,
|
background_color: Color32,
|
||||||
expand_bg: f32,
|
expand_bg: f32,
|
||||||
text_color: Option<Color32>,
|
text_color: Option<Color32>,
|
||||||
|
coords: VariationCoords,
|
||||||
code: bool,
|
code: bool,
|
||||||
strong: bool,
|
strong: bool,
|
||||||
weak: bool,
|
weak: bool,
|
||||||
@@ -55,6 +56,7 @@ impl Default for RichText {
|
|||||||
background_color: Default::default(),
|
background_color: Default::default(),
|
||||||
expand_bg: 1.0,
|
expand_bg: 1.0,
|
||||||
text_color: Default::default(),
|
text_color: Default::default(),
|
||||||
|
coords: Default::default(),
|
||||||
code: Default::default(),
|
code: Default::default(),
|
||||||
strong: Default::default(),
|
strong: Default::default(),
|
||||||
weak: Default::default(),
|
weak: Default::default(),
|
||||||
@@ -196,6 +198,23 @@ impl RichText {
|
|||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Add a variation coordinate.
|
||||||
|
#[inline]
|
||||||
|
pub fn variation(mut self, tag: impl IntoTag, coord: f32) -> Self {
|
||||||
|
self.coords.push(tag, coord);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Override the variation coordinates completely.
|
||||||
|
#[inline]
|
||||||
|
pub fn variations<T: IntoTag>(
|
||||||
|
mut self,
|
||||||
|
variations: impl IntoIterator<Item = (T, f32)>,
|
||||||
|
) -> Self {
|
||||||
|
self.coords = VariationCoords::new(variations);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
/// Override the [`TextStyle`].
|
/// Override the [`TextStyle`].
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn text_style(mut self, text_style: TextStyle) -> Self {
|
pub fn text_style(mut self, text_style: TextStyle) -> Self {
|
||||||
@@ -391,6 +410,7 @@ impl RichText {
|
|||||||
background_color,
|
background_color,
|
||||||
expand_bg,
|
expand_bg,
|
||||||
text_color: _, // already used by `get_text_color`
|
text_color: _, // already used by `get_text_color`
|
||||||
|
coords,
|
||||||
code,
|
code,
|
||||||
strong: _, // already used by `get_text_color`
|
strong: _, // already used by `get_text_color`
|
||||||
weak: _, // already used by `get_text_color`
|
weak: _, // already used by `get_text_color`
|
||||||
@@ -449,6 +469,7 @@ impl RichText {
|
|||||||
line_height,
|
line_height,
|
||||||
color: text_color,
|
color: text_color,
|
||||||
background: background_color,
|
background: background_color,
|
||||||
|
coords,
|
||||||
italics,
|
italics,
|
||||||
underline,
|
underline,
|
||||||
strikethrough,
|
strikethrough,
|
||||||
|
|||||||
@@ -48,7 +48,7 @@ mint = ["emath/mint"]
|
|||||||
rayon = ["dep:rayon"]
|
rayon = ["dep:rayon"]
|
||||||
|
|
||||||
## Allow serialization using [`serde`](https://docs.rs/serde).
|
## Allow serialization using [`serde`](https://docs.rs/serde).
|
||||||
serde = ["dep:serde", "ahash/serde", "emath/serde", "ecolor/serde"]
|
serde = ["dep:serde", "ahash/serde", "emath/serde", "ecolor/serde", "font-types/serde", "smallvec/serde"]
|
||||||
|
|
||||||
## Change Vertex layout to be compatible with unity
|
## Change Vertex layout to be compatible with unity
|
||||||
unity = []
|
unity = []
|
||||||
@@ -62,12 +62,14 @@ emath.workspace = true
|
|||||||
ecolor.workspace = true
|
ecolor.workspace = true
|
||||||
|
|
||||||
ahash.workspace = true
|
ahash.workspace = true
|
||||||
|
font-types.workspace = true
|
||||||
log.workspace = true
|
log.workspace = true
|
||||||
nohash-hasher.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.
|
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
|
self_cell.workspace = true
|
||||||
skrifa.workspace = true
|
skrifa.workspace = true
|
||||||
|
smallvec.workspace = true
|
||||||
vello_cpu.workspace = true
|
vello_cpu.workspace = true
|
||||||
|
|
||||||
#! ### Optional dependencies
|
#! ### Optional dependencies
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ use vello_cpu::{color, kurbo};
|
|||||||
use crate::{
|
use crate::{
|
||||||
TextOptions, TextureAtlas,
|
TextOptions, TextureAtlas,
|
||||||
text::{
|
text::{
|
||||||
FontTweak,
|
FontTweak, VariationCoords,
|
||||||
fonts::{Blob, CachedFamily, FontFaceKey},
|
fonts::{Blob, CachedFamily, FontFaceKey},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
@@ -145,8 +145,8 @@ struct GlyphCacheKey(u64);
|
|||||||
impl nohash_hasher::IsEnabled for GlyphCacheKey {}
|
impl nohash_hasher::IsEnabled for GlyphCacheKey {}
|
||||||
|
|
||||||
impl GlyphCacheKey {
|
impl GlyphCacheKey {
|
||||||
fn new(glyph_id: skrifa::GlyphId, metrics: &ScaledMetrics, bin: SubpixelBin) -> Self {
|
fn new(glyph_id: skrifa::GlyphId, metrics: &StyledMetrics, bin: SubpixelBin) -> Self {
|
||||||
let ScaledMetrics {
|
let StyledMetrics {
|
||||||
pixels_per_point,
|
pixels_per_point,
|
||||||
px_scale_factor,
|
px_scale_factor,
|
||||||
..
|
..
|
||||||
@@ -197,10 +197,10 @@ impl FontCell {
|
|||||||
fn allocate_glyph_uncached(
|
fn allocate_glyph_uncached(
|
||||||
&mut self,
|
&mut self,
|
||||||
atlas: &mut TextureAtlas,
|
atlas: &mut TextureAtlas,
|
||||||
metrics: &ScaledMetrics,
|
metrics: &StyledMetrics,
|
||||||
glyph_info: &GlyphInfo,
|
glyph_info: &GlyphInfo,
|
||||||
bin: SubpixelBin,
|
bin: SubpixelBin,
|
||||||
location: &skrifa::instance::Location,
|
location: skrifa::instance::LocationRef<'_>,
|
||||||
) -> Option<GlyphAllocation> {
|
) -> Option<GlyphAllocation> {
|
||||||
let glyph_id = glyph_info.id?;
|
let glyph_id = glyph_info.id?;
|
||||||
|
|
||||||
@@ -337,8 +337,6 @@ pub struct FontFace {
|
|||||||
font: FontCell,
|
font: FontCell,
|
||||||
tweak: FontTweak,
|
tweak: FontTweak,
|
||||||
|
|
||||||
/// Variable font location (for weight axis, etc.)
|
|
||||||
location: skrifa::instance::Location,
|
|
||||||
glyph_info_cache: ahash::HashMap<char, GlyphInfo>,
|
glyph_info_cache: ahash::HashMap<char, GlyphInfo>,
|
||||||
glyph_alloc_cache: ahash::HashMap<GlyphCacheKey, GlyphAllocation>,
|
glyph_alloc_cache: ahash::HashMap<GlyphCacheKey, GlyphAllocation>,
|
||||||
}
|
}
|
||||||
@@ -350,7 +348,6 @@ impl FontFace {
|
|||||||
font_data: Blob,
|
font_data: Blob,
|
||||||
index: u32,
|
index: u32,
|
||||||
tweak: FontTweak,
|
tweak: FontTweak,
|
||||||
preferred_weight: Option<u16>,
|
|
||||||
) -> Result<Self, Box<dyn std::error::Error>> {
|
) -> Result<Self, Box<dyn std::error::Error>> {
|
||||||
let font = FontCell::try_new(font_data, |font_data| {
|
let font = FontCell::try_new(font_data, |font_data| {
|
||||||
let skrifa_font =
|
let skrifa_font =
|
||||||
@@ -396,44 +393,10 @@ impl FontFace {
|
|||||||
})
|
})
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
// Use preferred_weight if provided, otherwise try to read from the OS/2 table or fvar default
|
|
||||||
let weight = preferred_weight.or_else(|| {
|
|
||||||
// First try OS/2 table
|
|
||||||
if let Some(w) = font
|
|
||||||
.borrow_dependent()
|
|
||||||
.skrifa
|
|
||||||
.os2()
|
|
||||||
.ok()
|
|
||||||
.map(|os2| os2.us_weight_class())
|
|
||||||
{
|
|
||||||
return Some(w);
|
|
||||||
}
|
|
||||||
// If no OS/2 or preferred_weight, try to get default from variable font's fvar table
|
|
||||||
font.borrow_dependent()
|
|
||||||
.skrifa
|
|
||||||
.axes()
|
|
||||||
.iter()
|
|
||||||
.find(|axis| axis.tag() == skrifa::raw::types::Tag::new(b"wght"))
|
|
||||||
.map(|axis| axis.default_value() as u16)
|
|
||||||
});
|
|
||||||
|
|
||||||
// Create location for variable font with weight axis
|
|
||||||
// If weight is provided (either from preferred_weight, OS/2, or fvar default), use it
|
|
||||||
// Otherwise fall back to Location::default() which uses all axis defaults
|
|
||||||
let location = if let Some(w) = weight {
|
|
||||||
font.borrow_dependent()
|
|
||||||
.skrifa
|
|
||||||
.axes()
|
|
||||||
.location([("wght", w as f32)])
|
|
||||||
} else {
|
|
||||||
skrifa::instance::Location::default()
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
name,
|
name,
|
||||||
font,
|
font,
|
||||||
tweak,
|
tweak,
|
||||||
location,
|
|
||||||
glyph_info_cache: Default::default(),
|
glyph_info_cache: Default::default(),
|
||||||
glyph_alloc_cache: Default::default(),
|
glyph_alloc_cache: Default::default(),
|
||||||
})
|
})
|
||||||
@@ -537,7 +500,7 @@ impl FontFace {
|
|||||||
#[inline]
|
#[inline]
|
||||||
pub(super) fn pair_kerning_pixels(
|
pub(super) fn pair_kerning_pixels(
|
||||||
&self,
|
&self,
|
||||||
metrics: &ScaledMetrics,
|
metrics: &StyledMetrics,
|
||||||
last_glyph_id: skrifa::GlyphId,
|
last_glyph_id: skrifa::GlyphId,
|
||||||
glyph_id: skrifa::GlyphId,
|
glyph_id: skrifa::GlyphId,
|
||||||
) -> f32 {
|
) -> f32 {
|
||||||
@@ -559,7 +522,7 @@ impl FontFace {
|
|||||||
#[inline]
|
#[inline]
|
||||||
pub fn pair_kerning(
|
pub fn pair_kerning(
|
||||||
&self,
|
&self,
|
||||||
metrics: &ScaledMetrics,
|
metrics: &StyledMetrics,
|
||||||
last_glyph_id: skrifa::GlyphId,
|
last_glyph_id: skrifa::GlyphId,
|
||||||
glyph_id: skrifa::GlyphId,
|
glyph_id: skrifa::GlyphId,
|
||||||
) -> f32 {
|
) -> f32 {
|
||||||
@@ -567,7 +530,12 @@ impl FontFace {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub fn scaled_metrics(&self, pixels_per_point: f32, font_size: f32) -> ScaledMetrics {
|
pub fn styled_metrics(
|
||||||
|
&self,
|
||||||
|
pixels_per_point: f32,
|
||||||
|
font_size: f32,
|
||||||
|
coords: &VariationCoords,
|
||||||
|
) -> StyledMetrics {
|
||||||
let pt_scale_factor = self.font.px_scale_factor(font_size * self.tweak.scale);
|
let pt_scale_factor = self.font.px_scale_factor(font_size * self.tweak.scale);
|
||||||
let font_data = self.font.borrow_dependent();
|
let font_data = self.font.borrow_dependent();
|
||||||
let ascent = (font_data.metrics.ascent * pt_scale_factor).round_ui();
|
let ascent = (font_data.metrics.ascent * pt_scale_factor).round_ui();
|
||||||
@@ -581,20 +549,32 @@ impl FontFace {
|
|||||||
+ self.tweak.y_offset)
|
+ self.tweak.y_offset)
|
||||||
.round_ui();
|
.round_ui();
|
||||||
|
|
||||||
ScaledMetrics {
|
let axes = font_data.skrifa.axes();
|
||||||
|
// Override the default coordinates with ones specified via FontTweak, then the ones specified directly via the
|
||||||
|
// argument (probably from TextFormat).
|
||||||
|
let settings = self
|
||||||
|
.tweak
|
||||||
|
.coords
|
||||||
|
.as_ref()
|
||||||
|
.iter()
|
||||||
|
.chain(coords.as_ref().iter());
|
||||||
|
let location = axes.location(settings);
|
||||||
|
|
||||||
|
StyledMetrics {
|
||||||
pixels_per_point,
|
pixels_per_point,
|
||||||
px_scale_factor,
|
px_scale_factor,
|
||||||
scale,
|
scale,
|
||||||
y_offset_in_points,
|
y_offset_in_points,
|
||||||
ascent,
|
ascent,
|
||||||
row_height: ascent - descent + line_gap,
|
row_height: ascent - descent + line_gap,
|
||||||
|
location,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn allocate_glyph(
|
pub fn allocate_glyph(
|
||||||
&mut self,
|
&mut self,
|
||||||
atlas: &mut TextureAtlas,
|
atlas: &mut TextureAtlas,
|
||||||
metrics: &ScaledMetrics,
|
metrics: &StyledMetrics,
|
||||||
glyph_info: GlyphInfo,
|
glyph_info: GlyphInfo,
|
||||||
chr: char,
|
chr: char,
|
||||||
h_pos: f32,
|
h_pos: f32,
|
||||||
@@ -628,7 +608,7 @@ impl FontFace {
|
|||||||
|
|
||||||
let allocation = self
|
let allocation = self
|
||||||
.font
|
.font
|
||||||
.allocate_glyph_uncached(atlas, metrics, &glyph_info, bin, &self.location)
|
.allocate_glyph_uncached(atlas, metrics, &glyph_info, bin, (&metrics.location).into())
|
||||||
.unwrap_or_default();
|
.unwrap_or_default();
|
||||||
|
|
||||||
entry.insert(allocation);
|
entry.insert(allocation);
|
||||||
@@ -665,12 +645,17 @@ impl Font<'_> {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn scaled_metrics(&self, pixels_per_point: f32, font_size: f32) -> ScaledMetrics {
|
pub fn styled_metrics(
|
||||||
|
&self,
|
||||||
|
pixels_per_point: f32,
|
||||||
|
font_size: f32,
|
||||||
|
coords: &VariationCoords,
|
||||||
|
) -> StyledMetrics {
|
||||||
self.cached_family
|
self.cached_family
|
||||||
.fonts
|
.fonts
|
||||||
.first()
|
.first()
|
||||||
.and_then(|key| self.fonts_by_id.get(key))
|
.and_then(|key| self.fonts_by_id.get(key))
|
||||||
.map(|font_face| font_face.scaled_metrics(pixels_per_point, font_size))
|
.map(|font_face| font_face.styled_metrics(pixels_per_point, font_size, coords))
|
||||||
.unwrap_or_default()
|
.unwrap_or_default()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -713,8 +698,8 @@ impl Font<'_> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Metrics for a font at a specific screen-space scale.
|
/// Metrics for a font at a specific screen-space scale.
|
||||||
#[derive(Clone, Copy, Debug, PartialEq, Default)]
|
#[derive(Clone, Debug, PartialEq, Default)]
|
||||||
pub struct ScaledMetrics {
|
pub struct StyledMetrics {
|
||||||
/// The DPI part of the screen-space scale.
|
/// The DPI part of the screen-space scale.
|
||||||
pub pixels_per_point: f32,
|
pub pixels_per_point: f32,
|
||||||
|
|
||||||
@@ -738,6 +723,9 @@ pub struct ScaledMetrics {
|
|||||||
///
|
///
|
||||||
/// Returns a value rounded to [`emath::GUI_ROUNDING`].
|
/// Returns a value rounded to [`emath::GUI_ROUNDING`].
|
||||||
pub row_height: f32,
|
pub row_height: f32,
|
||||||
|
|
||||||
|
/// Resolved variation coordinates.
|
||||||
|
pub location: skrifa::instance::Location,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Code points that will always be invisible (zero width).
|
/// Code points that will always be invisible (zero width).
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ use std::{
|
|||||||
use crate::{
|
use crate::{
|
||||||
TextureAtlas,
|
TextureAtlas,
|
||||||
text::{
|
text::{
|
||||||
Galley, LayoutJob, LayoutSection, TextOptions,
|
Galley, LayoutJob, LayoutSection, TextOptions, VariationCoords,
|
||||||
font::{Font, FontFace, GlyphInfo},
|
font::{Font, FontFace, GlyphInfo},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
@@ -125,12 +125,6 @@ pub struct FontData {
|
|||||||
|
|
||||||
/// Extra scale and vertical tweak to apply to all text of this font.
|
/// Extra scale and vertical tweak to apply to all text of this font.
|
||||||
pub tweak: FontTweak,
|
pub tweak: FontTweak,
|
||||||
|
|
||||||
/// The font weight (100-900), if available.
|
|
||||||
/// Standard values: 100 (Thin), 200 (Extra Light), 300 (Light), 400 (Regular),
|
|
||||||
/// 500 (Medium), 600 (Semi Bold), 700 (Bold), 800 (Extra Bold), 900 (Black).
|
|
||||||
/// `None` if the weight could not be determined.
|
|
||||||
pub weight: Option<u16>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FontData {
|
impl FontData {
|
||||||
@@ -139,7 +133,6 @@ impl FontData {
|
|||||||
font: Cow::Borrowed(font),
|
font: Cow::Borrowed(font),
|
||||||
index: 0,
|
index: 0,
|
||||||
tweak: Default::default(),
|
tweak: Default::default(),
|
||||||
weight: None,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -148,43 +141,12 @@ impl FontData {
|
|||||||
font: Cow::Owned(font),
|
font: Cow::Owned(font),
|
||||||
index: 0,
|
index: 0,
|
||||||
tweak: Default::default(),
|
tweak: Default::default(),
|
||||||
weight: None,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn tweak(self, tweak: FontTweak) -> Self {
|
pub fn tweak(self, tweak: FontTweak) -> Self {
|
||||||
Self { tweak, ..self }
|
Self { tweak, ..self }
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Set the font weight (100-900).
|
|
||||||
///
|
|
||||||
/// This is typically read automatically from the font file when loaded,
|
|
||||||
/// but can be overridden manually if needed.
|
|
||||||
///
|
|
||||||
/// Standard weight values:
|
|
||||||
/// - 100: Thin
|
|
||||||
/// - 200: Extra Light
|
|
||||||
/// - 300: Light
|
|
||||||
/// - 400: Regular/Normal
|
|
||||||
/// - 500: Medium
|
|
||||||
/// - 600: Semi Bold
|
|
||||||
/// - 700: Bold
|
|
||||||
/// - 800: Extra Bold
|
|
||||||
/// - 900: Black
|
|
||||||
///
|
|
||||||
/// # Example
|
|
||||||
/// ```
|
|
||||||
/// # use epaint::text::FontData;
|
|
||||||
/// let font_data = FontData::from_static(include_bytes!("../../../epaint_default_fonts/fonts/Ubuntu-Light.ttf"))
|
|
||||||
/// .weight(300); // Override to Light weight
|
|
||||||
/// assert_eq!(font_data.weight, Some(300));
|
|
||||||
/// ```
|
|
||||||
pub fn weight(self, weight: u16) -> Self {
|
|
||||||
Self {
|
|
||||||
weight: Some(weight),
|
|
||||||
..self
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AsRef<[u8]> for FontData {
|
impl AsRef<[u8]> for FontData {
|
||||||
@@ -196,7 +158,7 @@ impl AsRef<[u8]> for FontData {
|
|||||||
// ----------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------
|
||||||
|
|
||||||
/// Extra scale and vertical tweak to apply to all text of a certain font.
|
/// Extra scale and vertical tweak to apply to all text of a certain font.
|
||||||
#[derive(Copy, Clone, Debug, PartialEq)]
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
|
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
|
||||||
pub struct FontTweak {
|
pub struct FontTweak {
|
||||||
/// Scale the font's glyphs by this much.
|
/// Scale the font's glyphs by this much.
|
||||||
@@ -228,6 +190,9 @@ pub struct FontTweak {
|
|||||||
///
|
///
|
||||||
/// `None` means use the global setting.
|
/// `None` means use the global setting.
|
||||||
pub hinting_override: Option<bool>,
|
pub hinting_override: Option<bool>,
|
||||||
|
|
||||||
|
/// Override the font's default variation coordinates.
|
||||||
|
pub coords: VariationCoords,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for FontTweak {
|
impl Default for FontTweak {
|
||||||
@@ -237,6 +202,7 @@ impl Default for FontTweak {
|
|||||||
y_offset_factor: 0.0,
|
y_offset_factor: 0.0,
|
||||||
y_offset: 0.0,
|
y_offset: 0.0,
|
||||||
hinting_override: None,
|
hinting_override: None,
|
||||||
|
coords: VariationCoords::default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -701,7 +667,12 @@ impl FontsView<'_> {
|
|||||||
pub fn row_height(&mut self, font_id: &FontId) -> f32 {
|
pub fn row_height(&mut self, font_id: &FontId) -> f32 {
|
||||||
self.fonts
|
self.fonts
|
||||||
.font(&font_id.family)
|
.font(&font_id.family)
|
||||||
.scaled_metrics(self.pixels_per_point, font_id.size)
|
.styled_metrics(
|
||||||
|
self.pixels_per_point,
|
||||||
|
font_id.size,
|
||||||
|
// TODO(valadaptive): use font variation coords when calculating row height
|
||||||
|
&VariationCoords::default(),
|
||||||
|
)
|
||||||
.row_height
|
.row_height
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -807,15 +778,13 @@ impl FontsImpl {
|
|||||||
let mut fonts_by_id: nohash_hasher::IntMap<FontFaceKey, FontFace> = 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();
|
let mut fonts_by_name: ahash::HashMap<String, FontFaceKey> = Default::default();
|
||||||
for (name, font_data) in &definitions.font_data {
|
for (name, font_data) in &definitions.font_data {
|
||||||
let tweak = font_data.tweak;
|
|
||||||
let blob = blob_from_font_data(font_data);
|
let blob = blob_from_font_data(font_data);
|
||||||
let font_face = FontFace::new(
|
let font_face = FontFace::new(
|
||||||
options,
|
options,
|
||||||
name.clone(),
|
name.clone(),
|
||||||
blob,
|
blob,
|
||||||
font_data.index,
|
font_data.index,
|
||||||
tweak,
|
font_data.tweak.clone(),
|
||||||
font_data.weight,
|
|
||||||
)
|
)
|
||||||
.unwrap_or_else(|err| panic!("Error parsing {name:?} TTF/OTF font file: {err}"));
|
.unwrap_or_else(|err| panic!("Error parsing {name:?} TTF/OTF font file: {err}"));
|
||||||
let key = FontFaceKey::new();
|
let key = FontFaceKey::new();
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ use crate::{
|
|||||||
Color32, Mesh, Stroke, Vertex,
|
Color32, Mesh, Stroke, Vertex,
|
||||||
stroke::PathStroke,
|
stroke::PathStroke,
|
||||||
text::{
|
text::{
|
||||||
font::{ScaledMetrics, is_cjk, is_cjk_break_allowed},
|
font::{StyledMetrics, is_cjk, is_cjk_break_allowed},
|
||||||
fonts::FontFaceKey,
|
fonts::FontFaceKey,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
@@ -160,7 +160,7 @@ fn layout_section(
|
|||||||
} = section;
|
} = section;
|
||||||
let mut font = fonts.font(&format.font_id.family);
|
let mut font = fonts.font(&format.font_id.family);
|
||||||
let font_size = format.font_id.size;
|
let font_size = format.font_id.size;
|
||||||
let font_metrics = font.scaled_metrics(pixels_per_point, font_size);
|
let font_metrics = font.styled_metrics(pixels_per_point, font_size, &format.coords);
|
||||||
let line_height = section
|
let line_height = section
|
||||||
.format
|
.format
|
||||||
.line_height
|
.line_height
|
||||||
@@ -178,7 +178,7 @@ fn layout_section(
|
|||||||
|
|
||||||
// Optimization: only recompute `ScaledMetrics` when the concrete `FontImpl` changes.
|
// Optimization: only recompute `ScaledMetrics` when the concrete `FontImpl` changes.
|
||||||
let mut current_font = FontFaceKey::INVALID;
|
let mut current_font = FontFaceKey::INVALID;
|
||||||
let mut current_font_face_metrics = ScaledMetrics::default();
|
let mut current_font_face_metrics = StyledMetrics::default();
|
||||||
|
|
||||||
for chr in job.text[byte_range.clone()].chars() {
|
for chr in job.text[byte_range.clone()].chars() {
|
||||||
if job.break_on_newline && chr == '\n' {
|
if job.break_on_newline && chr == '\n' {
|
||||||
@@ -192,7 +192,9 @@ fn layout_section(
|
|||||||
current_font = font_id;
|
current_font = font_id;
|
||||||
current_font_face_metrics = font_face
|
current_font_face_metrics = font_face
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.map(|font_face| font_face.scaled_metrics(pixels_per_point, font_size))
|
.map(|font_face| {
|
||||||
|
font_face.styled_metrics(pixels_per_point, font_size, &format.coords)
|
||||||
|
})
|
||||||
.unwrap_or_default();
|
.unwrap_or_default();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -468,7 +470,7 @@ fn replace_last_glyph_with_overflow_character(
|
|||||||
let mut font_face = font.fonts_by_id.get_mut(&font_id);
|
let mut font_face = font.fonts_by_id.get_mut(&font_id);
|
||||||
let font_face_metrics = font_face
|
let font_face_metrics = font_face
|
||||||
.as_mut()
|
.as_mut()
|
||||||
.map(|f| f.scaled_metrics(pixels_per_point, font_size))
|
.map(|f| f.styled_metrics(pixels_per_point, font_size, §ion.format.coords))
|
||||||
.unwrap_or_default();
|
.unwrap_or_default();
|
||||||
|
|
||||||
let overflow_glyph_x = if let Some(prev_glyph) = row.glyphs.last() {
|
let overflow_glyph_x = if let Some(prev_glyph) = row.glyphs.last() {
|
||||||
@@ -519,7 +521,8 @@ fn replace_last_glyph_with_overflow_character(
|
|||||||
})
|
})
|
||||||
.unwrap_or_default();
|
.unwrap_or_default();
|
||||||
|
|
||||||
let font_metrics = font.scaled_metrics(pixels_per_point, font_size);
|
let font_metrics =
|
||||||
|
font.styled_metrics(pixels_per_point, font_size, §ion.format.coords);
|
||||||
let line_height = section
|
let line_height = section
|
||||||
.format
|
.format
|
||||||
.line_height
|
.line_height
|
||||||
@@ -1212,7 +1215,7 @@ mod tests {
|
|||||||
let font_id = FontId::default();
|
let font_id = FontId::default();
|
||||||
let font_height = fonts
|
let font_height = fonts
|
||||||
.font(&font_id.family)
|
.font(&font_id.family)
|
||||||
.scaled_metrics(pixels_per_point, font_id.size)
|
.styled_metrics(pixels_per_point, font_id.size, &VariationCoords::default())
|
||||||
.row_height;
|
.row_height;
|
||||||
|
|
||||||
let job = LayoutJob::simple(String::new(), font_id, Color32::WHITE, f32::INFINITY);
|
let job = LayoutJob::simple(String::new(), font_id, Color32::WHITE, f32::INFINITY);
|
||||||
@@ -1245,7 +1248,7 @@ mod tests {
|
|||||||
let font_id = FontId::default();
|
let font_id = FontId::default();
|
||||||
let font_height = fonts
|
let font_height = fonts
|
||||||
.font(&font_id.family)
|
.font(&font_id.family)
|
||||||
.scaled_metrics(pixels_per_point, font_id.size)
|
.styled_metrics(pixels_per_point, font_id.size, &VariationCoords::default())
|
||||||
.row_height;
|
.row_height;
|
||||||
|
|
||||||
let job = LayoutJob::simple("Hi!\n".to_owned(), font_id, Color32::WHITE, f32::INFINITY);
|
let job = LayoutJob::simple("Hi!\n".to_owned(), font_id, Color32::WHITE, f32::INFINITY);
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
use std::ops::Range;
|
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
use std::{ops::Range, str::FromStr as _};
|
||||||
|
|
||||||
use super::{
|
use super::{
|
||||||
cursor::{CCursor, LayoutCursor},
|
cursor::{CCursor, LayoutCursor},
|
||||||
@@ -7,6 +7,8 @@ use super::{
|
|||||||
};
|
};
|
||||||
use crate::{Color32, FontId, Mesh, Stroke, text::FontsView};
|
use crate::{Color32, FontId, Mesh, Stroke, text::FontsView};
|
||||||
use emath::{Align, GuiRounding as _, NumExt as _, OrderedFloat, Pos2, Rect, Vec2, pos2, vec2};
|
use emath::{Align, GuiRounding as _, NumExt as _, OrderedFloat, Pos2, Rect, Vec2, pos2, vec2};
|
||||||
|
pub use font_types::Tag;
|
||||||
|
use smallvec::SmallVec;
|
||||||
|
|
||||||
/// Describes the task of laying out text.
|
/// Describes the task of laying out text.
|
||||||
///
|
///
|
||||||
@@ -257,6 +259,107 @@ impl std::hash::Hash for LayoutSection {
|
|||||||
|
|
||||||
// ----------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
/// Helper trait for all types that can be parsed as a [`font_types::Tag`].
|
||||||
|
pub trait IntoTag {
|
||||||
|
fn into_tag(self) -> font_types::Tag;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl IntoTag for font_types::Tag {
|
||||||
|
#[inline(always)]
|
||||||
|
fn into_tag(self) -> font_types::Tag {
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl IntoTag for u32 {
|
||||||
|
#[inline(always)]
|
||||||
|
fn into_tag(self) -> font_types::Tag {
|
||||||
|
font_types::Tag::from_u32(self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl IntoTag for [u8; 4] {
|
||||||
|
#[inline(always)]
|
||||||
|
fn into_tag(self) -> font_types::Tag {
|
||||||
|
font_types::Tag::new_checked(&self).expect("Invalid variation axis tag")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl IntoTag for &[u8; 4] {
|
||||||
|
#[inline(always)]
|
||||||
|
fn into_tag(self) -> font_types::Tag {
|
||||||
|
font_types::Tag::new_checked(self).expect("Invalid variation axis tag")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl IntoTag for &str {
|
||||||
|
#[inline(always)]
|
||||||
|
fn into_tag(self) -> font_types::Tag {
|
||||||
|
font_types::Tag::from_str(self).expect("Invalid variation axis tag")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// List of font variation coordinates by axis tag. If more than one coordinate for a given axis is provided, the last
|
||||||
|
/// one added is used.
|
||||||
|
#[derive(Clone, Debug, PartialEq, Default)]
|
||||||
|
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
|
||||||
|
pub struct VariationCoords(SmallVec<[(font_types::Tag, f32); 2]>);
|
||||||
|
|
||||||
|
impl VariationCoords {
|
||||||
|
/// Create a list of variation coordinates from a sequence of (tag, value) pairs.
|
||||||
|
///
|
||||||
|
/// ## Example:
|
||||||
|
/// ```
|
||||||
|
/// use epaint::text::VariationCoords;
|
||||||
|
///
|
||||||
|
/// let coords = VariationCoords::new([
|
||||||
|
/// (b"wght", 500.0),
|
||||||
|
/// (b"wdth", 75.0),
|
||||||
|
/// ]);
|
||||||
|
/// ```
|
||||||
|
pub fn new<T: IntoTag>(values: impl IntoIterator<Item = (T, f32)>) -> Self {
|
||||||
|
Self(values.into_iter().map(|(t, c)| (t.into_tag(), c)).collect())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Add a variation coordinate to the list.
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn push(&mut self, tag: impl IntoTag, coord: f32) {
|
||||||
|
self.0.push((tag.into_tag(), coord));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Remove the coordinate at the given index.
|
||||||
|
pub fn remove(&mut self, index: usize) {
|
||||||
|
self.0.remove(index);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn clear(&mut self) {
|
||||||
|
self.0.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AsRef<[(font_types::Tag, f32)]> for VariationCoords {
|
||||||
|
#[inline(always)]
|
||||||
|
fn as_ref(&self) -> &[(font_types::Tag, f32)] {
|
||||||
|
&self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AsMut<[(font_types::Tag, f32)]> for VariationCoords {
|
||||||
|
fn as_mut(&mut self) -> &mut [(font_types::Tag, f32)] {
|
||||||
|
&mut self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::hash::Hash for VariationCoords {
|
||||||
|
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
|
||||||
|
self.0.len().hash(state);
|
||||||
|
for (tag, coord) in &self.0 {
|
||||||
|
tag.hash(state);
|
||||||
|
OrderedFloat(*coord).hash(state);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Formatting option for a section of text.
|
/// Formatting option for a section of text.
|
||||||
#[derive(Clone, Debug, PartialEq)]
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
|
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
|
||||||
@@ -287,6 +390,8 @@ pub struct TextFormat {
|
|||||||
/// Default: 1.0
|
/// Default: 1.0
|
||||||
pub expand_bg: f32,
|
pub expand_bg: f32,
|
||||||
|
|
||||||
|
pub coords: VariationCoords,
|
||||||
|
|
||||||
pub italics: bool,
|
pub italics: bool,
|
||||||
|
|
||||||
pub underline: Stroke,
|
pub underline: Stroke,
|
||||||
@@ -315,6 +420,7 @@ impl Default for TextFormat {
|
|||||||
color: Color32::GRAY,
|
color: Color32::GRAY,
|
||||||
background: Color32::TRANSPARENT,
|
background: Color32::TRANSPARENT,
|
||||||
expand_bg: 1.0,
|
expand_bg: 1.0,
|
||||||
|
coords: VariationCoords::default(),
|
||||||
italics: false,
|
italics: false,
|
||||||
underline: Stroke::NONE,
|
underline: Stroke::NONE,
|
||||||
strikethrough: Stroke::NONE,
|
strikethrough: Stroke::NONE,
|
||||||
@@ -333,6 +439,7 @@ impl std::hash::Hash for TextFormat {
|
|||||||
color,
|
color,
|
||||||
background,
|
background,
|
||||||
expand_bg,
|
expand_bg,
|
||||||
|
coords,
|
||||||
italics,
|
italics,
|
||||||
underline,
|
underline,
|
||||||
strikethrough,
|
strikethrough,
|
||||||
@@ -346,6 +453,7 @@ impl std::hash::Hash for TextFormat {
|
|||||||
color.hash(state);
|
color.hash(state);
|
||||||
background.hash(state);
|
background.hash(state);
|
||||||
emath::OrderedFloat(*expand_bg).hash(state);
|
emath::OrderedFloat(*expand_bg).hash(state);
|
||||||
|
coords.hash(state);
|
||||||
italics.hash(state);
|
italics.hash(state);
|
||||||
underline.hash(state);
|
underline.hash(state);
|
||||||
strikethrough.hash(state);
|
strikethrough.hash(state);
|
||||||
|
|||||||
Reference in New Issue
Block a user