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 }