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))
def test_paper_merge(): # Merge two drawings together. paper = Paper() p = Pen() p.fill_mode() p.turn_to(0) p.arc_left(180, 5) p.paper.center_on_x(0) paper.merge(p.paper) p = Pen() p.fill_mode() p.turn_to(180) p.arc_left(180, 5) p.paper.center_on_x(0) paper.merge(p.paper) assert_path_data( paper, 1, [ 'M-2.5,0.0 A 5.0,5.0 0 0 0 -2.5,-10.0', 'M2.5,0.0 A 5.0,5.0 0 0 0 2.5,10.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), )
def test_mirror_end_slant(): paper = Paper() p = Pen() p.stroke_mode(sqrt2) p.move_to((0, 0)) p.turn_to(-45) p.line_forward(5 * sqrt2, end_slant=45) p.paper.mirror_x(0) paper.merge(p.paper) p = Pen() p.stroke_mode(sqrt2) p.move_to((0, 0)) p.turn_to(45) p.line_forward(5 * sqrt2) paper.merge(p.paper) paper.join_paths() paper.fuse_paths() assert_path_data( paper, 1, 'M-5.5,4.5 L-4.5,5.5 L5.5,-4.5 L4.5,-5.5 L-5.5,4.5 z' )
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
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))
def test_text_merge(): p = Pen() p.move_to((0, 0)) p.text('abcd', 1) paper1 = p.paper assert '<text' in paper1.format_svg(0) paper2 = Paper() paper2.merge(paper1) assert '<text' in paper2.format_svg(0) paper3 = Paper() paper3.merge_under(paper1) assert '<text' in paper3.format_svg(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) )
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
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
def test_two_pens_one_paper(): paper = Paper() p1 = Pen(paper) p2 = Pen(paper) p1.fill_mode() p2.fill_mode() p1.move_to((0, 0)) p2.move_to((0, 0)) p1.line_to((0, 1)) p2.line_to((2, 0)) assert_path_data(paper, 0, ['M0,0 L0,-1', 'M0,0 L2,0'])
def test_mirror_end_slant(): paper = Paper() p = Pen() p.stroke_mode(sqrt2) p.move_to((0, 0)) p.turn_to(-45) p.line_forward(5 * sqrt2, end_slant=45) p.paper.mirror_x(0) paper.merge(p.paper) p = Pen() p.stroke_mode(sqrt2) p.move_to((0, 0)) p.turn_to(45) p.line_forward(5 * sqrt2) paper.merge(p.paper) paper.join_paths() paper.fuse_paths() assert_path_data(paper, 1, 'M-5.5,4.5 L-4.5,5.5 L5.5,-4.5 L4.5,-5.5 L-5.5,4.5 z')
def test_paper_merge(): # Merge two drawings together. paper = Paper() p = Pen() p.fill_mode() p.turn_to(0) p.arc_left(180, 5) p.paper.center_on_x(0) paper.merge(p.paper) p = Pen() p.fill_mode() p.turn_to(180) p.arc_left(180, 5) p.paper.center_on_x(0) paper.merge(p.paper) assert_path_data(paper, 1, [ 'M-2.5,0.0 A 5.0,5.0 0 0 0 -2.5,-10.0', 'M2.5,0.0 A 5.0,5.0 0 0 0 2.5,10.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))
def test_empty_bounds(): assert_raises(ValueError, lambda: Paper().bounds())
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
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
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))
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
def test_format_empty_bounds(): paper = Paper() svg_data = paper.format_svg() assert 'viewBox="-10 -10 20 20"' in svg_data
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))