Tools for generating tabletop cards

I’ve been working on a board game for about a year now. The code name is Calvinball because I make so many rule changes in the middle of a game. It’s a worker placement and resource management game. This post is about my latest attempt to improve the tools I use to print the cards for that game.

I’m pretty comfortable in Visio so I started out with a big Visio document with all the cards in it. This was annoying because I had a zillion instances of everything and if I wanted to change the way one of the icons looked I would have to go retouch all the cards. So I did some scripting in Visio to automate some of that. Unfortunately that made each element complex enough that Visio runs out of memory and starts corrupting things. It’s also not nearly procedural enough for my tastes and made me to a bunch of redundant dragging and clicking.

When I was trying to bring up a second card game (a coop wilderness exploration thing) I tried something completely different. Using Django and Python I generated the cards in HTML and then printed them from the browser. This kinda worked, but HTML is really not meant to have tight registration on its printed output so getting the front and back of cards to line up was constantly a problem. There were also strange scale and background color problems depending on what options I forgot to pick in the print dialog. So this approach wasn’t very satisfying either.

This weekend I started working on a new technique, and so far that’s going pretty well. I build card templates as SVG files in Inkscape. Then I load those with a Python script, process them a bit and dump out a card-specific SVG file. Then I use more Python to combine blocks of card files into pages. Then I turn those pages into PDFs and use PDFtk to make one big PDF with everything in it. At that point a PDF reader (I use FoxIt) can print everything in one batch.

The card templates are pretty straightforward. Here is one of them:

All the information required to render the cards is in one big list of dictionaries. When I get the rest of the cards switched over I’ll make this representation more powerful:

	cards = [
		{
			"params" : {
				"cardtitle" : "Construction Zone",
				"carddescription" : "Players take one victory point when they build a building.",
				},
			"back" : "back_goal",
			"front" : "front_goal",
		},
		{
			"params" : {
				"cardtitle" : "Research Center",
				"cost" : "RRR",
				},
			"back" : "back_goal",
			"front" : "front_goal_donate",
		},
		{
			"params" : {
				"cardtitle" : "Breeding Stock",
				"carddescription" : "Players take one victory point when they opt to not gain a colonist and instead donate it to the galactic core.",
				},
			"back" : "back_goal",
			"front" : "front_goal",
		},
		{
			"params" : {
				"cardtitle" : "One for All",
				"carddescription" : "Players take one victory point when they make a donation.",
				},
			"back" : "back_goal",
			"front" : "front_goal_allforone",
		},
	]

The code to combine the two is too long to paste inline, but the whole python script to do all of this is here. It walks through every entry in the cards list loading the front and back templates for each card. Then it tries to find elements for each entry in the “params” dictionary and replace the text of that element. You may have some difficulty getting this to run on any computer other than mind. Install lxml and svg_stack into Python, and put Inkscape and PDFtk in your path and you should be most of the way there.

One tricky bit is that the node layout for flowing text is different from a single line of text. This function finds the node with the text in it in each case:

def FindNode( root, name ):
	el = root.findall( ".//n:text[@id='" + name + "']/n:tspan", xmlNamespaces)
	if( el ) :
		return el[0]

	el = root.findall( ".//n:flowRoot[@id='" + name + "']/n:flowPara", xmlNamespaces )
	if( el ):
		return el[0]

	return None

It’s also worth noting that flowRoot doesn’t seem to work anywhere but Inkscape. Apparently it was in the final spec for some version of SVG that nobody supports. I use it for descriptions on some cards where I need word wrapping.

The other kind of parameterization that’s currently supported is recoloring or hiding the resource icons to match the cost string on the card definition. That just finds paths under a group with a special ID and sets their style to something specific to that resource type:

resourceStyles = {
	"F" : "fill: #16CD22; stroke: #000000; stroke-width: 0.5;",
	"E" : "fill: #D5FF2D; stroke: #000000; stroke-width: 0.5;",
	"M" : "fill: #404040; stroke: #000000; stroke-width: 0.5;",
	"R" : "fill: #FF69F7; stroke: #000000; stroke-width: 0.5;",
	"C" : "fill: #0000F7; stroke: #000000; stroke-width: 0.5;",
}
hiddenResourceStyle = "opacity:0"

def SubstCost( root, cost ):
	for n in range( 0, 15 ):
		style = hiddenResourceStyle
		if( n < len( cost ) ):
			style = resourceStyles[ cost[n] ]

		el = root.findall( ".//n:g[@id='cost" + str(n) + "']/n:path", xmlNamespaces )
		for path in el:
			path.set( "style", style )

The card SVG files are all dumped into a directory that contains all the script's output. For my game I write card back and fronts for every card. Most of the cards in my game are double-sided so having a shared back between multiple cards is actually kinda rare. You might be able to do something less spammy. Here are all the files generated by my initial test case:

From there the script builds "pages" where a page is a list of svg filenames. With standard 2.5" x 3.5" cards you can fit 8 cards on an 8.5" x 11.00" page, so every 8 cards forms a page. The pages alternate between fronts and backs with the fronts shown top to bottom and the backs shown bottom to top (to match the duplex feature on my printer.) If there aren't an even multiple of 8 cards blank cards are added to the pages so that each page is exactly 7" x 10". See lines 140-170 in the script for the code that does this pagination.

Using the lists of SVG filenames the script uses a Python module called svg_stack to combine them into bigger SVG files. svg_stack has horizontal and vertical box layouts, but it's pretty easy to combine those two to get a 2x4 grid to fit 8 cards per page. those cards are then written out to an SVG file that looks like this:

From there it's a quick trip through the Inkscape command line to converting that 7" x 10" SVG into a 7" x 10" PDF:
inkscape -f output\page0.svg -A output\page0.pdf

And once I have a bunch of those files I can cat them all together with pdftk:
pdftk output\*.pdf cat output output\allcards.pdf

Then you can print the whole thing in a PDF reader. In FoxIt I have to make sure to turn off scaling and auto-center on an 8.5"x11.0" page when I print:

Hopefully that brain dump is useful to somebody! I sure wish I'd had something like this to read yesterday morning.

~Joe


2 Responses to “Tools for generating tabletop cards”

  1. Dustin said on :

    This is definitely useful to me! I am working on a printed card/tile game and as we’re playing with rule changes it would be great to make a script change and re-print everything. Do you still using the same workflow now or have you made changes?

  2. Joe commented on :

    I’m still using more or less the same workflow. I have rotated the cards and switched to 9-up to make the output compatible with my KardKutter. I’ve also added more functionality here and there to handle the particular needs of the couple of games I’m working on. Other than that it’s still this set of tools.

Leave a Reply