font/CoreText: fix positioning for padded scaled glyphs (#8310)

When constraints increased or decreased the size significantly, the
fractional position was getting messed up by the scale. This change
separates that out so that it applies correctly.

I noticed this when messing around with constraints, adding this
constraint to every glyph and then running with `font-family=Arial` and
`adjust-cell-width = -35%` (if you want to reproduce this)
```zig
constraint = .{
    .size_horizontal = .stretch,
    .align_horizontal = .center,
    .pad_left = 0.1,
    .pad_right = 0.1,
};
```
The padding was disproportionately affecting thin glyphs that were
stretched a lot. The problem was that the padding was being multiplied
by the scale.

This also made it so the top or right of said thin glyphs often got
clipped off by the edge of the canvas.

Anyway I fixed it.

|Before|After|
|-|-|
|<img width="1824" height="1480" alt="image"
src="https://github.com/user-attachments/assets/32779f9d-a048-4a8c-b5ea-0e8a851d5119"
/>|<img width="1824" height="1480" alt="image"
src="https://github.com/user-attachments/assets/5bf449e5-699e-4bdc-ac96-2b776f9fb7fa"
/>|
pull/8322/head
Qwerasd 2025-08-20 21:55:51 -06:00 committed by GitHub
commit d2ac29c919
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
1 changed files with 24 additions and 8 deletions

View File

@ -408,18 +408,15 @@ pub const Face = struct {
const px_x: i32 = @intFromFloat(@floor(x)); const px_x: i32 = @intFromFloat(@floor(x));
const px_y: i32 = @intFromFloat(@floor(y)); const px_y: i32 = @intFromFloat(@floor(y));
// We offset our glyph by its bearings when we draw it, so that it's // We keep track of the fractional part of the pixel bearings, which
// rendered fully inside our canvas area, but we make sure to keep the // we will add as an offset when rasterizing to make sure we get the
// fractional pixel offset so that we rasterize with the appropriate // correct sub-pixel position.
// sub-pixel position.
const frac_x = x - @floor(x); const frac_x = x - @floor(x);
const frac_y = y - @floor(y); const frac_y = y - @floor(y);
const draw_x = -rect.origin.x + frac_x;
const draw_y = -rect.origin.y + frac_y;
// Add the fractional pixel to the width and height and take // Add the fractional pixel to the width and height and take
// the ceiling to get a canvas size that will definitely fit // the ceiling to get a canvas size that will definitely fit
// our drawn glyph. // our drawn glyph, including the fractional offset.
const px_width: u32 = @intFromFloat(@ceil(width + frac_x)); const px_width: u32 = @intFromFloat(@ceil(width + frac_x));
const px_height: u32 = @intFromFloat(@ceil(height + frac_y)); const px_height: u32 = @intFromFloat(@ceil(height + frac_y));
@ -525,6 +522,17 @@ pub const Face = struct {
context.setLineWidth(ctx, line_width); context.setLineWidth(ctx, line_width);
} }
// Translate our drawing context so that when we draw our
// glyph the bottom/left edge is at the correct sub-pixel
// position. The bottom/left edges are guaranteed to be at
// exactly [0, 0] relative to this because when we call to
// `drawGlyphs`, we pass the negated bearings.
context.translateCTM(
ctx,
frac_x,
frac_y,
);
// Scale the drawing context so that when we draw // Scale the drawing context so that when we draw
// our glyph it's stretched to the constrained size. // our glyph it's stretched to the constrained size.
context.scaleCTM( context.scaleCTM(
@ -534,7 +542,15 @@ pub const Face = struct {
); );
// Draw our glyph. // Draw our glyph.
self.font.drawGlyphs(&glyphs, &.{.{ .x = draw_x, .y = draw_y }}, ctx); //
// We offset the position by the negated bearings so that the
// glyph is drawn at exactly [0, 0], which is then offset to
// the appropriate fractional position by the translation we
// did before scaling.
self.font.drawGlyphs(&glyphs, &.{.{
.x = -rect.origin.x,
.y = -rect.origin.y,
}}, ctx);
// Write our rasterized glyph to the atlas. // Write our rasterized glyph to the atlas.
const region = try atlas.reserve(alloc, px_width, px_height); const region = try atlas.reserve(alloc, px_width, px_height);