Source code for neoscore.western.octave_line

from __future__ import annotations

from typing import Optional, cast

from neoscore.core import neoscore
from neoscore.core.brush import Brush
from neoscore.core.directions import DirectionY
from neoscore.core.has_music_font import HasMusicFont
from neoscore.core.layout_controllers import NewLine
from neoscore.core.music_char import MusicChar
from neoscore.core.music_font import MusicFont
from neoscore.core.music_text import MusicText
from neoscore.core.path import Path
from neoscore.core.pen import Pen
from neoscore.core.pen_pattern import PenPattern
from neoscore.core.point import ORIGIN, Point, PointDef
from neoscore.core.positioned_object import PositionedObject
from neoscore.core.spanner import Spanner
from neoscore.core.units import Unit

_GLYPHS = {
    "15ma": "quindicesimaAlta",
    "8va": "ottavaAlta",
    "8vb": "ottavaBassaVb",
    "15mb": "quindicesimaBassaMb",
    "(": "octaveParensLeft",
    ")": "octaveParensRight",
}


[docs]class OctaveLine(Spanner, PositionedObject, HasMusicFont): """An octave indication with a dashed line. This octave line is purely cosmetic, and does not result in any automatic transpositions. At the starting position the octave is written in text, followed by a dashed line ending in a small vertical hook pointing toward the staff. If the spanner goes across line breaks, the octave text is repeated in parentheses at the line beginning. """
[docs] def __init__( self, start: PointDef, start_parent: PositionedObject, end_x: Unit, end_parent: Optional[PositionedObject] = None, indication: str = "8va", direction: DirectionY = DirectionY.DOWN, font: Optional[MusicFont] = None, ): """ Args: start: The starting point. start_parent: The parent for the starting position. If no font is given, this or one of its ancestors must implement :obj:`.HasMusicFont`. end_x: The spanner end x position. The y position will be automatically calculated to be horizontal. end_parent: An object either in a Staff or a staff itself. The root staff of this *must* be the same as the root staff of ``start_parent``. If omitted, the stop point is relative to the start point. indication: A valid octave indication. currently supported indications are: * '15ma' (two octaves higher) * '8va' (one octave higher) * '8vb' (one octave lower) * '15mb' (two octaves lower) direction: The direction the line's ending hook points. For lines above staves, this should be down, and vice versa for below. font: If provided, this overrides any font found in the ancestor chain. """ PositionedObject.__init__(self, start, start_parent) Spanner.__init__(self, end_x, end_parent or self) if font is None: font = HasMusicFont.find_music_font(start_parent) self._music_font = font self.direction = direction self.line_path = Path( ORIGIN, self, Brush.no_brush(), Pen( thickness=font.engraving_defaults["octaveLineThickness"], pattern=PenPattern.DASH, ), ) self.line_text = _OctaveLineText( ORIGIN, self, self.breakable_length, indication, font ) # Vertically center the path relative to the text text_rect = self.line_text.bounding_rect path_x = text_rect.width path_y = cast(Unit, text_rect.height / -2) self.line_path.pos = Point(path_x, path_y) # Drawn main line part self.line_path.line_to(self.end_pos.x, path_y, self.end_parent) self.line_path.line_to( self.end_pos.x, (path_y + font.unit(0.75 * self.direction.value)), self.end_parent, )
@property def music_font(self) -> MusicFont: return self._music_font
class _OctaveLineText(MusicText): """An octave text mark recurring at line beginnings with added parenthesis. This is a private class meant to be used exclusively in the context of an OctaveLine """ def __init__( self, pos: PointDef, parent: PositionedObject, length: Unit, indication: str, font: MusicFont, ): super().__init__( pos, parent, _GLYPHS[indication], font, background_brush=neoscore.background_brush, ) open_paren_char = MusicChar(self.music_font, _GLYPHS["("]) close_paren_char = MusicChar(self.music_font, _GLYPHS[")"]) self.parenthesized_text = ( open_paren_char.codepoint + self.text + close_paren_char.codepoint ) self._length = length @property def breakable_length(self) -> Unit: return self._length def render_before_break(self, pos: Point, flowable_line: NewLine, flowable_x: Unit): super().render_complete(pos, flowable_line) def render_spanning_continuation( self, pos: Point, flowable_line: NewLine, object_x: Unit ): super().render_complete(pos, flowable_line) def render_after_break(self, pos: Point, flowable_line: NewLine, object_x: Unit): super().render_complete(pos, flowable_line)