summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorQwerasd <qwerasd205@users.noreply.github.com>2025-08-20 15:26:16 -0600
committerQwerasd <qwerasd205@users.noreply.github.com>2025-08-20 15:26:16 -0600
commit610ce94f2d5308392ea0fbf88a5a7c87b234040a (patch)
tree85df41f2a3ddec2c11d776a2fca4dbee2db8f21b
parenta909aac2520c9890f7115a10040cc14ae68d667a (diff)
font/CoreText: fix positioning for padded scaled glyphs
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.
-rw-r--r--src/font/face/coretext.zig32
1 files changed, 24 insertions, 8 deletions
diff --git a/src/font/face/coretext.zig b/src/font/face/coretext.zig
index 1b1c559fb..cb6e6b1f7 100644
--- a/src/font/face/coretext.zig
+++ b/src/font/face/coretext.zig
@@ -408,18 +408,15 @@ pub const Face = struct {
const px_x: i32 = @intFromFloat(@floor(x));
const px_y: i32 = @intFromFloat(@floor(y));
- // We offset our glyph by its bearings when we draw it, so that it's
- // rendered fully inside our canvas area, but we make sure to keep the
- // fractional pixel offset so that we rasterize with the appropriate
- // sub-pixel position.
+ // We keep track of the fractional part of the pixel bearings, which
+ // we will add as an offset when rasterizing to make sure we get the
+ // correct sub-pixel position.
const frac_x = x - @floor(x);
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
// 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_height: u32 = @intFromFloat(@ceil(height + frac_y));
@@ -525,6 +522,17 @@ pub const Face = struct {
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
// our glyph it's stretched to the constrained size.
context.scaleCTM(
@@ -534,7 +542,15 @@ pub const Face = struct {
);
// 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.
const region = try atlas.reserve(alloc, px_width, px_height);