<commit_before>"""
    weasyprint.layout.inline
    ------------------------

    Line breaking and layout for inline-level boxes.

"""

import unicodedata

from ..css import computed_from_cascaded
from ..css.computed_values import ex_ratio, strut_layout
from ..formatting_structure import boxes
from ..text import can_break_text, create_layout, split_first_line
from .absolute import AbsolutePlaceholder, absolute_layout
from .flex import flex_layout
from .float import avoid_collisions, float_layout
from .min_max import handle_min_max_height, handle_min_max_width
from .percentages import resolve_one_percentage, resolve_percentages
from .preferred import (
    inline_min_content_width, shrink_to_fit, trailing_whitespace_size)
from .tables import find_in_flow_baseline, table_wrapper_width


def iter_line_boxes(context, box, position_y, skip_stack, containing_block,
                    absolute_boxes, fixed_boxes, first_letter_style):
    """Return an iterator of ``(line, resume_at)``.

    ``line`` is a laid-out LineBox with as much content as possible that
    fits in the available width.

    :param box: a non-laid-out :class:`LineBox`
    :param position_y: vertical top position of the line box on the page
    :param skip_stack: ``None`` to start at the beginning of ``linebox``,
                       or a ``resume_at`` value to continue just after an
                       already laid-out line.
    :param containing_block: Containing block of the line box:
                             a :class:`BlockContainerBox`

    """
    resolve_percentages(box, containing_block)
    if skip_stack is None:
        # TODO: wrong, see https://github.com/Kozea/WeasyPrint/issues/679
        resolve_one_percentage(box, 'text_indent', containing_block.width)
    else:
        box.text_indent = 0
    while True:
        line, resume_at = get_next_linebox(
            context, box, position_y, skip_stack, containing_block,
            absolute_boxes, fixed_boxes, first_letter_style)
        if line:
            handle_leaders(context, line, containing_block)
            position_y = line.position_y + line.height
        if line is None:
            return
        yield line, resume_at
        if resume_at is None:
            return
        skip_stack = resume_at
        box.text_indent = 0
        first_letter_style = None


def handle_leaders(context, line, containing_block):
    after_leader = False
    for child in tuple(line.descendants()):
        if child.leader_string and not after_leader:
            after_leader = True
            available_width = containing_block.width - line.width
            line.width = containing_block.width
            child.width = available_width

            text_box = boxes.TextBox.anonymous_from(child, child.leader_string)
            resolve_percentages(text_box, containing_block)
            text_box, _, _ = split_text_box(
                context, text_box, float('inf'), None)

            # Abort if the leader has no width
            if text_box.width <= 0:
                return

            number_of_leaders = int(line.width // text_box.width)
            position_x = line.position_x + line.width
            children = []
            for i in range(number_of_leaders):
                position_x -= text_box.width
                if position_x < child.position_x:
                    # Don’t add leaders behind the text on the left
                    continue
                elif (position_x + text_box.width >
                        child.position_x + available_width):
                    # Don’t add leaders behind the text on the right
                    continue

                text_box = text_box.copy()
                text_box.position_x = position_x
                text_box.position_y = child.position_y
                children.append(text_box)

            child.children = tuple(children)
            continue

        # Translate the inline children after the leaders
        if after_leader:
            child.position_x += available_width


def get_next_linebox(context, linebox, position_y, skip_stack,
                     containing_block, absolute_boxes, fixed_boxes,
                     first_letter_style):
    """Return ``(line, resume_at)``."""
    skip_stack = skip_first_whitespace(linebox, skip_stack)
    if skip_stack == 'continue':
        return None, None

    skip_stack = first_letter_to_box(linebox, skip_stack, first_letter_style)

    linebox.position_y = position_y

    if context.excluded_shapes:
        # Width and height must be calculated to avoid floats
        linebox.width = inline_min_content_width(
            context, linebox, skip_stack=skip_stack, first_line=True)
        linebox.height, _ = strut_layout(linebox.style, context)
    else:
        # No float, width and height will be set by the lines
        linebox.width = linebox.height = 0
    position_x, position_y, available_width = avoid_collisions(
        context, linebox, containing_block, outer=False)

    candidate_height = linebox.height

    excluded_shapes = context.excluded_shapes[:]
<commit_msg>Find leader index before adding text and moving boxes<commit_after>"""
    weasyprint.layout.inline
    ------------------------

    Line breaking and layout for inline-level boxes.

"""

import unicodedata

from ..css import computed_from_cascaded
from ..css.computed_values import ex_ratio, strut_layout
from ..formatting_structure import boxes
from ..text import can_break_text, create_layout, split_first_line
from .absolute import AbsolutePlaceholder, absolute_layout
from .flex import flex_layout
from .float import avoid_collisions, float_layout
from .min_max import handle_min_max_height, handle_min_max_width
from .percentages import resolve_one_percentage, resolve_percentages
from .preferred import (
    inline_min_content_width, shrink_to_fit, trailing_whitespace_size)
from .tables import find_in_flow_baseline, table_wrapper_width


def iter_line_boxes(context, box, position_y, skip_stack, containing_block,
                    absolute_boxes, fixed_boxes, first_letter_style):
    """Return an iterator of ``(line, resume_at)``.

    ``line`` is a laid-out LineBox with as much content as possible that
    fits in the available width.

    :param box: a non-laid-out :class:`LineBox`
    :param position_y: vertical top position of the line box on the page
    :param skip_stack: ``None`` to start at the beginning of ``linebox``,
                       or a ``resume_at`` value to continue just after an
                       already laid-out line.
    :param containing_block: Containing block of the line box:
                             a :class:`BlockContainerBox`

    """
    resolve_percentages(box, containing_block)
    if skip_stack is None:
        # TODO: wrong, see https://github.com/Kozea/WeasyPrint/issues/679
        resolve_one_percentage(box, 'text_indent', containing_block.width)
    else:
        box.text_indent = 0
    while True:
        line, resume_at = get_next_linebox(
            context, box, position_y, skip_stack, containing_block,
            absolute_boxes, fixed_boxes, first_letter_style)
        if line:
            handle_leaders(context, line, containing_block)
            position_y = line.position_y + line.height
        if line is None:
            return
        yield line, resume_at
        if resume_at is None:
            return
        skip_stack = resume_at
        box.text_indent = 0
        first_letter_style = None


def leader_index(box):
    """Get the index of the first leader box in ``box``."""
    for i, child in enumerate(box.children):
        if child.leader_string:
            return (i, None), child
        if isinstance(child, boxes.ParentBox):
            child_leader_index, child_leader = leader_index(child)
            if child_leader_index is not None:
                return (i, child_leader_index), child_leader
    return None, None


def handle_leaders(context, line, containing_block):
    """Find a leader box in ``line`` and handle its text and its position."""
    index, leader_box = leader_index(line)
    if index is not None:
        available_width = containing_block.width - line.width
        line.width = containing_block.width
        leader_box.width = available_width

        # Build a text box that will be put multiple times in the leader box
        text_box = boxes.TextBox.anonymous_from(
            leader_box, leader_box.leader_string)
        resolve_percentages(text_box, containing_block)
        text_box, _, _ = split_text_box(context, text_box, float('inf'), None)
        text_box.position_y = leader_box.position_y

        # Abort if the leader text has no width
        if text_box.width <= 0:
            return

        # Add text boxes into the leader box
        number_of_leaders = int(line.width // text_box.width)
        position_x = line.position_x + line.width
        children = []
        for i in range(number_of_leaders):
            position_x -= text_box.width
            if position_x < leader_box.position_x:
                # Don’t add leaders behind the text on the left
                continue
            elif (position_x + text_box.width >
                    leader_box.position_x + available_width):
                # Don’t add leaders behind the text on the right
                continue
            text_box = text_box.copy()
            text_box.position_x = position_x
            children.append(text_box)
        leader_box.children = tuple(children)

    # Widen leader parent boxes and translate following boxes
    box = line
    while index is not None:
        for child in box.children[index[0] + 1:]:
            child.translate(dx=available_width)
        box = box.children[index[0]]
        box.width += available_width
        index = index[1]


def get_next_linebox(context, linebox, position_y, skip_stack,
                     containing_block, absolute_boxes, fixed_boxes,
                     first_letter_style):
    """Return ``(line, resume_at)``."""
    skip_stack = skip_first_whitespace(linebox, skip_stack)
    if skip_stack == 'continue':
        return None, None

    skip_stack = first_letter_to_box(linebox, skip_stack, first_letter_style)

    linebox.position_y = position_y

    if context.excluded_shapes:
        # Width and height must be calculated to avoid floats
        linebox.width = inline_min_content_width(
            context, linebox, skip_stack=skip_stack, first_line=True)
        linebox.height, _ = strut_layout(linebox.style, context)
    else:
        # No float, width and height will be set by the lines
        linebox.width = linebox.height = 0
    position_x, position_y, available_width = avoid_collisions(
        context, linebox, containing_block, outer=False)

    candidate_height = linebox.height

    excluded_shapes = context.excluded_shapes[:]

{'fraction_non_alphanumeric': 0.07469260414755001, 'fraction_numerical': 0.0016516792071939806, 'mean_word_length': 2.505628819556127, 'pattern_counts': {'":': 0, '<': 7, '<?xml version=': 0, '>': 5, 'https://': 2, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '749105', 'n_tokens_mistral': 3070, 'n_tokens_neox': 2968, 'n_words': 862}