Add metrics to attributes

This commit is contained in:
Jeremy Soller 2024-06-06 14:40:35 -06:00
parent 89503b254f
commit 8638ec29bb
4 changed files with 84 additions and 26 deletions

View file

@ -26,6 +26,13 @@ fn set_buffer_text<'a>(buffer: &mut BorrowedWithFontSystem<'a, Buffer>) {
let comic_attrs = attrs.family(Family::Name("Comic Neue"));
let spans: &[(&str, Attrs)] = &[
("Font size 8 ", attrs.metrics(Metrics::relative(8.0, 1.2))),
("Font size 20 ", attrs.metrics(Metrics::relative(20.0, 1.2))),
("Font size 14 ", attrs.metrics(Metrics::relative(14.0, 1.2))),
(
"Font size 48\n",
attrs.metrics(Metrics::relative(48.0, 1.2)),
),
("B", attrs.weight(Weight::BOLD)),
("old ", attrs),
("I", attrs.style(Style::Italic)),

View file

@ -8,7 +8,7 @@ use alloc::{
use core::ops::Range;
use rangemap::RangeMap;
use crate::CacheKeyFlags;
use crate::{CacheKeyFlags, Metrics};
pub use fontdb::{Family, Stretch, Style, Weight};
@ -101,6 +101,32 @@ impl FamilyOwned {
}
}
/// Metrics, but implementing Eq and Hash using u32 representation of f32
//TODO: what are the edge cases of this?
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
pub struct CacheMetrics {
pub font_size_bits: u32,
pub line_height_bits: u32,
}
impl From<Metrics> for CacheMetrics {
fn from(metrics: Metrics) -> Self {
Self {
font_size_bits: metrics.font_size.to_bits(),
line_height_bits: metrics.line_height.to_bits(),
}
}
}
impl From<CacheMetrics> for Metrics {
fn from(metrics: CacheMetrics) -> Self {
Self {
font_size: f32::from_bits(metrics.font_size_bits),
line_height: f32::from_bits(metrics.line_height_bits),
}
}
}
/// Text attributes
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
pub struct Attrs<'a> {
@ -112,6 +138,7 @@ pub struct Attrs<'a> {
pub weight: Weight,
pub metadata: usize,
pub cache_key_flags: CacheKeyFlags,
pub metrics_opt: Option<CacheMetrics>,
}
impl<'a> Attrs<'a> {
@ -127,6 +154,7 @@ impl<'a> Attrs<'a> {
weight: Weight::NORMAL,
metadata: 0,
cache_key_flags: CacheKeyFlags::empty(),
metrics_opt: None,
}
}
@ -172,6 +200,12 @@ impl<'a> Attrs<'a> {
self
}
/// Set [`Metrics`], overriding values in buffer
pub fn metrics(mut self, metrics: Metrics) -> Self {
self.metrics_opt = Some(metrics.into());
self
}
/// Check if font matches
pub fn matches(&self, face: &fontdb::FaceInfo) -> bool {
//TODO: smarter way of including emoji
@ -219,6 +253,7 @@ pub struct AttrsOwned {
pub weight: Weight,
pub metadata: usize,
pub cache_key_flags: CacheKeyFlags,
pub metrics_opt: Option<CacheMetrics>,
}
impl AttrsOwned {
@ -231,6 +266,7 @@ impl AttrsOwned {
weight: attrs.weight,
metadata: attrs.metadata,
cache_key_flags: attrs.cache_key_flags,
metrics_opt: attrs.metrics_opt,
}
}
@ -243,6 +279,7 @@ impl AttrsOwned {
weight: self.weight,
metadata: self.metadata,
cache_key_flags: self.cache_key_flags,
metrics_opt: self.metrics_opt,
}
}
}

View file

@ -199,6 +199,7 @@ pub struct Metrics {
}
impl Metrics {
/// Create metrics with given font size and line height
pub const fn new(font_size: f32, line_height: f32) -> Self {
Self {
font_size,
@ -206,6 +207,15 @@ impl Metrics {
}
}
/// Create metrics with given font size and calculate line height using relative scale
pub fn relative(font_size: f32, line_height_scale: f32) -> Self {
Self {
font_size,
line_height: font_size * line_height_scale,
}
}
/// Scale font size and line height
pub fn scale(self, scale: f32) -> Self {
Self {
font_size: self.font_size * scale,

View file

@ -14,7 +14,7 @@ use unicode_segmentation::UnicodeSegmentation;
use crate::fallback::FontFallbackIter;
use crate::{
math, Align, AttrsList, CacheKeyFlags, Color, Font, FontSystem, LayoutGlyph, LayoutLine,
ShapePlanCache, Wrap,
Metrics, ShapePlanCache, Wrap,
};
/// The shaping strategy of some text.
@ -163,6 +163,7 @@ fn shape_fallback(
color_opt: attrs.color_opt,
metadata: attrs.metadata,
cache_key_flags: attrs.cache_key_flags,
metrics_opt: attrs.metrics_opt.map(|x| x.into()),
});
}
@ -463,6 +464,7 @@ fn shape_skip(
color_opt: attrs.color_opt,
metadata: attrs.metadata,
cache_key_flags: attrs.cache_key_flags,
metrics_opt: attrs.metrics_opt.map(|x| x.into()),
}
}),
);
@ -485,6 +487,7 @@ pub struct ShapeGlyph {
pub color_opt: Option<Color>,
pub metadata: usize,
pub cache_key_flags: CacheKeyFlags,
pub metrics_opt: Option<Metrics>,
}
impl ShapeGlyph {
@ -513,6 +516,10 @@ impl ShapeGlyph {
cache_key_flags: self.cache_key_flags,
}
}
pub fn width(&self, font_size: f32) -> f32 {
self.metrics_opt.map_or(font_size, |x| x.font_size) * self.x_advance
}
}
/// A shaped word (for word wrapping)
@ -520,8 +527,6 @@ impl ShapeGlyph {
pub struct ShapeWord {
pub blank: bool,
pub glyphs: Vec<ShapeGlyph>,
pub x_advance: f32,
pub y_advance: f32,
}
impl ShapeWord {
@ -603,19 +608,15 @@ impl ShapeWord {
);
}
let mut x_advance = 0.0;
let mut y_advance = 0.0;
for glyph in &glyphs {
x_advance += glyph.x_advance;
y_advance += glyph.y_advance;
}
Self { blank, glyphs }
}
Self {
blank,
glyphs,
x_advance,
y_advance,
pub fn width(&self, font_size: f32) -> f32 {
let mut width = 0.0;
for glyph in self.glyphs.iter() {
width += glyph.width(font_size);
}
width
}
}
@ -1025,7 +1026,7 @@ impl ShapeLine {
let mut word_range_width = 0.;
let mut number_of_blanks: u32 = 0;
for word in span.words.iter() {
let word_width = font_size * word.x_advance;
let word_width = word.width(font_size);
word_range_width += word_width;
if word.blank {
number_of_blanks += 1;
@ -1051,7 +1052,7 @@ impl ShapeLine {
// incongruent directions
let mut fitting_start = (span.words.len(), 0);
for (i, word) in span.words.iter().enumerate().rev() {
let word_width = font_size * word.x_advance;
let word_width = word.width(font_size);
// Addition in the same order used to compute the final width, so that
// relayouts with that width as the `line_width` will produce the same
@ -1098,7 +1099,7 @@ impl ShapeLine {
}
for (glyph_i, glyph) in word.glyphs.iter().enumerate().rev() {
let glyph_width = font_size * glyph.x_advance;
let glyph_width = glyph.width(font_size);
if current_visual_line.w + (word_range_width + glyph_width)
<= line_width
{
@ -1179,7 +1180,7 @@ impl ShapeLine {
// congruent direction
let mut fitting_start = (0, 0);
for (i, word) in span.words.iter().enumerate() {
let word_width = font_size * word.x_advance;
let word_width = word.width(font_size);
if current_visual_line.w + (word_range_width + word_width)
<= line_width
// Include one blank word over the width limit since it won't be
@ -1222,7 +1223,7 @@ impl ShapeLine {
}
for (glyph_i, glyph) in word.glyphs.iter().enumerate() {
let glyph_width = font_size * glyph.x_advance;
let glyph_width = glyph.width(font_size);
if current_visual_line.w + (word_range_width + glyph_width)
<= line_width
{
@ -1383,9 +1384,12 @@ impl ShapeLine {
(true, true) => &word.glyphs[starting_glyph..ending_glyph],
};
let match_mono_em_width = match_mono_width.map(|w| w / font_size);
for glyph in included_glyphs {
// Use overridden font size
let font_size = glyph.metrics_opt.map_or(font_size, |x| x.font_size);
let match_mono_em_width = match_mono_width.map(|w| w / font_size);
let glyph_font_size = match (
match_mono_em_width,
glyph.font_monospace_em_width,
@ -1419,8 +1423,8 @@ impl ShapeLine {
x += x_advance;
}
y += y_advance;
max_ascent = max_ascent.max(glyph.ascent);
max_descent = max_descent.max(glyph.descent);
max_ascent = max_ascent.max(glyph_font_size * glyph.ascent);
max_descent = max_descent.max(glyph_font_size * glyph.descent);
}
}
}
@ -1445,8 +1449,8 @@ impl ShapeLine {
} else {
x
},
max_ascent: max_ascent * font_size,
max_descent: max_descent * font_size,
max_ascent,
max_descent,
glyphs,
});
}