takumi 1.8.3

Render UI component trees to images.
Documentation
use std::borrow::Cow;

use parley::{FontFeatures, FontVariations, TextStyle};
use smallvec::SmallVec;
use taffy::{Size, prelude::FromLength};

use crate::layout::inline::InlineBrush;
use crate::layout::style::{
  BorderStyle, Color, ComputedStyle, Display, FontSynthesis, SizedTextDecorationThickness,
  SizingContext, WordBreak,
};
use crate::rendering::{RenderContext, SizedShadow};

/// Sized font style with computed font size and line height.
#[derive(Clone)]
pub(crate) struct SizedFontStyle<'s> {
  pub parent: &'s ComputedStyle,
  pub line_height: parley::LineHeight,
  pub line_height_scales_with_text_fit: bool,
  pub stroke_width: f32,
  pub outline_width: f32,
  pub outline_offset: f32,
  pub letter_spacing: f32,
  pub word_spacing: f32,
  pub text_shadow: SmallVec<[SizedShadow; 4]>,
  pub color: Color,
  pub outline_color: Color,
  pub outline_style: BorderStyle,
  pub text_stroke_color: Color,
  pub text_decoration_color: Color,
  pub text_decoration_thickness: SizedTextDecorationThickness,
  pub sizing: SizingContext,
}

impl<'s> From<&'s SizedFontStyle<'s>> for TextStyle<'s, 's, InlineBrush> {
  fn from(style: &'s SizedFontStyle<'s>) -> Self {
    TextStyle {
      font_size: style.sizing.font_size,
      line_height: style.line_height,
      font_weight: style.parent.font_weight.into(),
      font_style: style.parent.font_style.into(),
      font_variations: FontVariations::List(Cow::Borrowed(
        style.parent.font_variation_settings.as_ref(),
      )),
      font_features: FontFeatures::List(Cow::Borrowed(style.parent.font_feature_settings.as_ref())),
      font_family: (&style.parent.font_family).into(),
      letter_spacing: style.letter_spacing,
      word_spacing: style.word_spacing,
      word_break: style.parent.word_break.into(),
      overflow_wrap: if style.parent.word_break == WordBreak::BreakWord {
        // When word-break is break-word, ignore the overflow-wrap property's value.
        // https://developer.mozilla.org/en-US/docs/Web/CSS/word-break#break-word
        parley::OverflowWrap::Anywhere
      } else {
        style.parent.overflow_wrap.into()
      },
      brush: InlineBrush {
        source_span_id: None,
        // Inline elements don't establish a stacking context, so we handle opacity here.
        opacity: if style.parent.display == Display::Inline {
          style.parent.opacity.0
        } else {
          1.0
        },
        color: style.color,
        decoration_color: style.text_decoration_color,
        decoration_thickness: style.text_decoration_thickness,
        decoration_line: style.parent.text_decoration_line.unwrap_or_default(),
        decoration_skip_ink: style.parent.text_decoration_skip_ink,
        stroke_color: style.text_stroke_color,
        font_synthesis: FontSynthesis {
          weight: style.parent.font_synthesis_weight,
          style: style.parent.font_synthesis_style,
        },
        line_height_scales_with_text_fit: style.line_height_scales_with_text_fit,
        vertical_align: style.parent.vertical_align,
      },
      text_wrap_mode: style.parent.text_wrap_mode_and_line_clamp().0.into(),
      font_width: style.parent.font_stretch.into(),

      locale: None,
      has_underline: false,
      underline_offset: None,
      underline_size: None,
      underline_brush: None,
      has_strikethrough: false,
      strikethrough_offset: None,
      strikethrough_size: None,
      strikethrough_brush: None,
    }
  }
}

#[inline]
fn resolved_text_shadows(
  style: &ComputedStyle,
  context: &RenderContext,
) -> SmallVec<[SizedShadow; 4]> {
  style
    .text_shadow
    .as_ref()
    .map_or_else(SmallVec::new, |shadows| {
      shadows
        .iter()
        .map(|shadow| {
          SizedShadow::from_text_shadow(
            *shadow,
            &context.sizing,
            context.current_color,
            Size::from_length(context.sizing.font_size),
          )
        })
        .collect()
    })
}

impl<'s> SizedFontStyle<'s> {
  pub(crate) fn from_style(style: &'s ComputedStyle, context: &RenderContext) -> Self {
    let line_height = style.line_height.into_parley(&context.sizing);

    Self {
      sizing: context.sizing.to_owned(),
      parent: style,
      line_height,
      line_height_scales_with_text_fit: style.line_height.scales_with_text_fit(),
      stroke_width: style
        .webkit_text_stroke_width
        .unwrap_or_default()
        .to_px(&context.sizing, context.sizing.font_size),
      outline_width: style.outline_width.to_px(&context.sizing, 0.0).max(0.0),
      outline_offset: style.outline_offset.to_px(&context.sizing, 0.0),
      letter_spacing: style
        .letter_spacing
        .to_px(&context.sizing, context.sizing.font_size),
      word_spacing: style
        .word_spacing
        .to_px(&context.sizing, context.sizing.font_size),
      text_shadow: resolved_text_shadows(style, context),
      color: style
        .webkit_text_fill_color
        .unwrap_or(style.color)
        .resolve(context.current_color),
      outline_color: style.outline_color.resolve(context.current_color),
      outline_style: style.outline_style,
      text_stroke_color: style
        .webkit_text_stroke_color
        .unwrap_or_default()
        .resolve(context.current_color),
      text_decoration_color: style.text_decoration_color.resolve(context.current_color),
      text_decoration_thickness: style.resolved_text_decoration_thickness(&context.sizing),
    }
  }
}