///|
fn SunRasterRenderer::draw_text_placeholder(
  self : SunRasterRenderer,
  run : @core.TextRun,
  opacity : Double,
) -> Unit {
  guard run.frame.size.width > 0.0 && run.frame.size.height > 0.0 else {
    return
  }
  guard run.text.length() > 0 else { return }
  let font_size = sun_text_placeholder_font_size(run.font.size)
  let unit = (font_size / 7.0).max(1.0)
  let advance = unit * 6.0
  let line_height = (font_size * 1.2).max(unit * 8.0)
  let paint = sun_paint(run.color, opacity)
  let lines = sun_text_placeholder_lines(run.text)
  let mut line_index = 0
  for line in lines {
    let line_top = run.frame.origin.y +
      line_index.to_double() * line_height +
      (line_height - unit * 7.0).max(0.0) * 0.5
    guard line_top < run.frame.origin.y + run.frame.size.height else { return }
    let line_width = line.length().to_double() * advance
    let start_x = sun_text_placeholder_start_x(run, line_width)
    self.draw_text_placeholder_line(
      line,
      start_x,
      line_top,
      unit,
      advance,
      run.frame,
      paint,
    )
    line_index = line_index + 1
  }
}

///|
fn SunRasterRenderer::draw_text_placeholder_line(
  self : SunRasterRenderer,
  line : Array[Char],
  start_x : Double,
  top : Double,
  unit : Double,
  advance : Double,
  frame : @core.Rect,
  paint : @sun_graphics.Paint,
) -> Unit {
  let left = frame.origin.x
  let top_bound = frame.origin.y
  let right = frame.origin.x + frame.size.width
  let bottom = frame.origin.y + frame.size.height
  let mut cursor_x = start_x
  for ch in line {
    if cursor_x >= right {
      return
    }
    let glyph = sun_debug_glyph_rows(ch)
    for row in 0.. Double {
  guard size == size && size > 0.0 else { return 12.0 }
  size.max(6.0).min(96.0)
}

///|
fn sun_text_placeholder_start_x(
  run : @core.TextRun,
  line_width : Double,
) -> Double {
  match run.align {
    TextStart => run.frame.origin.x
    TextCenter => run.frame.origin.x + (run.frame.size.width - line_width) * 0.5
    TextEnd => run.frame.origin.x + run.frame.size.width - line_width
  }
}

///|
fn sun_text_placeholder_lines(text : String) -> Array[Array[Char]] {
  let lines : Array[Array[Char]] = []
  let current : Array[Char] = []
  for ch in text {
    if ch.to_int() == 10 {
      lines.push(current.copy())
      current.clear()
    } else {
      current.push(ch)
    }
  }
  lines.push(current.copy())
  lines
}

///|
fn sun_debug_glyph_rows(ch : Char) -> Array[Int] {
  let code = ch.to_int()
  let normalized = if code >= 97 && code <= 122 { code - 32 } else { code }
  match normalized {
    32 | 9 => [0, 0, 0, 0, 0, 0, 0]
    33 => [4, 4, 4, 4, 4, 0, 4]
    34 => [10, 10, 10, 0, 0, 0, 0]
    35 => [10, 31, 10, 10, 31, 10, 0]
    36 => [4, 15, 20, 14, 5, 30, 4]
    37 => [24, 25, 2, 4, 8, 19, 3]
    38 => [12, 18, 20, 8, 21, 18, 13]
    39 => [4, 4, 8, 0, 0, 0, 0]
    40 => [2, 4, 8, 8, 8, 4, 2]
    41 => [8, 4, 2, 2, 2, 4, 8]
    42 => [0, 4, 21, 14, 21, 4, 0]
    43 => [0, 4, 4, 31, 4, 4, 0]
    44 => [0, 0, 0, 0, 4, 4, 8]
    45 => [0, 0, 0, 31, 0, 0, 0]
    46 => [0, 0, 0, 0, 0, 12, 12]
    47 => [1, 2, 4, 8, 16, 0, 0]
    48 => [14, 17, 19, 21, 25, 17, 14]
    49 => [4, 12, 4, 4, 4, 4, 14]
    50 => [14, 17, 1, 2, 4, 8, 31]
    51 => [30, 1, 1, 14, 1, 1, 30]
    52 => [2, 6, 10, 18, 31, 2, 2]
    53 => [31, 16, 30, 1, 1, 17, 14]
    54 => [6, 8, 16, 30, 17, 17, 14]
    55 => [31, 1, 2, 4, 8, 8, 8]
    56 => [14, 17, 17, 14, 17, 17, 14]
    57 => [14, 17, 17, 15, 1, 2, 12]
    58 => [0, 12, 12, 0, 12, 12, 0]
    59 => [0, 12, 12, 0, 4, 4, 8]
    60 => [2, 4, 8, 16, 8, 4, 2]
    61 => [0, 0, 31, 0, 31, 0, 0]
    62 => [8, 4, 2, 1, 2, 4, 8]
    63 => [14, 17, 1, 2, 4, 0, 4]
    64 => [14, 17, 23, 21, 23, 16, 14]
    65 => [14, 17, 17, 31, 17, 17, 17]
    66 => [30, 17, 17, 30, 17, 17, 30]
    67 => [14, 17, 16, 16, 16, 17, 14]
    68 => [30, 17, 17, 17, 17, 17, 30]
    69 => [31, 16, 16, 30, 16, 16, 31]
    70 => [31, 16, 16, 30, 16, 16, 16]
    71 => [14, 17, 16, 23, 17, 17, 15]
    72 => [17, 17, 17, 31, 17, 17, 17]
    73 => [14, 4, 4, 4, 4, 4, 14]
    74 => [7, 2, 2, 2, 18, 18, 12]
    75 => [17, 18, 20, 24, 20, 18, 17]
    76 => [16, 16, 16, 16, 16, 16, 31]
    77 => [17, 27, 21, 21, 17, 17, 17]
    78 => [17, 25, 21, 19, 17, 17, 17]
    79 => [14, 17, 17, 17, 17, 17, 14]
    80 => [30, 17, 17, 30, 16, 16, 16]
    81 => [14, 17, 17, 17, 21, 18, 13]
    82 => [30, 17, 17, 30, 20, 18, 17]
    83 => [15, 16, 16, 14, 1, 1, 30]
    84 => [31, 4, 4, 4, 4, 4, 4]
    85 => [17, 17, 17, 17, 17, 17, 14]
    86 => [17, 17, 17, 17, 17, 10, 4]
    87 => [17, 17, 17, 21, 21, 21, 10]
    88 => [17, 17, 10, 4, 10, 17, 17]
    89 => [17, 17, 10, 4, 4, 4, 4]
    90 => [31, 1, 2, 4, 8, 16, 31]
    91 => [14, 8, 8, 8, 8, 8, 14]
    92 => [16, 8, 4, 2, 1, 0, 0]
    93 => [14, 2, 2, 2, 2, 2, 14]
    94 => [4, 10, 17, 0, 0, 0, 0]
    95 => [0, 0, 0, 0, 0, 0, 31]
    96 => [8, 4, 2, 0, 0, 0, 0]
    123 => [2, 4, 4, 8, 4, 4, 2]
    124 => [4, 4, 4, 0, 4, 4, 4]
    125 => [8, 4, 4, 2, 4, 4, 8]
    126 => [0, 8, 21, 2, 0, 0, 0]
    _ => [31, 17, 21, 17, 21, 17, 31]
  }
}

///|
fn SunRasterRenderer::draw_text(
  self : SunRasterRenderer,
  run : @core.TextRun,
  opacity : Double,
) -> Unit {
  guard run.frame.size.width > 0.0 && run.frame.size.height > 0.0 else {
    return
  }
  let device_run = sun_transformed_text_run(self.canvas.get_transform(), run)
  self.canvas.save()
  self.canvas.intersect_clip_rect(
    @sun_graphics.Rect::new(
      x=device_run.frame.origin.x,
      y=device_run.frame.origin.y,
      width=device_run.frame.size.width,
      height=device_run.frame.size.height,
    ),
  )
  self.draw_text_clipped(device_run, opacity)
  ignore(self.canvas.restore())
}

///|
fn sun_transformed_text_run(
  transform : @sun_graphics.Transform,
  run : @core.TextRun,
) -> @core.TextRun {
  let frame = sun_core_rect_from_sun_rect(
    sun_transformed_rect_bounds(transform, run.frame),
  )
  let font_scale = sun_text_transform_font_scale(transform)
  @core.TextRun::new(
    text=run.text,
    frame~,
    font=@core.FontSpec::new(
      families=run.font.families,
      size=run.font.size * font_scale,
      weight=run.font.weight,
      style=run.font.style,
    ),
    color=run.color,
    align=run.align,
  )
}

///|
fn sun_core_rect_from_sun_rect(rect : @sun_graphics.Rect) -> @core.Rect {
  @core.Rect::new(x=rect.x, y=rect.y, width=rect.width, height=rect.height)
}

///|
fn sun_text_transform_font_scale(transform : @sun_graphics.Transform) -> Double {
  let (origin_x, origin_y) = transform.apply(0.0, 0.0)
  let (vertical_x, vertical_y) = transform.apply(0.0, 1.0)
  let dx = vertical_x - origin_x
  let dy = vertical_y - origin_y
  let scale = (dx * dx + dy * dy).sqrt()
  if scale > 0.0 {
    scale
  } else {
    1.0
  }
}

///|
fn SunRasterRenderer::draw_text_clipped(
  self : SunRasterRenderer,
  run : @core.TextRun,
  opacity : Double,
) -> Unit {
  let config = sun_text_layout_config(run.font, run.frame.size.width)
  let faces = self.resolved_faces(run.font)
  guard !faces.is_empty() else {
    self.draw_text_placeholder(run, opacity)
    return
  }
  let plan = sun_text_line_plan(faces, run.text, config)
  guard !plan.lines.is_empty() else { return }
  let paint = sun_paint(run.color, opacity)
  let line_height = plan.line_height
  let total_height = line_height * plan.lines.length().to_double()
  let origin_y = run.frame.origin.y +
    (run.frame.size.height - total_height).max(0.0) * 0.5
  let renderer = @sun_renderer.Renderer::new(self.canvas)
  let mut line_index = 0
  for line in plan.lines {
    let baseline_y = origin_y +
      line_index.to_double() * line_height +
      plan.baseline
    if baseline_y > run.frame.origin.y + run.frame.size.height + line_height {
      return
    }
    let x = sun_text_placeholder_start_x(run, line.width)
    let key = sun_text_cache_key(run, line_index, line.text)
    let result = renderer.draw_text_fallback_line_cached_with_renderer_resources(
      faces,
      line.text,
      x,
      baseline_y,
      config,
      self.resources,
      key,
      paint,
    )
    if result.get_plan().has_missing_glyphs() {
      self.record_unsupported_command(
        "DrawText",
        "Sun renderer fell back to placeholder glyphs for missing fallback spans in text: \{run.text}",
      )
      self.draw_text_placeholder(
        @core.TextRun::new(
          text=line.text,
          frame=@core.Rect::new(
            x~,
            y=origin_y + line_index.to_double() * line_height,
            width=run.frame.size.width,
            height=line_height,
          ),
          font=run.font,
          color=run.color,
          align=TextStart,
        ),
        opacity,
      )
    }
    line_index += 1
  }
}

///|
fn SunRasterRenderer::register_font_family_data(
  self : SunRasterRenderer,
  family : @core.FontFamily,
  data : Bytes,
) -> Unit {
  for key in sun_font_family_registration_keys(family) {
    ignore(self.text_system.register_font_bytes(key, data))
  }
}

///|
fn SunRasterRenderer::register_default_platform_fonts(
  self : SunRasterRenderer,
) -> Unit {
  for source in sun_default_platform_font_sources() {
    let data : Bytes? = Some(@fs.read_file_to_bytes(source.path)) catch {
      _ => None
    }
    match data {
      Some(data) =>
        for family in source.families {
          self.register_font_family_data(family, data)
        }
      None => ()
    }
  }
}

///|
priv struct SunDefaultPlatformFontSource {
  path : String
  families : Array[@core.FontFamily]
}

///|
fn sun_default_platform_font_sources() -> Array[SunDefaultPlatformFontSource] {
  [
    {
      path: "/System/Library/Fonts/Supplemental/Arial.ttf",
      families: [
        SystemUi,
        UiSansSerif,
        SansSerif,
        Name("Arial"),
        Name("Helvetica"),
      ],
    },
    {
      path: "/System/Library/Fonts/Supplemental/Courier New.ttf",
      families: [UiMonospace, Monospace, Name("Courier New"), Name("Menlo")],
    },
    {
      path: "/System/Library/Fonts/Supplemental/Times New Roman.ttf",
      families: [UiSerif, Serif, Name("Times New Roman")],
    },
  ]
}

///|
fn sun_font_family_registration_keys(
  family : @core.FontFamily,
) -> Array[String] {
  let keys : Array[String] = []
  match family {
    Name(name) => {
      keys.push(name)
      keys.push(name.to_lower())
    }
    _ => {
      let css = family.css_name()
      keys.push(css)
      keys.push(css.to_lower())
    }
  }
  keys
}

///|
fn SunRasterRenderer::resolved_faces(
  self : SunRasterRenderer,
  font : @core.FontSpec,
) -> Array[@sun_text.FontFace] {
  let faces : Array[@sun_text.FontFace] = []
  let seen : Map[String, Unit] = Map([])
  for key in sun_font_spec_candidate_keys(font) {
    if seen.contains(key) {
      continue
    }
    seen[key] = ()
    match self.text_system.get_font_face(key) {
      Ok(face) => faces.push(face)
      Err(_) => ()
    }
  }
  for key in self.text_system.get_font_keys() {
    if seen.contains(key) {
      continue
    }
    seen[key] = ()
    match self.text_system.get_font_face(key) {
      Ok(face) => faces.push(face)
      Err(_) => ()
    }
  }
  faces
}

///|
fn sun_font_spec_candidate_keys(font : @core.FontSpec) -> Array[String] {
  let keys : Array[String] = []
  for family in font.families.families {
    match family {
      Name(name) => {
        keys.push(name)
        keys.push(name.to_lower())
      }
      _ => {
        let css = family.css_name()
        keys.push(css)
        keys.push(css.to_lower())
      }
    }
  }
  keys
}

///|
fn sun_text_layout_config(
  font : @core.FontSpec,
  max_width : Double,
) -> @sun_text.LayoutConfig {
  @sun_text.LayoutConfig::new(
    max_width=max_width.max(0.0),
    font_size=font.size,
    line_height=(font.size * 1.2).max(1.0),
    alignment=Left,
  )
}

///|
fn SunRasterRenderer::measure_text_layout(
  self : SunRasterRenderer,
  input : @core.TextLayoutInput,
) -> @core.TextLayoutResult {
  let faces = self.resolved_faces(input.font)
  if faces.is_empty() {
    return @core.TextSystem::fallback().measure_text(input)
  }
  let config = sun_text_layout_config(
    input.font,
    input.max_width.unwrap_or(1000000000.0),
  )
  let plan = sun_text_line_plan(faces, input.text, config)
  let carets : Array[Double] = []
  let chars = input.text.to_array()
  for _ in 0..<(chars.length() + 1) {
    carets.push(0.0)
  }
  let mut max_width = 0.0
  let mut baseline = 0.0
  for line_index in 0.. @core.TextLayoutResult {
  ignore(self)
  if text == "" {
    return {
      size: @core.Size::new(width=0.0, height=config.line_height),
      baseline: sun_text_baseline_for_faces(faces, config),
      caret_positions: [0.0],
    }
  }
  let fallback = @sun_text.plan_font_fallback(faces, text)
  let carets : Array[Double] = [0.0]
  let mut width = 0.0
  let mut emitted_scalars = 0
  for span in fallback.get_spans() {
    let span_width = if span.is_missing() {
      sun_measure_missing_span_width(span.get_text(), config)
    } else {
      @sun_text.TextLayout::layout(
        faces[span.get_face_index()],
        span.get_text(),
        config,
      ).get_width()
    }
    let per_scalar = if span.get_char_count() > 0 {
      span_width / span.get_char_count().to_double()
    } else {
      0.0
    }
    for _ in 0.. 0 {
        carets.push(width)
      }
    }
  }
  while carets.length() < text.to_array().length() + 1 {
    carets.push(width)
  }
  {
    size: @core.Size::new(width~, height=config.line_height),
    baseline: sun_text_baseline_for_faces(faces, config),
    caret_positions: carets,
  }
}

///|
fn sun_measure_fallback_line_width(
  faces : Array[@sun_text.FontFace],
  text : String,
  config : @sun_text.LayoutConfig,
) -> Double {
  let result = sun_measure_single_line_text_like(text, faces, config)
  result
}

///|
fn sun_measure_single_line_text_like(
  text : String,
  faces : Array[@sun_text.FontFace],
  config : @sun_text.LayoutConfig,
) -> Double {
  if text == "" {
    return 0.0
  }
  let plan = @sun_text.plan_font_fallback(faces, text)
  let mut width = 0.0
  let mut emitted_scalars = 0
  for span in plan.get_spans() {
    if emitted_scalars > 0 {
      width += config.letter_spacing
    }
    let span_width = if span.is_missing() {
      sun_measure_missing_span_width(span.get_text(), config)
    } else {
      @sun_text.TextLayout::layout(
        faces[span.get_face_index()],
        span.get_text(),
        config,
      ).get_width()
    }
    width += span_width
    emitted_scalars += span.get_char_count()
  }
  width
}

///|
fn sun_measure_missing_span_width(
  text : String,
  config : @sun_text.LayoutConfig,
) -> Double {
  let scalar_count = text.to_array().length()
  if scalar_count <= 0 {
    0.0
  } else {
    config.font_size * 0.6 * scalar_count.to_double()
  }
}

///|
fn sun_text_baseline_for_faces(
  faces : Array[@sun_text.FontFace],
  config : @sun_text.LayoutConfig,
) -> Double {
  if faces.is_empty() {
    return config.font_size * 0.8
  }
  let units = faces[0].get_units_per_em().to_double()
  if units <= 0.0 {
    config.font_size * 0.8
  } else {
    faces[0].get_metrics().ascent.to_double() * config.font_size / units
  }
}

///|
fn sun_text_cache_key(
  run : @core.TextRun,
  line_index : Int,
  text : String,
) -> String {
  "sun:text:" +
  line_index.to_string() +
  ":" +
  run.font.size.to_string() +
  ":" +
  run.color.to_json().stringify() +
  ":" +
  text
}