Source code for neoscore.core.page

from __future__ import annotations

from typing import TYPE_CHECKING

from backports.cached_property import cached_property

from neoscore.core.brush import Brush
from neoscore.core.color import Color
from neoscore.core.directions import DirectionX
from neoscore.core.paper import Paper
from neoscore.core.pen import Pen
from neoscore.core.pen_pattern import PenPattern
from neoscore.core.point import Point, PointDef
from neoscore.core.positioned_object import PositionedObject
from neoscore.core.rect import Rect
from neoscore.core.units import ZERO, Mm, Unit

if TYPE_CHECKING:
    from neoscore.core.document import Document


_PREVIEW_OUTLINE_COLOR = Color("#551155")
_PREVIEW_SHADOW_COLOR = Color(0, 0, 0, 80)


[docs]class Page(PositionedObject): """A document page. All :obj:`.PositionedObject`\ s will have a ``Page`` as their ancestor. All ``Page``\ s are direct children of the global document. ``Page`` objects are automatically created by ``Document`` and should not be manually created or manipulated. """ _neoscore_page_type_marker = True
[docs] def __init__( self, pos: PointDef, document: Document, index: int, page_side: DirectionX, paper: Paper, ): """ Args: pos: The position of the top left corner of this page's live area in canvas space. Note that this refers to the corner of the *live* area bounded by margins, not the actual paper corner. document: The global document. This is used as the ``Page`` object's parent. index: The index of this page. This should be the same index this page can be found at in :obj:`.Document.pages`. page_side: The left/right side the page lies on when printed. paper: The paper geometry for this page. """ super().__init__(pos, document) # noqa self._document = document self._index = index self._page_side = page_side self.paper = paper self._geometry_preview_created = False
@property def index(self): """The index of this page in its managing :obj:`.PageSupplier`.""" return self._index @property def page_side(self): """The left/right side the page lies on when printed. This determines which side the gutter should be placed on. """ return self._page_side @cached_property def bounding_rect(self) -> Rect: """The page bounding rect, positioned relative to the ``pos``.""" if self.page_side == DirectionX.RIGHT: # Page is on right side, apply gutter on left side rect_x = -(self.paper.gutter + self.paper.margin_left) else: rect_x = -self.paper.margin_left return Rect( rect_x, -self.paper.margin_top, self.paper.width, self.paper.height, ) @cached_property def document_space_bounding_rect(self) -> Rect: """The page bounding rect relative to the document.""" local_rect = self.bounding_rect return Rect( local_rect.x + self.x, local_rect.y + self.y, local_rect.width, local_rect.height, ) @cached_property def full_margin_left(self) -> Unit: """The left margin, including any gutter if the gutter is on the left.""" if self.page_side == DirectionX.RIGHT: return self.paper.margin_left + self.paper.gutter else: return self.paper.margin_left @cached_property def full_margin_right(self) -> Unit: """The right margin, including any gutter if the gutter is on the right.""" if self.page_side == DirectionX.RIGHT: return self.paper.margin_right else: return self.paper.margin_right + self.paper.gutter @property def left_margin_x(self) -> Unit: """The X position of the edge of the left margin. This is always ``ZERO``, given here as a convenience synonym. """ return ZERO @property def right_margin_x(self) -> Unit: """The X position of the edge of the right margin. This is always ``page.paper.live_width``, given here as a convenience synonym. """ return self.paper.live_width @property def top_margin_y(self) -> Unit: """The Y position of the edge of the top margin. This is always ``ZERO``, given here as a convenience synonym. """ return ZERO @property def bottom_margin_y(self) -> Unit: """The Y position of the edge of the top margin. This is always ``page.paper.live_height``, given here as a convenience synonym. """ return self.paper.live_height @property def center_x(self) -> Unit: """The X position of the center of the live page area. This is a convenience method for ``page.paper.live_width / 2``. """ return self.paper.live_width / 2
[docs] def create_geometry_preview(self, background_brush: Brush): """Create and render child objects which show the page geometry. This shouldn't be called directly; use the setting in :obj:`.neoscore.score` instead. This is useful for interactive views, but should typically not be called in PDF and image export contexts. """ # An implementation note on page geometry previews: # Ideally it would make the most sense for page geometry preview objects to be # generated at the same time the page is created. However, this goal conflicts # with our desire for preview visibility to be set at the top-level # `neoscore.show()` call, typically at the end of a user script. This allows # users to switch between preview mode and pdf/image export without modifying # their setup line. Achieving this requires some ugly workarounds, like passing # preview information to the top-level `document.render()` call, and here # ensuring this is only ever called once with a private flag. There are more # elegant ways to approach this issue, but for now this seems ok. # Import here to avoid cyclic import from neoscore.core.path import Path # Ensure this is only executed once if self._geometry_preview_created: return self._geometry_preview_created = True # To ensure these preview objects appear below all real document objects, we # need to attach all preview objects to the *first* page. This is necessary # because the first page is drawn first. We furthermore need to ensure page # preview objects are placed at the beginning of the first page's child list to # ensure they are drawn before other first-page children. Some strange hacks # here are needed to guarantee this behavior. parent = self if self.index == 0 else None bounding_rect = self.bounding_rect page_drop_shadow_rect = Path.rect( self.pos + Point(bounding_rect.x + Mm(1), bounding_rect.y + Mm(1)), parent, bounding_rect.width, bounding_rect.height, Brush(_PREVIEW_SHADOW_COLOR), Pen.no_pen(), ) page_preview_rect = Path.rect( self.pos + Point(bounding_rect.x, bounding_rect.y), parent, bounding_rect.width, bounding_rect.height, background_brush, pen=Pen(_PREVIEW_OUTLINE_COLOR), ) live_area_preview_rect = Path.rect( self.pos, parent, self.paper.live_width, self.paper.live_height, Brush.no_brush(), pen=Pen(_PREVIEW_OUTLINE_COLOR, pattern=PenPattern.DOT), ) # Terrible hack to ensure preview is always drawn below document contents preview_objs_parent = live_area_preview_rect.parent preview_objs_parent.children = ( preview_objs_parent.children[-3:] + preview_objs_parent.children[:-3] )