<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/.


from collections import OrderedDict

import re

from .attribute import (Bool, Integer, Function, Attribute,
                        AttributesDictionary, RuleSet, WithAttributes,
                        RuleSetFile, AttributeType, Var)
from .dimension import DimensionBase, CM, PT
from .document import Document, DocumentPart, Page, PageOrientation, PORTRAIT
from .element import create_destination
from .float import BackgroundImage, Image
from .language import Language, EN
from .layout import (Container, DownExpandingContainer, UpExpandingContainer,
                     FlowablesContainer, FootnoteContainer, ChainedContainer,
                     BACKGROUND, CONTENT, HEADER_FOOTER, CHAPTER_TITLE)
from .number import NumberFormat
from .paper import Paper, A4
from .paragraph import Paragraph
from .reference import (Field, SECTION_NUMBER, SECTION_TITLE, PAGE_NUMBER,
                        NUMBER_OF_PAGES, Reference, NUMBER, TITLE)
from .resource import Resource
from .text import StyledText, Tab
from .strings import StringField, Strings
from .structure import Header, Footer, SectionTitles
from .style import StyleSheet
from .stylesheets import sphinx
from .util import NamedDescriptor, WithNamedDescriptors, NotImplementedAttribute


__all__ = ['SimplePage', 'TitlePage', 'PageTemplate', 'TitlePageTemplate',
           'ContentsPartTemplate', 'FixedDocumentPartTemplate',
           'DocumentTemplate', 'DocumentOptions', 'Option']


class Option(Attribute):
    """Descriptor used to describe a document option"""


class DefaultOptionException(Exception):
    pass


class Templated(object):
    def get_option(self, option, document):
        return document.get_template_option(self.template_name, option)


class Template(AttributesDictionary, NamedDescriptor):
    def get_value(self, attribute, document):
        try:
            return super().get_value(attribute, document)
        except KeyError:
            bases = []
            if isinstance(self.base, str):
                iter = document._find_templates(self.base)
                try:
                    bases.extend(iter)
                except KeyError:
                    raise ValueError("Could not find the base template '{}' "
                                     "for the '{}' page template."
                                     .format(self.base, self.name))
            elif self.base is not None:
                bases.append(self.base)
            for base_template in bases:
                return base_template.get_value(attribute, document)
            else:
                raise KeyError


class PageTemplateBase(Template):
    page_size = Option(Paper, A4, 'The format of the pages in the document')
    page_orientation = Option(PageOrientation, PORTRAIT,
                              'The orientation of pages in the document')
    left_margin = Option(DimensionBase, 3*CM, 'The margin size on the left of '
                                              'the page')
    right_margin = Option(DimensionBase, 3*CM, 'The margin size on the right '
                                               'of the page')
    top_margin = Option(DimensionBase, 3*CM, 'The margin size at the top of '
                                              'the page')
    bottom_margin = Option(DimensionBase, 3*CM, 'The margin size at the bottom '
                                                'of the page')
    background = Option(BackgroundImage, None, 'An image to place in the '
                                               'background of the page')
    after_break_background = Option(BackgroundImage, None, 'An image to place '
                                    'in the background after a page break')

    def page(self, document_part, page_number, chain, after_break, **kwargs):
        raise NotImplementedError


def chapter_title_flowables(section_id):
    yield Paragraph(StringField(SectionTitles, 'chapter'))
    yield Paragraph(Reference(section_id, NUMBER))
    yield Paragraph(Reference(section_id, TITLE))


class PageTemplate(PageTemplateBase):
    header_footer_distance = Option(DimensionBase, 14*PT, 'Distance of the '
                                    'header and footer to the content area')
    columns = Option(Integer, 1, 'The number of columns for the body text')
    column_spacing = Option(DimensionBase, 1*CM, 'The spacing between columns')
    header_text = Option(StyledText, Field(SECTION_NUMBER(1))
                         + ' ' + Field(SECTION_TITLE(1)),
                         'The text to place in the page header')
    footer_text = Option(StyledText, Tab() + Field(PAGE_NUMBER)
                         + '/' + Field(NUMBER_OF_PAGES),
                         'The text to place in the page footer')
    chapter_header_text = Option(StyledText, None, 'The text to place in the '
                                 'header on a page that starts a new chapter')
    chapter_footer_text = Option(StyledText, None, 'The text to place in the '
                                 'footer on a page that starts a new chapter')
    chapter_title_flowables = Option(Function, chapter_title_flowables,
                                     'Generator that yields the flowables to '
                                     'represent the chapter title')
    chapter_title_height = Option(DimensionBase, 150*PT, 'The height of the '
                                  'container holding the chapter title')

    def page(self, document_part, page_number, chain, after_break, **kwargs):
        return SimplePage(document_part, self.name, page_number, chain,
                          after_break, **kwargs)


class PageBase(Page, Templated):
    def __init__(self, document_part, template_name, page_number, after_break):
        self.template_name = template_name
        document = document_part.document
        paper = self.get_option('page_size', document)
        orientation = self.get_option('page_orientation', document)
        super().__init__(document_part, page_number, paper, orientation)
        self.left_margin = self.get_option('left_margin', document)
        self.right_margin = self.get_option('right_margin', document)
        self.top_margin = self.get_option('top_margin', document)
        self.bottom_margin = self.get_option('bottom_margin', document)
        self.body_width = self.width - (self.left_margin + self.right_margin)
        self.body_height = self.height - (self.top_margin + self.bottom_margin)
        background = self.get_option('background', document)
        after_break_background = self.get_option('after_break_background', document)
        bg = after_break_background or background
        if after_break and bg:
            self.background = FlowablesContainer('background', BACKGROUND, self)
            self.background << bg
        elif background:
            self.background = FlowablesContainer('background', BACKGROUND, self)
            self.background << background


class SimplePage(PageBase):
    def __init__(self, document_part, template_name, page_number, chain,
                 new_chapter):
        super().__init__(document_part, template_name, page_number, new_chapter)
        document = document_part.document
        num_cols = self.get_option('columns', document)
        header_footer_distance = self.get_option('header_footer_distance', document)
        column_spacing = self.get_option('column_spacing', document)
        total_column_spacing = column_spacing * (num_cols - 1)
        column_width = (self.body_width - total_column_spacing) / num_cols
        self.body = Container('body', self, self.left_margin, self.top_margin,
                              self.body_width, self.body_height)
        footnote_space = FootnoteContainer('footnotes', self.body, 0*PT,
                                           self.body.height)
        float_space = DownExpandingContainer('floats', CONTENT, self.body, 0, 0,
                                             max_height=self.body_height / 2)
        self.body.float_space = float_space
        if self.get_option('chapter_title_flowables', document) and new_chapter:
            height = self.get_option('chapter_title_height', document)
            self.chapter_title = FlowablesContainer('chapter title',
                                                    CHAPTER_TITLE, self.body,
                                                    0, 0, height=height)
            column_top = self.chapter_title.bottom
            header = self.get_option('chapter_header_text', document)
            footer = self.get_option('chapter_footer_text', document)
        else:
            self.chapter_title = None
            column_top = float_space.bottom
            header = self.get_option('header_text', document)
            footer = self.get_option('footer_text', document)
        self.columns = [ChainedContainer('column{}'.format(i + 1), CONTENT,
                                         self.body, chain,
                                         left=i * (column_width
                                                   + column_spacing),
                                         top=column_top, width=column_width,
                                         bottom=footnote_space.top)
                        for i in range(num_cols)]
        for column in self.columns:
            column._footnote_space = footnote_space

        if header:
            header_bottom = self.body.top - header_footer_distance
            self.header = UpExpandingContainer('header', HEADER_FOOTER, self,
                                               left=self.left_margin,
                                               bottom=header_bottom,
                                               width=self.body_width)
            self.header.append_flowable(Header(header))
        if footer:
            footer_vpos = self.body.bottom + header_footer_distance
            self.footer = DownExpandingContainer('footer', HEADER_FOOTER, self,
                                                 left=self.left_margin,
                                                 top=footer_vpos,
                                                 width=self.body_width)
            self.footer.append_flowable(Footer(footer))

    def create_chapter_title(self, heading):
        descender = None
        section_id = heading.section.get_id(self.document)
        create_destination(heading.section, self.chapter_title, False)
        create_destination(heading, self.chapter_title, False)
        chapter_title_flowables = self.get_option('chapter_title_flowables',
                                                  self.document)
        for flowable in chapter_title_flowables(section_id):
            _, _, descender = flowable.flow(self.chapter_title, descender)


class TitlePageTemplate(PageTemplateBase):
    show_date = Option(Bool, True, "Show or hide the document's date")
    show_author = Option(Bool, True, "Show or hide the document's author")
    extra = Option(StyledText, None, 'Extra text to include on the title '
                                     'page below the title')

    def page(self, document_part, page_number, chain, after_break, **kwargs):
        return TitlePage(document_part, self.name, page_number, after_break)


class TitlePage(PageBase):
    def __init__(self, document_part, template_name, page_number, after_break):
        super().__init__(document_part, template_name, page_number,
                         after_break)
        document = self.document
        metadata = document.metadata
        self.title = DownExpandingContainer('title', CONTENT, self,
                                            self.left_margin, self.top_margin,
                                            self.body_width)
        if 'logo' in metadata:
            self.title << Image(metadata['logo'],
                                style='title page logo')
        self.title << Paragraph(metadata['title'],
                                style='title page title')
        if 'subtitle' in metadata:
            self.title << Paragraph(metadata['subtitle'],
                                    style='title page subtitle')
        if 'author' in metadata and self.get_option('show_author', document):
            self.title << Paragraph(metadata['author'],
                                    style='title page author')
        try:
            abstract_location = self.document.get_option('abstract_location')
            if 'abstract' in metadata and abstract_location == TITLE:
                self.title << metadata['abstract']
        except KeyError:
            pass
        if self.get_option('show_date', document):
            date = metadata['date']
            try:
                self.title << Paragraph(date.strftime('%B %d, %Y'),
                                        style='title page date')
            except AttributeError:
                self.title << Paragraph(date, style='title page date')
        extra = self.get_option('extra', document)
        if extra:
            self.title << Paragraph(extra, style='title page extra')


class DocumentPartTemplate(Template):
    """A template that produces a document part, given a set of flowables,
    and page templates. The latter are looked up in the
    :class:`TemplateConfiguration`.

    Args:
        name (:class:`str`): a descriptive name for this document part template
    """

    page_number_format = Option(NumberFormat, NUMBER, "The number for page "
                                "numbers in this document part. If it is "
                                "different from the preceding part's number "
                                "format, numbering restarts at 1")

    skip_if_no_flowables = True

    @property
    def doc_repr(self):
        doc = ('**{}** (:class:`{}.{}`)\n\n'\<commit_msg>Reduce code duplication by introducing get_option()<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/.


import re

from collections import OrderedDict
from functools import partial

from .attribute import (Bool, Integer, Function, Attribute,
                        AttributesDictionary, RuleSet, WithAttributes,
                        RuleSetFile, AttributeType, Var)
from .dimension import DimensionBase, CM, PT
from .document import Document, DocumentPart, Page, PageOrientation, PORTRAIT
from .element import create_destination
from .float import BackgroundImage, Image
from .language import Language, EN
from .layout import (Container, DownExpandingContainer, UpExpandingContainer,
                     FlowablesContainer, FootnoteContainer, ChainedContainer,
                     BACKGROUND, CONTENT, HEADER_FOOTER, CHAPTER_TITLE)
from .number import NumberFormat
from .paper import Paper, A4
from .paragraph import Paragraph
from .reference import (Field, SECTION_NUMBER, SECTION_TITLE, PAGE_NUMBER,
                        NUMBER_OF_PAGES, Reference, NUMBER, TITLE)
from .resource import Resource
from .text import StyledText, Tab
from .strings import StringField, Strings
from .structure import Header, Footer, SectionTitles
from .style import StyleSheet
from .stylesheets import sphinx
from .util import NamedDescriptor, WithNamedDescriptors, NotImplementedAttribute


__all__ = ['SimplePage', 'TitlePage', 'PageTemplate', 'TitlePageTemplate',
           'ContentsPartTemplate', 'FixedDocumentPartTemplate',
           'DocumentTemplate', 'DocumentOptions', 'Option']


class Option(Attribute):
    """Descriptor used to describe a document option"""


class DefaultOptionException(Exception):
    pass


class Templated(object):
    def get_option(self, option, document):
        return document.get_template_option(self.template_name, option)


class Template(AttributesDictionary, NamedDescriptor):
    def get_value(self, attribute, document):
        try:
            return super().get_value(attribute, document)
        except KeyError:
            bases = []
            if isinstance(self.base, str):
                iter = document._find_templates(self.base)
                try:
                    bases.extend(iter)
                except KeyError:
                    raise ValueError("Could not find the base template '{}' "
                                     "for the '{}' page template."
                                     .format(self.base, self.name))
            elif self.base is not None:
                bases.append(self.base)
            for base_template in bases:
                return base_template.get_value(attribute, document)
            else:
                raise KeyError


class PageTemplateBase(Template):
    page_size = Option(Paper, A4, 'The format of the pages in the document')
    page_orientation = Option(PageOrientation, PORTRAIT,
                              'The orientation of pages in the document')
    left_margin = Option(DimensionBase, 3*CM, 'The margin size on the left of '
                                              'the page')
    right_margin = Option(DimensionBase, 3*CM, 'The margin size on the right '
                                               'of the page')
    top_margin = Option(DimensionBase, 3*CM, 'The margin size at the top of '
                                              'the page')
    bottom_margin = Option(DimensionBase, 3*CM, 'The margin size at the bottom '
                                                'of the page')
    background = Option(BackgroundImage, None, 'An image to place in the '
                                               'background of the page')
    after_break_background = Option(BackgroundImage, None, 'An image to place '
                                    'in the background after a page break')

    def page(self, document_part, page_number, chain, after_break, **kwargs):
        raise NotImplementedError


def chapter_title_flowables(section_id):
    yield Paragraph(StringField(SectionTitles, 'chapter'))
    yield Paragraph(Reference(section_id, NUMBER))
    yield Paragraph(Reference(section_id, TITLE))


class PageTemplate(PageTemplateBase):
    header_footer_distance = Option(DimensionBase, 14*PT, 'Distance of the '
                                    'header and footer to the content area')
    columns = Option(Integer, 1, 'The number of columns for the body text')
    column_spacing = Option(DimensionBase, 1*CM, 'The spacing between columns')
    header_text = Option(StyledText, Field(SECTION_NUMBER(1))
                         + ' ' + Field(SECTION_TITLE(1)),
                         'The text to place in the page header')
    footer_text = Option(StyledText, Tab() + Field(PAGE_NUMBER)
                         + '/' + Field(NUMBER_OF_PAGES),
                         'The text to place in the page footer')
    chapter_header_text = Option(StyledText, None, 'The text to place in the '
                                 'header on a page that starts a new chapter')
    chapter_footer_text = Option(StyledText, None, 'The text to place in the '
                                 'footer on a page that starts a new chapter')
    chapter_title_flowables = Option(Function, chapter_title_flowables,
                                     'Generator that yields the flowables to '
                                     'represent the chapter title')
    chapter_title_height = Option(DimensionBase, 150*PT, 'The height of the '
                                  'container holding the chapter title')

    def page(self, document_part, page_number, chain, after_break, **kwargs):
        return SimplePage(document_part, self.name, page_number, chain,
                          after_break, **kwargs)


class PageBase(Page, Templated):
    def __init__(self, document_part, template_name, page_number, after_break):
        get_option = partial(self.get_option, document=document_part.document)
        self.template_name = template_name
        paper = get_option('page_size')
        orientation = get_option('page_orientation')
        super().__init__(document_part, page_number, paper, orientation)
        self.left_margin = get_option('left_margin')
        self.right_margin = get_option('right_margin')
        self.top_margin = get_option('top_margin')
        self.bottom_margin = get_option('bottom_margin')
        self.body_width = self.width - (self.left_margin + self.right_margin)
        self.body_height = self.height - (self.top_margin + self.bottom_margin)
        background = get_option('background')
        after_break_background = get_option('after_break_background')
        bg = after_break_background or background
        if after_break and bg:
            self.background = FlowablesContainer('background', BACKGROUND, self)
            self.background << bg
        elif background:
            self.background = FlowablesContainer('background', BACKGROUND, self)
            self.background << background


class SimplePage(PageBase):
    def __init__(self, document_part, template_name, page_number, chain,
                 new_chapter):
        super().__init__(document_part, template_name, page_number, new_chapter)
        get_option = partial(self.get_option, document=document_part.document)
        num_cols = get_option('columns')
        header_footer_distance = get_option('header_footer_distance')
        column_spacing = get_option('column_spacing')
        total_column_spacing = column_spacing * (num_cols - 1)
        column_width = (self.body_width - total_column_spacing) / num_cols
        self.body = Container('body', self, self.left_margin, self.top_margin,
                              self.body_width, self.body_height)
        footnote_space = FootnoteContainer('footnotes', self.body, 0*PT,
                                           self.body.height)
        float_space = DownExpandingContainer('floats', CONTENT, self.body, 0, 0,
                                             max_height=self.body_height / 2)
        self.body.float_space = float_space
        if get_option('chapter_title_flowables') and new_chapter:
            height = get_option('chapter_title_height')
            self.chapter_title = FlowablesContainer('chapter title',
                                                    CHAPTER_TITLE, self.body,
                                                    0, 0, height=height)
            column_top = self.chapter_title.bottom
            header = get_option('chapter_header_text')
            footer = get_option('chapter_footer_text')
        else:
            self.chapter_title = None
            column_top = float_space.bottom
            header = get_option('header_text')
            footer = get_option('footer_text')
        self.columns = [ChainedContainer('column{}'.format(i + 1), CONTENT,
                                         self.body, chain,
                                         left=i * (column_width
                                                   + column_spacing),
                                         top=column_top, width=column_width,
                                         bottom=footnote_space.top)
                        for i in range(num_cols)]
        for column in self.columns:
            column._footnote_space = footnote_space

        if header:
            header_bottom = self.body.top - header_footer_distance
            self.header = UpExpandingContainer('header', HEADER_FOOTER, self,
                                               left=self.left_margin,
                                               bottom=header_bottom,
                                               width=self.body_width)
            self.header.append_flowable(Header(header))
        if footer:
            footer_vpos = self.body.bottom + header_footer_distance
            self.footer = DownExpandingContainer('footer', HEADER_FOOTER, self,
                                                 left=self.left_margin,
                                                 top=footer_vpos,
                                                 width=self.body_width)
            self.footer.append_flowable(Footer(footer))

    def create_chapter_title(self, heading):
        descender = None
        section_id = heading.section.get_id(self.document)
        create_destination(heading.section, self.chapter_title, False)
        create_destination(heading, self.chapter_title, False)
        chapter_title_flowables = self.get_option('chapter_title_flowables',
                                                  self.document)
        for flowable in chapter_title_flowables(section_id):
            _, _, descender = flowable.flow(self.chapter_title, descender)


class TitlePageTemplate(PageTemplateBase):
    show_date = Option(Bool, True, "Show or hide the document's date")
    show_author = Option(Bool, True, "Show or hide the document's author")
    extra = Option(StyledText, None, 'Extra text to include on the title '
                                     'page below the title')

    def page(self, document_part, page_number, chain, after_break, **kwargs):
        return TitlePage(document_part, self.name, page_number, after_break)


class TitlePage(PageBase):
    def __init__(self, document_part, template_name, page_number, after_break):
        super().__init__(document_part, template_name, page_number,
                         after_break)
        get_option = partial(self.get_option, document=self.document)
        metadata = self.document.metadata
        self.title = DownExpandingContainer('title', CONTENT, self,
                                            self.left_margin, self.top_margin,
                                            self.body_width)
        if 'logo' in metadata:
            self.title << Image(metadata['logo'],
                                style='title page logo')
        self.title << Paragraph(metadata['title'],
                                style='title page title')
        if 'subtitle' in metadata:
            self.title << Paragraph(metadata['subtitle'],
                                    style='title page subtitle')
        if 'author' in metadata and get_option('show_author'):
            self.title << Paragraph(metadata['author'],
                                    style='title page author')
        try:
            abstract_location = self.document.get_option('abstract_location')
            if 'abstract' in metadata and abstract_location == TITLE:
                self.title << metadata['abstract']
        except KeyError:
            pass
        if get_option('show_date'):
            date = metadata['date']
            try:
                self.title << Paragraph(date.strftime('%B %d, %Y'),
                                        style='title page date')
            except AttributeError:
                self.title << Paragraph(date, style='title page date')
        extra = get_option('extra')
        if extra:
            self.title << Paragraph(extra, style='title page extra')


class DocumentPartTemplate(Template):
    """A template that produces a document part, given a set of flowables,
    and page templates. The latter are looked up in the
    :class:`TemplateConfiguration`.

    Args:
        name (:class:`str`): a descriptive name for this document part template
    """

    page_number_format = Option(NumberFormat, NUMBER, "The number for page "
                                "numbers in this document part. If it is "
                                "different from the preceding part's number "
                                "format, numbering restarts at 1")

    skip_if_no_flowables = True

    @property
    def doc_repr(self):
        doc = ('**{}** (:class:`{}.{}`)\n\n'\
{'fraction_non_alphanumeric': 0.08143183114660582, 'fraction_numerical': 0.001782658300057045, 'mean_word_length': 2.025455722144321, 'pattern_counts': {'":': 0, '<': 45, '<?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': 122, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '1963731', 'n_tokens_mistral': 6981, 'n_tokens_neox': 6661, 'n_words': 1871}