diff --git a/README.md b/README.md index 25256bd..30349e7 100644 --- a/README.md +++ b/README.md @@ -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", @@ -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. diff --git a/card_model.py b/card_model.py index e0a60b9..936a832 100644 --- a/card_model.py +++ b/card_model.py @@ -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 @@ -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']) @@ -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)) @@ -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 diff --git a/cartas.json b/cartas.json index cdaa391..3108cd7 100644 --- a/cartas.json +++ b/cartas.json @@ -17,7 +17,7 @@ "text": "footer carta 1", "color": "#333333" }, - "manaCost": "5/6 * {r}", + "commandPoints": 12, "image": "BarcoPirata.jpg" }, "Carta2": { @@ -39,7 +39,7 @@ "color": "#FFD700", "font_style": "negrita" }, - "manaCost": "2", + "commandPoints": 7, "power": 60, "toughness": 9, "image": "MinionNapoleonics.jpg", diff --git a/draw_card.py b/draw_card.py index 2da4b45..11545a7 100644 --- a/draw_card.py +++ b/draw_card.py @@ -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: diff --git a/layout.py b/layout.py index 23a1fea..5fb6b26 100644 --- a/layout.py +++ b/layout.py @@ -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) @@ -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.""" diff --git a/tests/test_card_model.py b/tests/test_card_model.py index 462bb99..c0efea7 100644 --- a/tests/test_card_model.py +++ b/tests/test_card_model.py @@ -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", @@ -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") @@ -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) @@ -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",