///|
/// Parse grid-auto-flow value
pub fn parse_grid_auto_flow(value : String) -> @types.GridAutoFlow {
match value.trim().to_lower() {
"row" => Row
"column" => Column
"row dense" | "dense row" => RowDense
"column dense" | "dense column" => ColumnDense
_ => Row
}
}
///|
/// Parse a grid placement value (for grid-column-start, grid-row-end, etc.).
/// Supports the full `` grammar:
/// auto
/// -> Named
/// && ? -> Line / NamedLine (order-independent)
/// span && ? && ? -> Span / SpanNamed
pub fn parse_grid_placement(value : String) -> @types.GridPlacement {
@parser.parse_grid_placement_from_string(value)
}
///|
/// Parse grid-column or grid-row shorthand
/// Formats: , / , / span
pub fn parse_grid_line_shorthand(
value : String,
) -> (@types.GridPlacement, @types.GridPlacement) {
@parser.parse_grid_line_shorthand_from_string(value)
}
///|
/// Find the 1-based grid line number where `name` is first declared in the
/// given line-name table (table index 0 = grid line 1). Returns None if the
/// name is not present.
pub fn find_grid_named_line(
name : String,
line_names : Array[Array[String]],
) -> Int? {
for i = 0; i < line_names.length(); i = i + 1 {
let group = line_names[i]
for j = 0; j < group.length(); j = j + 1 {
if group[j] == name {
return Some(i + 1)
}
}
}
None
}
///|
/// Find the 1-based grid line number of the `n`-th line named `name`. Positive
/// `n` counts occurrences from the start (n=1 is the first); negative `n` counts
/// from the end (n=-1 is the last). Returns None if there is no such line.
pub fn find_grid_named_line_nth(
name : String,
n : Int,
line_names : Array[Array[String]],
) -> Int? {
// Collect the 1-based line numbers carrying `name`, in document order.
let hits : Array[Int] = []
for i = 0; i < line_names.length(); i = i + 1 {
let group = line_names[i]
for j = 0; j < group.length(); j = j + 1 {
if group[j] == name {
hits.push(i + 1)
break
}
}
}
if n == 0 || hits.length() == 0 {
return None
}
let idx = if n > 0 { n - 1 } else { hits.length() + n }
if idx < 0 || idx >= hits.length() {
None
} else {
Some(hits[idx])
}
}
///|
/// Resolve one side of a named grid placement to a concrete 1-based line number.
/// Falls back to the implicit named area edge (`-start` / `-end`)
/// when no explicit line carries the bare name. `is_start` selects the edge.
fn resolve_named_grid_side(
name : String,
is_start : Bool,
line_names : Array[Array[String]],
) -> Int? {
// 1. An explicit line declared with this exact name wins.
match find_grid_named_line(name, line_names) {
Some(n) => return Some(n)
None => ()
}
// 2. Implicit named area: `-start` / `-end`.
let edge = if is_start { name + "-start" } else { name + "-end" }
find_grid_named_line(edge, line_names)
}
///|
/// Resolve a grid item's start/end placement against a grid template's named
/// lines (as captured in `grid_template_column_line_names` /
/// `grid_template_row_line_names`). Returns concrete 1-based line numbers where
/// resolvable. `Line(n)` passes through; `Named` resolves against the table
/// (with implicit-area expansion); `Auto` / `Span` and unresolved names yield
/// None, leaving auto/span placement to the layout engine.
pub fn resolve_grid_line_placement(
start : @types.GridPlacement,
end : @types.GridPlacement,
line_names : Array[Array[String]],
) -> (Int?, Int?) {
let start_line = match start {
Line(n) => Some(n)
Named(name) => resolve_named_grid_side(name, true, line_names)
NamedLine(n, name) => find_grid_named_line_nth(name, n, line_names)
// Auto / Span / SpanNamed are relative and resolved by the layout engine.
_ => None
}
let end_line = match end {
Line(n) => Some(n)
Named(name) => resolve_named_grid_side(name, false, line_names)
NamedLine(n, name) => find_grid_named_line_nth(name, n, line_names)
_ => None
}
(start_line, end_line)
}
///|
/// Parse grid-area shorthand:
/// / / /
pub fn parse_grid_area_shorthand(
value : String,
) -> (
@types.GridPlacement,
@types.GridPlacement,
@types.GridPlacement,
@types.GridPlacement,
) {
@parser.parse_grid_area_shorthand_from_string(value)
}
///|
fn is_basic_custom_ident(value : StringView) -> Bool {
if value.length() == 0 {
return false
}
let first = value[0]
let first_ok = (first >= 'a' && first <= 'z') ||
(first >= 'A' && first <= 'Z') ||
first == '_' ||
first == '-'
if !first_ok {
return false
}
for i = 1; i < value.length(); i = i + 1 {
let c = value[i]
let ok = (c >= 'a' && c <= 'z') ||
(c >= 'A' && c <= 'Z') ||
(c >= '0' && c <= '9') ||
c == '_' ||
c == '-'
if !ok {
return false
}
}
true
}
///|
/// Parse a number value
pub fn parse_number(value : String) -> Double {
@string.parse_double(value.trim().to_owned()) catch {
_ => 0.0
}
}
///|
/// Parse an integer value
pub fn parse_integer(value : String) -> Int {
@string.parse_int(value.trim().to_owned()) catch {
_ => 0
}
}
///|
/// Parse grid-template-columns or grid-template-rows value
/// Delegate to the full parser to support minmax() and repeat().
pub fn parse_grid_template_tracks(
value : String,
) -> Array[@types.TrackSizingFunction] {
@parser.parse_grid_template_tracks_from_string(value)
}
///|
/// Parse grid-template-columns or grid-template-rows value, returning both the
/// track sizing functions and the named lines (index = grid line, length =
/// tracks + 1).
pub fn parse_grid_template_track_list(
value : String,
) -> (Array[@types.TrackSizingFunction], Array[Array[String]]) {
@parser.parse_grid_template_track_list_from_string(value)
}
///|
/// Parse the `grid-template` / `grid` areas form. Returns
/// (areas, rows, row_line_names, cols, col_line_names) or None.
pub fn parse_grid_template_areas_form(
value : String,
) -> (
Array[String],
Array[@types.TrackSizingFunction],
Array[Array[String]],
Array[@types.TrackSizingFunction],
Array[Array[String]],
)? {
@parser.parse_grid_template_areas_form_from_string(value)
}
///|
/// Parse the `grid` shorthand auto-flow form. Returns (auto-flow,
/// template-rows, row-names, template-cols, col-names, auto-rows, auto-cols) or
/// None when neither side has `auto-flow`.
pub fn parse_grid_autoflow_form(
value : String,
) -> (
@types.GridAutoFlow,
Array[@types.TrackSizingFunction],
Array[Array[String]],
Array[@types.TrackSizingFunction],
Array[Array[String]],
Array[@types.TrackSizingFunction],
Array[@types.TrackSizingFunction],
)? {
@parser.parse_grid_autoflow_form_from_string(value)
}
///|
/// Parse one grid template axis, detecting subgrid/masonry. Returns the axis
/// kind, the explicit track sizing functions, and the line names.
pub fn parse_grid_template_axis(
value : String,
) -> (
@types.GridTemplateKind,
Array[@types.TrackSizingFunction],
Array[Array[String]],
) {
@parser.parse_grid_template_axis_from_string(value)
}
///|
/// Parse grid-template shorthand: ` / `
pub fn parse_grid_template_shorthand(
value : String,
) -> (Array[@types.TrackSizingFunction], Array[@types.TrackSizingFunction])? {
let v = value.trim()
if !v.contains("/") {
return None
}
let parts = v.split("/").map(fn(s) { s.trim().to_owned() }).collect()
if parts.length() != 2 {
return None
}
let rows = parse_grid_template_tracks(parts[0])
let cols = parse_grid_template_tracks(parts[1])
Some((rows, cols))
}
///|
/// Parse grid-template shorthand ` / `, preserving named lines.
/// Returns (rows, row_line_names, cols, col_line_names).
pub fn parse_grid_template_shorthand_with_names(
value : String,
) -> (
Array[@types.TrackSizingFunction],
Array[Array[String]],
Array[@types.TrackSizingFunction],
Array[Array[String]],
)? {
let v = value.trim()
if !v.contains("/") {
return None
}
let parts = v.split("/").map(fn(s) { s.trim().to_owned() }).collect()
if parts.length() != 2 {
return None
}
let (rows, row_names) = parse_grid_template_track_list(parts[0])
let (cols, col_names) = parse_grid_template_track_list(parts[1])
Some((rows, row_names, cols, col_names))
}
///|
/// Parse grid-template-areas CSS property value.
/// The value is a series of quoted strings like: "header header" "sidebar main" "footer footer"
pub fn parse_grid_template_areas_value(value : String) -> Array[String] {
let result : Array[String] = []
let v = value.trim()
// Parse quoted strings - each quoted string is one row
let mut i = 0
while i < v.length() {
let c = v[i].unsafe_to_char()
if c == '"' || c == '\'' {
let quote = c
let mut end = i + 1
while end < v.length() && v[end].unsafe_to_char() != quote {
end = end + 1
}
if end > i + 1 {
let row_str = view_to_string(v.view(start_offset=i + 1, end_offset=end))
result.push(row_str.to_string())
}
i = end + 1
} else {
i = i + 1
}
}
result
}
///|
/// Check if a value is a named grid area identifier (not a number, auto, or span keyword)
pub fn is_grid_area_name(value : String) -> Bool {
let v = value.trim().to_lower()
if v == "auto" ||
v == "none" ||
v == "inherit" ||
v == "initial" ||
v == "unset" {
return false
}
if v.has_prefix("span") {
return false
}
// Check if it's a number
let parsed = @string.parse_int(v.to_owned()) catch { _ => 0 }
if parsed != 0 {
return false
}
// Must be a valid custom ident
is_basic_custom_ident(v)
}
///|
/// Parse aspect-ratio value
pub fn parse_aspect_ratio(value : String) -> Double? {
let v = value.trim()
if v == "auto" {
return None
}
// Check for ratio format: "16 / 9" or "16/9"
if v.contains("/") {
// Find the slash position
let mut slash_pos = -1
for i = 0; i < v.length(); i = i + 1 {
if v[i].to_int().unsafe_to_char() == '/' {
slash_pos = i
break
}
}
if slash_pos > 0 {
// Use view slicing
let width_str = view_to_string(v.view(end_offset=slash_pos)).trim()
let height_str = view_to_string(v.view(start_offset=slash_pos + 1)).trim()
let w = @string.parse_double(width_str.to_owned()) catch {
_ => return None
}
let h = @string.parse_double(height_str.to_owned()) catch {
_ => return None
}
if h != 0.0 {
return Some(w / h)
}
return None
}
}
// Try parsing as a single number
let n = @string.parse_double(v.to_owned()) catch { _ => return None }
Some(n)
}
///|