Rewrite for SVG support

heros and environments only at the moment
This commit is contained in:
Adam Goldsmith 2017-07-13 18:50:32 -04:00
parent 810369596d
commit 78ac4d1da3
11 changed files with 37818 additions and 77 deletions

1
.gitignore vendored
View File

@ -3,3 +3,4 @@
/__pycache__/
/build/
/dist/
/out/**

View File

@ -1,95 +1,100 @@
#!/usr/bin/env python3
#note: image templates: https://boardgamegeek.com/thread/813176/card-templates
#TODO: dynamic text sizes
#TODO: remote upload?
import json
from PIL import Image, ImageDraw, ImageFont
import textwrap
import math
import os
import subprocess
import sys
from lxml import etree
from PIL import Image
if getattr(sys, 'frozen', False): # we are running in a bundle
bundle_dir = sys._MEIPASS
else: # we are running in a normal Python environment
bundle_dir = os.path.dirname(os.path.abspath(__file__))
IMG_HERO_CHAR_FRONT = Image.open(bundle_dir + "/images/HeroCharFront.png")
IMG_HERO_CHAR_BACK = Image.open(bundle_dir + "/images/HeroCharBack.png")
IMG_HERO_DECK = Image.open(bundle_dir + "/images/HeroCard.png")
IMG_TARGET_HP = Image.open(bundle_dir + "/images/targetHP.png")
CARD_WIDTH = 181 * 4
CARD_HEIGHT = 253 * 4
FONT_KEYWORD = ImageFont.truetype(bundle_dir + "/fonts/RedStateBlueStateBB_reg.otf", size=40)
FONT_TITLE = ImageFont.truetype(bundle_dir + "/fonts/CrashLandingBB.otf", size=60)
FONT_DESCRIPTION = ImageFont.truetype(bundle_dir + "/fonts/RedStateBlueStateBB_reg.otf", size=30)
FONT_TARGET_HP = ImageFont.truetype(bundle_dir + "/fonts/CrashLandingBB.otf", size=120)
FONT_CHAR_HP = ImageFont.truetype(bundle_dir + "/fonts/ap.ttf", size=120)
def setText(tree, id, text):
element = tree.find('.//*[@id="' + id + '"]')
if element is None:
print("id", id, "not found")
return
elif element.tag == "{http://www.w3.org/2000/svg}flowRoot":
element.find("{http://www.w3.org/2000/svg}flowPara").text = text
else:
element[0].text = str(text)
def drawTextIf(draw, position, font, card, key, wrap=False):
if key in card:
text = str(card[key])
def removeElement(tree, id):
mark = tree.find('.//*[@id="' + id + '"]')
if mark is not None:
mark.getparent().remove(mark)
if wrap:
# badly wrap text to fit on card
text = '\n'.join(textwrap.fill(line.strip(), wrap,
break_long_words=False,
replace_whitespace=False)
for line in text.splitlines())
draw.text(position, text, font=font, fill="#000000")
def makeSVG(base, properties):
tree = etree.parse(base)
for id, text in properties.items():
if id not in ["count", "art"]:
setText(tree, id, text)
def addCardToBase(baseImg, baseX, cardNum, cardImg):
# remove HP mark if there is no hp
if "hp" not in properties:
removeElement(tree, "hpMark")
if "art" in properties:
art = tree.find('.//*[@id="art"]')
if art is not None:
art.set("{http://www.w3.org/1999/xlink}href",
"file://" + bundle_dir + "/" + properties["art"])
return tree
def addCardToBase(svg, baseImg, baseX, cardNum):
# TODO: possible to remove all this writing to temp files?
dest = "out/fig" + str(cardNum)
svg.write(dest + ".svg")
subprocess.call(["inkscape", "-z", "-f", dest + ".svg", "-w",
str(CARD_WIDTH), "-e", dest + ".png"])
cardImg = Image.open(dest + ".png")
baseImg.paste(cardImg, ((cardNum % baseX) * cardImg.width,
int(cardNum / baseX) * cardImg.height))
def makeFace(baseImage, baseX, cardNum, base, card):
fig = makeSVG(base, card)
addCardToBase(fig, baseImage, baseX, cardNum)
def makeFaces(deckJson, outfile):
# TODO: probably a more optimal way to fit cards
baseX = math.ceil(math.sqrt(len(deckJson['deck']) +
len(deckJson['character']) * 2))
baseImage = Image.new('RGB', (IMG_HERO_DECK.width * baseX,
IMG_HERO_DECK.height * baseX))
baseX = math.ceil(math.sqrt(len(deckJson['deck']) + len(deckJson['character']) * 2))
baseImage = Image.new('RGB', (CARD_WIDTH * baseX,
CARD_HEIGHT * baseX))
cardType = deckJson["type"]
cardNum = 0
# Make a card for each hero character card
for card in deckJson['character']:
cardFrontImg = IMG_HERO_CHAR_FRONT.copy()
draw = ImageDraw.Draw(cardFrontImg)
drawTextIf(draw, (150, 70), FONT_CHAR_HP, card, 'name')
drawTextIf(draw, (100, 900), FONT_DESCRIPTION, card, 'power', 40)
drawTextIf(draw, (53, 260), FONT_CHAR_HP, card, 'hp')
addCardToBase(baseImage, baseX, cardNum, cardFrontImg)
cardNum += 1
# Make a card for each character card
if cardType == "hero":
for card in deckJson['character']:
makeFace(baseImage, baseX, cardNum,
os.path.join("images", cardType, "charFront.svg"), card)
cardNum += 1
cardBackImg = IMG_HERO_CHAR_BACK.copy()
draw = ImageDraw.Draw(cardBackImg)
drawTextIf(draw, (150, 70), FONT_CHAR_HP, card, 'name')
drawTextIf(draw, (80, 800), FONT_DESCRIPTION, card, 'incapacitated', 43)
addCardToBase(baseImage, baseX, cardNum, cardBackImg)
cardNum += 1
makeFace(baseImage, baseX, cardNum,
os.path.join("images", cardType, "charBack.svg"), card)
cardNum += 1
# Make a card for each card in the deck
if cardType == "villain":
pass
# Make a card for each card
for card in deckJson['deck']:
cardImg = IMG_HERO_DECK.copy()
if 'hp' in card: # Draw HP marker, if it exists
# self mask for transparency
cardImg.paste(IMG_TARGET_HP, (510, 36), IMG_TARGET_HP)
draw = ImageDraw.Draw(cardImg)
drawTextIf(draw, (52, 50), FONT_TITLE, card, 'name')
drawTextIf(draw, (85, 610), FONT_KEYWORD, card, 'type')
drawTextIf(draw, (52, 670), FONT_DESCRIPTION, card, 'description', 46)
drawTextIf(draw, (120, 910), FONT_DESCRIPTION, card, 'flavor')
drawTextIf(draw, (600, 40), FONT_TARGET_HP, card, 'hp')
addCardToBase(baseImage, baseX, cardNum, cardImg)
makeFace(baseImage, baseX, cardNum,
os.path.join("images", cardType, "card.svg"), card)
cardNum += 1
baseImage.save(outfile + ".png", "PNG")
return (baseX, baseX)
return baseX
def makeJson(deckJson, imgWidth, imgHeight, outfile):
def makeJson(deckJson, imgWidth, outfile):
with open(bundle_dir + "/templates/deck.json") as f:
outJson = json.load(f)
with open(bundle_dir + "/templates/card.json") as f:
@ -98,7 +103,7 @@ def makeJson(deckJson, imgWidth, imgHeight, outfile):
# number of cards in x and y direction
outJson['ObjectStates'][0]['CustomDeck']['1'].update(
{"NumWidth": imgWidth,
"NumHeight": imgHeight,
"NumHeight": imgWidth,
#"FaceURL": "http://adam-desktop.dyn.wpi.edu:8000/" + outfile + ".png",
"FaceURL": "file://" + os.getcwd() + "/" + outfile + ".png",
"BackURL": "http://cloud-3.steamusercontent.com/ugc/156906385556221451/CE2C3AFE1759790CB0B532FFD636D05A99EC91F4/"})
@ -111,7 +116,7 @@ def makeJson(deckJson, imgWidth, imgHeight, outfile):
# add front card with back as second state
cardOutBack = cardTemplate.copy()
cardOutBack.update({"Nickname": card['name'],
"Description": "Incapacitated",
"Description": "Incapacitated",
"CardID": cardNum + 1})
cardOut = cardTemplate.copy()
cardOut.update({"Nickname": card['name'],
@ -148,15 +153,5 @@ if __name__ == '__main__':
with open(inputJson) as f:
deckJson = json.load(f)
imgWidth, imgHeight = makeFaces(deckJson, outfile)
makeJson(deckJson, imgWidth, imgHeight, outfile)
else:
with open("hero_3.json") as f:
deckJson = json.load(f)
outfile = "out"
imgWidth, imgHeight = makeFaces(deckJson, outfile)
makeJson(deckJson, imgWidth, imgHeight, outfile)
imgWidth = makeFaces(deckJson, outfile)
makeJson(deckJson, imgWidth, outfile)

1918
images/environment/card.svg Normal file

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 409 KiB

2599
images/hero/card.svg Normal file

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 492 KiB

9211
images/hero/charBack.svg Normal file

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 2.5 MiB

9345
images/hero/charFront.svg Normal file

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 2.5 MiB

File diff suppressed because one or more lines are too long

5483
images/source/Hero Deck.ai Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

29
templates/card.json Normal file
View File

@ -0,0 +1,29 @@
{
"Name": "Card",
"Transform": {
"posX": 0,
"posY": 0,
"posZ": 0,
"rotX": 0,
"rotY": 0,
"rotZ": 0,
"scaleX": 1.0,
"scaleY": 1.0,
"scaleZ": 1.0
},
"Nickname": "",
"Description": "",
"ColorDiffuse": {
"r": 0.713235259,
"g": 0.713235259,
"b": 0.713235259
},
"Locked": false,
"Grid": true,
"Snap": true,
"Autoraise": true,
"Sticky": true,
"Tooltip": true,
"CardID": 0,
"SidewaysCard": false
}

44
templates/deck.json Normal file
View File

@ -0,0 +1,44 @@
{
"SaveName": "",
"GameMode": "",
"Date": "",
"Table": "",
"Sky": "",
"Note": "",
"Rules": "",
"PlayerTurn": "",
"ObjectStates": [
{
"Name": "DeckCustom",
"Transform": {
"posX": 0,
"posY": 0,
"posZ": 0,
"rotX": 0,
"rotY": 0.0,
"rotZ": 0.0,
"scaleX": 1.0,
"scaleY": 1.0,
"scaleZ": 1.0
},
"Nickname": "",
"Description": "",
"ColorDiffuse": {
"r": 0.713239133,
"g": 0.713239133,
"b": 0.713239133
},
"Grid": true,
"Locked": false,
"SidewaysCard": false,
"DeckIDs": [],
"CustomDeck": {
"1": {
"FaceURL": "SET ME",
"BackURL": "SET ME"
}
},
"ContainedObjects": []
}
]
}