///|
/// Parse CSS clip property value
/// Supports: auto, rect(top, right, bottom, left)
/// Note: This is a deprecated property but widely used for accessibility
pub fn parse_clip(value : String) -> @types.ClipRect {
let v = value.trim().to_lower()
if v == "auto" || v == "initial" || v == "unset" {
return Auto
}
// Parse rect(top, right, bottom, left)
if v.has_prefix("rect(") && v.has_suffix(")") {
// Extract the content inside rect()
let content = view_to_string(
v.view(start_offset=5, end_offset=v.length() - 1),
).trim()
// Split by comma or space (both are valid in CSS)
let parts : Array[String] = []
let mut current = StringBuilder::new()
for c in content.iter() {
if c == ',' || c == ' ' {
let s = current.to_string().trim().to_owned()
if !s.is_empty() {
parts.push(s)
}
current = StringBuilder::new()
} else {
current.write_char(c)
}
}
let last = current.to_string().trim().to_owned()
if !last.is_empty() {
parts.push(last)
}
if parts.length() == 4 {
let top = parse_clip_value(parts[0])
let right = parse_clip_value(parts[1])
let bottom = parse_clip_value(parts[2])
let left = parse_clip_value(parts[3])
return Rect(top~, right~, bottom~, left~)
}
}
Auto
}
///|
/// Parse the supported subset of CSS clip-path.
/// Supports: none, inset( ), circle( at ).
pub fn parse_clip_path(value : String) -> @style.ClipPath {
let raw_v = value.trim().to_owned()
let v = raw_v.to_lower()
if v == "none" || v == "initial" || v == "unset" || v.is_empty() {
return None
}
if v.has_prefix("inset(") && v.has_suffix(")") {
let raw_content = view_to_string(
v.view(start_offset=6, end_offset=v.length() - 1),
)
.trim()
.to_owned()
let content = match raw_content.find("round") {
Some(idx) =>
raw_content.unsafe_substring(start=0, end=idx).trim().to_owned()
None => raw_content
}
let parts = split_clip_path_values(content)
if parts.length() > 0 {
let values = parts.map(parse_clip_path_inset_value)
match values.length() {
1 => {
let v = values[0]
return Inset(v, v, v, v)
}
2 => {
let vertical = values[0]
let horizontal = values[1]
return Inset(vertical, horizontal, vertical, horizontal)
}
3 => return Inset(values[0], values[1], values[2], values[1])
_ => return Inset(values[0], values[1], values[2], values[3])
}
}
}
if v.has_prefix("rect(") && v.has_suffix(")") {
let content = view_to_string(
v.view(start_offset=5, end_offset=v.length() - 1),
)
.trim()
.to_owned()
return parse_clip_path_rect(content)
}
if v.has_prefix("xywh(") && v.has_suffix(")") {
let raw_content = view_to_string(
v.view(start_offset=5, end_offset=v.length() - 1),
)
.trim()
.to_owned()
let content = match raw_content.find("round") {
Some(idx) =>
raw_content.unsafe_substring(start=0, end=idx).trim().to_owned()
None => raw_content
}
return parse_clip_path_xywh(content)
}
if v.has_prefix("circle(") && v.has_suffix(")") {
let content = view_to_string(
v.view(start_offset=7, end_offset=v.length() - 1),
)
.trim()
.to_owned()
return parse_clip_path_circle(content)
}
if v.has_prefix("ellipse(") && v.has_suffix(")") {
let content = view_to_string(
v.view(start_offset=8, end_offset=v.length() - 1),
)
.trim()
.to_owned()
return parse_clip_path_ellipse(content)
}
if v.has_prefix("polygon(") && v.has_suffix(")") {
let content = view_to_string(
v.view(start_offset=8, end_offset=v.length() - 1),
)
.trim()
.to_owned()
return parse_clip_path_polygon(content)
}
if v.has_prefix("path(") && v.has_suffix(")") {
let content = view_to_string(
raw_v.view(start_offset=5, end_offset=raw_v.length() - 1),
)
.trim()
.to_owned()
return parse_clip_path_path(content)
}
None
}
///|
fn parse_clip_path_rect(content : String) -> @style.ClipPath {
let parts = split_clip_path_values(content)
if parts.length() < 4 {
return None
}
let top = parse_clip_path_shape_value(parts[0]) catch { _ => return None }
let right = parse_clip_path_shape_value(parts[1]) catch { _ => return None }
let bottom = parse_clip_path_shape_value(parts[2]) catch { _ => return None }
let left = parse_clip_path_shape_value(parts[3]) catch { _ => return None }
Rect(top, right, bottom, left)
}
///|
fn parse_clip_path_xywh(content : String) -> @style.ClipPath {
let parts = split_clip_path_values(content)
if parts.length() < 4 {
return None
}
let x = parse_clip_path_shape_value(parts[0]) catch { _ => return None }
let y = parse_clip_path_shape_value(parts[1]) catch { _ => return None }
let width = parse_clip_path_shape_value(parts[2]) catch { _ => return None }
let height = parse_clip_path_shape_value(parts[3]) catch { _ => return None }
Xywh(x, y, width, height)
}
///|
fn parse_clip_path_circle(content : String) -> @style.ClipPath {
let parts = split_clip_path_values(content)
if parts.length() == 0 {
return None
}
let mut at_index = -1
for i in 0.. return None }
center_y = parse_clip_path_shape_value(parts[2]) catch { _ => return None }
} else if at_index > 0 {
radius = parse_clip_path_shape_value(parts[0]) catch { _ => return None }
if parts.length() <= at_index + 2 {
return None
}
center_x = parse_clip_path_shape_value(parts[at_index + 1]) catch {
_ => return None
}
center_y = parse_clip_path_shape_value(parts[at_index + 2]) catch {
_ => return None
}
} else {
radius = parse_clip_path_shape_value(parts[0]) catch { _ => return None }
}
Circle(radius, center_x, center_y)
}
///|
fn parse_clip_path_ellipse(content : String) -> @style.ClipPath {
let parts = split_clip_path_values(content)
if parts.length() == 0 {
return None
}
let mut at_index = -1
for i in 0.. return None }
center_y = parse_clip_path_shape_value(parts[2]) catch { _ => return None }
} else if at_index > 0 {
if at_index < 2 || parts.length() <= at_index + 2 {
return None
}
radius_x = parse_clip_path_shape_value(parts[0]) catch { _ => return None }
radius_y = parse_clip_path_shape_value(parts[1]) catch { _ => return None }
center_x = parse_clip_path_shape_value(parts[at_index + 1]) catch {
_ => return None
}
center_y = parse_clip_path_shape_value(parts[at_index + 2]) catch {
_ => return None
}
} else {
if parts.length() < 2 {
return None
}
radius_x = parse_clip_path_shape_value(parts[0]) catch { _ => return None }
radius_y = parse_clip_path_shape_value(parts[1]) catch { _ => return None }
}
Ellipse(radius_x, radius_y, center_x, center_y)
}
///|
fn parse_clip_path_polygon(content : String) -> @style.ClipPath {
let parts = split_clip_path_values(content)
let values : Array[Double] = []
for part in parts {
if part == "evenodd" || part == "nonzero" {
continue
}
let v = parse_clip_path_shape_value(part) catch { _ => return None }
values.push(v)
}
if values.length() < 6 || values.length() % 2 != 0 {
return None
}
let points : Array[(Double, Double)] = []
let mut i = 0
while i + 1 < values.length() {
points.push((values[i], values[i + 1]))
i = i + 2
}
Polygon(points)
}
///|
fn parse_clip_path_path(content : String) -> @style.ClipPath {
let path_data = match extract_clip_path_path_string(content) {
Some(v) => v
None => return None
}
parse_clip_path_path_data(path_data)
}
///|
fn extract_clip_path_path_string(content : String) -> String? {
match extract_clip_path_path_string_with_quote(content, '"') {
Some(v) => Some(v)
None => extract_clip_path_path_string_with_quote(content, '\'')
}
}
///|
fn extract_clip_path_path_string_with_quote(
content : String,
quote : Char,
) -> String? {
let out = StringBuilder::new()
let mut in_quote = false
for c in content.iter() {
if in_quote {
if c == quote {
return Some(out.to_string())
}
out.write_char(c)
} else if c == quote {
in_quote = true
}
}
None
}
///|
fn parse_clip_path_path_data(path_data : String) -> @style.ClipPath {
let tokens = tokenize_clip_path_path_data(path_data)
let points : Array[(Double, Double)] = []
let mut i = 0
let mut command = ""
let mut current_x = 0.0
let mut current_y = 0.0
let mut start_x = 0.0
let mut start_y = 0.0
let mut has_quadratic_control = false
let mut quadratic_control_x = 0.0
let mut quadratic_control_y = 0.0
let mut has_cubic_control = false
let mut cubic_control_x = 0.0
let mut cubic_control_y = 0.0
while i < tokens.length() {
let token = tokens[i]
if is_clip_path_path_command(token) {
if token == "Z" || token == "z" {
current_x = start_x
current_y = start_y
has_quadratic_control = false
has_cubic_control = false
command = ""
i += 1
continue
}
command = token
i += 1
continue
}
match command {
"M" | "m" => {
if i + 1 >= tokens.length() {
return None
}
let x = match parse_clip_path_path_number(tokens[i]) {
Some(v) => v
None => return None
}
let y = match parse_clip_path_path_number(tokens[i + 1]) {
Some(v) => v
None => return None
}
if command == "m" {
current_x += x
current_y += y
} else {
current_x = x
current_y = y
}
start_x = current_x
start_y = current_y
points.push((current_x, current_y))
command = if command == "m" { "l" } else { "L" }
has_quadratic_control = false
has_cubic_control = false
i += 2
}
"L" | "l" => {
if i + 1 >= tokens.length() {
return None
}
let x = match parse_clip_path_path_number(tokens[i]) {
Some(v) => v
None => return None
}
let y = match parse_clip_path_path_number(tokens[i + 1]) {
Some(v) => v
None => return None
}
if command == "l" {
current_x += x
current_y += y
} else {
current_x = x
current_y = y
}
points.push((current_x, current_y))
has_quadratic_control = false
has_cubic_control = false
i += 2
}
"H" | "h" => {
let x = match parse_clip_path_path_number(tokens[i]) {
Some(v) => v
None => return None
}
if command == "h" {
current_x += x
} else {
current_x = x
}
points.push((current_x, current_y))
has_quadratic_control = false
has_cubic_control = false
i += 1
}
"V" | "v" => {
let y = match parse_clip_path_path_number(tokens[i]) {
Some(v) => v
None => return None
}
if command == "v" {
current_y += y
} else {
current_y = y
}
points.push((current_x, current_y))
has_quadratic_control = false
has_cubic_control = false
i += 1
}
"Q" | "q" => {
if i + 3 >= tokens.length() {
return None
}
let control_x = match parse_clip_path_path_number(tokens[i]) {
Some(v) => v
None => return None
}
let control_y = match parse_clip_path_path_number(tokens[i + 1]) {
Some(v) => v
None => return None
}
let end_x = match parse_clip_path_path_number(tokens[i + 2]) {
Some(v) => v
None => return None
}
let end_y = match parse_clip_path_path_number(tokens[i + 3]) {
Some(v) => v
None => return None
}
let absolute_control_x = if command == "q" {
current_x + control_x
} else {
control_x
}
let absolute_control_y = if command == "q" {
current_y + control_y
} else {
control_y
}
let absolute_end_x = if command == "q" {
current_x + end_x
} else {
end_x
}
let absolute_end_y = if command == "q" {
current_y + end_y
} else {
end_y
}
append_quadratic_path_points(
points, current_x, current_y, absolute_control_x, absolute_control_y, absolute_end_x,
absolute_end_y,
)
current_x = absolute_end_x
current_y = absolute_end_y
has_quadratic_control = true
quadratic_control_x = absolute_control_x
quadratic_control_y = absolute_control_y
has_cubic_control = false
i += 4
}
"T" | "t" => {
if i + 1 >= tokens.length() {
return None
}
let end_x = match parse_clip_path_path_number(tokens[i]) {
Some(v) => v
None => return None
}
let end_y = match parse_clip_path_path_number(tokens[i + 1]) {
Some(v) => v
None => return None
}
let absolute_control_x = if has_quadratic_control {
2.0 * current_x - quadratic_control_x
} else {
current_x
}
let absolute_control_y = if has_quadratic_control {
2.0 * current_y - quadratic_control_y
} else {
current_y
}
let absolute_end_x = if command == "t" {
current_x + end_x
} else {
end_x
}
let absolute_end_y = if command == "t" {
current_y + end_y
} else {
end_y
}
append_quadratic_path_points(
points, current_x, current_y, absolute_control_x, absolute_control_y, absolute_end_x,
absolute_end_y,
)
current_x = absolute_end_x
current_y = absolute_end_y
has_quadratic_control = true
quadratic_control_x = absolute_control_x
quadratic_control_y = absolute_control_y
has_cubic_control = false
i += 2
}
"C" | "c" => {
if i + 5 >= tokens.length() {
return None
}
let control1_x = match parse_clip_path_path_number(tokens[i]) {
Some(v) => v
None => return None
}
let control1_y = match parse_clip_path_path_number(tokens[i + 1]) {
Some(v) => v
None => return None
}
let control2_x = match parse_clip_path_path_number(tokens[i + 2]) {
Some(v) => v
None => return None
}
let control2_y = match parse_clip_path_path_number(tokens[i + 3]) {
Some(v) => v
None => return None
}
let end_x = match parse_clip_path_path_number(tokens[i + 4]) {
Some(v) => v
None => return None
}
let end_y = match parse_clip_path_path_number(tokens[i + 5]) {
Some(v) => v
None => return None
}
let absolute_control1_x = if command == "c" {
current_x + control1_x
} else {
control1_x
}
let absolute_control1_y = if command == "c" {
current_y + control1_y
} else {
control1_y
}
let absolute_control2_x = if command == "c" {
current_x + control2_x
} else {
control2_x
}
let absolute_control2_y = if command == "c" {
current_y + control2_y
} else {
control2_y
}
let absolute_end_x = if command == "c" {
current_x + end_x
} else {
end_x
}
let absolute_end_y = if command == "c" {
current_y + end_y
} else {
end_y
}
append_cubic_path_points(
points, current_x, current_y, absolute_control1_x, absolute_control1_y,
absolute_control2_x, absolute_control2_y, absolute_end_x, absolute_end_y,
)
current_x = absolute_end_x
current_y = absolute_end_y
has_cubic_control = true
cubic_control_x = absolute_control2_x
cubic_control_y = absolute_control2_y
has_quadratic_control = false
i += 6
}
"S" | "s" => {
if i + 3 >= tokens.length() {
return None
}
let control2_x = match parse_clip_path_path_number(tokens[i]) {
Some(v) => v
None => return None
}
let control2_y = match parse_clip_path_path_number(tokens[i + 1]) {
Some(v) => v
None => return None
}
let end_x = match parse_clip_path_path_number(tokens[i + 2]) {
Some(v) => v
None => return None
}
let end_y = match parse_clip_path_path_number(tokens[i + 3]) {
Some(v) => v
None => return None
}
let absolute_control1_x = if has_cubic_control {
2.0 * current_x - cubic_control_x
} else {
current_x
}
let absolute_control1_y = if has_cubic_control {
2.0 * current_y - cubic_control_y
} else {
current_y
}
let absolute_control2_x = if command == "s" {
current_x + control2_x
} else {
control2_x
}
let absolute_control2_y = if command == "s" {
current_y + control2_y
} else {
control2_y
}
let absolute_end_x = if command == "s" {
current_x + end_x
} else {
end_x
}
let absolute_end_y = if command == "s" {
current_y + end_y
} else {
end_y
}
append_cubic_path_points(
points, current_x, current_y, absolute_control1_x, absolute_control1_y,
absolute_control2_x, absolute_control2_y, absolute_end_x, absolute_end_y,
)
current_x = absolute_end_x
current_y = absolute_end_y
has_cubic_control = true
cubic_control_x = absolute_control2_x
cubic_control_y = absolute_control2_y
has_quadratic_control = false
i += 4
}
"A" | "a" => {
if i + 6 >= tokens.length() {
return None
}
let radius_x = match parse_clip_path_path_number(tokens[i]) {
Some(v) => v
None => return None
}
let radius_y = match parse_clip_path_path_number(tokens[i + 1]) {
Some(v) => v
None => return None
}
let rotation_degrees = match
parse_clip_path_path_number(tokens[i + 2]) {
Some(v) => v
None => return None
}
let large_arc_flag = match parse_clip_path_path_number(tokens[i + 3]) {
Some(v) => v
None => return None
}
let sweep_flag = match parse_clip_path_path_number(tokens[i + 4]) {
Some(v) => v
None => return None
}
let end_x = match parse_clip_path_path_number(tokens[i + 5]) {
Some(v) => v
None => return None
}
let end_y = match parse_clip_path_path_number(tokens[i + 6]) {
Some(v) => v
None => return None
}
let absolute_end_x = if command == "a" {
current_x + end_x
} else {
end_x
}
let absolute_end_y = if command == "a" {
current_y + end_y
} else {
end_y
}
append_arc_path_points(
points,
current_x,
current_y,
radius_x,
radius_y,
rotation_degrees,
large_arc_flag != 0.0,
sweep_flag != 0.0,
absolute_end_x,
absolute_end_y,
)
current_x = absolute_end_x
current_y = absolute_end_y
has_quadratic_control = false
has_cubic_control = false
i += 7
}
_ => return None
}
}
if points.length() < 3 {
return None
}
Polygon(points)
}
///|
fn tokenize_clip_path_path_data(path_data : String) -> Array[String] {
let tokens : Array[String] = []
let current = StringBuilder::new()
for c in path_data.iter() {
if is_clip_path_path_command_char(c) {
flush_clip_path_path_token(tokens, current)
tokens.push(clip_path_path_char_to_string(c))
} else if c == ',' || c == ' ' || c == '\t' || c == '\n' || c == '\r' {
flush_clip_path_path_token(tokens, current)
} else if c == '-' || c == '+' {
let current_text = current.to_string()
if !current_text.is_empty() &&
!current_text.has_suffix("e") &&
!current_text.has_suffix("E") {
flush_clip_path_path_token(tokens, current)
}
current.write_char(c)
} else {
current.write_char(c)
}
}
flush_clip_path_path_token(tokens, current)
tokens
}
///|
fn flush_clip_path_path_token(
tokens : Array[String],
current : StringBuilder,
) -> Unit {
let token = current.to_string().trim().to_owned()
if !token.is_empty() {
tokens.push(token)
current.reset()
}
}
///|
fn append_quadratic_path_points(
points : Array[(Double, Double)],
start_x : Double,
start_y : Double,
control_x : Double,
control_y : Double,
end_x : Double,
end_y : Double,
) -> Unit {
let segments = 8
for step in 1..<(segments + 1) {
let t = step.to_double() / segments.to_double()
let one_minus_t = 1.0 - t
let x = one_minus_t * one_minus_t * start_x +
2.0 * one_minus_t * t * control_x +
t * t * end_x
let y = one_minus_t * one_minus_t * start_y +
2.0 * one_minus_t * t * control_y +
t * t * end_y
points.push((x, y))
}
}
///|
fn append_cubic_path_points(
points : Array[(Double, Double)],
start_x : Double,
start_y : Double,
control1_x : Double,
control1_y : Double,
control2_x : Double,
control2_y : Double,
end_x : Double,
end_y : Double,
) -> Unit {
let segments = 8
for step in 1..<(segments + 1) {
let t = step.to_double() / segments.to_double()
let one_minus_t = 1.0 - t
let x = one_minus_t * one_minus_t * one_minus_t * start_x +
3.0 * one_minus_t * one_minus_t * t * control1_x +
3.0 * one_minus_t * t * t * control2_x +
t * t * t * end_x
let y = one_minus_t * one_minus_t * one_minus_t * start_y +
3.0 * one_minus_t * one_minus_t * t * control1_y +
3.0 * one_minus_t * t * t * control2_y +
t * t * t * end_y
points.push((x, y))
}
}
///|
fn append_arc_path_points(
points : Array[(Double, Double)],
start_x : Double,
start_y : Double,
radius_x : Double,
radius_y : Double,
rotation_degrees : Double,
large_arc : Bool,
sweep : Bool,
end_x : Double,
end_y : Double,
) -> Unit {
if (start_x == end_x && start_y == end_y) ||
radius_x == 0.0 ||
radius_y == 0.0 {
points.push((end_x, end_y))
return
}
let mut rx = if radius_x < 0.0 { -radius_x } else { radius_x }
let mut ry = if radius_y < 0.0 { -radius_y } else { radius_y }
let pi = 3.14159265358979323846
let phi = rotation_degrees * pi / 180.0
let cos_phi = @math.cos(phi)
let sin_phi = @math.sin(phi)
let dx = (start_x - end_x) / 2.0
let dy = (start_y - end_y) / 2.0
let x1p = cos_phi * dx + sin_phi * dy
let y1p = -sin_phi * dx + cos_phi * dy
let lambda = x1p * x1p / (rx * rx) + y1p * y1p / (ry * ry)
if lambda > 1.0 {
let scale = lambda.sqrt()
rx = rx * scale
ry = ry * scale
}
let rx2 = rx * rx
let ry2 = ry * ry
let x1p2 = x1p * x1p
let y1p2 = y1p * y1p
let denominator = rx2 * y1p2 + ry2 * x1p2
if denominator == 0.0 {
points.push((end_x, end_y))
return
}
let sq = (rx2 * ry2 - rx2 * y1p2 - ry2 * x1p2) / denominator
let sq_abs = if sq < 0.0 { 0.0 } else { sq }
let coef = sq_abs.sqrt() * (if large_arc == sweep { -1.0 } else { 1.0 })
let cxp = coef * rx * y1p / ry
let cyp = -coef * ry * x1p / rx
let center_x = cos_phi * cxp - sin_phi * cyp + (start_x + end_x) / 2.0
let center_y = sin_phi * cxp + cos_phi * cyp + (start_y + end_y) / 2.0
let theta1 = clip_path_angle_between(
1.0,
0.0,
(x1p - cxp) / rx,
(y1p - cyp) / ry,
)
let mut dtheta = clip_path_angle_between(
(x1p - cxp) / rx,
(y1p - cyp) / ry,
(-x1p - cxp) / rx,
(-y1p - cyp) / ry,
)
if !sweep && dtheta > 0.0 {
dtheta = dtheta - 2.0 * pi
} else if sweep && dtheta < 0.0 {
dtheta = dtheta + 2.0 * pi
}
let segments = clip_path_max_int((dtheta.abs() / pi * 8.0).to_int(), 2)
for step in 1..<(segments + 1) {
let t = step.to_double() / segments.to_double()
let theta = theta1 + t * dtheta
let cos_t = @math.cos(theta)
let sin_t = @math.sin(theta)
let x = cos_phi * rx * cos_t - sin_phi * ry * sin_t + center_x
let y = sin_phi * rx * cos_t + cos_phi * ry * sin_t + center_y
points.push((x, y))
}
}
///|
fn clip_path_angle_between(
ux : Double,
uy : Double,
vx : Double,
vy : Double,
) -> Double {
let dot = ux * vx + uy * vy
let len_product = (ux * ux + uy * uy).sqrt() * (vx * vx + vy * vy).sqrt()
if len_product == 0.0 {
return 0.0
}
let mut cos_angle = dot / len_product
if cos_angle > 1.0 {
cos_angle = 1.0
}
if cos_angle < -1.0 {
cos_angle = -1.0
}
let angle = @math.acos(cos_angle)
let cross = ux * vy - uy * vx
if cross < 0.0 {
-angle
} else {
angle
}
}
///|
fn clip_path_max_int(a : Int, b : Int) -> Int {
if a > b {
a
} else {
b
}
}
///|
fn is_clip_path_path_command(token : String) -> Bool {
token == "M" ||
token == "m" ||
token == "L" ||
token == "l" ||
token == "H" ||
token == "h" ||
token == "V" ||
token == "v" ||
token == "Q" ||
token == "q" ||
token == "T" ||
token == "t" ||
token == "C" ||
token == "c" ||
token == "S" ||
token == "s" ||
token == "A" ||
token == "a" ||
token == "Z" ||
token == "z"
}
///|
fn is_clip_path_path_command_char(c : Char) -> Bool {
c == 'M' ||
c == 'm' ||
c == 'L' ||
c == 'l' ||
c == 'H' ||
c == 'h' ||
c == 'V' ||
c == 'v' ||
c == 'Z' ||
c == 'z' ||
c == 'C' ||
c == 'c' ||
c == 'S' ||
c == 's' ||
c == 'Q' ||
c == 'q' ||
c == 'T' ||
c == 't' ||
c == 'A' ||
c == 'a'
}
///|
fn clip_path_path_char_to_string(c : Char) -> String {
let sb = StringBuilder::new()
sb.write_char(c)
sb.to_string()
}
///|
fn parse_clip_path_path_number(token : String) -> Double? {
let value = @string.parse_double(token) catch { _ => return None }
Some(value)
}
///|
fn split_clip_path_values(content : String) -> Array[String] {
let parts : Array[String] = []
let mut current = StringBuilder::new()
for c in content.iter() {
if c == ',' || c == ' ' || c == '\t' || c == '\n' {
let s = current.to_string().trim().to_owned()
if !s.is_empty() {
parts.push(s)
}
current = StringBuilder::new()
} else {
current.write_char(c)
}
}
let last = current.to_string().trim().to_owned()
if !last.is_empty() {
parts.push(last)
}
parts
}
///|
fn parse_clip_path_shape_value(value : String) -> Double raise Error {
let v = value.trim().to_lower()
match v.strip_suffix("%") {
Some(num_str) => {
let n = @string.parse_double(num_str.to_owned().trim())
return -(n / 100.0)
}
None => ()
}
match v.strip_suffix("px") {
Some(num_str) => @string.parse_double(num_str.to_owned().trim())
None => @string.parse_double(v)
}
}
///|
fn parse_clip_path_inset_value(value : String) -> Double {
let v = value.trim().to_lower()
match v.strip_suffix("%") {
Some(num_str) => {
let n = @string.parse_double(num_str.to_owned().trim()) catch {
_ => return 0.0
}
return -(n / 100.0)
}
None => ()
}
match v.strip_suffix("px") {
Some(num_str) => {
let n = @string.parse_double(num_str.to_owned().trim()) catch {
_ => return 0.0
}
return n
}
None => ()
}
@string.parse_double(v) catch {
_ => 0.0
}
}
///|
/// Parse a single clip rect value (auto or length)
fn parse_clip_value(value : String) -> Double {
let v = value.trim().to_lower()
if v == "auto" {
// auto in clip rect context means 0 (no offset)
return 0.0
}
// Parse px value
match v.strip_suffix("px") {
Some(num_str) => {
let n = @string.parse_double(num_str.to_owned().trim()) catch {
_ => return 0.0
}
return n
}
None => ()
}
// Parse plain number
let n = @string.parse_double(v.to_owned()) catch { _ => return 0.0 }
n
}