///|
/// Draw text through the stable font-face layout pipeline.
pub fn Renderer::draw_text_face(
  self : Renderer,
  face : @text.FontFace,
  text : String,
  x : Double,
  y : Double,
  config : @text.LayoutConfig,
  paint : @graphics.Paint,
) -> Unit {
  let pixmap = self.canvas.get_pixmap()
  draw_text_face(
    self.canvas,
    face,
    text,
    x,
    y,
    pixmap.get_width(),
    pixmap.get_height(),
    config,
    paint,
  )
}

///|
/// Draw text through a caller-owned rendered text-mask cache.
pub fn Renderer::draw_text_face_cached(
  self : Renderer,
  face : @text.FontFace,
  text : String,
  x : Double,
  y : Double,
  config : @text.LayoutConfig,
  text_cache : @text.TextMaskCache,
  text_key : String,
  paint : @graphics.Paint,
) -> TextMaskDrawResult {
  let pixmap = self.canvas.get_pixmap()
  draw_text_face_cached(
    self.canvas,
    face,
    text,
    x,
    y,
    pixmap.get_width(),
    pixmap.get_height(),
    config,
    text_cache,
    text_key,
    paint,
  )
}

///|
/// Draw text through renderer-owned text resources and the rendered mask cache.
pub fn Renderer::draw_text_face_cached_with_resources(
  self : Renderer,
  face : @text.FontFace,
  text : String,
  x : Double,
  y : Double,
  config : @text.LayoutConfig,
  resources : RendererTextResources,
  text_key : String,
  paint : @graphics.Paint,
) -> TextMaskDrawResult {
  self.draw_text_face_cached(
    face,
    text,
    x,
    y,
    config,
    resources.get_text_mask_cache(),
    text_key,
    paint,
  )
}

///|
/// Draw text through renderer-owned resources and the rendered mask cache.
pub fn Renderer::draw_text_face_cached_with_renderer_resources(
  self : Renderer,
  face : @text.FontFace,
  text : String,
  x : Double,
  y : Double,
  config : @text.LayoutConfig,
  resources : RendererResources,
  text_key : String,
  paint : @graphics.Paint,
) -> TextMaskDrawResult {
  self.draw_text_face_cached_with_resources(
    face,
    text,
    x,
    y,
    config,
    resources.get_text_resources(),
    text_key,
    paint,
  )
}

///|
/// Draw a single left-to-right text line through an ordered font fallback stack.
pub fn Renderer::draw_text_fallback_line(
  self : Renderer,
  faces : Array[@text.FontFace],
  text : String,
  x : Double,
  y : Double,
  config : @text.LayoutConfig,
  paint : @graphics.Paint,
) -> TextFallbackDrawResult {
  let pixmap = self.canvas.get_pixmap()
  draw_text_fallback_line(
    self.canvas,
    faces,
    text,
    x,
    y,
    pixmap.get_width(),
    pixmap.get_height(),
    config,
    paint,
  )
}

///|
/// Draw a single fallback line through a caller-owned rendered text-mask cache.
pub fn Renderer::draw_text_fallback_line_cached(
  self : Renderer,
  faces : Array[@text.FontFace],
  text : String,
  x : Double,
  y : Double,
  config : @text.LayoutConfig,
  text_cache : @text.TextMaskCache,
  text_key_prefix : String,
  paint : @graphics.Paint,
) -> TextFallbackDrawResult {
  let pixmap = self.canvas.get_pixmap()
  draw_text_fallback_line_cached(
    self.canvas,
    faces,
    text,
    x,
    y,
    pixmap.get_width(),
    pixmap.get_height(),
    config,
    text_cache,
    text_key_prefix,
    paint,
  )
}

///|
/// Draw a cached fallback line through renderer-owned text resources.
pub fn Renderer::draw_text_fallback_line_cached_with_resources(
  self : Renderer,
  faces : Array[@text.FontFace],
  text : String,
  x : Double,
  y : Double,
  config : @text.LayoutConfig,
  resources : RendererTextResources,
  text_key_prefix : String,
  paint : @graphics.Paint,
) -> TextFallbackDrawResult {
  self.draw_text_fallback_line_cached(
    faces,
    text,
    x,
    y,
    config,
    resources.get_text_mask_cache(),
    text_key_prefix,
    paint,
  )
}

///|
/// Draw a cached fallback line through renderer-owned resources.
pub fn Renderer::draw_text_fallback_line_cached_with_renderer_resources(
  self : Renderer,
  faces : Array[@text.FontFace],
  text : String,
  x : Double,
  y : Double,
  config : @text.LayoutConfig,
  resources : RendererResources,
  text_key_prefix : String,
  paint : @graphics.Paint,
) -> TextFallbackDrawResult {
  self.draw_text_fallback_line_cached_with_resources(
    faces,
    text,
    x,
    y,
    config,
    resources.get_text_resources(),
    text_key_prefix,
    paint,
  )
}

///|
/// Draw a single fallback line through caller-owned glyph mask cache and atlas
/// resources.
pub fn Renderer::draw_text_fallback_line_atlas(
  self : Renderer,
  faces : Array[@text.FontFace],
  text : String,
  x : Double,
  y : Double,
  config : @text.LayoutConfig,
  glyph_cache : @text.GlyphMaskCache,
  atlas : @text.GlyphMaskAtlas,
  glyph_key_prefix : String,
  paint : @graphics.Paint,
) -> TextFallbackAtlasDrawResult {
  draw_text_fallback_line_atlas(
    self.canvas,
    faces,
    text,
    x,
    y,
    config,
    glyph_cache,
    atlas,
    glyph_key_prefix,
    paint,
  )
}

///|
/// Draw a fallback line through renderer-owned text resources and the glyph
/// atlas path.
pub fn Renderer::draw_text_fallback_line_atlas_with_resources(
  self : Renderer,
  faces : Array[@text.FontFace],
  text : String,
  x : Double,
  y : Double,
  config : @text.LayoutConfig,
  resources : RendererTextResources,
  glyph_key_prefix : String,
  paint : @graphics.Paint,
) -> TextFallbackAtlasDrawResult {
  self.draw_text_fallback_line_atlas(
    faces,
    text,
    x,
    y,
    config,
    resources.get_glyph_mask_cache(),
    resources.get_glyph_mask_atlas(),
    glyph_key_prefix,
    paint,
  )
}

///|
/// Draw a fallback line through renderer-owned resources and the glyph atlas
/// path.
pub fn Renderer::draw_text_fallback_line_atlas_with_renderer_resources(
  self : Renderer,
  faces : Array[@text.FontFace],
  text : String,
  x : Double,
  y : Double,
  config : @text.LayoutConfig,
  resources : RendererResources,
  glyph_key_prefix : String,
  paint : @graphics.Paint,
) -> TextFallbackAtlasDrawResult {
  self.draw_text_fallback_line_atlas_with_resources(
    faces,
    text,
    x,
    y,
    config,
    resources.get_text_resources(),
    glyph_key_prefix,
    paint,
  )
}

///|
/// Parse font bytes through renderer resources, then draw through the rendered
/// text-mask cache.
pub fn Renderer::draw_font_bytes_cached_with_renderer_resources(
  self : Renderer,
  font_data : Bytes,
  text : String,
  x : Double,
  y : Double,
  config : @text.LayoutConfig,
  resources : RendererResources,
  font_key : String,
  text_key : String,
  paint : @graphics.Paint,
) -> Result[CachedFontTextDrawResult, @text.FontParseError] {
  match
    resources.get_font_face_cache().get_or_parse_result(font_key, font_data) {
    Ok(parse_result) => {
      let text_result = self.draw_text_face_cached_with_renderer_resources(
        parse_result.get_face(),
        text,
        x,
        y,
        config,
        resources,
        text_key,
        paint,
      )
      Ok({ did_parse_font: parse_result.did_parse(), text_result, })
    }
    Err(error) => Err(error)
  }
}

///|
/// Load a font face through renderer resources, then draw through the rendered
/// text-mask cache.
pub fn[L : @text.FontLoader] Renderer::draw_loaded_font_cached_with_renderer_resources(
  self : Renderer,
  loader : L,
  text : String,
  x : Double,
  y : Double,
  config : @text.LayoutConfig,
  resources : RendererResources,
  font_key : String,
  text_key : String,
  paint : @graphics.Paint,
) -> Result[CachedFontTextDrawResult, @text.FontLoadError] {
  match resources.get_font_face_cache().get_or_load_result(font_key, loader) {
    Ok(load_result) => {
      let text_result = self.draw_text_face_cached_with_renderer_resources(
        load_result.get_face(),
        text,
        x,
        y,
        config,
        resources,
        text_key,
        paint,
      )
      Ok({ did_parse_font: load_result.did_parse(), text_result, })
    }
    Err(error) => Err(error)
  }
}

///|
/// Parse an ordered font-byte stack through renderer resources, then draw a
/// cached fallback line through the shared text-mask cache.
pub fn Renderer::draw_font_bytes_fallback_line_cached_with_renderer_resources(
  self : Renderer,
  sources : Array[FontBytesFallbackSource],
  text : String,
  x : Double,
  y : Double,
  config : @text.LayoutConfig,
  resources : RendererResources,
  text_key_prefix : String,
  paint : @graphics.Paint,
) -> Result[CachedFontFallbackDrawResult, @text.FontParseError] {
  let faces : Array[@text.FontFace] = []
  let mut parsed_font_count = 0
  let mut font_cache_hit_count = 0
  for source in sources {
    match
      resources
      .get_font_face_cache()
      .get_or_parse_result(source.get_font_key(), source.get_font_data()) {
      Ok(parse_result) => {
        faces.push(parse_result.get_face())
        if parse_result.did_parse() {
          parsed_font_count += 1
        } else {
          font_cache_hit_count += 1
        }
      }
      Err(error) => return Err(error)
    }
  }
  let fallback_result = self.draw_text_fallback_line_cached_with_renderer_resources(
    faces, text, x, y, config, resources, text_key_prefix, paint,
  )
  Ok({ parsed_font_count, font_cache_hit_count, fallback_result, })
}

///|
/// Parse an ordered font-byte stack through renderer resources, then draw a
/// fallback line through the shared glyph mask cache and atlas.
pub fn Renderer::draw_font_bytes_fallback_line_atlas_with_renderer_resources(
  self : Renderer,
  sources : Array[FontBytesFallbackSource],
  text : String,
  x : Double,
  y : Double,
  config : @text.LayoutConfig,
  resources : RendererResources,
  glyph_key_prefix : String,
  paint : @graphics.Paint,
) -> Result[CachedFontFallbackAtlasDrawResult, @text.FontParseError] {
  let faces : Array[@text.FontFace] = []
  let mut parsed_font_count = 0
  let mut font_cache_hit_count = 0
  for source in sources {
    match
      resources
      .get_font_face_cache()
      .get_or_parse_result(source.get_font_key(), source.get_font_data()) {
      Ok(parse_result) => {
        faces.push(parse_result.get_face())
        if parse_result.did_parse() {
          parsed_font_count += 1
        } else {
          font_cache_hit_count += 1
        }
      }
      Err(error) => return Err(error)
    }
  }
  let fallback_result = self.draw_text_fallback_line_atlas_with_renderer_resources(
    faces, text, x, y, config, resources, glyph_key_prefix, paint,
  )
  Ok({ parsed_font_count, font_cache_hit_count, fallback_result, })
}

///|
/// Draw text through caller-owned glyph mask cache and glyph atlas resources.
pub fn Renderer::draw_text_face_atlas(
  self : Renderer,
  face : @text.FontFace,
  text : String,
  x : Double,
  y : Double,
  config : @text.LayoutConfig,
  glyph_cache : @text.GlyphMaskCache,
  atlas : @text.GlyphMaskAtlas,
  glyph_key_prefix : String,
  paint : @graphics.Paint,
) -> TextAtlasDrawResult {
  draw_text_face_atlas(
    self.canvas,
    face,
    text,
    x,
    y,
    config,
    glyph_cache,
    atlas,
    glyph_key_prefix,
    paint,
  )
}

///|
/// Draw text through renderer-owned text resources and the glyph atlas path.
pub fn Renderer::draw_text_face_atlas_with_resources(
  self : Renderer,
  face : @text.FontFace,
  text : String,
  x : Double,
  y : Double,
  config : @text.LayoutConfig,
  resources : RendererTextResources,
  glyph_key_prefix : String,
  paint : @graphics.Paint,
) -> TextAtlasDrawResult {
  self.draw_text_face_atlas(
    face,
    text,
    x,
    y,
    config,
    resources.get_glyph_mask_cache(),
    resources.get_glyph_mask_atlas(),
    glyph_key_prefix,
    paint,
  )
}

///|
/// Draw text through renderer-owned resources and the glyph atlas path.
pub fn Renderer::draw_text_face_atlas_with_renderer_resources(
  self : Renderer,
  face : @text.FontFace,
  text : String,
  x : Double,
  y : Double,
  config : @text.LayoutConfig,
  resources : RendererResources,
  glyph_key_prefix : String,
  paint : @graphics.Paint,
) -> TextAtlasDrawResult {
  self.draw_text_face_atlas_with_resources(
    face,
    text,
    x,
    y,
    config,
    resources.get_text_resources(),
    glyph_key_prefix,
    paint,
  )
}

///|
/// Parse font bytes through renderer resources, then draw through glyph cache
/// and atlas resources.
pub fn Renderer::draw_font_bytes_atlas_with_renderer_resources(
  self : Renderer,
  font_data : Bytes,
  text : String,
  x : Double,
  y : Double,
  config : @text.LayoutConfig,
  resources : RendererResources,
  font_key : String,
  glyph_key_prefix : String,
  paint : @graphics.Paint,
) -> Result[CachedFontAtlasDrawResult, @text.FontParseError] {
  match
    resources.get_font_face_cache().get_or_parse_result(font_key, font_data) {
    Ok(parse_result) => {
      let atlas_result = self.draw_text_face_atlas_with_renderer_resources(
        parse_result.get_face(),
        text,
        x,
        y,
        config,
        resources,
        glyph_key_prefix,
        paint,
      )
      Ok({ did_parse_font: parse_result.did_parse(), atlas_result, })
    }
    Err(error) => Err(error)
  }
}

///|
/// Load a font face through renderer resources, then draw through glyph cache
/// and atlas resources.
pub fn[L : @text.FontLoader] Renderer::draw_loaded_font_atlas_with_renderer_resources(
  self : Renderer,
  loader : L,
  text : String,
  x : Double,
  y : Double,
  config : @text.LayoutConfig,
  resources : RendererResources,
  font_key : String,
  glyph_key_prefix : String,
  paint : @graphics.Paint,
) -> Result[CachedFontAtlasDrawResult, @text.FontLoadError] {
  match resources.get_font_face_cache().get_or_load_result(font_key, loader) {
    Ok(load_result) => {
      let atlas_result = self.draw_text_face_atlas_with_renderer_resources(
        load_result.get_face(),
        text,
        x,
        y,
        config,
        resources,
        glyph_key_prefix,
        paint,
      )
      Ok({ did_parse_font: load_result.did_parse(), atlas_result, })
    }
    Err(error) => Err(error)
  }
}

///|
/// Composite an existing text coverage mask on the renderer canvas.
pub fn Renderer::draw_coverage_mask(
  self : Renderer,
  mask : @text.CoverageMask,
  paint : @graphics.Paint,
) -> Unit {
  draw_coverage_mask(self.canvas, mask, paint)
}

///|
/// Draw one glyph entry from a coverage-mask atlas snapshot.
pub fn Renderer::draw_glyph_atlas_entry(
  self : Renderer,
  atlas_mask : @text.CoverageMask,
  entry : @text.GlyphAtlasEntry,
  x : Int,
  y : Int,
  paint : @graphics.Paint,
) -> Unit {
  draw_glyph_atlas_entry(self.canvas, atlas_mask, entry, x, y, paint)
}

///|
/// Rasterize font-face text and composite it on a canvas.
fn draw_text_face(
  canvas : @graphics.Canvas,
  face : @text.FontFace,
  text : String,
  x : Double,
  y : Double,
  width : Int,
  height : Int,
  config : @text.LayoutConfig,
  paint : @graphics.Paint,
) -> Unit {
  let mask = @text.render_text_mask_face(
    face, text, width, height, x, y, config,
  )
  draw_coverage_mask(canvas, mask, paint)
}

///|
/// Draw text through a caller-owned rendered text-mask cache.
fn draw_text_face_cached(
  canvas : @graphics.Canvas,
  face : @text.FontFace,
  text : String,
  x : Double,
  y : Double,
  width : Int,
  height : Int,
  config : @text.LayoutConfig,
  text_cache : @text.TextMaskCache,
  text_key : String,
  paint : @graphics.Paint,
) -> TextMaskDrawResult {
  let result = text_cache.get_or_render_face_result(
    text_key, face, text, width, height, x, y, config,
  )
  draw_coverage_mask(canvas, result.get_mask(), paint)
  { did_render_mask: result.did_render(), }
}

///|
/// Draw a single left-to-right text line through an ordered font fallback stack.
fn draw_text_fallback_line(
  canvas : @graphics.Canvas,
  faces : Array[@text.FontFace],
  text : String,
  x : Double,
  y : Double,
  width : Int,
  height : Int,
  config : @text.LayoutConfig,
  paint : @graphics.Paint,
) -> TextFallbackDrawResult {
  draw_text_fallback_line_with_cache(
    canvas,
    faces,
    text,
    x,
    y,
    width,
    height,
    config,
    None,
    "",
    paint,
  )
}

///|
/// Draw a single fallback line through a caller-owned rendered text-mask cache.
fn draw_text_fallback_line_cached(
  canvas : @graphics.Canvas,
  faces : Array[@text.FontFace],
  text : String,
  x : Double,
  y : Double,
  width : Int,
  height : Int,
  config : @text.LayoutConfig,
  text_cache : @text.TextMaskCache,
  text_key_prefix : String,
  paint : @graphics.Paint,
) -> TextFallbackDrawResult {
  draw_text_fallback_line_with_cache(
    canvas,
    faces,
    text,
    x,
    y,
    width,
    height,
    config,
    Some(text_cache),
    text_key_prefix,
    paint,
  )
}

///|
/// Draw a single fallback line through caller-owned glyph mask cache and atlas
/// resources.
fn draw_text_fallback_line_atlas(
  canvas : @graphics.Canvas,
  faces : Array[@text.FontFace],
  text : String,
  x : Double,
  y : Double,
  config : @text.LayoutConfig,
  glyph_cache : @text.GlyphMaskCache,
  atlas : @text.GlyphMaskAtlas,
  glyph_key_prefix : String,
  paint : @graphics.Paint,
) -> TextFallbackAtlasDrawResult {
  let plan = @text.plan_font_fallback(faces, text)
  let line_config = fallback_line_config(config)
  let mut cursor_x = x
  let mut drawn_span_count = 0
  let mut skipped_span_count = 0
  let mut emitted_char_count = 0
  let mut glyph_count = 0
  let mut drawn_glyph_count = 0
  let mut skipped_glyph_count = 0
  let mut cache_hit_count = 0
  let mut rasterized_glyph_count = 0
  let mut atlas_hit_count = 0
  let mut atlas_inserted_count = 0
  let mut did_clear_atlas = false

  for span in plan.get_spans() {
    if emitted_char_count > 0 {
      cursor_x += line_config.letter_spacing
    }
    if span.is_missing() {
      skipped_span_count += 1
      cursor_x += estimate_missing_span_advance(span.get_text(), line_config)
    } else {
      let face = faces[span.get_face_index()]
      let span_text = span.get_text()
      let result = draw_text_face_atlas(
        canvas,
        face,
        span_text,
        cursor_x,
        y,
        line_config,
        glyph_cache,
        atlas,
        fallback_glyph_atlas_cache_prefix(
          glyph_key_prefix,
          span.get_face_index(),
        ),
        paint,
      )
      glyph_count += result.get_glyph_count()
      drawn_glyph_count += result.get_drawn_glyph_count()
      skipped_glyph_count += result.get_skipped_glyph_count()
      cache_hit_count += result.get_cache_hit_count()
      rasterized_glyph_count += result.get_rasterized_glyph_count()
      atlas_hit_count += result.get_atlas_hit_count()
      atlas_inserted_count += result.get_atlas_inserted_count()
      if result.did_clear_atlas() {
        did_clear_atlas = true
      }
      cursor_x += @text.TextLayout::layout(face, span_text, line_config).get_width()
      drawn_span_count += 1
    }
    emitted_char_count += span.get_char_count()
  }

  {
    plan,
    drawn_span_count,
    skipped_span_count,
    advance_width: cursor_x - x,
    atlas_result: {
      glyph_count,
      drawn_glyph_count,
      skipped_glyph_count,
      cache_hit_count,
      rasterized_glyph_count,
      atlas_hit_count,
      atlas_inserted_count,
      did_clear_atlas,
    },
  }
}

///|
fn draw_text_fallback_line_with_cache(
  canvas : @graphics.Canvas,
  faces : Array[@text.FontFace],
  text : String,
  x : Double,
  y : Double,
  width : Int,
  height : Int,
  config : @text.LayoutConfig,
  text_cache : @text.TextMaskCache?,
  text_key_prefix : String,
  paint : @graphics.Paint,
) -> TextFallbackDrawResult {
  let plan = @text.plan_font_fallback(faces, text)
  let line_config = fallback_line_config(config)
  let mut cursor_x = x
  let mut drawn_span_count = 0
  let mut skipped_span_count = 0
  let mut rendered_mask_count = 0
  let mut cache_hit_count = 0
  let mut emitted_char_count = 0
  let mut span_index = 0

  for span in plan.get_spans() {
    if emitted_char_count > 0 {
      cursor_x += line_config.letter_spacing
    }
    if span.is_missing() {
      skipped_span_count += 1
      cursor_x += estimate_missing_span_advance(span.get_text(), line_config)
    } else {
      let face = faces[span.get_face_index()]
      let span_text = span.get_text()
      match text_cache {
        Some(cache) => {
          let key = fallback_text_cache_key(
            text_key_prefix,
            span_index,
            span.get_face_index(),
            span_text,
            width,
            height,
            cursor_x,
            y,
            line_config,
          )
          let result = cache.get_or_render_face_result(
            key, face, span_text, width, height, cursor_x, y, line_config,
          )
          draw_coverage_mask(canvas, result.get_mask(), paint)
          if result.did_render() {
            rendered_mask_count += 1
          } else {
            cache_hit_count += 1
          }
        }
        None =>
          draw_text_face(
            canvas, face, span_text, cursor_x, y, width, height, line_config, paint,
          )
      }
      cursor_x += @text.TextLayout::layout(face, span_text, line_config).get_width()
      drawn_span_count += 1
    }
    emitted_char_count += span.get_char_count()
    span_index += 1
  }

  {
    plan,
    drawn_span_count,
    skipped_span_count,
    rendered_mask_count,
    cache_hit_count,
    advance_width: cursor_x - x,
  }
}

///|
/// Draw text through caller-owned glyph mask cache and glyph atlas resources.
fn draw_text_face_atlas(
  canvas : @graphics.Canvas,
  face : @text.FontFace,
  text : String,
  x : Double,
  y : Double,
  config : @text.LayoutConfig,
  glyph_cache : @text.GlyphMaskCache,
  atlas : @text.GlyphMaskAtlas,
  glyph_key_prefix : String,
  paint : @graphics.Paint,
) -> TextAtlasDrawResult {
  let layout = @text.TextLayout::layout(face, text, config)
  let scale = config.font_size / face.get_units_per_em().to_double()
  let mut glyph_count = 0
  let mut drawn_glyph_count = 0
  let mut skipped_glyph_count = 0
  let mut cache_hit_count = 0
  let mut rasterized_glyph_count = 0
  let mut atlas_hit_count = 0
  let mut atlas_inserted_count = 0
  let mut did_clear_atlas = false

  for run in layout.get_runs() {
    for glyph in run.get_glyphs() {
      glyph_count += 1
      let glyph_id = glyph.get_glyph_id()
      let key = glyph_atlas_cache_key(
        glyph_key_prefix,
        glyph_id,
        config.font_size,
      )
      match
        glyph_cache.get_or_rasterize_face_glyph_result(
          key, face, glyph_id, scale,
        ) {
        Some(cache_result) => {
          if cache_result.did_rasterize() {
            rasterized_glyph_count += 1
          } else {
            cache_hit_count += 1
          }
          let mask = cache_result.get_mask()
          let placement = cache_result.get_placement()
          match atlas.insert_or_clear(key, mask) {
            Some(result) => {
              if result.did_clear() {
                did_clear_atlas = true
              }
              if result.did_reuse() {
                atlas_hit_count += 1
              } else {
                atlas_inserted_count += 1
              }
              let entry = result.get_entry()
              draw_glyph_atlas_entry(
                canvas,
                atlas.to_mask(),
                entry,
                floor_to_int(x + glyph.get_x()) + placement.get_draw_offset_x(),
                floor_to_int(y + run.get_baseline_y()) +
                placement.get_draw_offset_y(),
                paint,
              )
              drawn_glyph_count += 1
            }
            None => skipped_glyph_count += 1
          }
        }
        None => skipped_glyph_count += 1
      }
    }
  }

  {
    glyph_count,
    drawn_glyph_count,
    skipped_glyph_count,
    cache_hit_count,
    rasterized_glyph_count,
    atlas_hit_count,
    atlas_inserted_count,
    did_clear_atlas,
  }
}

///|
fn fallback_line_config(config : @text.LayoutConfig) -> @text.LayoutConfig {
  @text.LayoutConfig::new(
    max_width=1000000000.0,
    font_size=config.font_size,
    line_height=config.line_height,
    alignment=Left,
    letter_spacing=config.letter_spacing,
    word_spacing=config.word_spacing,
  )
}

///|
fn estimate_missing_span_advance(
  text : String,
  config : @text.LayoutConfig,
) -> Double {
  let mut width = 0.0
  let mut char_count = 0
  for ch in text {
    width += config.font_size * 0.6
    if ch is ' ' {
      width += config.word_spacing
    }
    char_count += 1
  }
  if char_count > 1 {
    width += config.letter_spacing * (char_count - 1).to_double()
  }
  width
}

///|
fn fallback_text_cache_key(
  prefix : String,
  span_index : Int,
  face_index : Int,
  text : String,
  width : Int,
  height : Int,
  x : Double,
  y : Double,
  config : @text.LayoutConfig,
) -> String {
  prefix +
  ":fallback-span:" +
  span_index.to_string() +
  ":face:" +
  face_index.to_string() +
  ":width:" +
  width.to_string() +
  ":height:" +
  height.to_string() +
  ":x:" +
  x.to_string() +
  ":y:" +
  y.to_string() +
  ":size:" +
  config.font_size.to_string() +
  ":letter:" +
  config.letter_spacing.to_string() +
  ":word:" +
  config.word_spacing.to_string() +
  ":text:" +
  text
}

///|
fn fallback_glyph_atlas_cache_prefix(
  prefix : String,
  face_index : Int,
) -> String {
  prefix + ":fallback-face:" + face_index.to_string()
}

///|
/// Composite a text coverage mask on a canvas.
fn draw_coverage_mask(
  canvas : @graphics.Canvas,
  mask : @text.CoverageMask,
  paint : @graphics.Paint,
) -> Unit {
  canvas.draw_mask(
    0,
    0,
    mask.get_width(),
    mask.get_height(),
    mask.get_pixels(),
    paint,
  )
}

///|
/// Composite one glyph entry from a coverage-mask atlas snapshot on a canvas.
fn draw_glyph_atlas_entry(
  canvas : @graphics.Canvas,
  atlas_mask : @text.CoverageMask,
  entry : @text.GlyphAtlasEntry,
  x : Int,
  y : Int,
  paint : @graphics.Paint,
) -> Unit {
  canvas.draw_mask_rect(
    x,
    y,
    atlas_mask.get_width(),
    atlas_mask.get_height(),
    atlas_mask.get_pixels(),
    @graphics.PixelRect::new(
      x=entry.get_x(),
      y=entry.get_y(),
      width=entry.get_width(),
      height=entry.get_height(),
    ),
    paint,
  )
}

///|
fn glyph_atlas_cache_key(
  prefix : String,
  glyph_id : Int,
  font_size : Double,
) -> String {
  prefix + ":glyph:" + glyph_id.to_string() + ":size:" + font_size.to_string()
}

///|
fn floor_to_int(value : Double) -> Int {
  let truncated = value.to_int()
  if truncated.to_double() > value {
    truncated - 1
  } else {
    truncated
  }
}

///|
fn ceil_to_int(value : Double) -> Int {
  let truncated = value.to_int()
  if truncated.to_double() < value {
    truncated + 1
  } else {
    truncated
  }
}