from typing import List, Iterator, Tuple, Dict, Optional, Union, Any from enum import Enum import os import tempfile import urllib.request import urllib.parse from PIL import Image as PILImage from .inline import Word, FormattedSpan from ..style import Font, FontWeight, FontStyle, TextDecoration from ..core import Hierarchical, Styleable, FontRegistry class BlockType(Enum): """Enumeration of different block types for classification purposes""" PARAGRAPH = 1 HEADING = 2 QUOTE = 3 CODE_BLOCK = 4 LIST = 5 LIST_ITEM = 6 TABLE = 7 TABLE_ROW = 8 TABLE_CELL = 9 HORIZONTAL_RULE = 10 LINE_BREAK = 11 IMAGE = 12 PAGE_BREAK = 13 class Block(Hierarchical): """ Base class for all block-level elements. Block elements typically represent visual blocks of content that stack vertically. Uses Hierarchical mixin for parent-child relationship management. """ def __init__(self, block_type: BlockType): """ Initialize a block element. Args: block_type: The type of block this element represents """ super().__init__() self._block_type = block_type @property def block_type(self) -> BlockType: """Get the type of this block element""" return self._block_type class Paragraph(Styleable, FontRegistry, Block): """ A paragraph is a block-level element that contains a sequence of words. Uses Styleable mixin for style property management. Uses FontRegistry mixin for font caching with parent delegation. """ def __init__(self, style=None): """ Initialize an empty paragraph Args: style: Optional default style for words in this paragraph """ super().__init__(style=style, block_type=BlockType.PARAGRAPH) self._words: List[Word] = [] self._spans: List[FormattedSpan] = [] @classmethod def create_and_add_to(cls, container, style=None) -> 'Paragraph': """ Create a new Paragraph and add it to a container, inheriting style from the container if not explicitly provided. Args: container: The container to add the paragraph to (must have add_block method and style property) style: Optional style override. If None, inherits from container Returns: The newly created Paragraph object Raises: AttributeError: If the container doesn't have the required add_block method """ # Inherit style from container if not provided if style is None and hasattr(container, 'style'): style = container.style elif style is None and hasattr(container, 'default_style'): style = container.default_style # Create the new paragraph paragraph = cls(style) # Add the paragraph to the container if hasattr(container, 'add_block'): container.add_block(paragraph) else: raise AttributeError(f"Container {type(container).__name__} must have an 'add_block' method") return paragraph def add_word(self, word: Word): """ Add a word to this paragraph. Args: word: The Word object to add """ self._words.append(word) def create_word(self, text: str, style=None, background=None) -> Word: """ Create a new word and add it to this paragraph, inheriting paragraph's style if not specified. This is a convenience method that uses Word.create_and_add_to() to create words that automatically inherit styling from this paragraph. Args: text: The text content of the word style: Optional Font style override. If None, attempts to inherit from paragraph background: Optional background color override Returns: The newly created Word object """ return Word.create_and_add_to(text, self, style, background) def add_span(self, span: FormattedSpan): """ Add a formatted span to this paragraph. Args: span: The FormattedSpan object to add """ self._spans.append(span) def create_span(self, style=None, background=None) -> FormattedSpan: """ Create a new formatted span with inherited style. Args: style: Optional Font style override. If None, inherits from paragraph background: Optional background color override Returns: The newly created FormattedSpan object """ return FormattedSpan.create_and_add_to(self, style, background) @property def words(self) -> List[Word]: """Get the list of words in this paragraph""" return self._words def words_iter(self) -> Iterator[Tuple[int, Word]]: """ Iterate over the words in this paragraph. Yields: Tuples of (index, word) for each word in the paragraph """ for i, word in enumerate(self._words): yield i, word def spans(self) -> Iterator[FormattedSpan]: """ Iterate over the formatted spans in this paragraph. Yields: Each FormattedSpan in the paragraph """ for span in self._spans: yield span @property def word_count(self) -> int: """Get the number of words in this paragraph""" return len(self._words) def __len__(self): return self.word_count # get_or_create_font() is provided by FontRegistry mixin class HeadingLevel(Enum): """Enumeration representing HTML heading levels (h1-h6)""" H1 = 1 H2 = 2 H3 = 3 H4 = 4 H5 = 5 H6 = 6 class Heading(Paragraph): """ A heading element (h1, h2, h3, etc.) that contains text with a specific heading level. Headings inherit from Paragraph as they contain words but have additional properties. """ def __init__(self, level: HeadingLevel = HeadingLevel.H1, style=None): """ Initialize a heading element. Args: level: The heading level (h1-h6) style: Optional default style for words in this heading """ super().__init__(style) self._block_type = BlockType.HEADING self._level = level @classmethod def create_and_add_to(cls, container, level: HeadingLevel = HeadingLevel.H1, style=None) -> 'Heading': """ Create a new Heading and add it to a container, inheriting style from the container if not explicitly provided. Args: container: The container to add the heading to (must have add_block method and style property) level: The heading level (h1-h6) style: Optional style override. If None, inherits from container Returns: The newly created Heading object Raises: AttributeError: If the container doesn't have the required add_block method """ # Inherit style from container if not provided if style is None and hasattr(container, 'style'): style = container.style elif style is None and hasattr(container, 'default_style'): style = container.default_style # Create the new heading heading = cls(level, style) # Add the heading to the container if hasattr(container, 'add_block'): container.add_block(heading) else: raise AttributeError(f"Container {type(container).__name__} must have an 'add_block' method") return heading @property def level(self) -> HeadingLevel: """Get the heading level""" return self._level @level.setter def level(self, level: HeadingLevel): """Set the heading level""" self._level = level class Quote(Block): """ A blockquote element that can contain other block elements. """ def __init__(self, style=None): """ Initialize an empty blockquote Args: style: Optional default style for child blocks """ super().__init__(BlockType.QUOTE) self._blocks: List[Block] = [] self._style = style @classmethod def create_and_add_to(cls, container, style=None) -> 'Quote': """ Create a new Quote and add it to a container, inheriting style from the container if not explicitly provided. Args: container: The container to add the quote to (must have add_block method and style property) style: Optional style override. If None, inherits from container Returns: The newly created Quote object Raises: AttributeError: If the container doesn't have the required add_block method """ # Inherit style from container if not provided if style is None and hasattr(container, 'style'): style = container.style elif style is None and hasattr(container, 'default_style'): style = container.default_style # Create the new quote quote = cls(style) # Add the quote to the container if hasattr(container, 'add_block'): container.add_block(quote) else: raise AttributeError(f"Container {type(container).__name__} must have an 'add_block' method") return quote @property def style(self): """Get the default style for this quote""" return self._style @style.setter def style(self, style): """Set the default style for this quote""" self._style = style def add_block(self, block: Block): """ Add a block element to this quote. Args: block: The Block object to add """ self._blocks.append(block) block.parent = self def create_paragraph(self, style=None) -> Paragraph: """ Create a new paragraph and add it to this quote. Args: style: Optional style override. If None, inherits from quote Returns: The newly created Paragraph object """ return Paragraph.create_and_add_to(self, style) def create_heading(self, level: HeadingLevel = HeadingLevel.H1, style=None) -> Heading: """ Create a new heading and add it to this quote. Args: level: The heading level style: Optional style override. If None, inherits from quote Returns: The newly created Heading object """ return Heading.create_and_add_to(self, level, style) def blocks(self) -> Iterator[Block]: """ Iterate over the blocks in this quote. Yields: Each Block in the quote """ for block in self._blocks: yield block class CodeBlock(Block): """ A code block element containing pre-formatted text with syntax highlighting. """ def __init__(self, language: str = ""): """ Initialize a code block. Args: language: The programming language for syntax highlighting """ super().__init__(BlockType.CODE_BLOCK) self._language = language self._lines: List[str] = [] @classmethod def create_and_add_to(cls, container, language: str = "") -> 'CodeBlock': """ Create a new CodeBlock and add it to a container. Args: container: The container to add the code block to (must have add_block method) language: The programming language for syntax highlighting Returns: The newly created CodeBlock object Raises: AttributeError: If the container doesn't have the required add_block method """ # Create the new code block code_block = cls(language) # Add the code block to the container if hasattr(container, 'add_block'): container.add_block(code_block) else: raise AttributeError(f"Container {type(container).__name__} must have an 'add_block' method") return code_block @property def language(self) -> str: """Get the programming language""" return self._language @language.setter def language(self, language: str): """Set the programming language""" self._language = language def add_line(self, line: str): """ Add a line of code to this code block. Args: line: The line of code to add """ self._lines.append(line) def lines(self) -> Iterator[Tuple[int, str]]: """ Iterate over the lines in this code block. Yields: Tuples of (line_number, line_text) for each line """ for i, line in enumerate(self._lines): yield i, line @property def line_count(self) -> int: """Get the number of lines in this code block""" return len(self._lines) class ListStyle(Enum): """Enumeration of list styles""" UNORDERED = 1 #