Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
62 changes: 62 additions & 0 deletions core/src/border.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,68 @@ pub struct Border {
pub radius: Radius,
}

/// A single edge of a box border.
///
/// Unlike [`Border`], which describes one uniform outline, a `Side` can be
/// used by widgets that need CSS-like dividers without drawing a second box.
#[derive(Debug, Clone, Copy, PartialEq, Default)]
pub struct Side {
/// The color of the edge.
pub color: Color,

/// The width of the edge.
pub width: f32,
}

/// Independent border edges for a box.
#[derive(Debug, Clone, Copy, PartialEq, Default)]
pub struct Sides {
/// The top edge.
pub top: Side,
/// The right edge.
pub right: Side,
/// The bottom edge.
pub bottom: Side,
/// The left edge.
pub left: Side,
}

/// Creates a box-border [`Side`].
pub fn side(color: Color, width: impl Into<Pixels>) -> Side {
Side {
color,
width: width.into().0,
}
}

impl Sides {
/// Sets the top edge.
pub fn top(self, side: Side) -> Self {
Self { top: side, ..self }
}

/// Sets the right edge.
pub fn right(self, side: Side) -> Self {
Self {
right: side,
..self
}
}

/// Sets the bottom edge.
pub fn bottom(self, side: Side) -> Self {
Self {
bottom: side,
..self
}
}

/// Sets the left edge.
pub fn left(self, side: Side) -> Self {
Self { left: side, ..self }
}
}

/// Creates a new [`Border`] with the given [`Radius`].
///
/// ```
Expand Down
117 changes: 117 additions & 0 deletions core/src/font.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,55 @@
//! Load and use fonts.
use std::hash::Hash;

/// Extra space between glyphs, as a fraction of the type size.
///
/// Tracking is expressed relative to the size rather than in pixels, so a
/// heading and a caption asking for the same tracking stay proportionate to
/// one another. A design that says `0.06em` means [`Tracking(0.06)`].
///
/// [`Tracking(0.06)`]: Tracking
#[derive(Debug, Clone, Copy, Default)]
pub struct Tracking(pub f32);

impl Tracking {
/// No extra space. Glyphs sit at their natural advance.
pub const NONE: Self = Self(0.0);

/// Whether this tracking would change anything.
pub fn is_none(self) -> bool {
self.0 == 0.0
}
}

// `Font` is a hash key throughout the text pipeline, so tracking has to be
// comparable and hashable by value. Compare and hash the bits, canonicalizing
// the two values that would otherwise break the `Eq`/`Hash` agreement: NaN is
// never equal to itself, and `-0.0 == 0.0` while their bits differ.
impl PartialEq for Tracking {
fn eq(&self, other: &Self) -> bool {
if self.0.is_nan() {
other.0.is_nan()
} else {
self.0 == other.0
}
}
}

impl Eq for Tracking {}

impl std::hash::Hash for Tracking {
fn hash<H: std::hash::Hasher>(&self, hasher: &mut H) {
const CANONICAL_NAN: u32 = 0x7fc0_0000;

let bits = if self.0.is_nan() {
CANONICAL_NAN
} else {
(self.0 + 0.0).to_bits()
};
bits.hash(hasher);
}
}

/// A font.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
pub struct Font {
Expand All @@ -12,6 +61,8 @@ pub struct Font {
pub stretch: Stretch,
/// The [`Style`] of the [`Font`].
pub style: Style,
/// The [`Tracking`] of the [`Font`].
pub tracking: Tracking,
}
Comment on lines 62 to 66

impl Font {
Expand All @@ -21,6 +72,7 @@ impl Font {
weight: Weight::Normal,
stretch: Stretch::Normal,
style: Style::Normal,
tracking: Tracking::NONE,
};

/// A monospaced font with normal [`Weight`].
Expand Down Expand Up @@ -55,6 +107,11 @@ impl Font {
Self { stretch, ..self }
}

/// Sets the [`Tracking`] of the [`Font`].
pub const fn tracking(self, tracking: Tracking) -> Self {
Self { tracking, ..self }
}

/// Sets the [`Style`] of the [`Font`].
pub const fn style(self, style: Style) -> Self {
Self { style, ..self }
Expand Down Expand Up @@ -198,3 +255,63 @@ pub enum Style {
/// A font error.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Error {}

#[cfg(test)]
mod tests {
use super::*;
use std::collections::hash_map::DefaultHasher;
use std::hash::{Hash, Hasher};

fn hash(font: Font) -> u64 {
let mut hasher = DefaultHasher::new();
font.hash(&mut hasher);
hasher.finish()
}

#[test]
fn a_font_defaults_to_the_face_s_own_tracking() {
assert!(Font::DEFAULT.tracking.is_none());
assert!(Font::MONOSPACE.tracking.is_none());
assert!(Font::new("Archivo").tracking.is_none());
}

#[test]
fn tracking_takes_part_in_equality_and_hashing() {
let plain = Font::new("JetBrains Mono");
let tracked = plain.tracking(Tracking(0.06));

assert_ne!(plain, tracked);
assert_ne!(hash(plain), hash(tracked));
assert_eq!(
tracked,
Font::new("JetBrains Mono").tracking(Tracking(0.06))
);
assert_eq!(
hash(tracked),
hash(Font::new("JetBrains Mono").tracking(Tracking(0.06)))
);
}

#[test]
fn equality_and_hashing_agree_on_the_awkward_values() {
// A hash key must never claim two values are equal while hashing them
// differently. Negative zero and NaN are the two that would.
let zero = Font::DEFAULT.tracking(Tracking(0.0));
let negative_zero = Font::DEFAULT.tracking(Tracking(-0.0));
assert_eq!(zero, negative_zero);
assert_eq!(hash(zero), hash(negative_zero));

let nan = Font::DEFAULT.tracking(Tracking(f32::NAN));
let other_nan = Font::DEFAULT.tracking(Tracking(-f32::NAN));
assert_eq!(nan, other_nan);
assert_eq!(hash(nan), hash(other_nan));
}

#[test]
fn only_a_real_tracking_counts_as_set() {
assert!(Tracking::NONE.is_none());
assert!(Tracking(0.0).is_none());
assert!(Tracking(-0.0).is_none());
assert!(!Tracking(0.06).is_none());
}
}
2 changes: 1 addition & 1 deletion core/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ pub use color::Color;
pub use content_fit::ContentFit;
pub use element::Element;
pub use event::Event;
pub use font::Font;
pub use font::{Font, Tracking};
pub use gradient::Gradient;
pub use image::Image;
pub use input_method::InputMethod;
Expand Down
13 changes: 11 additions & 2 deletions graphics/src/text.rs
Original file line number Diff line number Diff line change
Expand Up @@ -293,11 +293,20 @@ pub fn align(

/// Returns the attributes of the given [`Font`].
pub fn to_attributes(font: Font) -> cosmic_text::Attrs<'static> {
cosmic_text::Attrs::new()
let attributes = cosmic_text::Attrs::new()
.family(to_family(font.family))
.weight(to_weight(font.weight))
.stretch(to_stretch(font.stretch))
.style(to_style(font.style))
.style(to_style(font.style));

// Cosmic Text also takes tracking in em, so it passes through unscaled.
// Left unset when it would change nothing, so a font that asks for no
// tracking keeps whatever the face itself specifies.
if font.tracking.is_none() {
attributes
} else {
attributes.letter_spacing(font.tracking.0)
}
}

fn to_family(family: font::Family) -> cosmic_text::Family<'static> {
Expand Down
Loading