lib/ui/src/fact/unit.zig

daab053ee43316e1809a84551d573ddd1e5bf3d2

 1 const std = @import("std");
 2 const abi = @import("../abi/root.zig");
 3 const UnitMetrics = @import("records.zig").UnitMetrics;
 4 
 5 pub fn deviceRect(rect: abi.Rect, metrics: UnitMetrics) abi.IRect {
 6     std.debug.assert(std.math.isFinite(rect.x));
 7     std.debug.assert(std.math.isFinite(rect.y));
 8     std.debug.assert(std.math.isFinite(rect.width));
 9     std.debug.assert(std.math.isFinite(rect.height));
10     std.debug.assert(rect.width >= 0);
11     std.debug.assert(rect.height >= 0);
12     std.debug.assert(metrics.device_per_unit_x > 0);
13     std.debug.assert(metrics.device_per_unit_y > 0);
14     std.debug.assert(metrics.snap_x > 0);
15     std.debug.assert(metrics.snap_y > 0);
16     std.debug.assert(std.math.isFinite(metrics.device_per_unit_x));
17     std.debug.assert(std.math.isFinite(metrics.device_per_unit_y));
18     std.debug.assert(std.math.isFinite(metrics.snap_x));
19     std.debug.assert(std.math.isFinite(metrics.snap_y));
20     const left = @floor(rect.x * metrics.device_per_unit_x / metrics.snap_x) * metrics.snap_x;
21     const top = @floor(rect.y * metrics.device_per_unit_y / metrics.snap_y) * metrics.snap_y;
22     const right = @ceil((rect.x + rect.width) * metrics.device_per_unit_x / metrics.snap_x) *
23         metrics.snap_x;
24     const bottom = @ceil((rect.y + rect.height) * metrics.device_per_unit_y / metrics.snap_y) *
25         metrics.snap_y;
26     std.debug.assert(left >= std.math.minInt(i32));
27     std.debug.assert(top >= std.math.minInt(i32));
28     std.debug.assert(right < 2_147_483_648);
29     std.debug.assert(bottom < 2_147_483_648);
30     std.debug.assert(right >= left);
31     std.debug.assert(bottom >= top);
32     std.debug.assert(right - left < 4_294_967_296);
33     std.debug.assert(bottom - top < 4_294_967_296);
34     return .{
35         .x = @intFromFloat(left),
36         .y = @intFromFloat(top),
37         .width = @intFromFloat(right - left),
38         .height = @intFromFloat(bottom - top),
39     };
40 }
41 
42 test "deviceRect covers fractional far edges on both axes" {
43     const rect = abi.Rect{ .x = 1.2, .y = 2.3, .width = 3.1, .height = 4.2 };
44     try std.testing.expectEqual(
45         abi.IRect{ .x = 1, .y = 2, .width = 4, .height = 5 },
46         deviceRect(rect, .{}),
47     );
48 }
49 
50 test "deviceRect scales before rounding and covers negative origins" {
51     const rect = abi.Rect{ .x = -1.25, .y = -0.25, .width = 2.5, .height = 1.5 };
52     try std.testing.expectEqual(
53         abi.IRect{ .x = -3, .y = -1, .width = 6, .height = 4 },
54         deviceRect(rect, .{ .device_per_unit_x = 2, .device_per_unit_y = 2 }),
55     );
56 }
57 
58 test "deviceRect snaps outward on a coarse device grid" {
59     const rect = abi.Rect{ .x = 1, .y = 2, .width = 4, .height = 3 };
60     try std.testing.expectEqual(
61         abi.IRect{ .x = 0, .y = 0, .width = 6, .height = 6 },
62         deviceRect(rect, .{ .snap_x = 2, .snap_y = 3 }),
63     );
64 }