dominiontabs/domdiv/__init__.py
Nick Vance 250a9e4b51 Card db refactoring (#105)
* Code refactoring for card db change (#86)
This assumes the following file structure:
```
card_db/
    cards_db.json
    sets_db.json
    <language>/
        cards_text.json
        sets_text.json
```
Files that are needed are:

- cards_db.json
- cards_text.json and sets_text.json for each supported language

**Other changes:**

Added option called `--edition` that can be "1", "2", "latest", or "all". Defaults to "all".
This allows for a quick filtering for those that don't want everything (i.e., "all").

- "1" is for all 1st editions of expansions. It also includes 2nd edition update packs.
- "2" is for all 2nd editions of expansions. So base, Dominion 2nd ed., and Intrigue 2nd ed. (no update packs, since already in 2nd edition)
- "latest" is for base, Dominion 2nd ed., and Intrigue 2nd ed., and all the remaining 1st editions.

Cards can be grouped 3 ways:

- No grouping (default)
- In expansion grouping invoked with `--special_card_groups`.
- Grouping across expansions with `--exclude_events` and `--exclude_landmarks`.  These groups are placed in a set called "Extras".   `--exclude_prizes` is not currently implemented.

Added an option called `--upgrade_with_expansion` which will put the upgraded cards into the corresponding earlier expansion.  So all 1st edition cards as well as the upgrade cards appear in the 1st edition set.  That way the cards are listed on the expansion dividers and any tab/ordering fit the expansion as a whole.  And because of the deleted cards in 2nd edition, this is a different list of cards than just using the 2nd edition.

* update set image mapping for 2nd edition icons
* add improved set icons from https://boardgamegeek.com/filepage/73273/dominion-card-icons-vector-images
* recompress all images
* new format for cards_db.json and translations
* Added short name to sets
* Updates to allow blank set images for base cards and fix blank set image for the "Extras" set.  Also removed base_set.png which is no longer needed (again!)
* scaled all set images to be exactly 300 x 300 pixels for consistency and better printing
* Updated __init__.py and cards.py to automatically find the "lowest cost" value from all the cards in a group.
* Update carddb_tests.py
  * Updated set information in testcases
  * Changed test of cardset to test cardset_tag instead.  Since that is what everything keys off of now.
  * Updated the language tests to pull in the language parts before making the check.
* Standardize on ISO8859-15 (#98)
* Remove Trash Card from 2nd edition, replace HTML line breaks & unicode
* Better Error Handling for Font Errors
* Added text formating codes
Added text formatting codes for "extra" and "description" fields.  This includes:
<tab> and <t> to add a tab (4 spaces)
<n> as an alternative to \n (hard new line)
<br> as an alternative to <br /> (soft new line)
<c> and <center> to center text in this paragraph until the next hard new line
<l> and <left> to left align text in this paragraph until the next hard new line
<r> and <right> to right align text in this paragraph until the next hard new line.
<line> to add a hard new line, a centered dividing line, and a trailing hard new line.
<line> to put a centered line
This goes with the <b>..</b> for bold, <i>..</i> for italics, and <u>..</u> for underline that was already existing (but probably not remembered.
* Update card count presentation (#116)
* wvoigt added automatic bonus highlighting (#119)
2016-12-10 21:05:34 -08:00

1044 lines
41 KiB
Python

import os
import codecs
import json
import sys
import argparse
import copy
import reportlab.lib.pagesizes as pagesizes
from reportlab.lib.units import cm
from cards import Card
from cards import CardType
from draw import DividerDrawer
LOCATION_CHOICES = ["tab", "body-top", "hide"]
NAME_ALIGN_CHOICES = ["left", "right", "centre", "edge"]
TAB_SIDE_CHOICES = ["left", "right", "left-alternate", "right-alternate",
"centre", "full"]
TEXT_CHOICES = ["card", "rules", "blank"]
EDITION_CHOICES = ["1", "2", "latest", "all"]
EXPANSION_CHOICES = ["adventures", "alchemy", "cornucopia", "dark ages",
"dominion1stEdition", "dominion2ndEdition", "dominion2ndEditionUpgrade",
"empires", "guilds", "hinterlands",
"intrigue1stEdition", "intrigue2ndEdition", "intrigue2ndEditionUpgrade",
"promo", "prosperity", "seaside"]
LANGUAGE_DEFAULT = 'en_us' # the primary language used if a language's parts are missing
LANGUAGE_XX = 'xx' # a dummy language for starting translations
def get_languages(path):
languages = []
for name in os.listdir(path):
dir_path = os.path.join(path, name)
if os.path.isdir(dir_path):
cards_file = os.path.join(dir_path, "cards_" + name + ".json")
sets_file = os.path.join(dir_path, "sets_" + name + ".json")
types_file = os.path.join(dir_path, "types_" + name + ".json")
if (os.path.isfile(cards_file) and os.path.isfile(sets_file) and os.path.isfile(types_file)):
languages.append(name)
if LANGUAGE_XX in languages:
languages.remove(LANGUAGE_XX)
return languages
LANGUAGE_CHOICES = get_languages("card_db")
def add_opt(options, option, value):
assert not hasattr(options, option)
setattr(options, option, value)
def parse_opts(arglist):
parser = argparse.ArgumentParser(
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
description="Generate Dominion Dividers",
epilog="Source can be found at 'https://github.com/sumpfork/dominiontabs'. "
"An online version can be found at 'http://domtabs.sandflea.org/'. ")
# Basic Divider Information
group_basic = parser.add_argument_group(
'Basic Divider Options',
'Basic choices for the dividers.')
group_basic.add_argument(
'--outfile', '-o',
dest="outfile",
default="dominion_dividers.pdf",
help="The output file name.")
group_basic.add_argument(
"--papersize",
dest="papersize",
default=None,
help="The size of paper to use; '<%%f>x<%%f>' (size in cm), or 'A4', or 'LETTER'. "
"If not specified, it will default to system defaults, and if the system defaults "
"are not found, then to 'LETTER'.")
group_basic.add_argument(
"--language", "-l",
dest="language",
default=LANGUAGE_DEFAULT,
choices=LANGUAGE_CHOICES,
help="Language of divider text.")
group_basic.add_argument(
"--orientation",
choices=["horizontal", "vertical"],
dest="orientation",
default="horizontal",
help="Either horizontal or vertical divider orientation.")
group_basic.add_argument(
"--size",
dest="size",
default='normal',
help="Dimentions of the cards to use with the dividers '<%%f>x<%%f>' (size in cm), "
"or 'normal' = '9.1x5.9', or 'sleeved' = '9.4x6.15'.")
group_basic.add_argument(
"--sleeved",
action="store_true",
dest="sleeved",
help="Same as --size=sleeved.")
group_basic.add_argument(
"--order",
choices=["expansion", "global", "colour", "cost"],
default="expansion",
dest="order",
help="Sort order for the dividers: "
" 'global' will sort by card name;"
" 'expansion' will sort by expansion, then card name;"
" 'colour' will sort by card type, then card name;"
" 'cost' will sort by expansion, then card cost, then name.")
# Divider Body
group_body = parser.add_argument_group(
'Divider Body',
'Changes what is displayed on the body of the dividers.')
group_body.add_argument(
"--front",
choices=TEXT_CHOICES,
dest="text_front",
default="card",
help="Text to print on the front of the divider; "
"'card' will print the text from the game card; "
"'rules' will print additional rules for the game card; "
"'blank' will not print text on the divider.")
group_body.add_argument(
"--back",
choices=TEXT_CHOICES + ["none"],
dest="text_back",
default="rules",
help="Text to print on the back of the divider; "
"'card' will print the text from the game card; "
"'rules' will print additional rules for the game card; "
"'blank' will not print text on the divider; "
"'none' will prevent the back pages from printing. ")
group_body.add_argument(
"--count",
action="store_true",
dest="count",
help="Display card count on body of the divider.")
group_body.add_argument(
"--types",
action="store_true",
dest="types",
help="Display card type on the body of the divider.")
# Divider Tab
group_tab = parser.add_argument_group(
'Divider Tab',
'Changes what is displayed on on the Divider Tab.')
group_tab.add_argument(
"--tab_side",
choices=TAB_SIDE_CHOICES,
dest="tab_side",
default="right-alternate",
help="Alignment of tab; "
"'left'/'right' forces all tabs to left/right side; "
"'left-alternate' will start on the left and then toggle between left and right for the tabs; "
"'right-alternate' will start on the right and then toggle between right and left for the tabs; "
"'centre' will force all label tabs to the centre; "
"'full' will force all label tabs to be full width of the divider.")
group_tab.add_argument(
"--tab_name_align",
choices=NAME_ALIGN_CHOICES + ["center"],
dest="tab_name_align",
default="left",
help="Alignment of text on the tab; "
"The 'edge' option will align the card name to the outside edge of the "
"tab, so that when using tabs on alternating sides, "
"the name is less likely to be hidden by the tab in front "
"(edge will revert to left when tab_side is full since there is no edge in that case).")
group_tab.add_argument(
"--tabwidth",
type=float,
default=4.0,
help="Width in cm of stick-up tab (ignored if --tab_side is 'full' or --tabs_only is used).")
group_tab.add_argument(
"--cost",
action="append",
choices=LOCATION_CHOICES,
default=['tab'],
help="Where to display the card cost; may be set to "
"'hide' to indicate it should not be displayed, or "
"given multiple times to show it in multiple places.")
group_tab.add_argument(
"--set_icon",
action="append",
choices=LOCATION_CHOICES,
default=['tab'],
help="Where to display the set icon; may be set to "
"'hide' to indicate it should not be displayed, or "
"given multiple times to show it in multiple places.")
group_tab.add_argument(
"--no-tab-artwork",
action="store_true",
dest="no_tab_artwork",
help="Don't show background artwork on tabs.")
group_tab.add_argument(
"--use-text-set-icon",
action="store_true",
dest="use_text_set_icon",
help="Use text/letters to represent a card's set instead of the set icon.")
# Expanion Dividers
group_expansion = parser.add_argument_group(
'Expansion Dividers',
'Adding separator dividers for each expansion.')
group_expansion.add_argument(
"--expansion_dividers",
action="store_true",
dest="expansion_dividers",
help="Add dividers describing each expansion set. "
"A list of cards in the expansion will be shown on the front of the divider.")
group_expansion.add_argument(
"--centre_expansion_dividers",
action="store_true",
dest="centre_expansion_dividers",
help='Centre the tabs on expansion dividers.')
group_expansion.add_argument(
"--expansion_dividers_long_name",
action="store_true",
dest="expansion_dividers_long_name",
help="Use the long name with edition information on the expansion divider tab. "
"Without this, the shorter expansion name is used on the expansion divider tab.")
# Divider Selection
group_select = parser.add_argument_group(
'Divider Selection',
'What expansions are used, and grouping of dividers.')
group_select.add_argument(
"--expansions", "--expansion",
nargs="*",
action="append",
dest="expansions",
help="Limit dividers to only the specified expansions. "
"If no limits are set, then all expansions are included. "
"Expansion names can also be given in the language specified by "
"the --language parameter. Any expansion with a space in the name must "
"be enclosed in quotes. This may be called multiplle times. "
"Values are not case sensitive and can also match the starting characters "
"of an expansion name. For example, 'dominion' will match all expansions "
"that start with that name; Choices available in all languages include: %s" %
", ".join("%s" % x for x in EXPANSION_CHOICES))
group_select.add_argument(
"--edition",
choices=EDITION_CHOICES,
dest="edition",
default="all",
help="Editions to include: "
"'1' is for all 1st Editions; "
"'2' is for all 2nd Editions; "
"'latest' is for the latest edition for each expansion; "
"'all' is for all editions of expansions; "
" This can be combined with other options to refine the expansions to include in the output.")
group_select.add_argument(
"--upgrade_with_expansion",
action="store_true",
dest="upgrade_with_expansion",
help="Include any new edition upgrade cards with the expansion being upgraded.")
group_select.add_argument(
"--base_cards_with_expansion",
action="store_true",
help="Print the base cards as part of the expansion (i.e., a divider for 'Silver' "
"will be printed as both a 'Dominion' card and as an 'Intrigue 1st Edition' card). "
"If this option is not given, all base cards are placed in their own 'Base' expansion.")
group_select.add_argument(
"--special_card_groups",
action="store_true",
help="Group cards that generally are used together "
"(e.g., Shelters, Tournament and Prizes, Urchin/Mercenary, etc.).")
group_select.add_argument(
"--include_blanks",
action="store_true",
help="Include a few dividers with extra text.")
group_select.add_argument(
"--exclude_events",
action="store_true",
help="Group all 'Event' cards across all expansions into one divider.")
group_select.add_argument(
"--exclude_landmarks",
action="store_true",
help="Group all 'Landmark' cards across all expansions into one divider.")
# Divider Sleeves/Wrappers
group_wrapper = parser.add_argument_group(
'Card Sleeves/Wrappers',
'Generating dividers that are card sleeves/wrappers.')
group_wrapper.add_argument(
"--wrapper",
action="store_true",
dest="wrapper",
help="Draw sleeves (aka wrapper) for the cards instead of a divider for the cards.")
group_wrapper.add_argument(
"--thickness",
type=float,
default=2.0,
help="Thickness of a stack of 60 cards (Copper) in centimeters. "
"Typically unsleeved cards are 2.0, thin sleeved cards are 2.4, and thick sleeved cards are 3.2. "
"This is only valid with the --wrapper option.")
group_wrapper.add_argument(
"--sleeved_thick",
action="store_true",
dest="sleeved_thick",
help="Same as --size=sleeved --thickness 3.2.")
group_wrapper.add_argument(
"--sleeved_thin",
action="store_true",
dest="sleeved_thin",
help="Same as --size=sleeved --thickness 2.4.")
group_wrapper.add_argument(
"--notch_length",
type=float,
default=0.0,
help="Length of thumb notch on wrapper in centimeters "
"(a value of 0.0 means no notch on wrapper). "
"This can make it easier to remove the actual cards from the wrapper. "
"This is only valid with the --wrapper option.")
group_wrapper.add_argument(
"--notch",
action="store_true",
dest="notch",
help="Same as --notch_length thickness 1.5.")
# Printing
group_printing = parser.add_argument_group(
'Printing',
'Changes how the Dividers are printed.')
group_printing.add_argument(
"--minmargin",
dest="minmargin",
default="1x1",
help="Page margin in cm in the form '<%%f>x<%%f>', left/right x top/bottom).")
group_printing.add_argument(
"--cropmarks",
action="store_true",
dest="cropmarks",
help="Print crop marks on both sides, rather than tab outlines on the front side.")
group_printing.add_argument(
"--linewidth",
type=float,
default=0.1,
help="Width of lines for card outlines and crop marks.")
group_printing.add_argument(
"--back_offset",
type=float,
dest="back_offset",
default=0,
help="Back page horizontal offset points to shift to the right. Only needed for some printers.")
group_printing.add_argument(
"--back_offset_height",
type=float,
dest="back_offset_height",
default=0,
help="Back page vertical offset points to shift upward. Only needed for some printers.")
group_printing.add_argument(
"--vertical_gap",
type=float,
default=0.0,
help="Vertical gap between dividers in centimeters.")
group_printing.add_argument(
"--horizontal_gap",
type=float,
default=0.0,
help="Horizontal gap between dividers in centimeters.")
group_printing.add_argument(
"--no-page-footer",
action="store_true",
dest="no_page_footer",
help="Do not print the expansion name at the bottom of the page.")
group_printing.add_argument(
"--num_pages",
type=int,
default=-1,
help="Stop generating dividers after this many pages, -1 for all.")
group_printing.add_argument(
"--tabs-only",
action="store_true",
dest="tabs_only",
help="Draw only the divider tabs and no divider outlines. "
"Used to print the divider tabs on labels.")
# Special processing
group_special = parser.add_argument_group(
'Miscellaneous',
'These options are generally not used.')
group_special.add_argument(
"--cardlist",
dest="cardlist",
help="Path to file that enumerates each card to be printed on its own line.")
group_special.add_argument(
"--write_json",
action="store_true",
dest="write_json",
help="Write json version of card definitions and extras.")
options = parser.parse_args(arglist)
if options.sleeved_thick:
options.thickness = 3.2
options.sleeved = True
if options.sleeved_thin:
options.thickness = 2.4
options.sleeved = True
if options.notch:
options.notch_length = 1.5
if options.expansions:
# options.expansions is a list of lists. Reduce to single list
options.expansions = [item for sublist in options.expansions for item in sublist]
return options
def parseDimensions(dimensionsStr):
x, y = dimensionsStr.upper().split('X', 1)
return (float(x) * cm, float(y) * cm)
def generate_sample(options):
import cStringIO
from wand.image import Image
buf = cStringIO.StringIO()
options.num_pages = 1
generate(options, '.', buf)
with Image(blob=buf.getvalue()) as sample:
sample.format = 'png'
sample.save(filename='sample.png')
def parse_papersize(spec):
papersize = None
if not spec:
if os.path.exists("/etc/papersize"):
papersize = open("/etc/papersize").readline().upper()
else:
papersize = 'LETTER'
else:
papersize = spec.upper()
try:
paperwidth, paperheight = getattr(pagesizes, papersize)
except AttributeError:
try:
paperwidth, paperheight = parseDimensions(papersize)
print 'Using custom paper size, %.2fcm x %.2fcm' % (
paperwidth / cm, paperheight / cm)
except ValueError:
paperwidth, paperheight = pagesizes.LETTER
return paperwidth, paperheight
def parse_cardsize(spec, sleeved):
spec = spec.upper()
if spec == 'SLEEVED' or sleeved:
dominionCardWidth, dominionCardHeight = (9.4 * cm, 6.15 * cm)
print 'Using sleeved card size, %.2fcm x %.2fcm' % (
dominionCardWidth / cm, dominionCardHeight / cm)
elif spec in ['NORMAL', 'UNSLEEVED']:
dominionCardWidth, dominionCardHeight = (9.1 * cm, 5.9 * cm)
print 'Using normal card size, %.2fcm x%.2fcm' % (
dominionCardWidth / cm, dominionCardHeight / cm)
else:
dominionCardWidth, dominionCardHeight = parseDimensions(spec)
print 'Using custom card size, %.2fcm x %.2fcm' % (
dominionCardWidth / cm, dominionCardHeight / cm)
return dominionCardWidth, dominionCardHeight
def read_write_card_data(options):
# Read in the card types
types_db_filepath = os.path.join(options.data_path, "card_db", "types_db.json")
with codecs.open(types_db_filepath, "r", "utf-8") as typefile:
Card.types = json.load(typefile, object_hook=CardType.decode_json)
assert Card.types, "Could not load any card types from database"
# extract unique types
type_list = []
for c in Card.types:
type_list = list(set(c.getTypeNames()) | set(type_list))
# set up the basic type translation. The actual language will be added later.
Card.type_names = {}
for t in type_list:
Card.type_names[t] = t
# turn Card.types into a dictionary for later
Card.types = dict(((c.getTypeNames(), c) for c in Card.types))
# Read in the card database
card_db_filepath = os.path.join(options.data_path, "card_db", "cards_db.json")
with codecs.open(card_db_filepath, "r", "utf-8") as cardfile:
cards = json.load(cardfile, object_hook=Card.decode_json)
assert cards, "Could not load any cards from database"
set_db_filepath = os.path.join(options.data_path, "card_db", "sets_db.json")
with codecs.open(set_db_filepath, "r", "utf-8") as setfile:
Card.sets = json.load(setfile)
assert Card.sets, "Could not load any sets from database"
for s in Card.sets:
if 'no_randomizer' not in Card.sets[s]:
Card.sets[s]['no_randomizer'] = False
# Set cardset_tag and expand cards that are used in multiple sets
new_cards = []
for card in cards:
sets = list(card.cardset_tags)
if len(sets) > 0:
# Set and save the first one
card.cardset_tag = sets.pop(0)
new_cards.append(card)
for s in sets:
# for the rest, create a copy of the first
if s:
new_card = copy.deepcopy(card)
new_card.cardset_tag = s
new_cards.append(new_card)
cards = new_cards
# Make sure each card has the right image file.
for card in cards:
card.image = card.setImage()
if options.write_json:
fpath = "cards.json"
with codecs.open(fpath, 'w', encoding='utf-8') as ofile:
json.dump(cards,
ofile,
cls=Card.CardJSONEncoder,
ensure_ascii=False,
indent=True,
sort_keys=True)
return cards
class CardSorter(object):
def __init__(self, order, baseCards):
self.order = order
if order == "global":
self.sort_key = self.by_global_sort_key
elif order == "colour":
self.sort_key = self.by_colour_sort_key
elif order == "cost":
self.sort_key = self.by_cost_sort_key
else:
self.sort_key = self.by_expansion_sort_key
self.baseCards = baseCards
# When sorting cards, want to always put "base" cards after all
# kingdom cards, and order the base cards in a set order - the
# order they are listed in the database (ie, all normal treasures
# by worth, then potion, then all normal VP cards by worth, then
# trash)
def baseIndex(self, name):
try:
return self.baseCards.index(name)
except Exception:
return -1
def isBaseExpansionCard(self, card):
return card.cardset.lower() != 'base' and card.name in self.baseCards
def by_global_sort_key(self, card):
return int(card.isExpansion()), self.baseIndex(card.name), card.name
def by_expansion_sort_key(self, card):
return card.cardset, int(card.isExpansion()), self.baseIndex(
card.name), card.name
def by_colour_sort_key(self, card):
return card.getType().getTypeNames(), card.name
def by_cost_sort_key(self, card):
return card.cardset, int(card.isExpansion()), card.get_total_cost(card), card.name
def __call__(self, card):
return self.sort_key(card)
def add_card_text(options, cards, language='en_us'):
language = language.lower()
# Read in the card text file
card_text_filepath = os.path.join(options.data_path,
"card_db",
language,
"cards_" + language.lower() + ".json")
with codecs.open(card_text_filepath, 'r', 'utf-8') as card_text_file:
card_text = json.load(card_text_file)
assert language, "Could not load card text for %r" % language
# Now apply to all the cards
for card in cards:
if card.card_tag in card_text:
if 'name' in card_text[card.card_tag].keys():
card.name = card_text[card.card_tag]['name']
if 'description' in card_text[card.card_tag].keys():
card.description = card_text[card.card_tag]['description']
if 'extra' in card_text[card.card_tag].keys():
card.extra = card_text[card.card_tag]['extra']
return cards
def add_set_text(options, sets, language='en_us'):
language = language.lower()
# Read in the set text and store for later
set_text_filepath = os.path.join(options.data_path,
"card_db",
language,
"sets_" + language + ".json")
with codecs.open(set_text_filepath, 'r', 'utf-8') as set_text_file:
set_text = json.load(set_text_file)
assert set_text, "Could not load set text for %r" % language
# Now apply to all the sets
for s in sets:
if s in set_text:
for key in set_text[s]:
sets[s][key] = set_text[s][key]
return sets
def add_type_text(options, types={}, language='en_us'):
language = language.lower()
# Read in the type text and store for later
type_text_filepath = os.path.join(options.data_path,
"card_db",
language,
"types_" + language + ".json")
with codecs.open(type_text_filepath, 'r', 'utf-8') as type_text_file:
type_text = json.load(type_text_file)
assert type_text, "Could not load type text for %r" % language
# Now apply to all the types
used = {}
for type in types:
if type in type_text:
types[type] = type_text[type]
used[type] = True
for type in type_text:
if type not in used:
types[type] = type_text[type]
return types
def add_bonus_regex(options, language='en_us'):
language = language.lower()
# Read in the bonus regex terms
bonus_regex_filepath = os.path.join(options.data_path,
"card_db",
language,
"bonuses_" + language + ".json")
with codecs.open(bonus_regex_filepath, 'r', 'utf-8') as bonus_regex_file:
bonus_regex = json.load(bonus_regex_file)
assert bonus_regex, "Could not load bonus keywords for %r" % language
if not bonus_regex:
bonus_regex = {}
return bonus_regex
def combine_cards(cards, old_card_type, new_card_tag, new_cardset_tag, new_type):
holder = Card(name='*Replace Later*',
card_tag=new_card_tag,
group_tag=new_card_tag,
cardset_tag=new_cardset_tag,
types=(new_type, ),
count=0)
holder.image = holder.setImage()
filteredCards = []
for c in cards:
if c.isType(old_card_type):
holder.addCardCount(c.count) # keep track of count and skip card
else:
filteredCards.append(c) # Not the right type, keep card
if holder.getCardCount() > 0:
filteredCards.append(holder)
return filteredCards
def filter_sort_cards(cards, options):
# Filter out cards by edition
if options.edition and options.edition != "all":
keep_sets = []
for set_tag in Card.sets:
for edition in Card.sets[set_tag]["edition"]:
if options.edition == edition:
keep_sets.append(set_tag)
keep_cards = [] # holds the cards that are to be kept
for card in cards:
if card.cardset_tag in keep_sets:
keep_cards.append(card)
cards = keep_cards
# Combine upgrade cards with their expansion
if options.upgrade_with_expansion:
for card in cards:
if card.cardset_tag == 'dominion2ndEditionUpgrade':
card.cardset_tag = 'dominion1stEdition'
elif card.cardset_tag == 'intrigue2ndEditionUpgrade':
card.cardset_tag = 'intrigue1stEdition'
# Combine all Events across all expansions
if options.exclude_events:
cards = combine_cards(cards,
old_card_type="Event",
new_type="Events",
new_card_tag='events',
new_cardset_tag='extras'
)
if options.expansions:
options.expansions.append("extras")
# Combine all Landmarks across all expansions
if options.exclude_landmarks:
cards = combine_cards(cards,
old_card_type="Landmark",
new_type="Landmarks",
new_card_tag='landmarks',
new_cardset_tag='extras'
)
if options.expansions:
options.expansions.append("extras")
# FIX THIS: Combine all Prizes across all expansions
# if options.exclude_prizes:
# cards = combine_cards(cards, 'Prize', 'prizes')
# Group all the special cards together
if options.special_card_groups:
keep_cards = [] # holds the cards that are to be kept
group_cards = {} # holds the cards for each group
for card in cards:
if not card.group_tag:
keep_cards.append(card) # not part of a group, so just keep the card
else:
# have a card in a group
if card.group_tag not in group_cards:
# First card of a group
group_cards[card.group_tag] = card # save to update cost later
# this card becomes the card holder for the whole group.
card.card_tag = card.group_tag
# These text fields should be updated later if there is a translation for this group_tag.
error_msg = "ERROR: Missing language entry for group_tab '%s'." % card.group_tag
card.name = card.group_tag # For now, change the name to the group_tab
card.description = error_msg
card.extra = error_msg
if card.isEvent():
card.cost = "*"
if card.isLandmark():
card.cost = ""
# now save the card
keep_cards.append(card)
else:
# subsequent cards in the group. Update group info, but don't keep the card.
if card.group_top:
# this is a designated card to represent the group, so update important data
group_cards[card.group_tag].cost = card.cost
group_cards[card.group_tag].potcost = card.potcost
group_cards[card.group_tag].debtcost = card.debtcost
group_cards[card.group_tag].types = card.types
group_cards[card.group_tag].image = card.image
group_cards[card.group_tag].addCardCount(card.count) # increase the count
# group_cards[card.group_tag].set_lowest_cost(card) # set holder to lowest cost of the two cards
cards = keep_cards
# Now fix up card costs
for card in cards:
if card.card_tag in group_cards:
if group_cards[card.group_tag].isEvent():
group_cards[card.group_tag].cost = "*"
group_cards[card.group_tag].debtcost = 0
group_cards[card.group_tag].potcost = 0
if group_cards[card.group_tag].isLandmark():
group_cards[card.group_tag].cost = ""
group_cards[card.group_tag].debtcost = 0
group_cards[card.group_tag].potcost = 0
# Get the final type names in the requested language
Card.type_names = add_type_text(options, Card.type_names, LANGUAGE_DEFAULT)
if options.language != LANGUAGE_DEFAULT:
Card.type_names = add_type_text(options, Card.type_names, options.language)
for card in cards:
card.types_name = ' - '.join([Card.type_names[t] for t in card.types]).upper()
# Get the card bonus keywords in the requested language
bonus = add_bonus_regex(options, LANGUAGE_DEFAULT)
Card.addBonusRegex(bonus)
if options.language != LANGUAGE_DEFAULT:
bonus = add_bonus_regex(options, options.language)
Card.addBonusRegex(bonus)
# Fix up cardset text. Waited as long as possible.
Card.sets = add_set_text(options, Card.sets, LANGUAGE_DEFAULT)
if options.language != LANGUAGE_DEFAULT:
Card.sets = add_set_text(options, Card.sets, options.language)
for card in cards:
if card.cardset_tag in Card.sets:
if 'set_name' in Card.sets[card.cardset_tag].keys():
card.cardset = Card.sets[card.cardset_tag]['set_name']
# If expansion names given, then remove any cards not in those expansions
# Expansion names can be the names from the language or the cardset_tag
if options.expansions:
options.expansions = set([e.lower() for e in options.expansions])
wantedExpansions = set()
knownExpansions = set()
# Match sets that either start with the expansion set key (used by cardset_tag)
# or the actual name of the set/expansion in the specified language.
for e in options.expansions:
for s in Card.sets:
if (s.lower().startswith(e) or
Card.sets[s].get('set_name', "").lower().startswith(e)):
wantedExpansions.add(s)
knownExpansions.add(e)
# Give indication if an imput did not match anything
unknownExpansions = options.expansions - knownExpansions
if unknownExpansions:
print "Error - unknown expansion(s): %s" % ", ".join(unknownExpansions)
# Now keep only the cards that were in the expansions requested
filteredCards = []
for c in cards:
if c.cardset_tag in wantedExpansions:
filteredCards.append(c)
cards = filteredCards
# Now add text to the cards. Waited as long as possible to catch all groupings
cards = add_card_text(options, cards, LANGUAGE_DEFAULT)
if options.language != LANGUAGE_DEFAULT:
cards = add_card_text(options, cards, options.language)
# Get list of cards from a file
if options.cardlist:
cardlist = set()
with open(options.cardlist) as cardfile:
for line in cardfile:
cardlist.add(line.strip())
if cardlist:
cards = [card for card in cards if card.name in cardlist]
# Set up the card sorter
cardSorter = CardSorter(
options.order,
[card.name for card in cards if card.cardset.lower() == 'base'])
if options.base_cards_with_expansion:
cards = [card for card in cards if card.cardset.lower() != 'base']
else:
cards = [card for card in cards
if not cardSorter.isBaseExpansionCard(card)]
# Add expansion divider
if options.expansion_dividers:
cardnamesByExpansion = {}
for c in cards:
if cardSorter.isBaseExpansionCard(c):
continue
cardnamesByExpansion.setdefault(c.cardset, []).append(c.name.strip().replace(' ', '&nbsp;'))
for set_tag, set_values in Card.sets.iteritems():
exp = set_values["set_name"]
if exp in cardnamesByExpansion:
exp_name = exp
count = len(cardnamesByExpansion[exp])
if 'no_randomizer' in set_values:
if set_values['no_randomizer']:
count = 0
if not options.expansion_dividers_long_name:
if 'short_name' in set_values:
exp_name = set_values['short_name']
c = Card(name=exp_name,
cardset=exp,
cardset_tag=set_tag,
types=("Expansion", ),
cost=None,
description=' | '.join(sorted(cardnamesByExpansion[exp])),
count=count,
card_tag=set_tag)
cards.append(c)
# Now sort what is left
cards.sort(key=cardSorter)
return cards
def calculate_layout(options, cards=[]):
dominionCardWidth, dominionCardHeight = parse_cardsize(options.size,
options.sleeved)
paperwidth, paperheight = parse_papersize(options.papersize)
if options.orientation == "vertical":
dividerWidth, dividerBaseHeight = dominionCardHeight, dominionCardWidth
else:
dividerWidth, dividerBaseHeight = dominionCardWidth, dominionCardHeight
if options.tab_name_align == "center":
options.tab_name_align = "centre"
if options.tab_side == "full" and options.tab_name_align == "edge":
# This case does not make sense since there are two tab edges in this case. So picking left edge.
print >> sys.stderr, "** Warning: Aligning card name as 'left' for 'full' tabs **"
options.tab_name_align = "left"
fixedMargins = False
if options.tabs_only:
# fixed for Avery 8867 for now
minmarginwidth = 0.86 * cm # was 0.76
minmarginheight = 1.37 * cm # was 1.27
labelHeight = 1.07 * cm # was 1.27
labelWidth = 4.24 * cm # was 4.44
horizontalBorderSpace = 0.96 * cm # was 0.76
verticalBorderSpace = 0.20 * cm # was 0.01
dividerBaseHeight = 0
dividerWidth = labelWidth
fixedMargins = True
else:
minmarginwidth, minmarginheight = parseDimensions(options.minmargin)
if options.tab_side == "full":
labelWidth = dividerWidth
else:
labelWidth = options.tabwidth * cm
labelHeight = .9 * cm
horizontalBorderSpace = options.horizontal_gap * cm
verticalBorderSpace = options.vertical_gap * cm
dividerHeight = dividerBaseHeight + labelHeight
dividerWidthReserved = dividerWidth + horizontalBorderSpace
dividerHeightReserved = dividerHeight + verticalBorderSpace
if options.wrapper:
max_card_stack_height = max(c.getStackHeight(options.thickness)
for c in cards)
dividerHeightReserved = (dividerHeightReserved * 2) + (
max_card_stack_height * 2)
print "Max Card Stack Height: {:.2f}cm ".format(max_card_stack_height)
# Notch measurements
notch_height = 0.25 * cm # thumb notch height
notch_width1 = options.notch_length * cm # thumb notch width: top away from tab
notch_width2 = 0.00 * cm # thumb notch width: bottom on side of tab
add_opt(options, 'dividerWidth', dividerWidth)
add_opt(options, 'dividerHeight', dividerHeight)
add_opt(options, 'dividerBaseHeight', dividerBaseHeight)
add_opt(options, 'dividerWidthReserved', dividerWidthReserved)
add_opt(options, 'dividerHeightReserved', dividerHeightReserved)
add_opt(options, 'labelWidth', labelWidth)
add_opt(options, 'labelHeight', labelHeight)
add_opt(options, 'notch_height', notch_height)
add_opt(options, 'notch_width1', notch_width1)
add_opt(options, 'notch_width2', notch_width2)
# as we don't draw anything in the final border, it shouldn't count towards how many tabs we can fit
# so it gets added back in to the page size here
numDividersVerticalP = int(
(paperheight - 2 * minmarginheight + verticalBorderSpace) /
options.dividerHeightReserved)
numDividersHorizontalP = int(
(paperwidth - 2 * minmarginwidth + horizontalBorderSpace) /
options.dividerWidthReserved)
numDividersVerticalL = int(
(paperwidth - 2 * minmarginwidth + verticalBorderSpace) /
options.dividerHeightReserved)
numDividersHorizontalL = int(
(paperheight - 2 * minmarginheight + horizontalBorderSpace) /
options.dividerWidthReserved)
if ((numDividersVerticalL * numDividersHorizontalL > numDividersVerticalP *
numDividersHorizontalP) and not fixedMargins):
add_opt(options, 'numDividersVertical', numDividersVerticalL)
add_opt(options, 'numDividersHorizontal', numDividersHorizontalL)
add_opt(options, 'paperheight', paperwidth)
add_opt(options, 'paperwidth', paperheight)
add_opt(options, 'minHorizontalMargin', minmarginheight)
add_opt(options, 'minVerticalMargin', minmarginwidth)
else:
add_opt(options, 'numDividersVertical', numDividersVerticalP)
add_opt(options, 'numDividersHorizontal', numDividersHorizontalP)
add_opt(options, 'paperheight', paperheight)
add_opt(options, 'paperwidth', paperwidth)
add_opt(options, 'minHorizontalMargin', minmarginheight)
add_opt(options, 'minVerticalMargin', minmarginwidth)
if not fixedMargins:
# dynamically max margins
add_opt(options, 'horizontalMargin',
(options.paperwidth - options.numDividersHorizontal *
options.dividerWidthReserved + horizontalBorderSpace) / 2)
add_opt(options, 'verticalMargin',
(options.paperheight - options.numDividersVertical *
options.dividerHeightReserved + verticalBorderSpace) / 2)
else:
add_opt(options, 'horizontalMargin', minmarginwidth)
add_opt(options, 'verticalMargin', minmarginheight)
def generate(options, data_path):
add_opt(options, 'data_path', data_path)
cards = read_write_card_data(options)
assert cards, "No cards after reading"
cards = filter_sort_cards(cards, options)
assert cards, "No cards after filtering/sorting"
calculate_layout(options, cards)
print "Paper dimensions: {:.2f}cm (w) x {:.2f}cm (h)".format(
options.paperwidth / cm, options.paperheight / cm)
print "Tab dimensions: {:.2f}cm (w) x {:.2f}cm (h)".format(
options.dividerWidthReserved / cm, options.dividerHeightReserved / cm)
print '{} dividers horizontally, {} vertically'.format(
options.numDividersHorizontal, options.numDividersVertical)
print "Margins: {:.2f}cm h, {:.2f}cm v\n".format(
options.horizontalMargin / cm, options.verticalMargin / cm)
dd = DividerDrawer()
dd.draw(cards, options)
def main(arglist, data_path):
options = parse_opts(arglist)
return generate(options, data_path)