def bar_rectangle(w, h, n): """ Bar used for title and type bars """ return concat([ translate( circle( n, -1 / 4 * np.pi, 1 / 4 * np.pi, ) * h / 2 * np.sqrt(2), w - h / 2, h / 2, ), [ [w, h], [0, h], ], translate( circle( 8, 3 / 4 * np.pi, 5 / 4 * np.pi, ) * h / 2 * np.sqrt(2), h / 2, h / 2, ), [ [0, 0], [w, 0], ], ])
def test_translate_stroke(self): from pen_plots.strokes import translate stroke = np.array([[0.0, 0.0], [1.0, 0.0], [1.0, 1.0], [0.0, 1.0], [0.0, 0.0]]) translated = translate(stroke, 1, 2) expected = [[1.0, 2.0], [2.0, 2.0], [2.0, 3.0], [1.0, 3.0], [1.0, 2.0]] assert_array_almost_equal(translated, expected)
def glyphs_to_strokes(glyphs, font_size=21, alignment='left'): """ Converts a list of glyphs to strokes. Glyphs are placed from left to right, spaced according to their margins. This can be considered as rendering a single line of text without line breaks. """ offset = 0 strokes = [] for glyph in glyphs: strokes.extend(translate(glyph.lines, offset - glyph.left, 0)) offset += glyph.right - glyph.left if alignment == 'left': # Align left-most point to lie of y-axis pass elif alignment == 'right': # Align right-most point to lie of y-axis strokes = translate(strokes, -offset, 0) elif alignment == 'center': # Align center point to lie on y-axis strokes = translate(strokes, -offset / 2, 0) return scale(strokes, 25.4 / 72 * font_size / 21)
def test_translate_strokes(self): from pen_plots.strokes import translate strokes = [ np.array([[0.0, 0.0], [1.0, 0.0], [1.0, 1.0]]), np.array([[0.0, 1.0], [0.0, 0.0]]), ] translated = translate(strokes, 1, 2) expected = [ [[1.0, 2.0], [2.0, 2.0], [2.0, 3.0]], [[1.0, 3.0], [1.0, 2.0]], ] self.assertEqual(len(translated), len(expected)) for i in range(len(expected)): assert_array_almost_equal(translated[i], expected[i])
def create_card(card_title, card_type, mana_cost=None, oracle_text=None, power=None, toughness=None, loyalty=None): """ Creates a MtG card with strokes. Parameters ---------- card_title : str Name of the card. card_type : str Type lien of the card. mana_cost : str/None Mana cost of the card. Uses Scryfall encoding for special symbols, e.g. {R} for pne red mana. May be none to indicate no mana costs, e.g. for land. oracle_text : str/None Oracle text of the card. May be None to indicate no oracle text, i.e. vanilla cards. power, toughness : str/None Power and toughness of the card. May be None for non-creature cards. loyalty : str/None Loyality of the card. Mutually exclusive with power/toughness. May be None for non-planeswaker cards. Returns ------- list of strokes Vectorizes card. """ card_width, card_height = 63, 88 outer_rounding = 2.5 bottom_border_extra = 0 bar_height = 5 img_height = 39.3 x_left, x_right = 4.5, card_width - 4.5 outer_margin, inner_margin = x_left - outer_rounding, 0.5 y_title_top = card_height - outer_rounding - outer_margin y_title_bottom = y_title_top - bar_height y_img_top = y_title_bottom - inner_margin y_img_bottom = y_img_top - img_height y_type_top = y_img_bottom - inner_margin y_type_bottom = y_type_top - bar_height x_text_left, x_text_right = x_left, x_right y_text_top, y_text_bottom = y_type_bottom - inner_margin, outer_rounding + outer_margin + bottom_border_extra margin_x_text = 0.5 oracle_text_line_height = 8 / 3 oracle_text_par_skip = 2 / 3 oracle_text_font_size = 5 strokes = [] # Outer border strokes.append(rounded_rectangle(63, 88, outer_rounding, 8)) # Outer border inner strokes.append( translate( rounded_rectangle(63 - outer_rounding * 2, 88 - outer_rounding * 2 - bottom_border_extra, 1, 8), x=outer_rounding, y=outer_rounding + bottom_border_extra, )) # Title bar strokes.append( translate( bar_rectangle(x_right - x_left, y_title_top - y_title_bottom, 8), x_left, y_title_bottom)) # Image Box strokes.append( translate(rectangle(x_right - x_left, y_img_top - y_img_bottom), x_left, y_img_bottom)) # Type bar strokes.append( translate( bar_rectangle(x_right - x_left, y_type_top - y_type_bottom, 8), x_left, y_type_bottom)) # Power/Toughness, loyality if power is not None and toughness is not None or loyalty is not None: # P/T bar pt_bar_width = 8 if loyalty is None else 4 pt_bar = translate(bar_rectangle(pt_bar_width, 5, 8), x_right - pt_bar_width, y_text_bottom - 1) pt_bar_bbox = bounding_box(pt_bar) strokes.append(pt_bar) # P/T text strokes.extend( translate( scale_to_fit( glyphs_to_strokes( line_to_glyphs( power + '/' + toughness if loyalty is None else loyalty), font_size=7, alignment='center', ), max_width=pt_bar_width, ), x_right - pt_bar_width / 2, y_text_bottom - 1 + 5 / 2, )) else: pt_bar_bbox = None # Text box text_box = translate( rectangle(x_text_right - x_text_left, y_text_top - y_text_bottom), x_text_left, y_text_bottom) if pt_bar_bbox is None: strokes.append(text_box) else: # Draw around P/T bar strokes.extend(difference([text_box], pt_bar)) # Mana Cost if len(mana_cost) > 0: text_mana_cost = glyphs_to_strokes(line_to_glyphs(mana_cost), font_size=6, alignment='right') x_mana_cost_left = x_right + bounding_box(text_mana_cost)[ 0, 0] - margin_x_text strokes.extend( translate( scale_to_fit( text_mana_cost, max_width=x_right - x_left - margin_x_text * 2, ), x_right - margin_x_text, (y_title_bottom + y_title_top) / 2, )) else: x_mana_cost_left = x_right # Title text strokes.extend( translate( scale_to_fit( glyphs_to_strokes(line_to_glyphs(card_title), font_size=6), max_width=x_mana_cost_left - x_left - margin_x_text * 2, ), x_left + margin_x_text, (y_title_bottom + y_title_top) / 2, )) # Type Text strokes.extend( translate( scale_to_fit( glyphs_to_strokes(line_to_glyphs(card_type), font_size=6), max_width=x_right - x_left - margin_x_text * 2, ), x_left + margin_x_text, (y_type_bottom + y_type_top) / 2, )) # Oracle text if len(oracle_text) > 0: x_line_left = x_text_left + margin_x_text oracle_text_scale = 1 while True: oracle_text_strokes = [] oracle_text_top_offset = ( oracle_text_line_height + oracle_text_par_skip) * oracle_text_scale / 2 for oracle_text_line in oracle_text.split('\n'): while len(oracle_text_line) > 0: x_line_right = (x_text_right if pt_bar_bbox is None or pt_bar_bbox[1, 1] < y_text_top - oracle_text_top_offset - oracle_text_par_skip * oracle_text_scale else pt_bar_bbox[0, 0]) - margin_x_text n_line_chars = 42.5 * ( x_line_right - x_line_left - margin_x_text * 2) / ( x_text_right - x_text_left) / oracle_text_scale line = wrap(oracle_text_line, n_line_chars)[ 0] # TODO Calculate wrap based on actual char sizes oracle_text_line = oracle_text_line[len(line) + 1:] oracle_text_strokes.extend( translate( scale_to_fit( glyphs_to_strokes( line_to_glyphs(line), font_size=oracle_text_font_size * oracle_text_scale, ), max_width=x_line_right - x_line_left, ), x_line_left, y_text_top - oracle_text_top_offset, )) oracle_text_top_offset += oracle_text_line_height * oracle_text_scale oracle_text_top_offset += oracle_text_par_skip * oracle_text_scale # Check if text fits if bounding_box( oracle_text_strokes )[0][1] > y_text_bottom + oracle_text_par_skip * oracle_text_scale / 2: # Check if text doesn't collide with pt bar: collision = False if pt_bar_bbox is not None: for stroke in oracle_text_strokes: for p in stroke: if p[0] >= pt_bar_bbox[0][0] and p[ 0] <= pt_bar_bbox[1][0] and p[ 1] >= pt_bar_bbox[0][1] and p[ 1] <= pt_bar_bbox[1][1]: collision = True if not collision: strokes.extend(oracle_text_strokes) if oracle_text_scale < 1: print('Scaled oracle text to %f' % oracle_text_scale) break oracle_text_scale *= 0.99 return strokes