Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Ability to obtain baseline Y offset #123

Closed
JayTee42 opened this issue Apr 21, 2023 · 3 comments · Fixed by #141
Closed

Ability to obtain baseline Y offset #123

JayTee42 opened this issue Apr 21, 2023 · 3 comments · Fixed by #141

Comments

@JayTee42
Copy link

JayTee42 commented Apr 21, 2023

I try to utilize cosmic-text to draw text to a bitmap and to obtain a tight bounding box around the result. Right now, I am confused by the calculation of the vertical offset of the text. To elaborate further, let us assume that we draw a single glyph, i.e. the letter "g", using a Buffer. For the sake of simplicity, we set line_height == font_size == 72.0 (i.e. we do not apply leading aka additional distance between the lines). When we call Buffer::draw(), the code first creates a LayoutRunIter whose line_y property is initialized as follows:

line_y: buffer.metrics.y_offset(),

In our case, y_offset() evaluates to 0.0 (no leading, see above) and therefore, our run starts at a vertical offset of 0.0. Now, we call next() on the iterator to draw the first run and observe that line_y is incremented by the line height which is equal to the font size (aka 72.0):

self.line_y += self.buffer.metrics.line_height;

Finally, we obtain the glyph image from `swash' and draw it to the given offset:

cosmic-text/src/buffer.rs

Lines 718 to 720 in bfb5eef

cache.with_pixels(font_system, cache_key, glyph_color, |x, y, color| {
f(x_int + x, run.line_y as i32 + y_int + y, 1, 1, color);
});

run.line_y still evaluates to 72.0 in our example. However, if I understand correctly, the swash image is positioned relatively to the baseline of the glyph. I did not find explicit confirmation for this assumption in the swash documentation, but the swash_demo repo provides an extensive sample for this. When drawing its layout, it incorporates the baseline into the vertical offset as seen here:

https://github.com/dfrg/swash_demo/blob/55aedbc8604201f1b4925e2b986db59b838dc062/src/main.rs#L493

In opposition, cosmic-text effectively places the baseline at the bottom of the line. When I try to draw my "g" glyph to the top-left corner of a bitmap and set my bounding box height to 72 pix, I end up with the following result:

g

How to fix this? I am not adept in typography, but for my understanding, the vertical offset must be corrected by the descent of the font as displayed here:

glyph

Indeed, to draw my glyphs to the bitmap, I tried to calculate the baseline of the font by myself to apply this correction. This approach works, but cosmic-text makes it pretty hard to implement it. Basically, I have to layout my line, obtain the FontID from the glyphs, query the Font from the FontSystem and do something like this:

let metrics = font.as_swash().metrics(&[]);
let total_height = metrics.ascent + metrics.descent;
let baseline = font_size * metrics.ascent / total_height;

In fact, this is about the same calculation that is done in the swash demo, see here:

https://github.com/dfrg/swash_demo/blob/d08ced5f1e92b62cd637415c1f66c440d737b31b/src/layout/line_breaker.rs#L324-L326

By using baseline instead of font_size (resp. the total line height) as vertical offset, I finally get the correct result. I can live with this approach, but it would be nice if cosmic-text made it easier for me to access the baseline offsets of the layout lines.

Sorry for the long explanation. I hope the issue has been made clear - and thanks for your awesome work on this crate!

@tigregalis
Copy link
Contributor

tigregalis commented Apr 22, 2023

Thanks so much for this write-up. I've been struggling with this without knowing what was going on, and was about to submit my own issue until I saw this and realised it was (likely, I might be wrong) symptomatic of the same underlying issue (not rendering relative to the baseline).

Below is font size = 100 (pixels), font family = Fira Sans.

Left is based on the "rich-text" example in the cosmic text repo, right is Google Chrome. Red and green lines are for emphasis. We can see that Chrome renders higher up.
image

In fact it's somewhat clear just by selecting the text: the glyphs partially render below the rectangular selection:
image

I think that more than just expose the baseline offsets, cosmic text should render relative to the baseline. I think what you've put together is a nice workaround, but doing the offsets manually I assume loses subpixel information.

Additionally, (as a feature request) I think cosmic-text should expose the total buffer height, just as it exposes the lines' widths. This would make it straightforward to:

  1. input the text and attributes
  2. shape and layout the text
  3. determine the size of the text area*
  4. allocate an image buffer with that size
  5. fill the image buffer using the draw command

*currently I'm approximating this with (buffer.layout_runs().count() as f32 * line_height).ceil()

@hecrj
Copy link
Contributor

hecrj commented May 9, 2023

We have encountered this issue as well in iced. Line height does not seem to work as you would expect: iced-rs/iced#1830 (comment)

@hecrj
Copy link
Contributor

hecrj commented Jun 16, 2023

Opened #141 that I believe should fix this.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

3 participants