<commit_before># This file is part of RinohType, the Python document preparation system.
#
# Copyright (c) <NAME>.
#
# Use of this source code is subject to the terms of the GNU Affero General
# Public License v3. See the LICENSE file or http://www.gnu.org/licenses/.

"""
The layout engine. The container classes allow defining rectangular areas on a
page to which :class:`Flowable`\ s can be rendered.

* :class:`Container`: A rectangular area on a page to which flowables are
                      rendered.
* :class:`DownExpandingContainer`: A container that dynamically grows downwards
                                   as flowables are rendered to it.
* :class:`UpExpandingContainer`: Similar to a :class:`DownExpandingContainer`:,
                                 but upwards expanding.
* :class:`VirtualContainer`: A container who's rendered content is not
                             automatically placed on the page. Afterwards, it
                             can be manually placed, however.
* :exc:`EndOfContainer`: Exception raised when a contianer "overflows" during
                         the rendering of flowables.
* :class:`Chain`: A chain of containers. When a container overflows, the
                  rendering of the chain's flowables is continued in the next
                  container in the chain.
* :class:`FootnoteContainer`: TODO

"""

from collections import deque
from contextlib import contextmanager
from copy import copy

from .dimension import Dimension, PT, DimensionAddition


__all__ = ['Container', 'FlowablesContainer', 'ChainedContainer',
           'DownExpandingContainer', 'InlineDownExpandingContainer',
           'UpExpandingContainer', 'VirtualContainer', 'Chain',
           'EndOfContainer', 'FootnoteContainer', 'MaybeContainer',
           'discard_state']


class EndOfContainer(Exception):
    """The end of the :class:`FlowableContainer` has been reached."""

    def __init__(self, flowable_state=None, page_break=False):
        """`flowable_state` represents the rendering state of the
        :class:`Flowable` at the time the :class:`FlowableContainer`" overflows.
        """
        self.flowable_state = flowable_state
        self.page_break = page_break


class ReflowRequired(Exception):
    """Reflow of the current page is required due to insertion of a float."""


class FlowableTarget(object):
    """Something that takes :class:`Flowable`\ s to be rendered."""

    def __init__(self, document_part, *args, **kwargs):
        """Initialize this flowable target.

        `document_part` is the :class:`Document` this flowable target is part
        of."""
        self.flowables = []
        document_part.flowable_targets.append(self)
        super().__init__(*args, **kwargs)

    def append_flowable(self, flowable):
        """Append a `flowable` to the list of flowables to be rendered."""
        self.flowables.append(flowable)

    def __lshift__(self, flowable):
        """Shorthand for :meth:`append_flowable`. Returns `self` so that it can
        be chained."""
        self.append_flowable(flowable)
        return self

    def prepare(self, document):
        for flowable in self.flowables:
            flowable.prepare(document)

    def render(self):
        """Render the flowables assigned to this flowable target, in the order
        that they have been added."""
        raise NotImplementedError


class Container(object):
    """Rectangular area that contains elements to be rendered to a
    :class:`Page`. A :class:`Container` has an origin (the top-left corner), a
    width and a height. It's contents are rendered relative to the container's
    position in its parent :class:`Container`."""

    def __init__(self, name, parent, left=None, top=None, width=None,
                 height=None, right=None, bottom=None):
        """Initialize a this container as a child of the `parent` container.

        The horizontal position and width of the container are determined from
        `left`, `width` and `right`. If only `left` or `right` is specified,
        the container's opposite edge will be placed at the corresponding edge
        of the parent container.

        Similarly, the vertical position and height of the container are
        determined from `top`, `height` and `bottom`. If only one of `top` or
        `bottom` is specified, the container's opposite edge is placed at the
        corresponding edge of the parent container."""
        if left is None:
            left = 0*PT if (right and width) is None else (right - width)
        if width is None:
            width = (parent.width - left) if right is None else (right - left)
        if right is None:
            right = left + width
        self.left = left
        self.width = width
        self.right = right

        if top is None:
            top = 0*PT if (bottom and height) is None else (bottom - height)
        if height is None:
            height = (parent.height - top) if bottom is None else (bottom - top)
        if bottom is None:
            bottom = top + height
        self.top = top
        self.height = height
        self.bottom = bottom

        self.name = name
        self.parent = parent
        if parent is not None:
            self.parent.children.append(self)
        self.children = []
        self.clear()

    @property
    def document_part(self):
        return self.parent.document_part

    @property
    def document(self):
        return self.document_part.document

    def clear(self):
        self.empty_canvas()

    def __repr__(self):
        return "{}('{}')".format(self.__class__.__name__, self.name)

    def __getattr__(self, name):
        if name in ('_footnote_space', 'float_space'):
            return getattr(self.parent, name)
        raise AttributeError(name)

    @property
    def chained_ancestor(self):
        if self.chain:
            return self
        else:
            return self.parent.chained_ancestor

    @property
    def page(self):
        """The :class:`Page` this container is located on."""
        return self.parent.page

    def empty_canvas(self):
        self.canvas = self.parent.canvas.new()

    def render(self, rerender=False):
        """Render the contents of this container to its canvas.

        Note that the rendered contents need to be :meth:`place`d on the parent
        container's canvas before they become visible.

        This method returns an iterator yielding all the :class:`Chain`\ s that
        have run out of containers."""
        for child in self.children:
            for chain in child.render(rerender):
                yield chain

    def check_overflow(self):
        for child in self.children:
            child.check_overflow()

    def place_children(self):
        for child in self.children:
            child.place()

    def place(self):
        """Place this container's canvas onto the parent container's canvas."""
        self.place_children()
        self.canvas.append(float(self.left), float(self.top))


class FlowablesContainerBase(Container):
    """A :class:`Container` that renders :class:`Flowable`\ s to a rectangular
    area on a page. The first flowable is rendered at the top of the container.
    The next flowable is rendered below the first one, and so on."""

    def __init__(self, name, parent, left=None, top=None, width=None, height=None,
                 right=None, bottom=None):
        self._self_cursor = Dimension(0)  # initialized at container's top edge
        self._cursor = DimensionAddition(self._self_cursor)
        super().__init__(name, parent, left=left, top=top, width=width,
                         height=height, right=right, bottom=bottom)

    def clear(self):
        super().clear()
        del self.children[:]
        self._self_cursor._value = 0  # initialized at container's top edge
        del self._cursor.addends[1:]

    @property
    def cursor(self):
        """Keeps track of where the next flowable is to be placed. As flowables
        are flowed into the container, the cursor moves down."""
        return float(self._cursor)

    @property
    def remaining_height(self):
        return self.height - self.cursor

    def render(self, rerender=False):
        raise NotImplementedError

    def advance(self, height, ignore_overflow=False):
        """Advance the cursor by `height`. If this would cause the cursor to
        point beyond the bottom of the container, an :class:`EndOfContainer`
        exception is raised."""
        if height <= self.remaining_height:
            self._self_cursor.grow(height)
        elif not ignore_overflow:
            raise EndOfContainer

    def check_overflow(self):
        if self.remaining_height < 0:
            raise ReflowRequired


class FlowablesContainer(FlowableTarget, FlowablesContainerBase):
    """A container that renders a predefined series of flowables."""

    def __init__(self, name, parent, left=None, top=None, width=None,
                 height=None, right=None, bottom=None):
        super().__init__(parent.document_part, name, parent, left=left, top=top,
                         width=width, height=height, right=right, bottom=bottom)

    def render(self, rerender=False):
        last_descender = None
        for flowable in self.flowables:
            height, last_descender = flowable.flow(self, last_descender)
        return iter(())   # no chains to yield


class ChainedContainer(FlowablesContainerBase):
    """A container that renders flowables from the :class:`Chain` it is part
    of."""

    def __init__(self, name, parent, chain, left=None, top=None, width=None, height=None,
                 right=None, bottom=None):
        super().__init__(name, parent, left=left, top=top, width=width,
                         height=height, right=right, bottom=bottom)
        chain.containers.append(self)
        self.chain = chain

    def render(self, rerender=False):
        last_descender = None
        if self.chain.render(self, rerender=rerender,
                             last_descender=last_descender):
            yield self.chain


class ExpandingContainer(FlowablesContainer):
    """A dynamically, vertically growing :class:`Container`."""

    def __init__(self, name, parent, left, top, width, right, bottom,
                 max_height=None, extra_space_below=0):
        """See :class:`ContainerBase` for information on the `parent`, `left`,<commit_msg>Do not re-render FlowablesContainers<commit_after># This file is part of RinohType, the Python document preparation system.
#
# Copyright (c) <NAME>.
#
# Use of this source code is subject to the terms of the GNU Affero General
# Public License v3. See the LICENSE file or http://www.gnu.org/licenses/.

"""
The layout engine. The container classes allow defining rectangular areas on a
page to which :class:`Flowable`\ s can be rendered.

* :class:`Container`: A rectangular area on a page to which flowables are
                      rendered.
* :class:`DownExpandingContainer`: A container that dynamically grows downwards
                                   as flowables are rendered to it.
* :class:`UpExpandingContainer`: Similar to a :class:`DownExpandingContainer`:,
                                 but upwards expanding.
* :class:`VirtualContainer`: A container who's rendered content is not
                             automatically placed on the page. Afterwards, it
                             can be manually placed, however.
* :exc:`EndOfContainer`: Exception raised when a contianer "overflows" during
                         the rendering of flowables.
* :class:`Chain`: A chain of containers. When a container overflows, the
                  rendering of the chain's flowables is continued in the next
                  container in the chain.
* :class:`FootnoteContainer`: TODO

"""

from collections import deque
from contextlib import contextmanager
from copy import copy

from .dimension import Dimension, PT, DimensionAddition


__all__ = ['Container', 'FlowablesContainer', 'ChainedContainer',
           'DownExpandingContainer', 'InlineDownExpandingContainer',
           'UpExpandingContainer', 'VirtualContainer', 'Chain',
           'EndOfContainer', 'FootnoteContainer', 'MaybeContainer',
           'discard_state']


class EndOfContainer(Exception):
    """The end of the :class:`FlowableContainer` has been reached."""

    def __init__(self, flowable_state=None, page_break=False):
        """`flowable_state` represents the rendering state of the
        :class:`Flowable` at the time the :class:`FlowableContainer`" overflows.
        """
        self.flowable_state = flowable_state
        self.page_break = page_break


class ReflowRequired(Exception):
    """Reflow of the current page is required due to insertion of a float."""


class FlowableTarget(object):
    """Something that takes :class:`Flowable`\ s to be rendered."""

    def __init__(self, document_part, *args, **kwargs):
        """Initialize this flowable target.

        `document_part` is the :class:`Document` this flowable target is part
        of."""
        self.flowables = []
        document_part.flowable_targets.append(self)
        super().__init__(*args, **kwargs)

    def append_flowable(self, flowable):
        """Append a `flowable` to the list of flowables to be rendered."""
        self.flowables.append(flowable)

    def __lshift__(self, flowable):
        """Shorthand for :meth:`append_flowable`. Returns `self` so that it can
        be chained."""
        self.append_flowable(flowable)
        return self

    def prepare(self, document):
        for flowable in self.flowables:
            flowable.prepare(document)

    def render(self):
        """Render the flowables assigned to this flowable target, in the order
        that they have been added."""
        raise NotImplementedError


class Container(object):
    """Rectangular area that contains elements to be rendered to a
    :class:`Page`. A :class:`Container` has an origin (the top-left corner), a
    width and a height. It's contents are rendered relative to the container's
    position in its parent :class:`Container`."""

    def __init__(self, name, parent, left=None, top=None, width=None,
                 height=None, right=None, bottom=None):
        """Initialize a this container as a child of the `parent` container.

        The horizontal position and width of the container are determined from
        `left`, `width` and `right`. If only `left` or `right` is specified,
        the container's opposite edge will be placed at the corresponding edge
        of the parent container.

        Similarly, the vertical position and height of the container are
        determined from `top`, `height` and `bottom`. If only one of `top` or
        `bottom` is specified, the container's opposite edge is placed at the
        corresponding edge of the parent container."""
        if left is None:
            left = 0*PT if (right and width) is None else (right - width)
        if width is None:
            width = (parent.width - left) if right is None else (right - left)
        if right is None:
            right = left + width
        self.left = left
        self.width = width
        self.right = right

        if top is None:
            top = 0*PT if (bottom and height) is None else (bottom - height)
        if height is None:
            height = (parent.height - top) if bottom is None else (bottom - top)
        if bottom is None:
            bottom = top + height
        self.top = top
        self.height = height
        self.bottom = bottom

        self.name = name
        self.parent = parent
        if parent is not None:
            self.parent.children.append(self)
        self.children = []
        self.clear()

    @property
    def document_part(self):
        return self.parent.document_part

    @property
    def document(self):
        return self.document_part.document

    def clear(self):
        self.empty_canvas()

    def __repr__(self):
        return "{}('{}')".format(self.__class__.__name__, self.name)

    def __getattr__(self, name):
        if name in ('_footnote_space', 'float_space'):
            return getattr(self.parent, name)
        raise AttributeError(name)

    @property
    def chained_ancestor(self):
        if self.chain:
            return self
        else:
            return self.parent.chained_ancestor

    @property
    def page(self):
        """The :class:`Page` this container is located on."""
        return self.parent.page

    def empty_canvas(self):
        self.canvas = self.parent.canvas.new()

    def render(self, rerender=False):
        """Render the contents of this container to its canvas.

        Note that the rendered contents need to be :meth:`place`d on the parent
        container's canvas before they become visible.

        This method returns an iterator yielding all the :class:`Chain`\ s that
        have run out of containers."""
        for child in self.children:
            for chain in child.render(rerender):
                yield chain

    def check_overflow(self):
        for child in self.children:
            child.check_overflow()

    def place_children(self):
        for child in self.children:
            child.place()

    def place(self):
        """Place this container's canvas onto the parent container's canvas."""
        self.place_children()
        self.canvas.append(float(self.left), float(self.top))


class FlowablesContainerBase(Container):
    """A :class:`Container` that renders :class:`Flowable`\ s to a rectangular
    area on a page. The first flowable is rendered at the top of the container.
    The next flowable is rendered below the first one, and so on."""

    def __init__(self, name, parent, left=None, top=None, width=None, height=None,
                 right=None, bottom=None):
        self._self_cursor = Dimension(0)  # initialized at container's top edge
        self._cursor = DimensionAddition(self._self_cursor)
        super().__init__(name, parent, left=left, top=top, width=width,
                         height=height, right=right, bottom=bottom)

    def clear(self):
        super().clear()
        del self.children[:]
        self._self_cursor._value = 0  # initialized at container's top edge
        del self._cursor.addends[1:]

    @property
    def cursor(self):
        """Keeps track of where the next flowable is to be placed. As flowables
        are flowed into the container, the cursor moves down."""
        return float(self._cursor)

    @property
    def remaining_height(self):
        return self.height - self.cursor

    def render(self, rerender=False):
        raise NotImplementedError

    def advance(self, height, ignore_overflow=False):
        """Advance the cursor by `height`. If this would cause the cursor to
        point beyond the bottom of the container, an :class:`EndOfContainer`
        exception is raised."""
        if height <= self.remaining_height:
            self._self_cursor.grow(height)
        elif not ignore_overflow:
            raise EndOfContainer

    def check_overflow(self):
        if self.remaining_height < 0:
            raise ReflowRequired


class FlowablesContainer(FlowableTarget, FlowablesContainerBase):
    """A container that renders a predefined series of flowables."""

    def __init__(self, name, parent, left=None, top=None, width=None,
                 height=None, right=None, bottom=None):
        super().__init__(parent.document_part, name, parent, left=left, top=top,
                         width=width, height=height, right=right, bottom=bottom)

    def render(self, rerender=False):
        if not self.cursor:
            last_descender = None
            for flowable in self.flowables:
                height, last_descender = flowable.flow(self, last_descender)
        return iter(())   # no chains to yield


class ChainedContainer(FlowablesContainerBase):
    """A container that renders flowables from the :class:`Chain` it is part
    of."""

    def __init__(self, name, parent, chain, left=None, top=None, width=None, height=None,
                 right=None, bottom=None):
        super().__init__(name, parent, left=left, top=top, width=width,
                         height=height, right=right, bottom=bottom)
        chain.containers.append(self)
        self.chain = chain

    def render(self, rerender=False):
        last_descender = None
        if self.chain.render(self, rerender=rerender,
                             last_descender=last_descender):
            yield self.chain


class ExpandingContainer(FlowablesContainer):
    """A dynamically, vertically growing :class:`Container`."""

    def __init__(self, name, parent, left, top, width, right, bottom,
                 max_height=None, extra_space_below=0):
        """See :class:`ContainerBase` for information on the `parent`, `left`,
{'fraction_non_alphanumeric': 0.09201927205075609, 'fraction_numerical': 0.0007632495348948147, 'mean_word_length': 2.5768640163794574, 'pattern_counts': {'":': 0, '<': 9, '<?xml version=': 0, '>': 5, 'https://': 0, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 6, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '3549251', 'n_tokens_mistral': 5562, 'n_tokens_neox': 5334, 'n_words': 1961}