Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ El archivo de definicion de cartas es un archivo en jormato json con el siguente
"color": "#RRGGBB",
"font_style": "normal | negrita | itálica"
},
"manaCost": "str",
"commandPoints": int,
"power": int,
"toughness": int,
"image": "str",
Expand All @@ -82,6 +82,7 @@ El archivo de definicion de cartas es un archivo en jormato json con el siguente
El objeto `header` define el texto visible en la parte superior de la carta. El campo `color` ajusta el color del texto, mientras que los campos `banner` y `banner_color` permiten activar un recuadro de color sólido detrás del encabezado cuando sea necesario.
El bloque `card_text` permite especificar el texto del cuerpo y el color con el que debe renderizarse. Para las imágenes puedes indicar un nombre de archivo directamente o un objeto con las claves `source` y `full_frame`. Cuando `full_frame_image` (o `full_frame` en el objeto de imagen) es `true`, la ilustración se ampliará para cubrir toda la carta; en caso contrario se mantendrá dentro del marco de arte.
Puedes controlar el color de fondo del lienzo con el campo opcional `background_color`. Debe indicarse en formato hexadecimal (`#RRGGBB`) y solo se aplica cuando la carta no utiliza una imagen a pantalla completa (`full_frame_image: false`).
El valor `commandPoints` representa los puntos de mando de la carta. Siempre se mostrará como un número dentro de un escudo con borde negro en la esquina superior derecha.
El bloque `footer` es opcional y permite mostrar una nota en la parte inferior de la carta. Puedes personalizar el texto, su color y el estilo de fuente (`normal`, `negrita` o `itálica`). Si no se especifica `font_style`, se utilizará `normal` por defecto.
Las imagenes deben almacenarse en el directorio "images" que se encuentra en la misma carpeta que LWCProto.py, el formato de las imagenes es indiferente y su tamaño tambien estas seran redimensionadas automaticamente para adaptarse al tamaño disponible en el layout. Puedes utilizar el argumento `--output-dir` para indicar otro directorio base donde almacenar las cartas generadas, lo que facilita mantener varios prototipos separados.

Expand Down
34 changes: 28 additions & 6 deletions card_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ def __init__(self, name=None, db=None):
self.typeStr = "TYPE - SUBTYPE"
self.cardText = "Some text"
self.cardTextColour = "#000000"
self.manaCost = "\{W\}"
self.commandPoints = 0
self.power = None
self.toughness = None
self.image = None
Expand Down Expand Up @@ -75,10 +75,11 @@ def load(self, data: dict):
self.cardText = ""
self.cardTextColour = '#000000'

if ('manaCost' in data):
self.manaCost = data['manaCost']
else:
self.manaCost = ""
command_points_value = data.get('commandPoints')
if command_points_value is None:
command_points_value = data.get('manaCost')

self.commandPoints = self._parse_command_points(command_points_value)

if 'power' in data:
self.power = int(data['power'])
Expand Down Expand Up @@ -118,7 +119,7 @@ def load(self, data: dict):
self.footerFontStyle = self._normalise_footer_style(footer_style)

def __str__(self):
return f'{self.headerText} - {self.manaCost} ({self.typeStr})'
return f'{self.headerText} - {self.commandPoints} ({self.typeStr})'

def get_text_color_rgb(self):
return self._hex_to_rgb(self.cardTextColour, default=(0.0, 0.0, 0.0))
Expand Down Expand Up @@ -178,3 +179,24 @@ def _normalise_footer_style(value: str) -> str:
}

return style_map.get(normalised, 'normal')

@staticmethod
def _parse_command_points(value) -> int:
if value is None:
return 0

if isinstance(value, (int, float)):
return int(value)

text = str(value).strip()
if not text:
return 0

digits = ''.join(ch for ch in text if ch.isdigit() or ch == '-')
if not digits:
return 0

try:
return int(digits)
except ValueError:
return 0
4 changes: 2 additions & 2 deletions cartas.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
"text": "footer carta 1",
"color": "#333333"
},
"manaCost": "5/6 * {r}",
"commandPoints": 12,
"image": "BarcoPirata.jpg"
},
"Carta2": {
Expand All @@ -39,7 +39,7 @@
"color": "#FFD700",
"font_style": "negrita"
},
"manaCost": "2",
"commandPoints": 7,
"power": 60,
"toughness": 9,
"image": "MinionNapoleonics.jpg",
Expand Down
56 changes: 48 additions & 8 deletions draw_card.py
Original file line number Diff line number Diff line change
Expand Up @@ -102,14 +102,54 @@ def drawCard(
ctx.move_to(*layout.ptBL)
ctx.show_text(ptStr)

# Draw Mana Cost
ctx.set_source_rgb(*body_color)
ctx.set_font_size(layout.nameH)
ctx.move_to(
layout.manaRight - ctx.text_extents(card.manaCost).width,
layout.nameBL[1]
)
ctx.show_text(card.manaCost)
# Draw command points shield
ctx.save()
shield_x, shield_y = layout.commandPointsShieldTL
shield_width, shield_height = layout.commandPointsShieldSize
point_height = layout.commandPointsShieldPointHeight

ctx.move_to(shield_x, shield_y)
ctx.line_to(shield_x + shield_width, shield_y)
ctx.line_to(shield_x + shield_width, shield_y + shield_height * 0.65)
ctx.line_to(shield_x + shield_width / 2.0, shield_y + shield_height + point_height)
ctx.line_to(shield_x, shield_y + shield_height * 0.65)
ctx.close_path()

ctx.set_source_rgb(1.0, 1.0, 1.0)
ctx.fill_preserve()
ctx.set_line_width(layout.commandPointsBorderWidth)
ctx.set_source_rgb(0.0, 0.0, 0.0)
ctx.stroke()

ctx.set_font_size(layout.commandPointsFontSize)
text = str(card.commandPoints)
extents = ctx.text_extents(text)
center_x = shield_x + shield_width / 2.0
center_y = shield_y + (shield_height + point_height) / 2.0
text_x = center_x - (extents.x_bearing + extents.width / 2.0)
text_y = center_y - (extents.y_bearing + extents.height / 2.0)

ctx.set_source_rgb(0.0, 0.0, 0.0)
ctx.move_to(text_x, text_y)
ctx.show_text(text)
ctx.restore()

# Draw footer text
if card.footerText:
footer_slant = cairo.FONT_SLANT_NORMAL
footer_weight = cairo.FONT_WEIGHT_NORMAL

if card.footerFontStyle == 'italic':
footer_slant = cairo.FONT_SLANT_ITALIC
if card.footerFontStyle == 'bold':
footer_weight = cairo.FONT_WEIGHT_BOLD

ctx.set_source_rgb(*card.get_footer_text_color_rgb())
ctx.set_font_size(layout.footerH)
ctx.select_font_face('serif', footer_slant, footer_weight)
ctx.move_to(*layout.footerBL)
ctx.show_text(card.footerText)
ctx.select_font_face('serif')

# Draw footer text
if card.footerText:
Expand Down
9 changes: 6 additions & 3 deletions layout.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,6 @@
# Card Measurements
nameBL = (6,8.5)
nameH = 2.5
manaRight = 57
mana0TL = (54,5.5)
manaOS = 3.5
typeBL = (6,52.5)
typeH = 2
cardTextBL = (6,58)
Expand All @@ -39,6 +36,12 @@
SINGLE_CARD_DPI = 300
CARD_CORNER_RADIUS_MM = 3.0

commandPointsShieldTL = (CARD_WIDTH_MM - 12.0, 4.0)
commandPointsShieldSize = (9.0, 9.5)
commandPointsShieldPointHeight = 3.0
commandPointsFontSize = 4.0
commandPointsBorderWidth = 0.5


def mm_to_pixels(value_mm: float, dpi: float) -> int:
"""Convert a millimetre measurement to whole pixels for a given DPI."""
Expand Down
34 changes: 31 additions & 3 deletions tests/test_card_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ def test_load_with_optional_fields(self):
"text": "Draw a card",
"colour": "#112233",
},
"manaCost": "{1}{U}",
"commandPoints": 5,
"power": 2,
"toughness": 3,
"image": "wizard.png",
Expand All @@ -43,7 +43,7 @@ def test_load_with_optional_fields(self):
self.assertEqual(card.cardText, "Draw a card")
self.assertEqual(card.cardTextColour, "#112233")
self.assertEqual(card.get_text_color_rgb(), (0x11 / 255.0, 0x22 / 255.0, 0x33 / 255.0))
self.assertEqual(card.manaCost, "{1}{U}")
self.assertEqual(card.commandPoints, 5)
self.assertEqual(card.power, 2)
self.assertEqual(card.toughness, 3)
self.assertEqual(card.image, "wizard.png")
Expand Down Expand Up @@ -72,7 +72,7 @@ def test_load_with_defaults(self):
self.assertEqual(card.cardText, "")
self.assertEqual(card.cardTextColour, "#000000")
self.assertEqual(card.get_text_color_rgb(), (0.0, 0.0, 0.0))
self.assertEqual(card.manaCost, "")
self.assertEqual(card.commandPoints, 0)
self.assertIsNone(card.power)
self.assertIsNone(card.toughness)
self.assertIsNone(card.image)
Expand All @@ -98,6 +98,34 @@ def test_load_supports_image_object(self):
self.assertEqual(card.image, "object.png")
self.assertTrue(card.imageFullFrame)

def test_load_legacy_mana_cost_field(self):
data = {
"header": {
"text": "Legacy",
},
"type": "Enchantment",
"manaCost": "12",
}

card = CardModel()
card.load(data)

self.assertEqual(card.commandPoints, 12)

def test_load_coerces_string_command_points(self):
data = {
"header": {
"text": "Numbers",
},
"type": "Instant",
"commandPoints": " CP: 08 ",
}

card = CardModel()
card.load(data)

self.assertEqual(card.commandPoints, 8)

def test_load_supports_legacy_name_field(self):
data = {
"name": "Legacy",
Expand Down