From 7318fd8539cf0369c1e769ce3c495ca23013ec26 Mon Sep 17 00:00:00 2001 From: Zack Radisic <56137411+zackradisic@users.noreply.github.com> Date: Wed, 25 Sep 2024 16:18:59 -0700 Subject: [PATCH] Stuff --- src/css/css_parser.zig | 199 ++++++++++++++++++++++++++++++++++----- src/css/values/angle.zig | 2 +- src/css/values/color.zig | 8 +- 3 files changed, 183 insertions(+), 26 deletions(-) diff --git a/src/css/css_parser.zig b/src/css/css_parser.zig index f60f1a70bf8b0..bb3ba8d4d4374 100644 --- a/src/css/css_parser.zig +++ b/src/css/css_parser.zig @@ -5649,8 +5649,7 @@ pub const serializer = struct { } pub fn serializeDimension(value: f32, unit: []const u8, comptime W: type, dest: *Printer(W)) PrintErr!void { - // const int_value: ?i32 = if (@rem(value, 1) == 0.0) @as(i32, @intFromFloat(value)) else null; - const int_value: ?i32 = if (1.0 - @trunc(value) == 0.0) @intFromFloat(value) else null; + const int_value: ?i32 = if (fract(value) == 0.0) @intFromFloat(value) else null; const token = Token{ .dimension = .{ .num = .{ .has_sign = value < 0.0, @@ -5720,39 +5719,46 @@ pub const serializer = struct { return writer.writeAll(value[chunk_start..]); } + // pub fn writeNumeric(value: f32, int_value: ?i32, has_sign: bool, writer: anytype) !void { + // // `value >= 0` is true for negative 0. + // if (has_sign and !std.math.signbit(value)) { + // try writer.writeAll("+"); + // } + + // if (value == 0.0 and signfns.isSignNegative(value)) { + // // Negative zero. Work around #20596. + // try writer.writeAll("-0"); + // if (int_value == null and @mod(value, 1) == 0) { + // try writer.writeAll(".0"); + // } + // } else { + // var buf: [124]u8 = undefined; + // const bytes = bun.fmt.FormatDouble.dtoa(&buf, @floatCast(value)); + // try writer.writeAll(bytes); + // } + // } + pub fn writeNumeric(value: f32, int_value: ?i32, has_sign: bool, writer: anytype) !void { // `value >= 0` is true for negative 0. if (has_sign and !std.math.signbit(value)) { try writer.writeAll("+"); } - const notation: struct { decimal_point: bool, scientific: bool } = if (value == 0.0 and std.math.signbit(value)) notation: { + const notation: Notation = if (value == 0.0 and std.math.signbit(value)) notation: { // Negative zero. Work around #20596. try writer.writeAll("-0"); - break :notation .{ + break :notation Notation{ .decimal_point = false, .scientific = false, }; } else notation: { - // TODO: calculate the actual number of chars here - var buf: [64]u8 = undefined; - // PERF/TODO: Compare this to Rust dtoa-short crate - // const floats = std.fmt.formatFloat(buf[0..], value, .{ - // .mode = .scientific, - // .precision = 6, - // }) catch unreachable; - const floats = std.fmt.formatFloat(buf[0..], value, .{ - .mode = .decimal, - }) catch unreachable; - try writer.writeAll(floats); - // TODO: this is not correct, might need to copy impl from dtoa_short here - break :notation .{ - .decimal_point = true, - .scientific = false, - }; + var buf: [129]u8 = undefined; + const str, const notation = dtoa_short(&buf, value, 6); + try writer.writeAll(str); + break :notation notation; }; - if (int_value == null and @mod(value, 1) == 0) { + if (int_value == null and fract(value) == 0) { if (!notation.decimal_point and !notation.scientific) { try writer.writeAll(".0"); } @@ -6119,3 +6125,154 @@ pub fn deepDeinit(comptime V: type, allocator: Allocator, list: *ArrayList(V)) v list.deinit(allocator); } + +const Notation = struct { + decimal_point: bool, + scientific: bool, + + pub fn integer() Notation { + return .{ + .decimal_point = false, + .scientific = false, + }; + } +}; + +pub fn dtoa_short(buf: *[129]u8, value: f32, comptime precision: u8) struct { []u8, Notation } { + buf[0] = '0'; + const buf_len = bun.fmt.FormatDouble.dtoa(@ptrCast(buf[1..].ptr), @floatCast(value)).len; + return restrict_prec(buf[0 .. buf_len + 1], precision); +} + +fn restrict_prec(buf: []u8, comptime prec: u8) struct { []u8, Notation } { + const len: u8 = @intCast(buf.len); + + // Put a leading zero to capture any carry. + // Caller must prepare an empty byte for us; + bun.debugAssert(buf[0] == '0'); + buf[0] = '0'; + // Remove the sign for now. We will put it back at the end. + const sign = switch (buf[1]) { + '+', '-' => brk: { + const s = buf[1]; + buf[1] = '0'; + break :brk s; + }, + else => null, + }; + + // Locate dot, exponent, and the first significant digit. + var _pos_dot: ?u8 = null; + var pos_exp: ?u8 = null; + var _prec_start: ?u8 = null; + for (1..len) |i| { + if (buf[i] == '.') { + bun.debugAssert(_pos_dot == null); + _pos_dot = @intCast(i); + } else if (buf[i] == 'e') { + pos_exp = @intCast(i); + // We don't change exponent part, so stop here. + break; + } else if (_prec_start == null and buf[i] != '0') { + bun.debugAssert(buf[i] >= '1' and buf[i] <= '9'); + _prec_start = @intCast(i); + } + } + + const prec_start = if (_prec_start) |i| + i + else + // If there is no non-zero digit at all, it is just zero. + return .{ + buf[0..1], + Notation.integer(), + }; + + // Coefficient part ends at 'e' or the length. + const coeff_end = pos_exp orelse len; + // Decimal dot is effectively at the end of coefficient part if no + // dot presents before that. + const pos_dot = _pos_dot orelse coeff_end; + // Find the end position of the number within the given precision. + const prec_end: u8 = brk: { + const end = prec_start + prec; + break :brk if (pos_dot > prec_start and pos_dot <= end) end + 1 else end; + }; + var new_coeff_end = coeff_end; + if (prec_end < coeff_end) { + // Round to the given precision. + const next_char = buf[prec_end]; + new_coeff_end = prec_end; + if (next_char >= '5') { + var i = prec_end; + while (i != 0) { + i -= 1; + if (buf[i] == '.') { + continue; + } + if (buf[i] != '9') { + buf[i] += 1; + new_coeff_end = i + 1; + break; + } + buf[i] = '0'; + } + } + } + if (new_coeff_end < pos_dot) { + // If the precision isn't enough to reach the dot, set all digits + // in-between to zero and keep the number until the dot. + for (new_coeff_end..pos_dot) |i| { + buf[i] = '0'; + } + new_coeff_end = pos_dot; + } else { + // Strip any trailing zeros. + var i = new_coeff_end; + while (i != 0) { + i -= 1; + if (buf[i] != '0') { + if (buf[i] == '.') { + new_coeff_end = i; + } + break; + } + new_coeff_end = i; + } + } + // Move exponent part if necessary. + const real_end = if (pos_exp) |posexp| brk: { + const exp_len = len - posexp; + if (new_coeff_end != posexp) { + for (0..exp_len) |i| { + buf[new_coeff_end + i] = buf[posexp + 1]; + } + } + break :brk new_coeff_end + exp_len; + } else new_coeff_end; + // Add back the sign and strip the leading zero. + const result = if (sign) |sgn| brk: { + if (buf[1] == '0' and buf[2] != '.') { + buf[1] = sgn; + break :brk buf[1..real_end]; + } + bun.debugAssert(buf[0] == '0'); + buf[0] = sgn; + break :brk buf[0..real_end]; + } else brk: { + if (buf[0] == '0' and buf[1] != '.') { + break :brk buf[1..real_end]; + } + break :brk buf[0..real_end]; + }; + // Generate the notation info. + const notation = Notation{ + .decimal_point = pos_dot < new_coeff_end, + .scientific = pos_exp != null, + }; + return .{ result, notation }; +} + +pub inline fn fract(val: f32) f32 { + return 1.0 - @trunc(val); +} diff --git a/src/css/values/angle.zig b/src/css/values/angle.zig index 7de3f7277d879..7c9ea9e5f6927 100644 --- a/src/css/values/angle.zig +++ b/src/css/values/angle.zig @@ -87,7 +87,7 @@ pub const Angle = union(Tag) { // We print 5 digits of precision by default. // Switch to degrees if there are an even number of them. - if (std.math.round(deg * 100000.0) - (deg - @trunc(deg)) == 0) { + if (css.fract(std.math.round(deg * 100000.0)) == 0) { break :brk .{ val, "deg" }; } else { break :brk .{ val, "rad" }; diff --git a/src/css/values/color.zig b/src/css/values/color.zig index f20f3621a58dd..a4a5a91b3d951 100644 --- a/src/css/values/color.zig +++ b/src/css/values/color.zig @@ -167,7 +167,7 @@ pub const CssColor = union(enum) { if (hex == expandHex(compact)) { try dest.writeFmt("#{x:0>4}", .{compact}); } else { - try dest.writeFmt("#{x:0>8}", .{compact}); + try dest.writeFmt("#{x:0>8}", .{hex}); } } return; @@ -184,7 +184,7 @@ pub const CssColor = union(enum) { dest, ), .lch => |*lch| writeComponents( - "lab", + "lch", lch.l, lch.c, lch.h, @@ -193,7 +193,7 @@ pub const CssColor = union(enum) { dest, ), .oklab => |*oklab| writeComponents( - "lab", + "oklab", oklab.l, oklab.a, oklab.b, @@ -202,7 +202,7 @@ pub const CssColor = union(enum) { dest, ), .oklch => |*oklch| writeComponents( - "lab", + "oklch", oklch.l, oklch.c, oklch.h,