///|
fn parse_linecap(value : String) -> LineCap {
let v = trim_string(value)
match v {
"round" => Round
"square" => Square
_ => Butt // Default
}
}
///|
fn parse_image_sampling(value : String) -> ImageSampling {
match trim_string(value).to_lower() {
"pixelated" | "crisp-edges" | "optimizespeed" => Nearest
"optimizequality" => Bicubic
_ => Bilinear
}
}
///|
fn parse_linejoin(value : String) -> LineJoin {
let v = trim_string(value)
match v {
"round" => Round
"bevel" => Bevel
_ => Miter // Default
}
}
///|
fn parse_dasharray(
value : String,
length_context? : LengthContext,
) -> Array[Double]? {
let v = trim_string(value)
if v == "none" || v.length() == 0 {
return None
}
let result : Array[Double] = []
let mut current = ""
for c in v {
if c == ',' || c == ' ' {
if current.length() > 0 {
result.push(
match length_context {
Some(context) =>
parse_length_with_context(current, Diagonal, context)
None => parse_length(current)
},
)
current = ""
}
} else {
current = current + c.to_string()
}
}
if current.length() > 0 {
result.push(
match length_context {
Some(context) => parse_length_with_context(current, Diagonal, context)
None => parse_length(current)
},
)
}
if result.is_empty() {
None
} else {
Some(result)
}
}
///|
fn parse_fill_rule(value : String) -> FillRule {
let v = trim_string(value)
match v {
"evenodd" => EvenOdd
_ => NonZero // Default
}
}
///|
fn trim_string(s : String) -> String {
let mut start = 0
let mut end = s.length()
while start < end {
let c = Int::unsafe_to_char(s[start].to_int())
if c == ' ' || c == '\t' || c == '\n' || c == '\r' {
start = start + 1
} else {
break
}
}
while end > start {
let c = Int::unsafe_to_char(s[end - 1].to_int())
if c == ' ' || c == '\t' || c == '\n' || c == '\r' {
end = end - 1
} else {
break
}
}
build_substring(s, start, end)
}
///|
fn string_starts_with(s : String, prefix : String) -> Bool {
if prefix.length() > s.length() {
return false
}
for i in 0.. Bool {
if suffix.length() > s.length() {
return false
}
let start = s.length() - suffix.length()
for i in 0.. Int {
for i in 0.. LengthContext {
{
viewport_width,
viewport_height,
font_size,
root_font_size,
css_viewport_width,
css_viewport_height,
}
}
///|
fn LengthContext::reference(self : LengthContext, axis : LengthAxis) -> Double {
match axis {
Horizontal => self.viewport_width
Vertical => self.viewport_height
Diagonal =>
(self.viewport_width * self.viewport_width +
self.viewport_height * self.viewport_height).sqrt() /
2.0.sqrt()
}
}
///|
fn parse_length_with_context(
value : String,
axis : LengthAxis,
context : LengthContext,
) -> Double {
parse_length_with_context_optional(value, axis, context).unwrap_or(0.0)
}
///|
fn parse_length_with_context_optional(
value : String,
axis : LengthAxis,
context : LengthContext,
) -> Double? {
let compute_context : @css_computed.ComputeContext = {
..@css_computed.ComputeContext::with_viewport(
context.css_viewport_width,
context.css_viewport_height,
),
root_font_size: context.root_font_size,
font_size: context.font_size,
}
match @css_computed.resolve_length_dimension(value, compute_context) {
Some(dimension) => dimension.resolve(context.reference(axis))
None => None
}
}
///|
fn parse_number_strict(value : String) -> Double? {
let value = trim_string(value)
if value.length() == 0 {
return None
}
let mut i = 0
let first = Int::unsafe_to_char(value[i].to_int())
if first == '+' || first == '-' {
i += 1
}
let mut digit_count = 0
while i < value.length() &&
is_ascii_digit(Int::unsafe_to_char(value[i].to_int())) {
digit_count += 1
i += 1
}
if i < value.length() && Int::unsafe_to_char(value[i].to_int()) == '.' {
i += 1
while i < value.length() &&
is_ascii_digit(Int::unsafe_to_char(value[i].to_int())) {
digit_count += 1
i += 1
}
}
if digit_count == 0 {
return None
}
if i < value.length() {
let exponent = Int::unsafe_to_char(value[i].to_int())
if exponent == 'e' || exponent == 'E' {
i += 1
if i < value.length() {
let sign = Int::unsafe_to_char(value[i].to_int())
if sign == '+' || sign == '-' {
i += 1
}
}
let exponent_start = i
while i < value.length() &&
is_ascii_digit(Int::unsafe_to_char(value[i].to_int())) {
i += 1
}
if i == exponent_start {
return None
}
}
}
if i != value.length() {
return None
}
Some(parse_number(value))
}
///|
fn svg_length_number_text(value : String) -> String? {
let value = trim_string(value)
if parse_length_with_context_optional(
value,
Diagonal,
LengthContext::new(100.0, 100.0),
)
is Some(_) {
Some(value)
} else {
None
}
}
///|
fn parse_length(s : String) -> Double {
parse_length_with_context(s, Horizontal, LengthContext::new(100.0, 100.0))
}
///|
fn parse_length_or_percent(s : String) -> (Double, Bool) {
let s = trim_string(s)
if string_ends_with(s, "%") {
let cleaned = build_substring(s, 0, s.length() - 1)
(parse_number(cleaned) / 100.0, true)
} else {
(parse_length(s), false)
}
}
///|
fn parse_mask_length(value : String, units : MaskUnits) -> (Double, Bool) {
let (val, is_percent) = parse_length_or_percent(value)
match units {
ObjectBoundingBox => (val, false)
UserSpaceOnUse => (val, is_percent)
}
}
///|
fn parse_number(s : String) -> Double {
let s = trim_string(s)
if s.length() == 0 {
return 0.0
}
let mut result = 0.0
let mut sign = 1.0
let mut i = 0
let len = s.length()
fn char_at(str : String, idx : Int) -> Char {
Int::unsafe_to_char(str[idx].to_int())
}
// Sign
if i < len && char_at(s, i) == '-' {
sign = -1.0
i = i + 1
} else if i < len && char_at(s, i) == '+' {
i = i + 1
}
// Integer part
while i < len {
let c = char_at(s, i)
if c >= '0' && c <= '9' {
result = result * 10.0 + (c.to_int() - 48).to_double()
i = i + 1
} else {
break
}
}
// Fractional part
if i < len && char_at(s, i) == '.' {
i = i + 1
let mut frac = 0.1
while i < len {
let c = char_at(s, i)
if c >= '0' && c <= '9' {
result = result + (c.to_int() - 48).to_double() * frac
frac = frac * 0.1
i = i + 1
} else {
break
}
}
}
if i < len && (char_at(s, i) == 'e' || char_at(s, i) == 'E') {
let exponent_marker = i
i += 1
let mut exponent_sign = 1.0
if i < len && char_at(s, i) == '-' {
exponent_sign = -1.0
i += 1
} else if i < len && char_at(s, i) == '+' {
i += 1
}
let exponent_start = i
let mut exponent = 0.0
while i < len {
let c = char_at(s, i)
if c >= '0' && c <= '9' {
exponent = exponent * 10.0 + (c.to_int() - 48).to_double()
i += 1
} else {
break
}
}
if i > exponent_start {
result = result * @math.pow(10.0, exponent * exponent_sign)
} else {
i = exponent_marker
}
}
sign * result
}
///|
fn parse_paint(value : String) -> Paint {
let value = trim_string(value)
let lower = value.to_lower()
if value.length() == 0 {
return None
}
if lower == "none" {
return None
}
if lower == "currentcolor" {
return CurrentColor
}
// Paint server with optional fallback: url(#id)
if string_starts_with(value, "url(") {
let close_index = find_first(value, ')')
if close_index >= 0 {
let url_part = build_substring(value, 0, close_index + 1)
let rest = trim_string(
build_substring(value, close_index + 1, value.length()),
)
match parse_url_ref(url_part) {
Some(id) => {
let fallback = if rest.length() == 0 {
PaintFallback::NoPaint
} else {
parse_paint_fallback(rest)
}
return PaintServerRef(id, fallback)
}
None => ()
}
}
}
// Parse color
let color = parse_color(value)
SolidColor(color)
}
///|
fn parse_paint_fallback(value : String) -> PaintFallback {
let value = trim_string(value)
let lower = value.to_lower()
if value.length() == 0 || lower == "none" {
return NoPaint
}
if lower == "currentcolor" {
return CurrentColor
}
SolidColor(parse_color(value))
}
///|
fn parse_paint_order(value : String) -> PaintOrder {
let v = trim_string(value)
if v.length() == 0 || v == "normal" {
return PaintOrder::default()
}
let tokens = split_tokens(v)
if tokens.length() == 0 {
return PaintOrder::default()
}
let order : Array[PaintOrderItem] = []
for t in tokens {
match t {
"fill" => if !order.contains(Fill) { order.push(Fill) }
"stroke" => if !order.contains(Stroke) { order.push(Stroke) }
"markers" => if !order.contains(Markers) { order.push(Markers) }
_ => ()
}
}
if order.length() == 0 {
return PaintOrder::default()
}
if !order.contains(Fill) {
order.push(Fill)
}
if !order.contains(Stroke) {
order.push(Stroke)
}
if !order.contains(Markers) {
order.push(Markers)
}
{ order, }
}
///|
fn parse_color(value : String) -> Color {
match @css_computed.parse_color(value) {
Resolved(color) =>
Color::rgba(
color.r,
color.g,
color.b,
clamp_int((color.a * 255.0).round().to_int(), 0, 255),
)
CurrentColor | Inherit | Invalid => Color::black()
}
}
///|
fn build_substring(s : String, start : Int, end : Int) -> String {
let buf = StringBuilder::new()
for i in start.. Bool {
c == ' ' || c == ',' || c == '\n' || c == '\r' || c == '\t'
}
///|
fn is_ascii_digit(c : Char) -> Bool {
c >= '0' && c <= '9'
}
///|
fn parse_svg_number_sequence(value : String) -> Array[Double]? {
let nums : Array[Double] = []
let mut i = 0
while i < value.length() {
while i < value.length() &&
is_svg_number_separator(Int::unsafe_to_char(value[i].to_int())) {
i += 1
}
if i >= value.length() {
break
}
let start = i
let first = Int::unsafe_to_char(value[i].to_int())
if first == '+' || first == '-' {
i += 1
}
let mut digit_count = 0
while i < value.length() &&
is_ascii_digit(Int::unsafe_to_char(value[i].to_int())) {
digit_count += 1
i += 1
}
if i < value.length() && Int::unsafe_to_char(value[i].to_int()) == '.' {
i += 1
while i < value.length() &&
is_ascii_digit(Int::unsafe_to_char(value[i].to_int())) {
digit_count += 1
i += 1
}
}
if digit_count == 0 {
return None
}
if i < value.length() {
let exponent = Int::unsafe_to_char(value[i].to_int())
if exponent == 'e' || exponent == 'E' {
let exponent_start = i
i += 1
if i < value.length() {
let sign = Int::unsafe_to_char(value[i].to_int())
if sign == '+' || sign == '-' {
i += 1
}
}
let exponent_digits_start = i
while i < value.length() &&
is_ascii_digit(Int::unsafe_to_char(value[i].to_int())) {
i += 1
}
if i == exponent_digits_start {
i = exponent_start
}
}
}
let token = build_substring(value, start, i)
try @string.parse_double(token) catch {
_ => return None
} noraise {
number => nums.push(number)
}
if i < value.length() {
let next = Int::unsafe_to_char(value[i].to_int())
if !is_svg_number_separator(next) && next != '-' {
return None
}
}
}
Some(nums)
}
///|
fn parse_points(value : String) -> Array[(Double, Double)] {
let result : Array[(Double, Double)] = []
let nums = match parse_svg_number_sequence(value) {
Some(nums) => nums
None => return result
}
if nums.length() % 2 != 0 {
return result
}
let mut i = 0
while i + 1 < nums.length() {
result.push((nums[i], nums[i + 1]))
i = i + 2
}
result
}
///|
pub fn parse_transform(value : String) -> Transform {
// Parse transform attribute: translate(x, y) scale(sx, sy) rotate(angle) etc.
let mut result = Transform::identity()
let mut i = 0
while i < value.length() {
// Skip whitespace
while i < value.length() {
let c = Int::unsafe_to_char(value[i].to_int())
if c == ' ' || c == ',' {
i = i + 1
} else {
break
}
}
if i >= value.length() {
break
}
// Read function name
let name_start = i
while i < value.length() {
let c = Int::unsafe_to_char(value[i].to_int())
if (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') {
i = i + 1
} else {
break
}
}
let name = build_substring(value, name_start, i)
// Find '('
while i < value.length() && Int::unsafe_to_char(value[i].to_int()) != '(' {
i = i + 1
}
i = i + 1 // Skip '('
// Find ')' and parse arguments
let args_start = i
while i < value.length() && Int::unsafe_to_char(value[i].to_int()) != ')' {
i = i + 1
}
let args_str = build_substring(value, args_start, i)
i = i + 1 // Skip ')'
// Parse arguments
let args = parse_transform_args(args_str)
// Apply transform
let t = match name {
"translate" =>
if args.length() >= 2 {
Transform::translate(args[0], args[1])
} else if args.length() >= 1 {
Transform::translate(args[0], 0.0)
} else {
Transform::identity()
}
"scale" =>
if args.length() >= 2 {
Transform::scale(args[0], args[1])
} else if args.length() >= 1 {
Transform::scale(args[0], args[0])
} else {
Transform::identity()
}
"rotate" =>
if args.length() >= 3 {
Transform::rotate_around(
degrees_to_radians(args[0]),
args[1],
args[2],
)
} else if args.length() >= 1 {
Transform::rotate(args[0] |> degrees_to_radians)
} else {
Transform::identity()
}
"skewX" =>
if args.length() >= 1 {
Transform::skew_x(args[0] |> degrees_to_radians)
} else {
Transform::identity()
}
"skewY" =>
if args.length() >= 1 {
Transform::skew_y(args[0] |> degrees_to_radians)
} else {
Transform::identity()
}
"matrix" =>
if args.length() >= 6 {
Transform::matrix(
args[0],
args[1],
args[2],
args[3],
args[4],
args[5],
)
} else {
Transform::identity()
}
_ => Transform::identity()
}
result = result.multiply(t)
}
result
}
///|
fn parse_transform_args(s : String) -> Array[Double] {
let result : Array[Double] = []
let mut buf = StringBuilder::new()
for i in 0.. 0 {
result.push(parse_number(buf.to_string()))
buf = StringBuilder::new()
}
} else {
buf.write_char(c)
}
}
if buf.to_string().length() > 0 {
result.push(parse_number(buf.to_string()))
}
result
}