Exemplo n.º 1
0
def draw_letter(
    letter,
    mode,
    fixed_width=None,
    show_template=False,
    show_bounds=False,
    fuse=True,
):
    """
    Draw the given letter and return a Paper.

    The letter is located centered on x=0, and with y=0 as the
    character baseline.

    If `fixed_width` is specified, use that for the paper width.
    """
    if DEBUG_OUTPUT:
        print(str(letter), file=sys.stderr)

    try:
        character_paper = letter.draw_character(mode, fuse=fuse)
    except Exception:
        if DEBUG_OUTPUT:
            traceback.print_exc()
            # Return an error pattern.
            pen = Pen()
            pen.fill_mode()
            pen.square(1)
            character_paper = pen.paper
        else:
            raise

    if fixed_width is not None:
        bounds = character_paper.bounds()
        bounds.left = -fixed_width / 2
        bounds.right = +fixed_width / 2
        character_paper.override_bounds(bounds)

    template_paper = Paper()
    if show_template:
        template_paper = draw_template_path()
    else:
        template_paper = Paper()

    letter_paper = Paper()
    letter_paper.merge(template_paper)
    letter_paper.merge(character_paper)

    # Set proper bounds for typesetting. Use the character bounds as our basis
    # so the template doesn't increase the size.
    bounds = character_paper.bounds()
    letter_paper.override_bounds(bounds)

    if show_bounds:
        pen = Pen()
        pen.fill_mode('#aaa')
        bounds.draw(pen)
        letter_paper.merge_under(pen.paper)

    return letter_paper
Exemplo n.º 2
0
    def draw_character(self, mode, **kwargs):
        side_ending = self.side_ending_class(
            self,
            self.side_flipped,
        )

        paper = Paper()

        pen = Pen()
        pen.set_mode(mode)
        pen.move_to((0, TOP - mode.width / 2))
        pen.turn_to(0)
        pen.line_forward(2.0)
        pen.last_segment().start_cap = stub_cap

        side_ending.draw(pen)
        paper.merge(pen.paper)

        bounds = paper.bounds()
        bounds.top = OVER
        bounds.bottom = MIDDLE
        bounds.left = 0
        paper.override_bounds(bounds)

        return paper
Exemplo n.º 3
0
def test_override_bounds_copy():
    # Get the bounds of a Paper, modify them, then set them back changed.
    paper = Paper()
    paper.override_bounds(0, 0, 1, 1)

    bounds = paper.bounds()
    bounds.right = 5

    assert_equal(paper.bounds(), Bounds(0, 0, 1, 1))
    paper.override_bounds(bounds)
    assert_equal(paper.bounds(), Bounds(0, 0, 5, 1))

    # This works on non-overridden Papers as well.
    paper = Paper()

    p = Pen()
    p.fill_mode()
    p.move_to((0.5, 0.5))
    p.circle(0.5)

    bounds = p.paper.bounds()
    bounds.right = 5

    assert_equal(p.paper.bounds(), Bounds(0, 0, 1, 1))
    p.paper.override_bounds(bounds)
    assert_equal(p.paper.bounds(), Bounds(0, 0, 5, 1))
Exemplo n.º 4
0
def test_translate_override_bounds():
    # Translate a paper that has overridden bounds. The bounds update as well.
    paper = Paper()
    paper.override_bounds(0, 0, 1, 1)
    paper.translate((3, 4))
    assert_equal(
        paper.bounds(),
        Bounds(3, 4, 4, 5)
    )

    # When bounds=False is passed, then the bounds do not update.
    paper = Paper()
    paper.override_bounds(0, 0, 1, 1)
    paper.translate((3, 4), bounds=False)
    assert_equal(paper.bounds(), Bounds(0, 0, 1, 1))

    # This also works if the bounds are not overridden.
    p = Pen()
    p.fill_mode()
    p.move_to((0.5, 0.5))
    p.circle(0.5)
    assert_equal(p.paper.bounds(), Bounds(0, 0, 1, 1))

    p.paper.translate((3, 4), bounds=False)

    assert_equal(p.paper.bounds(), Bounds(0, 0, 1, 1))
    assert_equal(p.last_path().bounds(), Bounds(3, 4, 4, 5))
Exemplo n.º 5
0
def test_override_bounds_copy():
    # Get the bounds of a Paper, modify them, then set them back changed.
    paper = Paper()
    paper.override_bounds(0, 0, 1, 1)

    bounds = paper.bounds()
    bounds.right = 5

    assert_equal(paper.bounds(), Bounds(0, 0, 1, 1))
    paper.override_bounds(bounds)
    assert_equal(paper.bounds(), Bounds(0, 0, 5, 1))

    # This works on non-overridden Papers as well.
    paper = Paper()

    p = Pen()
    p.fill_mode()
    p.move_to((0.5, 0.5))
    p.circle(0.5)

    bounds = p.paper.bounds()
    bounds.right = 5

    assert_equal(p.paper.bounds(), Bounds(0, 0, 1, 1))
    p.paper.override_bounds(bounds)
    assert_equal(p.paper.bounds(), Bounds(0, 0, 5, 1))
Exemplo n.º 6
0
def test_mirror_bounds():
    paper = Paper()
    paper.override_bounds(0, 0, 1, 1)
    paper.mirror_x(2)
    assert_equal(paper.bounds(), Bounds(3, 0, 4, 1))
    paper.mirror_y(-1)
    assert_equal(paper.bounds(), Bounds(3, -3, 4, -2))
Exemplo n.º 7
0
    def draw_character(self, mode, **kwargs):
        side_ending = self.side_ending_class(
            self,
            self.side_flipped,
        )

        paper = Paper()

        pen = Pen()
        pen.set_mode(mode)
        pen.move_to((0, TOP - mode.width / 2))
        pen.turn_to(0)
        pen.line_forward(2.0)
        pen.last_segment().start_cap = stub_cap

        side_ending.draw(pen)
        paper.merge(pen.paper)

        bounds = paper.bounds()
        bounds.top = OVER
        bounds.bottom = MIDDLE
        bounds.left = 0
        paper.override_bounds(bounds)

        return paper
Exemplo n.º 8
0
def test_copy_override_bounds():
    paper1 = Paper()
    paper1.override_bounds(0, 0, 1, 1)
    paper2 = paper1.copy()
    assert_equal(
        paper2.bounds(),
        Bounds(0, 0, 1, 1),
    )
Exemplo n.º 9
0
def test_copy_override_bounds():
    paper1 = Paper()
    paper1.override_bounds(0, 0, 1, 1)
    paper2 = paper1.copy()
    assert_equal(
        paper2.bounds(),
        Bounds(0, 0, 1, 1),
    )
Exemplo n.º 10
0
def test_mirror_bounds():
    paper = Paper()
    paper.override_bounds(0, 0, 1, 1)
    paper.mirror_x(2)
    assert_equal(
        paper.bounds(),
        Bounds(3, 0, 4, 1)
    )
    paper.mirror_y(-1)
    assert_equal(
        paper.bounds(),
        Bounds(3, -3, 4, -2)
    )
Exemplo n.º 11
0
def test_override_bounds():
    # Test that the view box gets set correctly.
    paper = Paper()
    paper.override_bounds(0, 0, 8, 11)

    # The view box is transformed into svg coordinates by flipping the
    # Y-coordinate and adjusting for height.
    svg_data = paper.format_svg()
    assert 'viewBox="0 -11 8 11"' in svg_data

    paper.override_bounds(-10, -10, 10, 10)
    svg_data = paper.format_svg()
    assert 'viewBox="-10 -10 20 20"' in svg_data
Exemplo n.º 12
0
def test_override_bounds():
    # Test that the view box gets set correctly.
    paper = Paper()
    paper.override_bounds(0, 0, 8, 11)

    # The view box is transformed into svg coordinates by flipping the
    # Y-coordinate and adjusting for height.
    svg_data = paper.format_svg()
    assert 'viewBox="0 -11 8 11"' in svg_data

    paper.override_bounds(-10, -10, 10, 10)
    svg_data = paper.format_svg()
    assert 'viewBox="-10 -10 20 20"' in svg_data
Exemplo n.º 13
0
def test_translate_override_bounds():
    # Translate a paper that has overridden bounds. The bounds update as well.
    paper = Paper()
    paper.override_bounds(0, 0, 1, 1)
    paper.translate((3, 4))
    assert_equal(paper.bounds(), Bounds(3, 4, 4, 5))

    # When bounds=False is passed, then the bounds do not update.
    paper = Paper()
    paper.override_bounds(0, 0, 1, 1)
    paper.translate((3, 4), bounds=False)
    assert_equal(paper.bounds(), Bounds(0, 0, 1, 1))

    # This also works if the bounds are not overridden.
    p = Pen()
    p.fill_mode()
    p.move_to((0.5, 0.5))
    p.circle(0.5)
    assert_equal(p.paper.bounds(), Bounds(0, 0, 1, 1))

    p.paper.translate((3, 4), bounds=False)

    assert_equal(p.paper.bounds(), Bounds(0, 0, 1, 1))
    assert_equal(p.last_path().bounds(), Bounds(3, 4, 4, 5))
Exemplo n.º 14
0
def test_merge_bounds():
    def draw():
        p = Pen()
        p.fill_mode()
        p.move_to((0, 0))
        p.circle(2)
        paper1 = p.paper

        p = Pen()
        p.fill_mode()
        p.move_to((3, 0))
        p.circle(1)
        paper2 = p.paper

        return paper1, paper2

    # Empty papers, no overridden bounds.
    paper1 = Paper()
    paper2 = Paper()
    paper1.merge(paper2)
    assert_raises(ValueError, lambda: paper1.bounds())

    # Empty papers with overridden bounds on both sides.
    paper1 = Paper()
    paper1.override_bounds(0, 0, 1, 1)

    paper2 = Paper()
    paper2.override_bounds(1, 0, 2, 1)

    paper1.merge(paper2)
    assert_equal(paper1.bounds(), Bounds(0, 0, 2, 1))

    # No bounds overriding or merging.
    paper1, paper2 = draw()
    assert_equal(paper1.bounds(), Bounds(-2, -2, 2, 2))
    assert_equal(paper2.bounds(), Bounds(2, -1, 4, 1))

    # Merge with no overriding.
    paper1, paper2 = draw()
    paper1.merge(paper2)
    assert_equal(paper1.bounds(), Bounds(-2, -2, 4, 2))

    # Override the top one.
    paper1, paper2 = draw()
    paper2.override_bounds(-1, -1, 1, 1)
    paper1.merge(paper2)
    assert_equal(paper1.bounds(), Bounds(-2, -2, 2, 2))

    # Override the bottom one.
    paper1, paper2 = draw()
    bounds = paper1.bounds()
    bounds.top = 10
    paper1.override_bounds(bounds)
    paper1.merge(paper2)
    assert_equal(paper1.bounds(), Bounds(-2, -2, 4, 10))

    # Empty bounds on bottom page.
    paper1, paper2 = draw()
    paper1.override_bounds(-1, -1, 1, 1)
    paper3 = Paper()
    paper3.merge(paper1)
    assert_equal(paper3.bounds(), Bounds(-1, -1, 1, 1))

    # Empty bounds on top page.
    paper1, paper2 = draw()
    paper3 = Paper()
    paper1.override_bounds(-1, -1, 1, 1)
    paper1.merge(paper3)
    assert_equal(paper1.bounds(), Bounds(-1, -1, 1, 1))
Exemplo n.º 15
0
    def draw_character(self, mode, fuse=True):
        side_ending = self.side_ending_class(self, self.side_flipped)
        bottom_ending = self.bottom_ending_class(
            self,
            self.bottom_straight,
            self.bottom_flipped,
        )

        paper = Paper()

        # When drawing the body of the consonant, subclasses will start
        # where the side ending is, and end where the bottom ending is.
        pen = Pen()
        pen.set_mode(mode)
        pen.move_to((0, TOP - pen.mode.width / 2))
        side_ending_position = pen.position
        self.draw(pen)
        bottom_ending_position = pen.position
        bottom_ending_heading = pen.heading
        paper.merge(pen.paper)

        # Draw the side ending.
        pen = Pen()
        pen.set_mode(mode)
        pen.move_to(side_ending_position)
        pen.turn_to(0)
        side_ending.draw(pen)
        paper.merge(pen.paper)

        # Draw the bottom ending.
        pen = Pen()
        pen.set_mode(mode)
        pen.move_to(bottom_ending_position)

        # If the bottom orientation is slanted left, then we have to
        # start the bottom ending from a flipped heading so when it flips
        # again later it will be correct.
        if not self.bottom_straight and self.bottom_flipped:
            bottom_ending_heading = bottom_ending_heading.flipped_x()
        pen.turn_to(bottom_ending_heading)

        # Draw the ending, and maybe flip it horizontally.
        bottom_ending.draw(pen)
        if not self.bottom_straight and self.bottom_flipped:
            pen.paper.mirror_x(bottom_ending_position.x)
        paper.merge(pen.paper)

        if fuse:
            paper.join_paths()
            paper.fuse_paths()

        # Override the bounds so that the letter reaches the full line height.
        bounds = paper.bounds()
        bounds.bottom = UNDER
        bounds.top = OVER
        paper.override_bounds(bounds)

        # We need to center on x=0 here because otherwise flipped
        # consonants wouldn't flip at the right x value.
        paper.center_on_x(0)

        return paper
Exemplo n.º 16
0
def typeset(
    papers,
    letter_spacing=0,
    line_width=None,
    letters_per_line=None,
    line_spacing=0,
    page_margin=0,
    resolution=10,
):
    """
    Arrange letters on the page.

    Takes a list of paper objects.

    `letter_spacing` is how much space to leave between successive letters.
    `line_width` is how wide each line is before it wraps. A value of None
        means to not wrap based on this at all.
    `letters_per_line` is how many characters to place before wrapping. A
        value of None means to not wrap based on this at all.
    `line_spacing` is the vertical space left between lines.
    `page_margin` is how much space to leave around the edge of the page.
    `resolution` is the number of pixels per one unit.
    """
    # The letters are arranged into lines first, then the lines are arranged on
    # the page. The coordinates on each line have x=0 as the left edge, and y=0
    # as the text baseline. We trust each letter to be arranged correctly with
    # regard to the baseline.
    lines = [Paper()]
    letter_count = 0
    x = 0
    for letter_paper in papers:
        bounds = letter_paper.bounds()
        letter_paper.translate((-bounds.left, 0))

        # Start a new line if needed. Keep at least one letter per line.
        line_break = False
        if line_width is not None:
            if bounds.right + x > line_width:
                line_break = True
        if letters_per_line is not None:
            if letter_count >= letters_per_line:
                line_break = True
        if letter_count == 0:
            line_break = False

        if line_break:
            letter_count = 0
            x = 0
            lines.append(Paper())

        letter_paper.translate((x, 0))
        lines[-1].merge(letter_paper)

        letter_count += 1
        x += bounds.width + letter_spacing

    # Now we arrange completed lines on the page.
    page = Paper()
    y = 0
    for line in lines:
        bounds = line.bounds()
        line.translate((0, -bounds.top + y))
        page.merge(line)
        y -= bounds.height + line_spacing

    # Set page margins and pixel size.
    page_bounds = page.bounds()
    page_bounds.left -= page_margin
    page_bounds.right += page_margin
    page_bounds.bottom -= page_margin
    page_bounds.top += page_margin
    page.override_bounds(page_bounds)

    return page
Exemplo n.º 17
0
def test_merge_bounds():
    def draw():
        p = Pen()
        p.fill_mode()
        p.move_to((0, 0))
        p.circle(2)
        paper1 = p.paper

        p = Pen()
        p.fill_mode()
        p.move_to((3, 0))
        p.circle(1)
        paper2 = p.paper

        return paper1, paper2

    # Empty papers, no overridden bounds.
    paper1 = Paper()
    paper2 = Paper()
    paper1.merge(paper2)
    assert_raises(ValueError, lambda: paper1.bounds())

    # Empty papers with overridden bounds on both sides.
    paper1 = Paper()
    paper1.override_bounds(0, 0, 1, 1)

    paper2 = Paper()
    paper2.override_bounds(1, 0, 2, 1)

    paper1.merge(paper2)
    assert_equal(paper1.bounds(), Bounds(0, 0, 2, 1))

    # No bounds overriding or merging.
    paper1, paper2 = draw()
    assert_equal(paper1.bounds(), Bounds(-2, -2, 2, 2))
    assert_equal(paper2.bounds(), Bounds(2, -1, 4, 1))

    # Merge with no overriding.
    paper1, paper2 = draw()
    paper1.merge(paper2)
    assert_equal(paper1.bounds(), Bounds(-2, -2, 4, 2))

    # Override the top one.
    paper1, paper2 = draw()
    paper2.override_bounds(-1, -1, 1, 1)
    paper1.merge(paper2)
    assert_equal(paper1.bounds(), Bounds(-2, -2, 2, 2))

    # Override the bottom one.
    paper1, paper2 = draw()
    bounds = paper1.bounds()
    bounds.top = 10
    paper1.override_bounds(bounds)
    paper1.merge(paper2)
    assert_equal(paper1.bounds(), Bounds(-2, -2, 4, 10))

    # Empty bounds on bottom page.
    paper1, paper2 = draw()
    paper1.override_bounds(-1, -1, 1, 1)
    paper3 = Paper()
    paper3.merge(paper1)
    assert_equal(paper3.bounds(), Bounds(-1, -1, 1, 1))

    # Empty bounds on top page.
    paper1, paper2 = draw()
    paper3 = Paper()
    paper1.override_bounds(-1, -1, 1, 1)
    paper1.merge(paper3)
    assert_equal(paper1.bounds(), Bounds(-1, -1, 1, 1))
Exemplo n.º 18
0
    def draw_character(self, mode, fuse=True):
        paper = Paper()

        top_ending = self.top_ending_class(
            self,
            self.top_straight,
            self.top_flipped,
        )
        bottom_ending = self.bottom_ending_class(
            self,
            self.bottom_straight,
            self.bottom_flipped,
        )

        # While drawing the body of the primary character, two copies of the
        # pen are created, one ready to draw each of the endings.
        pen = Pen()
        pen.set_mode(mode)

        top_pen, bottom_pen = self.draw(pen)
        top_start_position = top_pen.position
        bottom_start_position = bottom_pen.position

        # Draw the endings.
        # If the ending orientations are to the left, then we have to flip them
        # horizontally.
        # The top ending is drawn as if it were a bottom ending, so
        # mirror it to the top.
        if self.top_flipped:
            top_pen.turn_to(top_pen.heading.flipped_x())
        top_pen.move_to(top_pen.position.flipped_y(MIDDLE))
        top_pen.turn_to(top_pen.heading.flipped_y())
        top_ending.draw(top_pen)
        top_pen.paper.mirror_y(MIDDLE)
        if self.top_flipped:
            top_pen.paper.mirror_x(top_start_position.x)

        if self.bottom_flipped:
            bottom_pen.turn_to(bottom_pen.heading.flipped_x())
        bottom_ending.draw(bottom_pen)
        if self.bottom_flipped:
            bottom_pen.paper.mirror_x(bottom_start_position.x)

        paper.merge(pen.paper)
        paper.merge(top_pen.paper)
        paper.merge(bottom_pen.paper)
        if fuse:
            paper.join_paths()
            paper.fuse_paths()

        # Override the bounds so that the letter reaches the full line height.
        bounds = paper.bounds()
        bounds.bottom = UNDER
        bounds.top = OVER
        paper.override_bounds(bounds)

        # We need to center on x=0 here because otherwise flipped
        # consonants wouldn't flip at the right x value.
        paper.center_on_x(0)

        return paper
Exemplo n.º 19
0
    def draw_character(self, mode, fuse=True):
        paper = Paper()

        top_ending = self.top_ending_class(
            self,
            self.top_straight,
            self.top_flipped,
        )
        bottom_ending = self.bottom_ending_class(
            self,
            self.bottom_straight,
            self.bottom_flipped,
        )

        # While drawing the body of the primary character, two copies of the
        # pen are created, one ready to draw each of the endings.
        pen = Pen()
        pen.set_mode(mode)

        top_pen, bottom_pen = self.draw(pen)
        top_start_position = top_pen.position
        bottom_start_position = bottom_pen.position

        # Draw the endings.
        # If the ending orientations are to the left, then we have to flip them
        # horizontally.
        # The top ending is drawn as if it were a bottom ending, so
        # mirror it to the top.
        if self.top_flipped:
            top_pen.turn_to(top_pen.heading.flipped_x())
        top_pen.move_to(top_pen.position.flipped_y(MIDDLE))
        top_pen.turn_to(top_pen.heading.flipped_y())
        top_ending.draw(top_pen)
        top_pen.paper.mirror_y(MIDDLE)
        if self.top_flipped:
            top_pen.paper.mirror_x(top_start_position.x)

        if self.bottom_flipped:
            bottom_pen.turn_to(bottom_pen.heading.flipped_x())
        bottom_ending.draw(bottom_pen)
        if self.bottom_flipped:
            bottom_pen.paper.mirror_x(bottom_start_position.x)

        paper.merge(pen.paper)
        paper.merge(top_pen.paper)
        paper.merge(bottom_pen.paper)
        if fuse:
            paper.join_paths()
            paper.fuse_paths()

        # Override the bounds so that the letter reaches the full line height.
        bounds = paper.bounds()
        bounds.bottom = UNDER
        bounds.top = OVER
        paper.override_bounds(bounds)

        # We need to center on x=0 here because otherwise flipped
        # consonants wouldn't flip at the right x value.
        paper.center_on_x(0)

        return paper
Exemplo n.º 20
0
    def draw_character(self, mode, fuse=True):
        side_ending = self.side_ending_class(self, self.side_flipped)
        bottom_ending = self.bottom_ending_class(
            self,
            self.bottom_straight,
            self.bottom_flipped,
        )

        paper = Paper()

        # When drawing the body of the consonant, subclasses will start
        # where the side ending is, and end where the bottom ending is.
        pen = Pen()
        pen.set_mode(mode)
        pen.move_to((0, TOP - pen.mode.width / 2))
        side_ending_position = pen.position
        self.draw(pen)
        bottom_ending_position = pen.position
        bottom_ending_heading = pen.heading
        paper.merge(pen.paper)

        # Draw the side ending.
        pen = Pen()
        pen.set_mode(mode)
        pen.move_to(side_ending_position)
        pen.turn_to(0)
        side_ending.draw(pen)
        paper.merge(pen.paper)

        # Draw the bottom ending.
        pen = Pen()
        pen.set_mode(mode)
        pen.move_to(bottom_ending_position)

        # If the bottom orientation is slanted left, then we have to
        # start the bottom ending from a flipped heading so when it flips
        # again later it will be correct.
        if not self.bottom_straight and self.bottom_flipped:
            bottom_ending_heading = bottom_ending_heading.flipped_x()
        pen.turn_to(bottom_ending_heading)

        # Draw the ending, and maybe flip it horizontally.
        bottom_ending.draw(pen)
        if not self.bottom_straight and self.bottom_flipped:
            pen.paper.mirror_x(bottom_ending_position.x)
        paper.merge(pen.paper)

        if fuse:
            paper.join_paths()
            paper.fuse_paths()

        # Override the bounds so that the letter reaches the full line height.
        bounds = paper.bounds()
        bounds.bottom = UNDER
        bounds.top = OVER
        paper.override_bounds(bounds)

        # We need to center on x=0 here because otherwise flipped
        # consonants wouldn't flip at the right x value.
        paper.center_on_x(0)

        return paper