>> {
+ self.first_box
+ }
+}
+
+/// A `LayoutBox` represents a series of nested content boxes, each of which
+/// may contain a single line of text with a given text format applied to it.
+///
+/// Layout boxes are nested and can optionally be associated with an HTML
+/// element. The relationship between elements and boxes are nullably
+/// one-to-many: an HTML element may be represented by multiple layout boxes,
+/// while a layout may have zero or one HTML elements attached to it. This
+/// allows inline content
+///
+/// They also have margins, padding, and borders which are calculated and
+/// rendered according to CSS spec.
+///
+/// For example, an HTML tree that looks like this:
+///
+/// ```I'm a layout element node!
```
+///
+/// produces a top-level `LayoutBox` for the `` tag, which contains one or
+/// more generated boxes for each run of text. The `` tag is cut at it's
+/// whitespace if necessary and the cut pieces are added to their respective
+/// generated boxes.
+#[derive(Clone, Debug, Collect)]
+#[collect(no_drop)]
+pub struct LayoutBox<'gc> {
+ /// The rectangle corresponding to the outer boundaries of the
+ bounds: BoxBounds,
+
+ /// The layout box to be placed after this one.
+ next_sibling: Option>>,
+
+ /// What content is contained by the content box.
+ content: LayoutContent<'gc>,
+}
+
+#[derive(Clone, Debug, Collect)]
+#[collect(require_static)]
+pub struct CollecTwips(Twips);
+
+/// Represents different content modes of a given layout box.
+#[derive(Clone, Debug, Collect)]
+#[collect(no_drop)]
+pub enum LayoutContent<'gc> {
+ /// A layout box containing some part of a text span.
+ ///
+ /// The text is assumed to be pulled from the same `FormatSpans`
+ Text {
+ /// The start position of the text to render.
+ start: usize,
+ end: usize,
+ text_format: TextFormat,
+ font: Font<'gc>,
+ font_size: CollecTwips,
+ },
+}
+
+impl<'gc> LayoutBox<'gc> {
+ /// Construct a text box for an HTML text node.
+ pub fn from_text(
+ mc: MutationContext<'gc, '_>,
+ start: usize,
+ end: usize,
+ text_format: TextFormat,
+ font: Font<'gc>,
+ font_size: Twips,
+ ) -> GcCell<'gc, Self> {
+ GcCell::allocate(
+ mc,
+ Self {
+ bounds: Default::default(),
+ next_sibling: None,
+ content: LayoutContent::Text {
+ start,
+ end,
+ text_format,
+ font,
+ font_size: CollecTwips(font_size),
+ },
+ },
+ )
+ }
+
+ /// Returns the next sibling box.
+ pub fn next_sibling(&self) -> Option>> {
+ self.next_sibling
+ }
+
+ ///
+ pub fn append_text_fragment(
+ mc: MutationContext<'gc, '_>,
+ lc: &mut LayoutContext<'gc>,
+ text: &str,
+ start: usize,
+ end: usize,
+ span: &TextSpan,
+ ) {
+ let font_size = Twips::from_pixels(span.size);
+ let text_size = Size::from(lc.font().unwrap().measure(text, font_size));
+ let text_bounds = BoxBounds::from_position_and_size(*lc.cursor(), text_size);
+ let new_text = Self::from_text(
+ mc,
+ start,
+ end,
+ span.get_text_format(),
+ lc.font().unwrap(),
+ font_size,
+ );
+ let mut write = new_text.write(mc);
+
+ write.bounds = text_bounds;
+
+ *lc.cursor_mut() += Position::from((text_size.width(), Twips::default()));
+ lc.append_box(mc, new_text);
+ }
+
+ /// Construct a new layout hierarchy from text spans.
+ pub fn lower_from_text_spans(
+ fs: &FormatSpans,
+ context: &mut UpdateContext<'_, 'gc, '_>,
+ movie: Arc,
+ bounds: Twips,
+ ) -> Option>> {
+ let mut layout_context: LayoutContext = Default::default();
+
+ for (start, _end, text, span) in fs.iter_spans() {
+ if let Some(font) = layout_context.resolve_font(context, movie.clone(), &span.font) {
+ let font_size = Twips::from_pixels(span.size);
+ let breakpoint_list =
+ font.split_wrapped_lines(&text, font_size, bounds, layout_context.cursor().x());
+
+ let end = text.len();
+
+ let mut last_breakpoint = 0;
+
+ for breakpoint in breakpoint_list {
+ if last_breakpoint != breakpoint {
+ Self::append_text_fragment(
+ context.gc_context,
+ &mut layout_context,
+ &text[last_breakpoint..breakpoint],
+ start + last_breakpoint,
+ start + breakpoint,
+ span,
+ );
+ }
+
+ last_breakpoint = breakpoint;
+ }
+
+ Self::append_text_fragment(
+ context.gc_context,
+ &mut layout_context,
+ &text[last_breakpoint..end],
+ start + last_breakpoint,
+ start + end,
+ span,
+ );
+ }
+ }
+
+ layout_context.end_layout()
+ }
+
+ pub fn bounds(&self) -> BoxBounds {
+ self.bounds
+ }
+
+ /// Returns a reference to the text this box contains.
+ pub fn text_node(&self) -> Option<(usize, usize, &TextFormat, Font<'gc>, Twips)> {
+ match &self.content {
+ LayoutContent::Text {
+ start,
+ end,
+ text_format,
+ font,
+ font_size,
+ } => Some((*start, *end, &text_format, *font, font_size.0)),
+ }
+ }
+}