Skip to content

Commit

Permalink
Add font glyph layout concepts docs
Browse files Browse the repository at this point in the history
  • Loading branch information
alexheretic committed Jun 21, 2024
1 parent 75d10ae commit f6eb81a
Show file tree
Hide file tree
Showing 4 changed files with 41 additions and 8 deletions.
4 changes: 4 additions & 0 deletions glyph/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
# Unreleased
* Add `Font` glyph layout concept documentation demonstrating "ascent", "descent", "h_side_bearing",
"h_advance", "height", "line_gap", "baseline".

# 0.2.26
* Update _ttf-parser_ to `0.21`.

Expand Down
31 changes: 27 additions & 4 deletions glyph/src/font.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,27 @@ use crate::{
///
/// ab_glyph uses a non-standard scale [`PxScale`] which is the pixel height
/// of the text. See [`Font::pt_to_px_scale`] to convert standard point sizes.
///
/// ## Glyph layout concepts
/// Fonts provide several properties to inform layout of glyphs.
/// ```text
/// ‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
/// | .:x++++== |
/// | .#+ |
/// | :@ =++=++x=: |
/// ascent | +# x: +x x+ |
/// | =# #: :#:---:#: | height
/// | -@- #: .#--:-- |
/// | =#:-.-==#: #x+===:. |
/// baseline ____________ .-::-. .. #: .:@. |
/// | #+--..-=#. |
/// descent | -::=::- |
/// ____________________________________
/// | | | | line_gap
/// | | h_advance | ‾
/// ^
/// h_side_bearing
/// ```
pub trait Font {
/// Get the size of the font unit
///
Expand Down Expand Up @@ -56,25 +77,25 @@ pub trait Font {
Some(PxScale::from(px_per_em * height / units_per_em))
}

/// Unscaled glyph ascent.
/// Unscaled glyph ascent. See [glyph layout concepts](Font#glyph-layout-concepts).
///
/// Scaling can be done with [`as_scaled`](Self::as_scaled).
fn ascent_unscaled(&self) -> f32;

/// Unscaled glyph descent.
/// Unscaled glyph descent. See [glyph layout concepts](Font#glyph-layout-concepts).
///
/// Scaling can be done with [`as_scaled`](Self::as_scaled).
fn descent_unscaled(&self) -> f32;

/// Unscaled height `ascent - descent`.
/// Unscaled height `ascent - descent`. See [glyph layout concepts](Font#glyph-layout-concepts).
///
/// Scaling can be done with [`as_scaled`](Self::as_scaled).
#[inline]
fn height_unscaled(&self) -> f32 {
self.ascent_unscaled() - self.descent_unscaled()
}

/// Unscaled line gap.
/// Unscaled line gap. See [glyph layout concepts](Font#glyph-layout-concepts).
///
/// Scaling can be done with [`as_scaled`](Self::as_scaled).
fn line_gap_unscaled(&self) -> f32;
Expand All @@ -85,13 +106,15 @@ pub trait Font {
fn glyph_id(&self, c: char) -> GlyphId;

/// Unscaled horizontal advance for a given glyph id.
/// See [glyph layout concepts](Font#glyph-layout-concepts).
///
/// Returns `0.0` if the font does not define this value.
///
/// Scaling can be done with [`as_scaled`](Self::as_scaled).
fn h_advance_unscaled(&self, id: GlyphId) -> f32;

/// Unscaled horizontal side bearing for a given glyph id.
/// See [glyph layout concepts](Font#glyph-layout-concepts).
///
/// Returns `0.0` if the font does not define this value.
///
Expand Down
4 changes: 4 additions & 0 deletions glyph/src/glyph.rs
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,10 @@ pub struct Glyph {
/// Pixel scale of this glyph.
pub scale: PxScale,
/// Position of this glyph.
///
/// Horizontally this is to the left of the glyph before applying

This comment has been minimized.

Copy link
@djeedai

djeedai Jun 22, 2024

The formulation is still not clear to me. Saying "the position is at the baseline" is confusing I think. A position is the offset of something relative to the origin of the coordinate system.

  • Either the position is the position of the baseline itself, that means there's a coordinate system origin somewhere.
  • Or on the other hand the origin of the coordinate system is centered on the baseline, then the position is the position of something else (the glyph bounds?) relative to the baseline.

My understanding is that:

  • OutlineGlyph::px_bounds is the position of the top left corner of the glyph's raster bounding rectangle relative to what I call the "pen position", which is the point on the baseline used as reference before applying e.g. the advance. (this would be the top row in the diagram below)
  • Glyph::position as returned by glyph_brush_layout::Layout is the position of that "pen position" origin (and therefore, the position of the baseline) relative to a reference point which depends on HorizontalAlign and VerticalAlign, and can be called the "rendering point". (this would be the bottom row in the diagram below; the origin depends on the alignment)

typography

This comment has been minimized.

Copy link
@alexheretic

alexheretic Jun 22, 2024

Author Owner

I don't think I understand what you are driving at sorry. The origin of the position vector is not defined in this crate, but regardless the position is still exactly the point now described in the docs. It is always this point relative to the glyph.

And it's a useful position to use as all glyphs on the same line have the same y position.

glyph_brush_layout is downstream of this crate. None of it's usage changes the core concepts of this crate. Probably better to raise issues with that on its own repo.

This comment has been minimized.

Copy link
@djeedai

djeedai Jun 23, 2024

Sorry I'm not sure how to rephrase. Let's ignore layouting and glyph_brush_layout. My point is that:

Vertically this is at the "baseline".

makes no sense to me. A position cannot be "at" something. A position is the offset of something relative to something else (the origin of the coordinate system you're expressing the position in). For example, the position of a pixel on screen is (generally) relative to the top left corner of the screen. You can't interpret "position" without knowing where's that reference origin.

So it seems to me the position is the offset of the glyph top edge (for example; or something else?) relative to the baseline. That would make sense; when you render text, your input is the baseline position, and your output is the rendered glyph, which you need to position relative to that baseline.

This comment has been minimized.

Copy link
@alexheretic

alexheretic Jun 23, 2024

Author Owner

makes no sense to me. A position cannot be "at" something

It can and is "at" something. The glyph position has, and must have for the API to work, a well defined concrete location relative to the glyph. This position is exactly where I've documented it. This concept is important as the rects returned by px_bounds & glyph_bounds rely on it.

For example, if I wanted to layout the word "baz" at the top left corner of a view, lets call it (0, 0), and I want the word to be in view but without any padding, I would position the glyphs something like (lets ignore kerning)

  • 'b' at (0, font.ascent)
  • 'a' at ('b'.h_advance, font.ascent)
  • 'z' at ('b'.h_advance + 'a'.h_advance, font.ascent)

I'd use font.ascent to set the y position here because I don't want the word to overlap the top of my view and since the y position is at the baseline I need to push it down at least font.ascent to ensure all glyphs will be in view.

If I wanted to put another word "qux" underneath that I'd use positions:

  • 'q' at (0, font.ascent + font.height + font.line_gap)
  • 'u' at ('q'.h_advance, font.ascent + font.height + font.line_gap)
  • 'x' at ('q'.h_advance + 'u'.h_advance, font.ascent + font.height + font.line_gap)

It would end up looking like this (with green dots marking approximately where each glyph position is)
baz-qux-positions

This comment has been minimized.

Copy link
@djeedai

djeedai Jun 24, 2024

Thanks for your example, this is very helpful. I agree with it, although I would call all of that layouting (you're describing the placement of multiple characters, and even multiple lines), which I thought was done by glyph_brush_layout? Apparently it's split with ab_glyph then.

So, based on your example, I understand that the position of a glyph is the position of its origin (on the baseline) relative to the draw point, which in the example is the top left corner of the text section (and happens to be at (0,0), but that's a detail).

That means that to draw a glyph you need to:

  1. Start from you draw point
  2. Add the glyph position, which moves you at the baseline on the immediate left of the glyph
  3. Add the bounds to obtain the exact rectangle where the glyph needs to be rendered

Is that correct?

If so, Glyph::position is "the position of the glyph relative to the origin of the text section (top left corner)", and is (surprisingly to me) a layouting position (it depends on the entire text, not on the glyph taken in isolation).

This comment has been minimized.

Copy link
@alexheretic

alexheretic Jun 25, 2024

Author Owner

(you're describing the placement of multiple characters, and even multiple lines), which I thought was done by glyph_brush_layout

It is, my example I did it manually. ab_glyph glyphs are designed to be positioned somewhere but there is no layout logic provided. The (sub)pixel position matters as it affects the draw coverage calculation per pixel.

I'm glad we agree on where the position is now relative to the glyph.

Glyph::position is "the position of the glyph relative to the origin of the text section (top left corner)", and is (surprisingly to me) a layouting position

There is no definition of where the origin of the position vector is provided by this crate. Glyph::position just needs to be in pixels and the origin should be a whole pixel position for rendering to work correctly.

glyph_brush uses absolute screen position.

(it depends on the entire text, not on the glyph taken in isolation).

No each glyph has a position. If you want to layout the glyphs you'll need to position them in a row, but nothing about that means an individual glyph's position doesn't make sense in isolation.

/// `h_advance` or `h_side_bearing`. Vertically this is at the "baseline".
/// See [glyph layout concepts](trait.Font.html#glyph-layout-concepts).
pub position: Point,
}

Expand Down
10 changes: 6 additions & 4 deletions glyph/src/scale.rs
Original file line number Diff line number Diff line change
Expand Up @@ -103,27 +103,27 @@ pub trait ScaleFont<F: Font> {
}
}

/// Pixel scaled glyph ascent.
/// Pixel scaled glyph ascent. See [glyph layout concepts](Font#glyph-layout-concepts).
#[inline]
fn ascent(&self) -> f32 {
self.v_scale_factor() * self.font().ascent_unscaled()
}

/// Pixel scaled glyph descent.
/// Pixel scaled glyph descent. See [glyph layout concepts](Font#glyph-layout-concepts).
#[inline]
fn descent(&self) -> f32 {
self.v_scale_factor() * self.font().descent_unscaled()
}

/// Pixel scaled height `ascent - descent`.
/// Pixel scaled height `ascent - descent`. See [glyph layout concepts](Font#glyph-layout-concepts).
///
/// By definition of [`PxScale`], this is `self.scale().y`.
#[inline]
fn height(&self) -> f32 {
self.scale().y
}

/// Pixel scaled line gap.
/// Pixel scaled line gap. See [glyph layout concepts](Font#glyph-layout-concepts).
#[inline]
fn line_gap(&self) -> f32 {
self.v_scale_factor() * self.font().line_gap_unscaled()
Expand Down Expand Up @@ -157,12 +157,14 @@ pub trait ScaleFont<F: Font> {
}

/// Pixel scaled horizontal advance for a given glyph.
/// See [glyph layout concepts](Font#glyph-layout-concepts).
#[inline]
fn h_advance(&self, id: GlyphId) -> f32 {
self.h_scale_factor() * self.font().h_advance_unscaled(id)
}

/// Pixel scaled horizontal side bearing for a given glyph.
/// See [glyph layout concepts](Font#glyph-layout-concepts).
#[inline]
fn h_side_bearing(&self, id: GlyphId) -> f32 {
self.h_scale_factor() * self.font().h_side_bearing_unscaled(id)
Expand Down

0 comments on commit f6eb81a

Please sign in to comment.