mirror of
https://github.com/oven-sh/bun
synced 2026-02-11 03:18:53 +00:00
299 lines
11 KiB
Zig
299 lines
11 KiB
Zig
const std = @import("std");
|
|
const bun = @import("root").bun;
|
|
pub const css = @import("../css_parser.zig");
|
|
const Result = css.Result;
|
|
const ArrayList = std.ArrayListUnmanaged;
|
|
const Printer = css.Printer;
|
|
const PrintErr = css.PrintErr;
|
|
const CSSNumber = css.css_values.number.CSSNumber;
|
|
const CSSNumberFns = css.css_values.number.CSSNumberFns;
|
|
const Calc = css.css_values.calc.Calc;
|
|
|
|
const Tag = enum(u8) {
|
|
deg = 1,
|
|
rad = 2,
|
|
grad = 4,
|
|
turn = 8,
|
|
};
|
|
|
|
/// A CSS [`<angle>`](https://www.w3.org/TR/css-values-4/#angles) value.
|
|
///
|
|
/// Angles may be explicit or computed by `calc()`, but are always stored and serialized
|
|
/// as their computed value.
|
|
pub const Angle = union(Tag) {
|
|
/// An angle in degrees. There are 360 degrees in a full circle.
|
|
deg: CSSNumber,
|
|
/// An angle in radians. There are 2π radians in a full circle.
|
|
rad: CSSNumber,
|
|
/// An angle in gradians. There are 400 gradians in a full circle.
|
|
grad: CSSNumber,
|
|
/// An angle in turns. There is 1 turn in a full circle.
|
|
turn: CSSNumber,
|
|
|
|
// ~toCssImpl
|
|
const This = @This();
|
|
|
|
pub fn parse(input: *css.Parser) Result(Angle) {
|
|
return Angle.parseInternal(input, false);
|
|
}
|
|
|
|
fn parseInternal(input: *css.Parser, allow_unitless_zero: bool) Result(Angle) {
|
|
if (input.tryParse(Calc(Angle).parse, .{}).asValue()) |calc_value| {
|
|
if (calc_value == .value) return .{ .result = calc_value.value.* };
|
|
// Angles are always compatible, so they will always compute to a value.
|
|
return .{ .err = input.newCustomError(css.ParserError.invalid_value) };
|
|
}
|
|
|
|
const location = input.currentSourceLocation();
|
|
const token = switch (input.next()) {
|
|
.result => |vv| vv,
|
|
.err => |e| return .{ .err = e },
|
|
};
|
|
switch (token.*) {
|
|
.dimension => |*dim| {
|
|
const value = dim.num.value;
|
|
const unit = dim.unit;
|
|
// todo_stuff.match_ignore_ascii_case
|
|
if (bun.strings.eqlCaseInsensitiveASCIIICheckLength("deg", unit)) {
|
|
return .{ .result = Angle{ .deg = value } };
|
|
} else if (bun.strings.eqlCaseInsensitiveASCIIICheckLength("grad", unit)) {
|
|
return .{ .result = Angle{ .grad = value } };
|
|
} else if (bun.strings.eqlCaseInsensitiveASCIIICheckLength("turn", unit)) {
|
|
return .{ .result = Angle{ .turn = value } };
|
|
} else if (bun.strings.eqlCaseInsensitiveASCIIICheckLength("rad", unit)) {
|
|
return .{ .result = Angle{ .rad = value } };
|
|
} else {
|
|
return .{ .err = location.newUnexpectedTokenError(token.*) };
|
|
}
|
|
},
|
|
.number => |num| {
|
|
if (num.value == 0.0 and allow_unitless_zero) return .{ .result = Angle.zero() };
|
|
},
|
|
else => {},
|
|
}
|
|
return .{ .err = location.newUnexpectedTokenError(token.*) };
|
|
}
|
|
|
|
pub fn parseWithUnitlessZero(input: *css.Parser) Result(Angle) {
|
|
return Angle.parseInternal(input, true);
|
|
}
|
|
|
|
pub fn toCss(this: *const This, comptime W: type, dest: *Printer(W)) PrintErr!void {
|
|
const value, const unit = switch (this.*) {
|
|
.deg => |val| .{ val, "deg" },
|
|
.grad => |val| .{ val, "grad" },
|
|
.rad => |val| brk: {
|
|
const deg = this.toDegrees();
|
|
|
|
// We print 5 digits of precision by default.
|
|
// Switch to degrees if there are an even number of them.
|
|
if (css.fract(std.math.round(deg * 100000.0)) == 0) {
|
|
break :brk .{ val, "deg" };
|
|
} else {
|
|
break :brk .{ val, "rad" };
|
|
}
|
|
},
|
|
.turn => |val| .{ val, "turn" },
|
|
};
|
|
css.serializer.serializeDimension(value, unit, W, dest) catch return dest.addFmtError();
|
|
}
|
|
|
|
pub fn toCssWithUnitlessZero(this: *const This, comptime W: type, dest: *Printer(W)) PrintErr!void {
|
|
if (this.isZero()) {
|
|
const v: f32 = 0.0;
|
|
try CSSNumberFns.toCss(&v, W, dest);
|
|
} else {
|
|
return this.toCss(W, dest);
|
|
}
|
|
}
|
|
|
|
pub fn tryFromAngle(angle: Angle) ?This {
|
|
return angle;
|
|
}
|
|
|
|
pub fn tryFromToken(token: *const css.Token) css.Maybe(Angle, void) {
|
|
if (token.* == .dimension) {
|
|
const value = token.dimension.num.value;
|
|
const unit = token.dimension.unit;
|
|
if (bun.strings.eqlCaseInsensitiveASCIIICheckLength(unit, "deg")) {
|
|
return .{ .result = .{ .deg = value } };
|
|
} else if (bun.strings.eqlCaseInsensitiveASCIIICheckLength(unit, "grad")) {
|
|
return .{ .result = .{ .grad = value } };
|
|
} else if (bun.strings.eqlCaseInsensitiveASCIIICheckLength(unit, "turn")) {
|
|
return .{ .result = .{ .turn = value } };
|
|
} else if (bun.strings.eqlCaseInsensitiveASCIIICheckLength(unit, "rad")) {
|
|
return .{ .result = .{ .rad = value } };
|
|
}
|
|
}
|
|
return .{ .err = {} };
|
|
}
|
|
|
|
/// Returns the angle in radians.
|
|
pub fn toRadians(this: *const Angle) CSSNumber {
|
|
const RAD_PER_DEG: f32 = std.math.pi / 180.0;
|
|
return switch (this.*) {
|
|
.deg => |deg| return deg * RAD_PER_DEG,
|
|
.rad => |rad| return rad,
|
|
.grad => |grad| return grad * 180.0 / 200.0 * RAD_PER_DEG,
|
|
.turn => |turn| return turn * 360.0 * RAD_PER_DEG,
|
|
};
|
|
}
|
|
|
|
/// Returns the angle in degrees.
|
|
pub fn toDegrees(this: *const Angle) CSSNumber {
|
|
const DEG_PER_RAD: f32 = 180.0 / std.math.pi;
|
|
switch (this.*) {
|
|
.deg => |deg| return deg,
|
|
.rad => |rad| return rad * DEG_PER_RAD,
|
|
.grad => |grad| return grad * 180.0 / 200.0,
|
|
.turn => |turn| return turn * 360.0,
|
|
}
|
|
}
|
|
|
|
pub fn zero() Angle {
|
|
return .{ .deg = 0.0 };
|
|
}
|
|
|
|
pub fn isZero(this: *const Angle) bool {
|
|
const v = switch (this.*) {
|
|
.deg => |deg| deg,
|
|
.rad => |rad| rad,
|
|
.grad => |grad| grad,
|
|
.turn => |turn| turn,
|
|
};
|
|
return v == 0.0;
|
|
}
|
|
|
|
pub fn intoCalc(this: *const Angle, allocator: std.mem.Allocator) Calc(Angle) {
|
|
return Calc(Angle){
|
|
.value = bun.create(allocator, Angle, this.*),
|
|
};
|
|
}
|
|
|
|
pub fn map(this: *const Angle, comptime opfn: *const fn (f32) f32) Angle {
|
|
return switch (this.*) {
|
|
.deg => |deg| .{ .deg = opfn(deg) },
|
|
.rad => |rad| .{ .rad = opfn(rad) },
|
|
.grad => |grad| .{ .grad = opfn(grad) },
|
|
.turn => |turn| .{ .turn = opfn(turn) },
|
|
};
|
|
}
|
|
|
|
pub fn tryMap(this: *const Angle, comptime opfn: *const fn (f32) f32) ?Angle {
|
|
return map(this, opfn);
|
|
}
|
|
|
|
pub fn add(this: Angle, rhs: Angle) Angle {
|
|
const addfn = struct {
|
|
pub fn add(_: void, a: f32, b: f32) f32 {
|
|
return a + b;
|
|
}
|
|
};
|
|
return Angle.op(&this, &rhs, {}, addfn.add);
|
|
}
|
|
|
|
pub fn tryAdd(this: *const Angle, _: std.mem.Allocator, rhs: *const Angle) ?Angle {
|
|
return .{ .deg = this.toDegrees() + rhs.toDegrees() };
|
|
}
|
|
|
|
pub fn eql(lhs: *const Angle, rhs: *const Angle) bool {
|
|
return lhs.toDegrees() == rhs.toDegrees();
|
|
}
|
|
|
|
pub fn mulF32(this: Angle, _: std.mem.Allocator, other: f32) Angle {
|
|
// return Angle.op(&this, &other, Angle.mulF32);
|
|
return switch (this) {
|
|
.deg => |v| .{ .deg = v * other },
|
|
.rad => |v| .{ .rad = v * other },
|
|
.grad => |v| .{ .grad = v * other },
|
|
.turn => |v| .{ .turn = v * other },
|
|
};
|
|
}
|
|
|
|
pub fn partialCmp(this: *const Angle, other: *const Angle) ?std.math.Order {
|
|
return css.generic.partialCmpF32(&this.toDegrees(), &other.toDegrees());
|
|
}
|
|
|
|
pub fn tryOp(
|
|
this: *const Angle,
|
|
other: *const Angle,
|
|
ctx: anytype,
|
|
comptime op_fn: *const fn (@TypeOf(ctx), a: f32, b: f32) f32,
|
|
) ?Angle {
|
|
return Angle.op(this, other, ctx, op_fn);
|
|
}
|
|
|
|
pub fn tryOpTo(
|
|
this: *const Angle,
|
|
other: *const Angle,
|
|
comptime R: type,
|
|
ctx: anytype,
|
|
comptime op_fn: *const fn (@TypeOf(ctx), a: f32, b: f32) R,
|
|
) ?R {
|
|
return Angle.opTo(this, other, R, ctx, op_fn);
|
|
}
|
|
|
|
pub fn op(
|
|
this: *const Angle,
|
|
other: *const Angle,
|
|
ctx: anytype,
|
|
comptime op_fn: *const fn (@TypeOf(ctx), a: f32, b: f32) f32,
|
|
) Angle {
|
|
// PERF: not sure if this is faster
|
|
const self_tag: u8 = @intFromEnum(this.*);
|
|
const other_tag: u8 = @intFromEnum(this.*);
|
|
const DEG: u8 = @intFromEnum(Tag.deg);
|
|
const GRAD: u8 = @intFromEnum(Tag.grad);
|
|
const RAD: u8 = @intFromEnum(Tag.rad);
|
|
const TURN: u8 = @intFromEnum(Tag.turn);
|
|
|
|
const switch_val: u8 = self_tag | other_tag;
|
|
return switch (switch_val) {
|
|
DEG | DEG => Angle{ .deg = op_fn(ctx, this.deg, other.deg) },
|
|
RAD | RAD => Angle{ .rad = op_fn(ctx, this.rad, other.rad) },
|
|
GRAD | GRAD => Angle{ .grad = op_fn(ctx, this.grad, other.grad) },
|
|
TURN | TURN => Angle{ .turn = op_fn(ctx, this.turn, other.turn) },
|
|
else => Angle{ .deg = op_fn(ctx, this.toDegrees(), other.toDegrees()) },
|
|
};
|
|
}
|
|
|
|
pub fn opTo(
|
|
this: *const Angle,
|
|
other: *const Angle,
|
|
comptime T: type,
|
|
ctx: anytype,
|
|
comptime op_fn: *const fn (@TypeOf(ctx), a: f32, b: f32) T,
|
|
) T {
|
|
// PERF: not sure if this is faster
|
|
const self_tag: u8 = @intFromEnum(this.*);
|
|
const other_tag: u8 = @intFromEnum(this.*);
|
|
const DEG: u8 = @intFromEnum(Tag.deg);
|
|
const GRAD: u8 = @intFromEnum(Tag.grad);
|
|
const RAD: u8 = @intFromEnum(Tag.rad);
|
|
const TURN: u8 = @intFromEnum(Tag.turn);
|
|
|
|
const switch_val: u8 = self_tag | other_tag;
|
|
return switch (switch_val) {
|
|
DEG | DEG => op_fn(ctx, this.deg, other.deg),
|
|
RAD | RAD => op_fn(ctx, this.rad, other.rad),
|
|
GRAD | GRAD => op_fn(ctx, this.grad, other.grad),
|
|
TURN | TURN => op_fn(ctx, this.turn, other.turn),
|
|
else => op_fn(ctx, this.toDegrees(), other.toDegrees()),
|
|
};
|
|
}
|
|
|
|
pub fn sign(this: *const Angle) f32 {
|
|
return switch (this.*) {
|
|
.deg, .rad, .grad, .turn => |v| CSSNumberFns.sign(&v),
|
|
};
|
|
}
|
|
|
|
pub fn hash(this: *const @This(), hasher: *std.hash.Wyhash) void {
|
|
return css.implementHash(@This(), this, hasher);
|
|
}
|
|
};
|
|
|
|
/// A CSS [`<angle-percentage>`](https://www.w3.org/TR/css-values-4/#typedef-angle-percentage) value.
|
|
/// May be specified as either an angle or a percentage that resolves to an angle.
|
|
pub const AnglePercentage = css.css_values.percentage.DimensionPercentage(Angle);
|