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__/ /__pycache__/
/build/ /build/
/dist/ /dist/
/out/**

View File

@ -1,95 +1,100 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
#note: image templates: https://boardgamegeek.com/thread/813176/card-templates
#TODO: dynamic text sizes
#TODO: remote upload?
import json import json
from PIL import Image, ImageDraw, ImageFont
import textwrap
import math import math
import os import os
import subprocess
import sys import sys
from lxml import etree
from PIL import Image
if getattr(sys, 'frozen', False): # we are running in a bundle if getattr(sys, 'frozen', False): # we are running in a bundle
bundle_dir = sys._MEIPASS bundle_dir = sys._MEIPASS
else: # we are running in a normal Python environment else: # we are running in a normal Python environment
bundle_dir = os.path.dirname(os.path.abspath(__file__)) bundle_dir = os.path.dirname(os.path.abspath(__file__))
IMG_HERO_CHAR_FRONT = Image.open(bundle_dir + "/images/HeroCharFront.png") CARD_WIDTH = 181 * 4
IMG_HERO_CHAR_BACK = Image.open(bundle_dir + "/images/HeroCharBack.png") CARD_HEIGHT = 253 * 4
IMG_HERO_DECK = Image.open(bundle_dir + "/images/HeroCard.png")
IMG_TARGET_HP = Image.open(bundle_dir + "/images/targetHP.png")
FONT_KEYWORD = ImageFont.truetype(bundle_dir + "/fonts/RedStateBlueStateBB_reg.otf", size=40) def setText(tree, id, text):
FONT_TITLE = ImageFont.truetype(bundle_dir + "/fonts/CrashLandingBB.otf", size=60) element = tree.find('.//*[@id="' + id + '"]')
FONT_DESCRIPTION = ImageFont.truetype(bundle_dir + "/fonts/RedStateBlueStateBB_reg.otf", size=30) if element is None:
FONT_TARGET_HP = ImageFont.truetype(bundle_dir + "/fonts/CrashLandingBB.otf", size=120) print("id", id, "not found")
FONT_CHAR_HP = ImageFont.truetype(bundle_dir + "/fonts/ap.ttf", size=120) 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): def removeElement(tree, id):
if key in card: mark = tree.find('.//*[@id="' + id + '"]')
text = str(card[key]) if mark is not None:
mark.getparent().remove(mark)
if wrap: def makeSVG(base, properties):
# badly wrap text to fit on card tree = etree.parse(base)
text = '\n'.join(textwrap.fill(line.strip(), wrap, for id, text in properties.items():
break_long_words=False, if id not in ["count", "art"]:
replace_whitespace=False) setText(tree, id, text)
for line in text.splitlines())
draw.text(position, text, font=font, fill="#000000")
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, baseImg.paste(cardImg, ((cardNum % baseX) * cardImg.width,
int(cardNum / baseX) * cardImg.height)) 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): 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))
baseX = math.ceil(math.sqrt(len(deckJson['deck']) + baseImage = Image.new('RGB', (CARD_WIDTH * baseX,
len(deckJson['character']) * 2)) CARD_HEIGHT * baseX))
baseImage = Image.new('RGB', (IMG_HERO_DECK.width * baseX,
IMG_HERO_DECK.height * baseX)) cardType = deckJson["type"]
cardNum = 0 cardNum = 0
# Make a card for each hero character card # Make a card for each character card
for card in deckJson['character']: if cardType == "hero":
cardFrontImg = IMG_HERO_CHAR_FRONT.copy() for card in deckJson['character']:
draw = ImageDraw.Draw(cardFrontImg) makeFace(baseImage, baseX, cardNum,
drawTextIf(draw, (150, 70), FONT_CHAR_HP, card, 'name') os.path.join("images", cardType, "charFront.svg"), card)
drawTextIf(draw, (100, 900), FONT_DESCRIPTION, card, 'power', 40) cardNum += 1
drawTextIf(draw, (53, 260), FONT_CHAR_HP, card, 'hp')
addCardToBase(baseImage, baseX, cardNum, cardFrontImg)
cardNum += 1
cardBackImg = IMG_HERO_CHAR_BACK.copy() makeFace(baseImage, baseX, cardNum,
draw = ImageDraw.Draw(cardBackImg) os.path.join("images", cardType, "charBack.svg"), card)
drawTextIf(draw, (150, 70), FONT_CHAR_HP, card, 'name') cardNum += 1
drawTextIf(draw, (80, 800), FONT_DESCRIPTION, card, 'incapacitated', 43)
addCardToBase(baseImage, baseX, cardNum, cardBackImg)
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']: for card in deckJson['deck']:
cardImg = IMG_HERO_DECK.copy() makeFace(baseImage, baseX, cardNum,
os.path.join("images", cardType, "card.svg"), card)
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)
cardNum += 1 cardNum += 1
baseImage.save(outfile + ".png", "PNG") 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: with open(bundle_dir + "/templates/deck.json") as f:
outJson = json.load(f) outJson = json.load(f)
with open(bundle_dir + "/templates/card.json") as 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 # number of cards in x and y direction
outJson['ObjectStates'][0]['CustomDeck']['1'].update( outJson['ObjectStates'][0]['CustomDeck']['1'].update(
{"NumWidth": imgWidth, {"NumWidth": imgWidth,
"NumHeight": imgHeight, "NumHeight": imgWidth,
#"FaceURL": "http://adam-desktop.dyn.wpi.edu:8000/" + outfile + ".png", #"FaceURL": "http://adam-desktop.dyn.wpi.edu:8000/" + outfile + ".png",
"FaceURL": "file://" + os.getcwd() + "/" + outfile + ".png", "FaceURL": "file://" + os.getcwd() + "/" + outfile + ".png",
"BackURL": "http://cloud-3.steamusercontent.com/ugc/156906385556221451/CE2C3AFE1759790CB0B532FFD636D05A99EC91F4/"}) "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 # add front card with back as second state
cardOutBack = cardTemplate.copy() cardOutBack = cardTemplate.copy()
cardOutBack.update({"Nickname": card['name'], cardOutBack.update({"Nickname": card['name'],
"Description": "Incapacitated", "Description": "Incapacitated",
"CardID": cardNum + 1}) "CardID": cardNum + 1})
cardOut = cardTemplate.copy() cardOut = cardTemplate.copy()
cardOut.update({"Nickname": card['name'], cardOut.update({"Nickname": card['name'],
@ -148,15 +153,5 @@ if __name__ == '__main__':
with open(inputJson) as f: with open(inputJson) as f:
deckJson = json.load(f) deckJson = json.load(f)
imgWidth, imgHeight = makeFaces(deckJson, outfile) imgWidth = makeFaces(deckJson, outfile)
makeJson(deckJson, imgWidth, imgHeight, outfile) makeJson(deckJson, imgWidth, 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)

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": []
}
]
}