<gh_stars>0
import numpy as np
from pen_plots.strokes import to_strokes, translate, rounded_rectangle, circle, concat, rectangle, bounding_box, scale_to_fit
from pen_plots.fonts import glyphs_to_strokes
from pen_plots.mtg.glyphs import line_to_glyphs
from textwrap import wrap

from pen_plots.mtg import strip_reminders, join_keyword_lines


def _replace_chars(string):
    """
    Replaces some chars with ones which have a hershey code equivalent.
    """
    for replacement in [('\'', '’'), ('—', '-'), ('−', '-'), ('•', '*')]:
        string = string.replace(*replacement)
    return string


def vectorize_card(card_data):
    """
    Converts a MtG card to strokes.
    """
    return create_card(
        card_title=_replace_chars(card_data["name"]),
        card_type=_replace_chars(card_data["type_line"]),
        mana_cost=card_data["mana_cost"],
        oracle_text=join_keyword_lines(strip_reminders(_replace_chars(card_data["oracle_text"]))),
        power=card_data.get("power"),
        toughness=card_data.get("toughness"),
        loyalty=card_data.get("loyalty"),
    )


def difference(strokes, area):
    """
    Computes set difference of a list of strokes and an area specified by a stroke.
    """
    from shapely.geometry import Polygon, MultiLineString

    return to_strokes(MultiLineString(strokes).difference(Polygon(area)))


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


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],
            ],
        ]
    )

import numpy as np
from pen_plots.strokes import to_strokes, translate, rounded_rectangle, circle, concat, rectangle, bounding_box, scale_to_fit
from pen_plots.fonts import glyphs_to_strokes
from pen_plots.mtg.glyphs import line_to_glyphs
from textwrap import wrap

from pen_plots.mtg import strip_reminders, join_keyword_lines


def _replace_chars(string):
    """
    Replaces some chars with ones which have a hershey code equivalent.
    """
    for replacement in [('\'', '’'), ('—', '-'), ('−', '-'), ('•', '*')]:
        string = string.replace(*replacement)
    return string


def vectorize_card(card_data):
    """
    Converts a MtG card to strokes.
    """
    return create_card(
        card_title=_replace_chars(card_data["name"]),
        card_type=_replace_chars(card_data["type_line"]),
        mana_cost=card_data["mana_cost"],
        oracle_text=join_keyword_lines(strip_reminders(_replace_chars(card_data["oracle_text"]))),
        power=card_data.get("power"),
        toughness=card_data.get("toughness"),
        loyalty=card_data.get("loyalty"),
    )


def difference(strokes, area):
    """
    Computes set difference of a list of strokes and an area specified by a stroke.
    """
    from shapely.geometry import Polygon, MultiLineString

    return to_strokes(MultiLineString(strokes).difference(Polygon(area)))


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


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],
            ],
        ]
    )
Replaces some chars with ones which have a hershey code equivalent. Converts a MtG card to strokes. Computes set difference of a list of strokes and an area specified by a stroke. 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. # Outer border # Outer border inner # Title bar # Image Box # Type bar # Power/Toughness, loyality # P/T bar # P/T text # Text box # Draw around P/T bar # Mana Cost # Title text # Type Text # Oracle text # TODO Calculate wrap based on actual char sizes # Check if text fits # Check if text doesn't collide with pt bar: Bar used for title and type bars