This repository has no description
0

Configure Feed

Select the types of activity you want to include in your feed.

font: reshuffle glyph sizing types to Glyph.zig

author
Mitchell Hashimoto
date (Jun 5, 2026, 6:58 AM -0700) commit 1c0aac54 parent d271b271 change-id zylllxrp
+648 -639
+1 -1
src/font/CodepointResolver.zig
··· 24 24 const Glyph = font.Glyph; 25 25 const Library = font.Library; 26 26 const Presentation = font.Presentation; 27 - const RenderOptions = font.face.RenderOptions; 27 + const RenderOptions = font.Glyph.RenderOptions; 28 28 const SpriteFace = font.SpriteFace; 29 29 const Style = font.Style; 30 30
+611
src/font/Glyph.zig
··· 1 1 //! Glyph is a single loaded glyph for a face. 2 2 const Glyph = @This(); 3 3 4 + const std = @import("std"); 5 + const Metrics = @import("Metrics.zig"); 6 + 4 7 /// width of glyph in pixels 5 8 width: u32, 6 9 ··· 17 20 /// be normalized to be between 0 and 1 prior to use in shaders. 18 21 atlas_x: u32, 19 22 atlas_y: u32, 23 + 24 + /// The size and position of a glyph. 25 + pub const Size = struct { 26 + width: f64, 27 + height: f64, 28 + x: f64, 29 + y: f64, 30 + }; 31 + 32 + /// Metrics describing the authored glyph coordinate space. 33 + pub const DesignMetrics = struct { 34 + /// Units-per-em for outline/design coordinates. 35 + units_per_em: u32, 36 + 37 + /// Authored advance width in design units. 38 + advance_width: u32, 39 + 40 + /// Authored line height in design units. 41 + line_height: u32, 42 + }; 43 + 44 + /// Additional options for rendering glyphs. 45 + pub const RenderOptions = struct { 46 + /// The metrics that are defining the grid layout. These are usually 47 + /// the metrics of the primary font face. The grid metrics are used 48 + /// by the font face to better layout the glyph in situations where 49 + /// the font is not exactly the same size as the grid. 50 + grid_metrics: Metrics, 51 + 52 + /// The number of grid cells this glyph will take up. This can be used 53 + /// optionally by the rasterizer to better layout the glyph. 54 + cell_width: ?u2 = null, 55 + 56 + /// Constraint and alignment properties for the glyph. The rasterizer 57 + /// should call the `constrain` function on this with the original size 58 + /// and bearings of the glyph to get remapped values that the glyph 59 + /// should be scaled/moved to. 60 + constraint: Constraint = .none, 61 + 62 + /// The number of cells, horizontally that the glyph is free to take up 63 + /// when resized and aligned by `constraint`. This is usually 1, but if 64 + /// there's whitespace to the right of the cell then it can be 2. 65 + constraint_width: u2 = 1, 66 + 67 + /// Thicken the glyph. This draws the glyph with a thicker stroke width. 68 + /// This is purely an aesthetic setting. 69 + /// 70 + /// This only works with CoreText currently. 71 + thicken: bool = false, 72 + 73 + /// "Strength" of the thickening, between `0` and `255`. 74 + /// Only has an effect when `thicken` is enabled. 75 + /// 76 + /// `0` does not correspond to *no* thickening, 77 + /// just the *lightest* thickening available. 78 + /// 79 + /// CoreText only. 80 + thicken_strength: u8 = 255, 81 + 82 + /// See the `constraint` field. 83 + pub const Constraint = struct { 84 + /// Don't constrain the glyph in any way. 85 + pub const none: Constraint = .{}; 86 + 87 + /// Sizing rule. 88 + size: Constraint.Size = .none, 89 + 90 + /// Vertical alignment rule. 91 + align_vertical: Align = .none, 92 + /// Horizontal alignment rule. 93 + align_horizontal: Align = .none, 94 + 95 + /// Top padding when resizing. 96 + pad_top: f64 = 0.0, 97 + /// Left padding when resizing. 98 + pad_left: f64 = 0.0, 99 + /// Right padding when resizing. 100 + pad_right: f64 = 0.0, 101 + /// Bottom padding when resizing. 102 + pad_bottom: f64 = 0.0, 103 + 104 + // Size and bearings of the glyph relative 105 + // to the bounding box of its scale group. 106 + relative_width: f64 = 1.0, 107 + relative_height: f64 = 1.0, 108 + relative_x: f64 = 0.0, 109 + relative_y: f64 = 0.0, 110 + 111 + /// Maximum aspect ratio (width/height) to allow when stretching. 112 + max_xy_ratio: ?f64 = null, 113 + 114 + /// Maximum number of cells horizontally to use. 115 + max_constraint_width: u2 = 2, 116 + 117 + /// What to use as the height metric when constraining the glyph and 118 + /// the constraint width is 1, 119 + height: Height = .cell, 120 + 121 + pub const Size = enum { 122 + /// Don't change the size of this glyph. 123 + none, 124 + /// Scale the glyph down if needed to fit within the bounds, 125 + /// preserving aspect ratio. 126 + fit, 127 + /// Scale the glyph up or down to exactly match the bounds, 128 + /// preserving aspect ratio. 129 + cover, 130 + /// Scale the glyph down if needed to fit within the bounds, 131 + /// preserving aspect ratio. If the glyph doesn't cover a 132 + /// single cell, scale up. If the glyph exceeds a single 133 + /// cell but is within the bounds, do nothing. 134 + /// (Nerd Font specific rule.) 135 + fit_cover1, 136 + /// Stretch the glyph to exactly fit the bounds in both 137 + /// directions, disregarding aspect ratio. 138 + stretch, 139 + }; 140 + 141 + pub const Align = enum { 142 + /// Don't move the glyph on this axis. 143 + none, 144 + /// Move the glyph so that its leading (bottom/left) 145 + /// edge aligns with the leading edge of the axis. 146 + start, 147 + /// Move the glyph so that its trailing (top/right) 148 + /// edge aligns with the trailing edge of the axis. 149 + end, 150 + /// Move the glyph so that it is centered on this axis. 151 + center, 152 + /// Move the glyph so that it is centered on this axis, 153 + /// but always with respect to the first cell even for 154 + /// multi-cell constraints. (Nerd Font specific rule.) 155 + center1, 156 + }; 157 + 158 + pub const Height = enum { 159 + /// Use the full line height of the primary face for 160 + /// constraining this glyph. 161 + cell, 162 + /// Use the icon height from the grid metrics for 163 + /// constraining this glyph. Unlike `cell`, the value of 164 + /// this height depends on both the constraint width and the 165 + /// affected by the `adjust-icon-height` config option. 166 + icon, 167 + }; 168 + 169 + /// Returns true if the constraint does anything. If it doesn't, 170 + /// because it neither sizes nor positions the glyph, then this 171 + /// returns false. 172 + pub inline fn doesAnything(self: Constraint) bool { 173 + return self.size != .none or 174 + self.align_horizontal != .none or 175 + self.align_vertical != .none; 176 + } 177 + 178 + /// Apply this constraint to the provided glyph 179 + /// size, given the available width and height. 180 + pub fn constrain( 181 + self: Constraint, 182 + glyph: Glyph.Size, 183 + metrics: Metrics, 184 + /// Number of cells horizontally available for this glyph. 185 + constraint_width: u2, 186 + ) Glyph.Size { 187 + if (!self.doesAnything()) return glyph; 188 + 189 + switch (self.size) { 190 + .stretch => { 191 + // Stretched glyphs are usually meant to align across cell 192 + // boundaries, which works best if they're scaled and 193 + // aligned to the grid rather than the face. This is most 194 + // easily done by inserting this little fib in the metrics. 195 + var m = metrics; 196 + m.face_width = @floatFromInt(m.cell_width); 197 + m.face_height = @floatFromInt(m.cell_height); 198 + m.face_y = 0.0; 199 + 200 + // Negative padding for stretched glyphs is a band-aid to 201 + // avoid gaps due to pixel rounding, but at the cost of 202 + // unsightly overlap artifacts. Since we scale and align to 203 + // the grid rather than the face, we don't need it. 204 + var c = self; 205 + c.pad_bottom = @max(0, c.pad_bottom); 206 + c.pad_top = @max(0, c.pad_top); 207 + c.pad_left = @max(0, c.pad_left); 208 + c.pad_right = @max(0, c.pad_right); 209 + 210 + return c.constrainInner(glyph, m, constraint_width); 211 + }, 212 + else => return self.constrainInner(glyph, metrics, constraint_width), 213 + } 214 + } 215 + 216 + fn constrainInner( 217 + self: Constraint, 218 + glyph: Glyph.Size, 219 + metrics: Metrics, 220 + constraint_width: u2, 221 + ) Glyph.Size { 222 + // For extra wide font faces, never stretch glyphs across two cells. 223 + // This mirrors font_patcher. 224 + const min_constraint_width: u2 = if ((self.size == .stretch) and (metrics.face_width > 0.9 * metrics.face_height)) 225 + 1 226 + else 227 + @min(self.max_constraint_width, constraint_width); 228 + 229 + // The bounding box for the glyph's scale group. 230 + // Scaling and alignment rules are calculated for 231 + // this box and then applied to the glyph. 232 + var group: Glyph.Size = group: { 233 + const group_width = glyph.width / self.relative_width; 234 + const group_height = glyph.height / self.relative_height; 235 + break :group .{ 236 + .width = group_width, 237 + .height = group_height, 238 + .x = glyph.x - (group_width * self.relative_x), 239 + .y = glyph.y - (group_height * self.relative_y), 240 + }; 241 + }; 242 + 243 + // Apply prescribed scaling, preserving the 244 + // center bearings of the group bounding box 245 + const width_factor, const height_factor = self.scale_factors(group, metrics, min_constraint_width); 246 + const center_x = group.x + (group.width / 2); 247 + const center_y = group.y + (group.height / 2); 248 + group.width *= width_factor; 249 + group.height *= height_factor; 250 + group.x = center_x - (group.width / 2); 251 + group.y = center_y - (group.height / 2); 252 + 253 + // NOTE: font_patcher jumps through a lot of hoops at this 254 + // point to ensure that the glyph remains within the target 255 + // bounding box after rounding to font definition units. 256 + // This is irrelevant here as we're not rounding, we're 257 + // staying in f64 and heading straight to rendering. 258 + 259 + // Apply prescribed alignment 260 + group.y = self.aligned_y(group, metrics); 261 + group.x = self.aligned_x(group, metrics, min_constraint_width); 262 + 263 + // Transfer the scaling and alignment back to the glyph and return. 264 + return .{ 265 + .width = width_factor * glyph.width, 266 + .height = height_factor * glyph.height, 267 + .x = group.x + (group.width * self.relative_x), 268 + .y = group.y + (group.height * self.relative_y), 269 + }; 270 + } 271 + 272 + /// Return width and height scaling factors for this scaling group. 273 + fn scale_factors( 274 + self: Constraint, 275 + group: Glyph.Size, 276 + metrics: Metrics, 277 + min_constraint_width: u2, 278 + ) struct { f64, f64 } { 279 + if (self.size == .none) { 280 + return .{ 1.0, 1.0 }; 281 + } 282 + 283 + const multi_cell = (min_constraint_width > 1); 284 + 285 + const pad_width_factor = @as(f64, @floatFromInt(min_constraint_width)) - (self.pad_left + self.pad_right); 286 + const pad_height_factor = 1 - (self.pad_bottom + self.pad_top); 287 + 288 + const target_width = pad_width_factor * metrics.face_width; 289 + const target_height = pad_height_factor * switch (self.height) { 290 + .cell => metrics.face_height, 291 + // Like font-patcher, the icon constraint height depends on the 292 + // constraint width. Unlike font-patcher, the multi-cell 293 + // icon_height may be different from face_height due to the 294 + // `adjust-icon-height` config option. 295 + .icon => if (multi_cell) 296 + metrics.icon_height 297 + else 298 + metrics.icon_height_single, 299 + }; 300 + 301 + var width_factor = target_width / group.width; 302 + var height_factor = target_height / group.height; 303 + 304 + switch (self.size) { 305 + .none => unreachable, 306 + .fit => { 307 + // Scale down to fit if needed 308 + height_factor = @min(1, width_factor, height_factor); 309 + width_factor = height_factor; 310 + }, 311 + .cover => { 312 + // Scale to cover 313 + height_factor = @min(width_factor, height_factor); 314 + width_factor = height_factor; 315 + }, 316 + .fit_cover1 => { 317 + // Scale down to fit or up to cover at least one cell 318 + // NOTE: This is similar to font_patcher's "pa" mode, 319 + // however, font_patcher will only do the upscaling 320 + // part if the constraint width is 1, resulting in 321 + // some icons becoming smaller when the constraint 322 + // width increases. You'd see icons shrinking when 323 + // opening up a space after them. This makes no 324 + // sense, so we've fixed the rule such that these 325 + // icons are scaled to the same size for multi-cell 326 + // constraints as they would be for single-cell. 327 + height_factor = @min(width_factor, height_factor); 328 + if (multi_cell and (height_factor > 1)) { 329 + // Call back into this function with 330 + // constraint width 1 to get single-cell scale 331 + // factors. We use the height factor as width 332 + // could have been modified by max_xy_ratio. 333 + _, const single_height_factor = self.scale_factors(group, metrics, 1); 334 + height_factor = @max(1, single_height_factor); 335 + } 336 + width_factor = height_factor; 337 + }, 338 + .stretch => {}, 339 + } 340 + 341 + // Reduce aspect ratio if required 342 + if (self.max_xy_ratio) |ratio| { 343 + if (group.width * width_factor > group.height * height_factor * ratio) { 344 + width_factor = group.height * height_factor * ratio / group.width; 345 + } 346 + } 347 + 348 + return .{ width_factor, height_factor }; 349 + } 350 + 351 + /// Return vertical bearing for aligning this group 352 + fn aligned_y( 353 + self: Constraint, 354 + group: Glyph.Size, 355 + metrics: Metrics, 356 + ) f64 { 357 + if ((self.size == .none) and (self.align_vertical == .none)) { 358 + // If we don't have any constraints affecting the vertical axis, 359 + // we don't touch vertical alignment. 360 + return group.y; 361 + } 362 + // We use face_height and offset by face_y, rather than 363 + // using cell_height directly, to account for the asymmetry 364 + // of the pixel cell around the face (a consequence of 365 + // aligning the baseline with a pixel boundary rather than 366 + // vertically centering the face). 367 + const pad_bottom_dy = self.pad_bottom * metrics.face_height; 368 + const pad_top_dy = self.pad_top * metrics.face_height; 369 + const start_y = metrics.face_y + pad_bottom_dy; 370 + const end_y = metrics.face_y + (metrics.face_height - group.height - pad_top_dy); 371 + const center_y = (start_y + end_y) / 2; 372 + return switch (self.align_vertical) { 373 + // NOTE: Even if there is no prescribed alignment, we ensure 374 + // that the group doesn't protrude outside the padded cell, 375 + // since this is implied by every available size constraint. If 376 + // the group is too high we fall back to centering, though if we 377 + // hit the .none prong we always have self.size != .none, so 378 + // this should never happen. 379 + .none => if (end_y < start_y) 380 + center_y 381 + else 382 + @max(start_y, @min(group.y, end_y)), 383 + .start => start_y, 384 + .end => end_y, 385 + .center, .center1 => center_y, 386 + }; 387 + } 388 + 389 + /// Return horizontal bearing for aligning this group 390 + fn aligned_x( 391 + self: Constraint, 392 + group: Glyph.Size, 393 + metrics: Metrics, 394 + min_constraint_width: u2, 395 + ) f64 { 396 + if ((self.size == .none) and (self.align_horizontal == .none)) { 397 + // If we don't have any constraints affecting the horizontal 398 + // axis, we don't touch horizontal alignment. 399 + return group.x; 400 + } 401 + // For multi-cell constraints, we align relative to the span 402 + // from the left edge of the first cell to the right edge of 403 + // the last face cell assuming it's left-aligned within the 404 + // rounded and adjusted pixel cell. Any horizontal offset to 405 + // center the face within the grid cell is the responsibility 406 + // of the backend-specific rendering code, and should be done 407 + // after applying constraints. 408 + const full_face_span = metrics.face_width + @as(f64, @floatFromInt((min_constraint_width - 1) * metrics.cell_width)); 409 + const pad_left_dx = self.pad_left * metrics.face_width; 410 + const pad_right_dx = self.pad_right * metrics.face_width; 411 + const start_x = pad_left_dx; 412 + const end_x = full_face_span - group.width - pad_right_dx; 413 + return switch (self.align_horizontal) { 414 + // NOTE: Even if there is no prescribed alignment, we ensure 415 + // that the glyph doesn't protrude outside the padded cell, 416 + // since this is implied by every available size constraint. The 417 + // left-side bound has priority if the group is too wide, though 418 + // if we hit the .none prong we always have self.size != .none, 419 + // so this should never happen. 420 + .none => @max(start_x, @min(group.x, end_x)), 421 + .start => start_x, 422 + .end => @max(start_x, end_x), 423 + .center => @max(start_x, (start_x + end_x) / 2), 424 + // NOTE: .center1 implements the font_patcher rule of centering 425 + // in the first cell even for multi-cell constraints. Since glyphs 426 + // are not allowed to protrude to the left, this results in the 427 + // left-alignment like .start when the glyph is wider than a cell. 428 + .center1 => center1: { 429 + const end1_x = metrics.face_width - group.width - pad_right_dx; 430 + break :center1 @max(start_x, (start_x + end1_x) / 2); 431 + }, 432 + }; 433 + } 434 + }; 435 + }; 436 + 437 + test "Constraints" { 438 + const comparison = @import("../datastruct/comparison.zig"); 439 + const getConstraint = @import("nerd_font_attributes.zig").getConstraint; 440 + const GlyphSize = Size; 441 + 442 + // Hardcoded data matches metrics from CoreText at size 12 and DPI 96. 443 + 444 + // Define grid metrics (matches font-family = JetBrains Mono) 445 + const metrics: Metrics = .{ 446 + .cell_width = 10, 447 + .cell_height = 22, 448 + .cell_baseline = 5, 449 + .underline_position = 19, 450 + .underline_thickness = 1, 451 + .strikethrough_position = 12, 452 + .strikethrough_thickness = 1, 453 + .overline_position = 0, 454 + .overline_thickness = 1, 455 + .box_thickness = 1, 456 + .cursor_thickness = 1, 457 + .cursor_height = 22, 458 + .icon_height = 21.12, 459 + .icon_height_single = 44.48 / 3.0, 460 + .face_width = 9.6, 461 + .face_height = 21.12, 462 + .face_y = 0.2, 463 + }; 464 + 465 + // ASCII (no constraint). 466 + { 467 + const constraint: RenderOptions.Constraint = .none; 468 + 469 + // BBox of 'x' from JetBrains Mono. 470 + const glyph_x: GlyphSize = .{ 471 + .width = 6.784, 472 + .height = 15.28, 473 + .x = 1.408, 474 + .y = 4.84, 475 + }; 476 + 477 + // Any constraint width: do nothing. 478 + inline for (.{ 1, 2 }) |constraint_width| { 479 + try comparison.expectApproxEqual( 480 + glyph_x, 481 + constraint.constrain(glyph_x, metrics, constraint_width), 482 + ); 483 + } 484 + } 485 + 486 + // Symbol (same constraint as hardcoded in Renderer.addGlyph). 487 + { 488 + const constraint: RenderOptions.Constraint = .{ .size = .fit }; 489 + 490 + // BBox of '■' (0x25A0 black square) from Iosevka. 491 + // NOTE: This glyph is designed to span two cells. 492 + const glyph_25A0: GlyphSize = .{ 493 + .width = 10.272, 494 + .height = 10.272, 495 + .x = 2.864, 496 + .y = 5.304, 497 + }; 498 + 499 + // Constraint width 1: scale down and shift to fit a single cell. 500 + try comparison.expectApproxEqual( 501 + GlyphSize{ 502 + .width = metrics.face_width, 503 + .height = metrics.face_width, 504 + .x = 0, 505 + .y = 5.64, 506 + }, 507 + constraint.constrain(glyph_25A0, metrics, 1), 508 + ); 509 + 510 + // Constraint width 2: do nothing. 511 + try comparison.expectApproxEqual( 512 + glyph_25A0, 513 + constraint.constrain(glyph_25A0, metrics, 2), 514 + ); 515 + } 516 + 517 + // Emoji (same constraint as hardcoded in SharedGrid.renderGlyph). 518 + { 519 + const constraint: RenderOptions.Constraint = .{ 520 + .size = .cover, 521 + .align_horizontal = .center, 522 + .align_vertical = .center, 523 + .pad_left = 0.025, 524 + .pad_right = 0.025, 525 + }; 526 + 527 + // BBox of '🥸' (0x1F978) from Apple Color Emoji. 528 + const glyph_1F978: GlyphSize = .{ 529 + .width = 20, 530 + .height = 20, 531 + .x = 0.46, 532 + .y = 1, 533 + }; 534 + 535 + // Constraint width 2: scale to cover two cells with padding, center; 536 + try comparison.expectApproxEqual( 537 + GlyphSize{ 538 + .width = 18.72, 539 + .height = 18.72, 540 + .x = 0.44, 541 + .y = 1.4, 542 + }, 543 + constraint.constrain(glyph_1F978, metrics, 2), 544 + ); 545 + } 546 + 547 + // Nerd Font default. 548 + { 549 + const constraint = getConstraint(0xea61).?; 550 + 551 + // Verify that this is the constraint we expect. 552 + try std.testing.expectEqual(.fit_cover1, constraint.size); 553 + try std.testing.expectEqual(.icon, constraint.height); 554 + try std.testing.expectEqual(.center1, constraint.align_horizontal); 555 + try std.testing.expectEqual(.center1, constraint.align_vertical); 556 + 557 + // BBox of '' (0xEA61 nf-cod-lightbulb) from Symbols Only. 558 + // NOTE: This icon is part of a group, so the 559 + // constraint applies to a larger bounding box. 560 + const glyph_EA61: GlyphSize = .{ 561 + .width = 9.015625, 562 + .height = 13.015625, 563 + .x = 3.015625, 564 + .y = 3.76525, 565 + }; 566 + 567 + // Constraint width 1: scale and shift group to fit a single cell. 568 + try comparison.expectApproxEqual( 569 + GlyphSize{ 570 + .width = 7.2125, 571 + .height = 10.4125, 572 + .x = 0.8125, 573 + .y = 5.950695224719102, 574 + }, 575 + constraint.constrain(glyph_EA61, metrics, 1), 576 + ); 577 + 578 + // Constraint width 2: no scaling; left-align and vertically center group. 579 + try comparison.expectApproxEqual( 580 + GlyphSize{ 581 + .width = glyph_EA61.width, 582 + .height = glyph_EA61.height, 583 + .x = 1.015625, 584 + .y = 4.7483690308988775, 585 + }, 586 + constraint.constrain(glyph_EA61, metrics, 2), 587 + ); 588 + } 589 + 590 + // Nerd Font stretch. 591 + { 592 + const constraint = getConstraint(0xe0c0).?; 593 + 594 + // Verify that this is the constraint we expect. 595 + try std.testing.expectEqual(.stretch, constraint.size); 596 + try std.testing.expectEqual(.cell, constraint.height); 597 + try std.testing.expectEqual(.start, constraint.align_horizontal); 598 + try std.testing.expectEqual(.center1, constraint.align_vertical); 599 + 600 + // BBox of ' ' (0xE0C0 nf-ple-flame_thick) from Symbols Only. 601 + const glyph_E0C0: GlyphSize = .{ 602 + .width = 16.796875, 603 + .height = 16.46875, 604 + .x = -0.796875, 605 + .y = 1.7109375, 606 + }; 607 + 608 + // Constraint width 1: stretch and position to exactly cover one cell. 609 + try comparison.expectApproxEqual( 610 + GlyphSize{ 611 + .width = @floatFromInt(metrics.cell_width), 612 + .height = @floatFromInt(metrics.cell_height), 613 + .x = 0, 614 + .y = 0, 615 + }, 616 + constraint.constrain(glyph_E0C0, metrics, 1), 617 + ); 618 + 619 + // Constraint width 1: stretch and position to exactly cover two cells. 620 + try comparison.expectApproxEqual( 621 + GlyphSize{ 622 + .width = @floatFromInt(2 * metrics.cell_width), 623 + .height = @floatFromInt(metrics.cell_height), 624 + .x = 0, 625 + .y = 0, 626 + }, 627 + constraint.constrain(glyph_E0C0, metrics, 2), 628 + ); 629 + } 630 + }
+4
src/font/Metrics.zig
··· 560 560 } 561 561 562 562 test "formatConfig percent" { 563 + if (comptime @import("terminal_options").artifact == .lib) return; 564 + 563 565 const configpkg = @import("../config.zig"); 564 566 const testing = std.testing; 565 567 var buf: std.Io.Writer.Allocating = .init(testing.allocator); ··· 571 573 } 572 574 573 575 test "formatConfig absolute" { 576 + if (comptime @import("terminal_options").artifact == .lib) return; 577 + 574 578 const configpkg = @import("../config.zig"); 575 579 const testing = std.testing; 576 580 var buf: std.Io.Writer.Allocating = .init(testing.allocator);
+1 -1
src/font/SharedGrid.zig
··· 33 33 const Metrics = font.Metrics; 34 34 const Presentation = font.Presentation; 35 35 const Style = font.Style; 36 - const RenderOptions = font.face.RenderOptions; 36 + const RenderOptions = font.Glyph.RenderOptions; 37 37 38 38 const log = std.log.scoped(.font_shared_grid); 39 39
-596
src/font/face.zig
··· 2 2 const builtin = @import("builtin"); 3 3 const build_config = @import("../build_config.zig"); 4 4 const options = @import("main.zig").options; 5 - const Metrics = @import("main.zig").Metrics; 6 5 const config = @import("../config.zig"); 7 6 const freetype = @import("face/freetype.zig"); 8 7 const coretext = @import("face/coretext.zig"); ··· 94 93 }; 95 94 }; 96 95 97 - /// The size and position of a glyph. 98 - pub const GlyphSize = struct { 99 - width: f64, 100 - height: f64, 101 - x: f64, 102 - y: f64, 103 - }; 104 - 105 - /// Additional options for rendering glyphs. 106 - pub const RenderOptions = struct { 107 - /// The metrics that are defining the grid layout. These are usually 108 - /// the metrics of the primary font face. The grid metrics are used 109 - /// by the font face to better layout the glyph in situations where 110 - /// the font is not exactly the same size as the grid. 111 - grid_metrics: Metrics, 112 - 113 - /// The number of grid cells this glyph will take up. This can be used 114 - /// optionally by the rasterizer to better layout the glyph. 115 - cell_width: ?u2 = null, 116 - 117 - /// Constraint and alignment properties for the glyph. The rasterizer 118 - /// should call the `constrain` function on this with the original size 119 - /// and bearings of the glyph to get remapped values that the glyph 120 - /// should be scaled/moved to. 121 - constraint: Constraint = .none, 122 - 123 - /// The number of cells, horizontally that the glyph is free to take up 124 - /// when resized and aligned by `constraint`. This is usually 1, but if 125 - /// there's whitespace to the right of the cell then it can be 2. 126 - constraint_width: u2 = 1, 127 - 128 - /// Thicken the glyph. This draws the glyph with a thicker stroke width. 129 - /// This is purely an aesthetic setting. 130 - /// 131 - /// This only works with CoreText currently. 132 - thicken: bool = false, 133 - 134 - /// "Strength" of the thickening, between `0` and `255`. 135 - /// Only has an effect when `thicken` is enabled. 136 - /// 137 - /// `0` does not correspond to *no* thickening, 138 - /// just the *lightest* thickening available. 139 - /// 140 - /// CoreText only. 141 - thicken_strength: u8 = 255, 142 - 143 - /// See the `constraint` field. 144 - pub const Constraint = struct { 145 - /// Don't constrain the glyph in any way. 146 - pub const none: Constraint = .{}; 147 - 148 - /// Sizing rule. 149 - size: Size = .none, 150 - 151 - /// Vertical alignment rule. 152 - align_vertical: Align = .none, 153 - /// Horizontal alignment rule. 154 - align_horizontal: Align = .none, 155 - 156 - /// Top padding when resizing. 157 - pad_top: f64 = 0.0, 158 - /// Left padding when resizing. 159 - pad_left: f64 = 0.0, 160 - /// Right padding when resizing. 161 - pad_right: f64 = 0.0, 162 - /// Bottom padding when resizing. 163 - pad_bottom: f64 = 0.0, 164 - 165 - // Size and bearings of the glyph relative 166 - // to the bounding box of its scale group. 167 - relative_width: f64 = 1.0, 168 - relative_height: f64 = 1.0, 169 - relative_x: f64 = 0.0, 170 - relative_y: f64 = 0.0, 171 - 172 - /// Maximum aspect ratio (width/height) to allow when stretching. 173 - max_xy_ratio: ?f64 = null, 174 - 175 - /// Maximum number of cells horizontally to use. 176 - max_constraint_width: u2 = 2, 177 - 178 - /// What to use as the height metric when constraining the glyph and 179 - /// the constraint width is 1, 180 - height: Height = .cell, 181 - 182 - pub const Size = enum { 183 - /// Don't change the size of this glyph. 184 - none, 185 - /// Scale the glyph down if needed to fit within the bounds, 186 - /// preserving aspect ratio. 187 - fit, 188 - /// Scale the glyph up or down to exactly match the bounds, 189 - /// preserving aspect ratio. 190 - cover, 191 - /// Scale the glyph down if needed to fit within the bounds, 192 - /// preserving aspect ratio. If the glyph doesn't cover a 193 - /// single cell, scale up. If the glyph exceeds a single 194 - /// cell but is within the bounds, do nothing. 195 - /// (Nerd Font specific rule.) 196 - fit_cover1, 197 - /// Stretch the glyph to exactly fit the bounds in both 198 - /// directions, disregarding aspect ratio. 199 - stretch, 200 - }; 201 - 202 - pub const Align = enum { 203 - /// Don't move the glyph on this axis. 204 - none, 205 - /// Move the glyph so that its leading (bottom/left) 206 - /// edge aligns with the leading edge of the axis. 207 - start, 208 - /// Move the glyph so that its trailing (top/right) 209 - /// edge aligns with the trailing edge of the axis. 210 - end, 211 - /// Move the glyph so that it is centered on this axis. 212 - center, 213 - /// Move the glyph so that it is centered on this axis, 214 - /// but always with respect to the first cell even for 215 - /// multi-cell constraints. (Nerd Font specific rule.) 216 - center1, 217 - }; 218 - 219 - pub const Height = enum { 220 - /// Use the full line height of the primary face for 221 - /// constraining this glyph. 222 - cell, 223 - /// Use the icon height from the grid metrics for 224 - /// constraining this glyph. Unlike `cell`, the value of 225 - /// this height depends on both the constraint width and the 226 - /// affected by the `adjust-icon-height` config option. 227 - icon, 228 - }; 229 - 230 - /// Returns true if the constraint does anything. If it doesn't, 231 - /// because it neither sizes nor positions the glyph, then this 232 - /// returns false. 233 - pub inline fn doesAnything(self: Constraint) bool { 234 - return self.size != .none or 235 - self.align_horizontal != .none or 236 - self.align_vertical != .none; 237 - } 238 - 239 - /// Apply this constraint to the provided glyph 240 - /// size, given the available width and height. 241 - pub fn constrain( 242 - self: Constraint, 243 - glyph: GlyphSize, 244 - metrics: Metrics, 245 - /// Number of cells horizontally available for this glyph. 246 - constraint_width: u2, 247 - ) GlyphSize { 248 - if (!self.doesAnything()) return glyph; 249 - 250 - switch (self.size) { 251 - .stretch => { 252 - // Stretched glyphs are usually meant to align across cell 253 - // boundaries, which works best if they're scaled and 254 - // aligned to the grid rather than the face. This is most 255 - // easily done by inserting this little fib in the metrics. 256 - var m = metrics; 257 - m.face_width = @floatFromInt(m.cell_width); 258 - m.face_height = @floatFromInt(m.cell_height); 259 - m.face_y = 0.0; 260 - 261 - // Negative padding for stretched glyphs is a band-aid to 262 - // avoid gaps due to pixel rounding, but at the cost of 263 - // unsightly overlap artifacts. Since we scale and align to 264 - // the grid rather than the face, we don't need it. 265 - var c = self; 266 - c.pad_bottom = @max(0, c.pad_bottom); 267 - c.pad_top = @max(0, c.pad_top); 268 - c.pad_left = @max(0, c.pad_left); 269 - c.pad_right = @max(0, c.pad_right); 270 - 271 - return c.constrainInner(glyph, m, constraint_width); 272 - }, 273 - else => return self.constrainInner(glyph, metrics, constraint_width), 274 - } 275 - } 276 - 277 - fn constrainInner( 278 - self: Constraint, 279 - glyph: GlyphSize, 280 - metrics: Metrics, 281 - constraint_width: u2, 282 - ) GlyphSize { 283 - // For extra wide font faces, never stretch glyphs across two cells. 284 - // This mirrors font_patcher. 285 - const min_constraint_width: u2 = if ((self.size == .stretch) and (metrics.face_width > 0.9 * metrics.face_height)) 286 - 1 287 - else 288 - @min(self.max_constraint_width, constraint_width); 289 - 290 - // The bounding box for the glyph's scale group. 291 - // Scaling and alignment rules are calculated for 292 - // this box and then applied to the glyph. 293 - var group: GlyphSize = group: { 294 - const group_width = glyph.width / self.relative_width; 295 - const group_height = glyph.height / self.relative_height; 296 - break :group .{ 297 - .width = group_width, 298 - .height = group_height, 299 - .x = glyph.x - (group_width * self.relative_x), 300 - .y = glyph.y - (group_height * self.relative_y), 301 - }; 302 - }; 303 - 304 - // Apply prescribed scaling, preserving the 305 - // center bearings of the group bounding box 306 - const width_factor, const height_factor = self.scale_factors(group, metrics, min_constraint_width); 307 - const center_x = group.x + (group.width / 2); 308 - const center_y = group.y + (group.height / 2); 309 - group.width *= width_factor; 310 - group.height *= height_factor; 311 - group.x = center_x - (group.width / 2); 312 - group.y = center_y - (group.height / 2); 313 - 314 - // NOTE: font_patcher jumps through a lot of hoops at this 315 - // point to ensure that the glyph remains within the target 316 - // bounding box after rounding to font definition units. 317 - // This is irrelevant here as we're not rounding, we're 318 - // staying in f64 and heading straight to rendering. 319 - 320 - // Apply prescribed alignment 321 - group.y = self.aligned_y(group, metrics); 322 - group.x = self.aligned_x(group, metrics, min_constraint_width); 323 - 324 - // Transfer the scaling and alignment back to the glyph and return. 325 - return .{ 326 - .width = width_factor * glyph.width, 327 - .height = height_factor * glyph.height, 328 - .x = group.x + (group.width * self.relative_x), 329 - .y = group.y + (group.height * self.relative_y), 330 - }; 331 - } 332 - 333 - /// Return width and height scaling factors for this scaling group. 334 - fn scale_factors( 335 - self: Constraint, 336 - group: GlyphSize, 337 - metrics: Metrics, 338 - min_constraint_width: u2, 339 - ) struct { f64, f64 } { 340 - if (self.size == .none) { 341 - return .{ 1.0, 1.0 }; 342 - } 343 - 344 - const multi_cell = (min_constraint_width > 1); 345 - 346 - const pad_width_factor = @as(f64, @floatFromInt(min_constraint_width)) - (self.pad_left + self.pad_right); 347 - const pad_height_factor = 1 - (self.pad_bottom + self.pad_top); 348 - 349 - const target_width = pad_width_factor * metrics.face_width; 350 - const target_height = pad_height_factor * switch (self.height) { 351 - .cell => metrics.face_height, 352 - // Like font-patcher, the icon constraint height depends on the 353 - // constraint width. Unlike font-patcher, the multi-cell 354 - // icon_height may be different from face_height due to the 355 - // `adjust-icon-height` config option. 356 - .icon => if (multi_cell) 357 - metrics.icon_height 358 - else 359 - metrics.icon_height_single, 360 - }; 361 - 362 - var width_factor = target_width / group.width; 363 - var height_factor = target_height / group.height; 364 - 365 - switch (self.size) { 366 - .none => unreachable, 367 - .fit => { 368 - // Scale down to fit if needed 369 - height_factor = @min(1, width_factor, height_factor); 370 - width_factor = height_factor; 371 - }, 372 - .cover => { 373 - // Scale to cover 374 - height_factor = @min(width_factor, height_factor); 375 - width_factor = height_factor; 376 - }, 377 - .fit_cover1 => { 378 - // Scale down to fit or up to cover at least one cell 379 - // NOTE: This is similar to font_patcher's "pa" mode, 380 - // however, font_patcher will only do the upscaling 381 - // part if the constraint width is 1, resulting in 382 - // some icons becoming smaller when the constraint 383 - // width increases. You'd see icons shrinking when 384 - // opening up a space after them. This makes no 385 - // sense, so we've fixed the rule such that these 386 - // icons are scaled to the same size for multi-cell 387 - // constraints as they would be for single-cell. 388 - height_factor = @min(width_factor, height_factor); 389 - if (multi_cell and (height_factor > 1)) { 390 - // Call back into this function with 391 - // constraint width 1 to get single-cell scale 392 - // factors. We use the height factor as width 393 - // could have been modified by max_xy_ratio. 394 - _, const single_height_factor = self.scale_factors(group, metrics, 1); 395 - height_factor = @max(1, single_height_factor); 396 - } 397 - width_factor = height_factor; 398 - }, 399 - .stretch => {}, 400 - } 401 - 402 - // Reduce aspect ratio if required 403 - if (self.max_xy_ratio) |ratio| { 404 - if (group.width * width_factor > group.height * height_factor * ratio) { 405 - width_factor = group.height * height_factor * ratio / group.width; 406 - } 407 - } 408 - 409 - return .{ width_factor, height_factor }; 410 - } 411 - 412 - /// Return vertical bearing for aligning this group 413 - fn aligned_y( 414 - self: Constraint, 415 - group: GlyphSize, 416 - metrics: Metrics, 417 - ) f64 { 418 - if ((self.size == .none) and (self.align_vertical == .none)) { 419 - // If we don't have any constraints affecting the vertical axis, 420 - // we don't touch vertical alignment. 421 - return group.y; 422 - } 423 - // We use face_height and offset by face_y, rather than 424 - // using cell_height directly, to account for the asymmetry 425 - // of the pixel cell around the face (a consequence of 426 - // aligning the baseline with a pixel boundary rather than 427 - // vertically centering the face). 428 - const pad_bottom_dy = self.pad_bottom * metrics.face_height; 429 - const pad_top_dy = self.pad_top * metrics.face_height; 430 - const start_y = metrics.face_y + pad_bottom_dy; 431 - const end_y = metrics.face_y + (metrics.face_height - group.height - pad_top_dy); 432 - const center_y = (start_y + end_y) / 2; 433 - return switch (self.align_vertical) { 434 - // NOTE: Even if there is no prescribed alignment, we ensure 435 - // that the group doesn't protrude outside the padded cell, 436 - // since this is implied by every available size constraint. If 437 - // the group is too high we fall back to centering, though if we 438 - // hit the .none prong we always have self.size != .none, so 439 - // this should never happen. 440 - .none => if (end_y < start_y) 441 - center_y 442 - else 443 - @max(start_y, @min(group.y, end_y)), 444 - .start => start_y, 445 - .end => end_y, 446 - .center, .center1 => center_y, 447 - }; 448 - } 449 - 450 - /// Return horizontal bearing for aligning this group 451 - fn aligned_x( 452 - self: Constraint, 453 - group: GlyphSize, 454 - metrics: Metrics, 455 - min_constraint_width: u2, 456 - ) f64 { 457 - if ((self.size == .none) and (self.align_horizontal == .none)) { 458 - // If we don't have any constraints affecting the horizontal 459 - // axis, we don't touch horizontal alignment. 460 - return group.x; 461 - } 462 - // For multi-cell constraints, we align relative to the span 463 - // from the left edge of the first cell to the right edge of 464 - // the last face cell assuming it's left-aligned within the 465 - // rounded and adjusted pixel cell. Any horizontal offset to 466 - // center the face within the grid cell is the responsibility 467 - // of the backend-specific rendering code, and should be done 468 - // after applying constraints. 469 - const full_face_span = metrics.face_width + @as(f64, @floatFromInt((min_constraint_width - 1) * metrics.cell_width)); 470 - const pad_left_dx = self.pad_left * metrics.face_width; 471 - const pad_right_dx = self.pad_right * metrics.face_width; 472 - const start_x = pad_left_dx; 473 - const end_x = full_face_span - group.width - pad_right_dx; 474 - return switch (self.align_horizontal) { 475 - // NOTE: Even if there is no prescribed alignment, we ensure 476 - // that the glyph doesn't protrude outside the padded cell, 477 - // since this is implied by every available size constraint. The 478 - // left-side bound has priority if the group is too wide, though 479 - // if we hit the .none prong we always have self.size != .none, 480 - // so this should never happen. 481 - .none => @max(start_x, @min(group.x, end_x)), 482 - .start => start_x, 483 - .end => @max(start_x, end_x), 484 - .center => @max(start_x, (start_x + end_x) / 2), 485 - // NOTE: .center1 implements the font_patcher rule of centering 486 - // in the first cell even for multi-cell constraints. Since glyphs 487 - // are not allowed to protrude to the left, this results in the 488 - // left-alignment like .start when the glyph is wider than a cell. 489 - .center1 => center1: { 490 - const end1_x = metrics.face_width - group.width - pad_right_dx; 491 - break :center1 @max(start_x, (start_x + end1_x) / 2); 492 - }, 493 - }; 494 - } 495 - }; 496 - }; 497 - 498 96 test { 499 97 @import("std").testing.refAllDecls(@This()); 500 98 } ··· 512 110 try testing.expectEqual(@as(u32, 1936486004), @as(u32, @bitCast(id))); 513 111 try testing.expectEqualStrings("slnt", &(id.str())); 514 112 } 515 - 516 - test "Constraints" { 517 - const comparison = @import("../datastruct/comparison.zig"); 518 - const getConstraint = @import("nerd_font_attributes.zig").getConstraint; 519 - 520 - // Hardcoded data matches metrics from CoreText at size 12 and DPI 96. 521 - 522 - // Define grid metrics (matches font-family = JetBrains Mono) 523 - const metrics: Metrics = .{ 524 - .cell_width = 10, 525 - .cell_height = 22, 526 - .cell_baseline = 5, 527 - .underline_position = 19, 528 - .underline_thickness = 1, 529 - .strikethrough_position = 12, 530 - .strikethrough_thickness = 1, 531 - .overline_position = 0, 532 - .overline_thickness = 1, 533 - .box_thickness = 1, 534 - .cursor_thickness = 1, 535 - .cursor_height = 22, 536 - .icon_height = 21.12, 537 - .icon_height_single = 44.48 / 3.0, 538 - .face_width = 9.6, 539 - .face_height = 21.12, 540 - .face_y = 0.2, 541 - }; 542 - 543 - // ASCII (no constraint). 544 - { 545 - const constraint: RenderOptions.Constraint = .none; 546 - 547 - // BBox of 'x' from JetBrains Mono. 548 - const glyph_x: GlyphSize = .{ 549 - .width = 6.784, 550 - .height = 15.28, 551 - .x = 1.408, 552 - .y = 4.84, 553 - }; 554 - 555 - // Any constraint width: do nothing. 556 - inline for (.{ 1, 2 }) |constraint_width| { 557 - try comparison.expectApproxEqual( 558 - glyph_x, 559 - constraint.constrain(glyph_x, metrics, constraint_width), 560 - ); 561 - } 562 - } 563 - 564 - // Symbol (same constraint as hardcoded in Renderer.addGlyph). 565 - { 566 - const constraint: RenderOptions.Constraint = .{ .size = .fit }; 567 - 568 - // BBox of '■' (0x25A0 black square) from Iosevka. 569 - // NOTE: This glyph is designed to span two cells. 570 - const glyph_25A0: GlyphSize = .{ 571 - .width = 10.272, 572 - .height = 10.272, 573 - .x = 2.864, 574 - .y = 5.304, 575 - }; 576 - 577 - // Constraint width 1: scale down and shift to fit a single cell. 578 - try comparison.expectApproxEqual( 579 - GlyphSize{ 580 - .width = metrics.face_width, 581 - .height = metrics.face_width, 582 - .x = 0, 583 - .y = 5.64, 584 - }, 585 - constraint.constrain(glyph_25A0, metrics, 1), 586 - ); 587 - 588 - // Constraint width 2: do nothing. 589 - try comparison.expectApproxEqual( 590 - glyph_25A0, 591 - constraint.constrain(glyph_25A0, metrics, 2), 592 - ); 593 - } 594 - 595 - // Emoji (same constraint as hardcoded in SharedGrid.renderGlyph). 596 - { 597 - const constraint: RenderOptions.Constraint = .{ 598 - .size = .cover, 599 - .align_horizontal = .center, 600 - .align_vertical = .center, 601 - .pad_left = 0.025, 602 - .pad_right = 0.025, 603 - }; 604 - 605 - // BBox of '🥸' (0x1F978) from Apple Color Emoji. 606 - const glyph_1F978: GlyphSize = .{ 607 - .width = 20, 608 - .height = 20, 609 - .x = 0.46, 610 - .y = 1, 611 - }; 612 - 613 - // Constraint width 2: scale to cover two cells with padding, center; 614 - try comparison.expectApproxEqual( 615 - GlyphSize{ 616 - .width = 18.72, 617 - .height = 18.72, 618 - .x = 0.44, 619 - .y = 1.4, 620 - }, 621 - constraint.constrain(glyph_1F978, metrics, 2), 622 - ); 623 - } 624 - 625 - // Nerd Font default. 626 - { 627 - const constraint = getConstraint(0xea61).?; 628 - 629 - // Verify that this is the constraint we expect. 630 - try std.testing.expectEqual(.fit_cover1, constraint.size); 631 - try std.testing.expectEqual(.icon, constraint.height); 632 - try std.testing.expectEqual(.center1, constraint.align_horizontal); 633 - try std.testing.expectEqual(.center1, constraint.align_vertical); 634 - 635 - // BBox of '' (0xEA61 nf-cod-lightbulb) from Symbols Only. 636 - // NOTE: This icon is part of a group, so the 637 - // constraint applies to a larger bounding box. 638 - const glyph_EA61: GlyphSize = .{ 639 - .width = 9.015625, 640 - .height = 13.015625, 641 - .x = 3.015625, 642 - .y = 3.76525, 643 - }; 644 - 645 - // Constraint width 1: scale and shift group to fit a single cell. 646 - try comparison.expectApproxEqual( 647 - GlyphSize{ 648 - .width = 7.2125, 649 - .height = 10.4125, 650 - .x = 0.8125, 651 - .y = 5.950695224719102, 652 - }, 653 - constraint.constrain(glyph_EA61, metrics, 1), 654 - ); 655 - 656 - // Constraint width 2: no scaling; left-align and vertically center group. 657 - try comparison.expectApproxEqual( 658 - GlyphSize{ 659 - .width = glyph_EA61.width, 660 - .height = glyph_EA61.height, 661 - .x = 1.015625, 662 - .y = 4.7483690308988775, 663 - }, 664 - constraint.constrain(glyph_EA61, metrics, 2), 665 - ); 666 - } 667 - 668 - // Nerd Font stretch. 669 - { 670 - const constraint = getConstraint(0xe0c0).?; 671 - 672 - // Verify that this is the constraint we expect. 673 - try std.testing.expectEqual(.stretch, constraint.size); 674 - try std.testing.expectEqual(.cell, constraint.height); 675 - try std.testing.expectEqual(.start, constraint.align_horizontal); 676 - try std.testing.expectEqual(.center1, constraint.align_vertical); 677 - 678 - // BBox of ' ' (0xE0C0 nf-ple-flame_thick) from Symbols Only. 679 - const glyph_E0C0: GlyphSize = .{ 680 - .width = 16.796875, 681 - .height = 16.46875, 682 - .x = -0.796875, 683 - .y = 1.7109375, 684 - }; 685 - 686 - // Constraint width 1: stretch and position to exactly cover one cell. 687 - try comparison.expectApproxEqual( 688 - GlyphSize{ 689 - .width = @floatFromInt(metrics.cell_width), 690 - .height = @floatFromInt(metrics.cell_height), 691 - .x = 0, 692 - .y = 0, 693 - }, 694 - constraint.constrain(glyph_E0C0, metrics, 1), 695 - ); 696 - 697 - // Constraint width 1: stretch and position to exactly cover two cells. 698 - try comparison.expectApproxEqual( 699 - GlyphSize{ 700 - .width = @floatFromInt(2 * metrics.cell_width), 701 - .height = @floatFromInt(metrics.cell_height), 702 - .x = 0, 703 - .y = 0, 704 - }, 705 - constraint.constrain(glyph_E0C0, metrics, 2), 706 - ); 707 - } 708 - }
+1 -1
src/font/face/coretext.zig
··· 291 291 alloc: Allocator, 292 292 atlas: *font.Atlas, 293 293 glyph_index: u32, 294 - opts: font.face.RenderOptions, 294 + opts: font.Glyph.RenderOptions, 295 295 ) !font.Glyph { 296 296 var glyphs = [_]macos.graphics.Glyph{@intCast(glyph_index)}; 297 297
+2 -2
src/font/face/freetype.zig
··· 393 393 } 394 394 395 395 /// Get a rect that represents the position and size of the loaded glyph. 396 - fn getGlyphSize(glyph: freetype.c.FT_GlyphSlot) font.face.GlyphSize { 396 + fn getGlyphSize(glyph: freetype.c.FT_GlyphSlot) font.Glyph.Size { 397 397 // If we're dealing with an outline glyph then we get the 398 398 // outline's bounding box instead of using the built-in 399 399 // metrics, since that's more precise and allows better ··· 427 427 alloc: Allocator, 428 428 atlas: *font.Atlas, 429 429 glyph_index: u32, 430 - opts: font.face.RenderOptions, 430 + opts: font.Glyph.RenderOptions, 431 431 ) !Glyph { 432 432 self.ft_mutex.lock(); 433 433 defer self.ft_mutex.unlock();
+1 -1
src/font/face/web_canvas.zig
··· 189 189 alloc: Allocator, 190 190 atlas: *font.Atlas, 191 191 glyph_index: u32, 192 - opts: font.face.RenderOptions, 192 + opts: font.Glyph.RenderOptions, 193 193 ) !font.Glyph { 194 194 _ = opts; 195 195
+10 -21
src/font/glyf_rasterize.zig
··· 9 9 const Allocator = std.mem.Allocator; 10 10 const z2d = @import("z2d"); 11 11 12 - const face = @import("face.zig"); 12 + const Glyph = @import("Glyph.zig"); 13 13 const glyf = @import("opentype/glyf.zig"); 14 14 15 - /// Metrics describing the authored glyf coordinate space, since 16 - /// a glyf table doesn't contain this on its own. 17 - pub const DesignMetrics = struct { 18 - /// Units-per-em for outline/design coordinates. 19 - units_per_em: u32, 20 - 21 - /// Authored advance width in design units. 22 - advance_width: u32, 23 - 24 - /// Authored line height in design units. 25 - line_height: u32, 26 - }; 15 + const DesignMetrics = Glyph.DesignMetrics; 27 16 28 17 /// An owned, tightly packed alpha8 bitmap. 29 18 pub const Bitmap = struct { ··· 52 41 /// 53 42 /// The returned bitmap is always `grid_metrics.cell_width * cell_width` by 54 43 /// `grid_metrics.cell_height`. `opts.constraint` is applied using the same 55 - /// `face.RenderOptions.Constraint` machinery used by the platform font 44 + /// `RenderOptions.Constraint` machinery used by the platform font 56 45 /// backends. 57 46 /// 58 47 /// The caller owns the returned bitmap. ··· 60 49 alloc: Allocator, 61 50 outline: glyf.Glyf.Outline, 62 51 design: DesignMetrics, 63 - opts: face.RenderOptions, 52 + opts: Glyph.RenderOptions, 64 53 ) Error!Bitmap { 65 54 assert(design.units_per_em > 0); 66 55 assert(design.advance_width > 0); ··· 207 196 208 197 /// Bottom edge of the rasterized outline bounds in bitmap pixels, measured 209 198 /// from the bitmap's bottom edge. This matches the cell-relative y axis 210 - /// used by font.face.GlyphSize and is converted to z2d's y-down axis when 199 + /// used by font.Glyph.Size and is converted to z2d's y-down axis when 211 200 /// points are transformed. 212 201 y: f64, 213 202 214 203 /// Width of the rasterized outline bounds in bitmap pixels after applying 215 - /// font.face.RenderOptions.Constraint. 204 + /// font.Glyph.RenderOptions.Constraint. 216 205 width: f64, 217 206 218 207 /// Height of the rasterized outline bounds in bitmap pixels after applying 219 - /// font.face.RenderOptions.Constraint. 208 + /// font.Glyph.RenderOptions.Constraint. 220 209 height: f64, 221 210 222 211 /// Full bitmap height in pixels, used to convert cell-relative y-up-ish ··· 240 229 fn init( 241 230 bounds: Bounds, 242 231 design: DesignMetrics, 243 - opts: face.RenderOptions, 232 + opts: Glyph.RenderOptions, 244 233 ) Placement { 245 234 // Start with protocol-like design units mapped so that the em square 246 235 // occupies one cell. This makes units_per_em the scale reference and ··· 253 242 // Convert the decoded point bounds into the same pixel coordinate space 254 243 // expected by RenderOptions.Constraint. This rectangle is the visible 255 244 // outline bounds, not the full advance/line-height layout box. 256 - const glyph: face.GlyphSize = .{ 245 + const glyph: Glyph.Size = .{ 257 246 .width = bounds.width() * scale, 258 247 .height = bounds.height() * scale, 259 248 .x = bounds.x_min * scale, ··· 268 257 // Apply the same fit/cover/stretch/alignment/padding rules used by 269 258 // normal font rendering. The result is still the outline bounds, but 270 259 // placed as if its containing advance/line-height box was constrained. 271 - const constraint: face.RenderOptions.Constraint = constraint: { 260 + const constraint: Glyph.RenderOptions.Constraint = constraint: { 272 261 var constraint = opts.constraint; 273 262 if (group_width > 0 and group_height > 0) { 274 263 // Tell Constraint that `glyph` is a sub-rectangle of the
+1 -1
src/font/nerd_font_attributes.zig
··· 4 4 //! This file provides info extracted from the nerd fonts patcher script, 5 5 //! specifying the scaling/positioning attributes of various glyphs. 6 6 7 - const Constraint = @import("face.zig").RenderOptions.Constraint; 7 + const Constraint = @import("Glyph.zig").RenderOptions.Constraint; 8 8 9 9 /// Get the constraints for the provided codepoint. 10 10 pub fn getConstraint(cp: u21) ?Constraint {
+1 -1
src/font/sprite/Face.zig
··· 176 176 alloc: Allocator, 177 177 atlas: *font.Atlas, 178 178 cp: u32, 179 - opts: font.face.RenderOptions, 179 + opts: font.Glyph.RenderOptions, 180 180 ) !font.Glyph { 181 181 if (std.debug.runtime_safety) { 182 182 if (!self.hasCodepoint(cp, null)) {
+1 -1
src/terminal/apc/glyph.zig
··· 155 155 pub const execute = @import("glyph/execute.zig").execute; 156 156 157 157 pub const CommandParser = request.CommandParser; 158 - pub const Glossary = @import("glyph/Glossary.zig"); 159 158 pub const Request = request.Request; 160 159 pub const Response = response.Response; 160 + pub const Glossary = @import("glyph/Glossary.zig"); 161 161 162 162 test { 163 163 std.testing.refAllDecls(@This());
+14 -13
src/terminal/apc/glyph/Glossary.zig
··· 7 7 const assert = std.debug.assert; 8 8 const Allocator = std.mem.Allocator; 9 9 const CircBuf = @import("../../../datastruct/circ_buf.zig").CircBuf; 10 - const face = @import("../../../font/face.zig"); 10 + const FontGlyph = @import("../../../font/Glyph.zig"); 11 11 const Glyf = @import("../../../font/opentype/glyf.zig").Glyf; 12 - const glyf_rasterize = @import("../../../font/glyf_rasterize.zig"); 13 12 14 13 const request = @import("request.zig"); 15 14 const RegisterReq = request.Request.Register; 15 + 16 + const DesignMetrics = FontGlyph.DesignMetrics; 17 + const Constraint = FontGlyph.RenderOptions.Constraint; 16 18 17 19 /// Maximum entries allowed in the glossary before eviction. 18 20 /// Defined by the specification. ··· 124 126 glyph: Glyph, 125 127 126 128 /// Authored metrics for the glyph's design coordinate space. 127 - design: glyf_rasterize.DesignMetrics, 129 + design: DesignMetrics, 128 130 129 131 /// Unicode cell width requested by the registration. 130 132 width: request.Width, 131 133 132 134 /// Normalized scale, alignment, and padding behavior for rasterization. 133 - constraint: face.RenderOptions.Constraint, 135 + constraint: Constraint, 134 136 135 137 /// Errors that can occur while constructing a glossary entry from a 136 138 /// register request. ··· 152 154 pub fn init(alloc: Allocator, req: RegisterReq) Entry.InitError!Entry { 153 155 // Validate format 154 156 const fmt = req.get(.fmt) orelse return error.InvalidOptions; 155 - const design: glyf_rasterize.DesignMetrics = .{ 157 + const design: DesignMetrics = .{ 156 158 .units_per_em = req.get(.upm) orelse return error.InvalidOptions, 157 159 .advance_width = req.get(.aw) orelse return error.InvalidOptions, 158 160 .line_height = req.get(.lh) orelse return error.InvalidOptions, ··· 194 196 /// Return the renderer constraint for a register request. 195 197 /// 196 198 /// Glyph Protocol §8.5 defines sizing, alignment, and padding in terms of 197 - /// the authored extent and render span. Ghostty's existing constraint type 198 - /// is the closest renderer-native representation for these controls, but 199 - /// it does not have exact equivalents for every protocol size mode, so this 200 - /// function is the single normalization point for those policy choices. 199 + /// the authored extent and render span. This function is the single 200 + /// normalization point for how protocol sizing choices map to the 201 + /// renderer-neutral constraint stored here. 201 202 fn constraintFromRegister( 202 203 req: RegisterReq, 203 - ) error{InvalidOptions}!face.RenderOptions.Constraint { 204 + ) error{InvalidOptions}!Constraint { 204 205 // Register.get applies the Glyph Protocol §6.1 defaults when options 205 206 // are omitted: size=height, align=center,center, and pad=0,0,0,0. 206 207 const size = req.get(.size) orelse return error.InvalidOptions; ··· 307 308 try testing.expectEqual(@as(u32, 1024), entry.design.advance_width); 308 309 try testing.expectEqual(@as(u32, 1536), entry.design.line_height); 309 310 try testing.expectEqual(request.Width.wide, entry.width); 310 - try testing.expectEqual(face.RenderOptions.Constraint.Size.stretch, entry.constraint.size); 311 - try testing.expectEqual(face.RenderOptions.Constraint.Align.end, entry.constraint.align_horizontal); 312 - try testing.expectEqual(face.RenderOptions.Constraint.Align.start, entry.constraint.align_vertical); 311 + try testing.expectEqual(Constraint.Size.stretch, entry.constraint.size); 312 + try testing.expectEqual(Constraint.Align.end, entry.constraint.align_horizontal); 313 + try testing.expectEqual(Constraint.Align.start, entry.constraint.align_vertical); 313 314 try testing.expectEqual(@as(f64, 0.1), entry.constraint.pad_top); 314 315 try testing.expectEqual(@as(f64, 0.2), entry.constraint.pad_right); 315 316 try testing.expectEqual(@as(f64, 0.3), entry.constraint.pad_bottom);