Source code for pdfje.page

from __future__ import annotations

import abc
from dataclasses import dataclass
from itertools import chain
from operator import methodcaller
from typing import Iterable, Iterator, Literal, Sequence, final

from . import atoms
from .atoms import OBJ_ID_PAGETREE, OBJ_ID_RESOURCES
from .common import (
    XY,
    Sides,
    SidesLike,
    Streamable,
    add_slots,
    flatten,
    setattr_frozen,
)
from .resources import Resources
from .style import StyleFull
from .units import A4, Pt, inch

Rotation = Literal[0, 90, 180, 270]


[docs] class Drawing(abc.ABC): """Base class for all drawing operations wich can be put on a :class:`~pdfje.Page`.""" __slots__ = () @abc.abstractmethod def render(self, r: Resources, s: StyleFull, /) -> Streamable: ...
[docs] @final @add_slots @dataclass(frozen=True, init=False) class Column: """A column to lay out block elements in. Parameters ---------- origin The bottom left corner of the column. Can be parsed from a 2-tuple. width The width of the column. Must be larger than 0. height The height of the column. """ origin: XY width: Pt height: Pt def __init__( self, origin: XY | tuple[Pt, Pt], width: Pt, height: Pt ) -> None: setattr_frozen(self, "origin", XY.parse(origin)) setattr_frozen(self, "width", width) setattr_frozen(self, "height", height) assert self.width > 0
@add_slots @dataclass(frozen=True) class RenderedPage: rotate: Rotation size: XY stream: Streamable def to_atoms(self, i: atoms.ObjectID) -> Iterable[atoms.Object]: yield i, atoms.Dictionary( (b"Type", atoms.Name(b"Page")), (b"Parent", atoms.Ref(OBJ_ID_PAGETREE)), (b"MediaBox", atoms.Array(map(atoms.Real, [0, 0, *self.size]))), (b"Contents", atoms.Ref(i + 1)), (b"Resources", atoms.Ref(OBJ_ID_RESOURCES)), (b"Rotate", atoms.Int(self.rotate)), ) yield i + 1, atoms.Stream(self.stream)
[docs] @final @add_slots @dataclass(frozen=True, init=False) class Page: """A single page within a document. Contains drawings at given positions. Example ------- .. code-block:: python from pdfje import Page, Line, Rect, Text, A5 title_page = Page([ Text((100, 200), "My awesome story"), Line((100, 100), (200, 100)), Rect((50, 50), width=200, height=300), ], size=A5) Parameters ---------- content The drawings to render on the page. size The size of the page in points. Common page sizes are available as constants: .. code-block:: python from pdfje.units import Page, A4, A5, A6, letter, legal, tabloid rotate The rotation of the page in degrees. margin The margin around the page in points, used for layout. Can be a single value, or a 2, 3 or 4-tuple following the CSS shorthand convention. see https://www.w3schools.com/css/css_margin.asp columns The columns to use for laying out the content. If not given, the content is laid out in a single column based on the page size and margin. """ content: Iterable[Drawing] size: XY rotate: Rotation columns: Sequence[Column] def __init__( self, content: Iterable[Drawing] = (), size: XY | tuple[Pt, Pt] = A4, rotate: Rotation = 0, margin: SidesLike = Sides.parse(inch(1)), columns: Sequence[Column] = (), ) -> None: size = XY.parse(size) setattr_frozen(self, "content", content) setattr_frozen(self, "rotate", rotate) setattr_frozen(self, "columns", columns or [_column(size, margin)]) setattr_frozen(self, "size", size)
[docs] def add(self, d: Drawing, /) -> Page: """Create a new page with the given drawing added Parameters ---------- d The drawing to add to the page """ return Page( (*self.content, d), self.size, self.rotate, columns=self.columns )
def render( self, r: Resources, s: StyleFull, pnum: int, / ) -> Iterator[RenderedPage]: yield RenderedPage( self.rotate, self.size, flatten(map(methodcaller("render", r, s), self.content)), ) def fill( self, r: Resources, s: StyleFull, extra: Iterable[bytes] ) -> RenderedPage: return RenderedPage( self.rotate, self.size, chain( flatten(map(methodcaller("render", r, s), self.content)), extra, ), )
def _column(page: XY, margin: SidesLike) -> Column: top, right, bottom, left = Sides.parse(margin) return Column( XY(left, bottom), page.x - left - right, page.y - top - bottom )