From 0ef68bce99e3331fb213455e93b10cdbe03d6d78 Mon Sep 17 00:00:00 2001 From: Paul Molodowitch Date: Sat, 14 Dec 2013 10:15:30 -0800 Subject: [PATCH 01/11] fixes to get 4x2 landscape layout working - made it properly pick landscape if can cram more cards in that way - tabs are on alternating sides as long as horz number of tabs is even (not just 2) - set labels are printed on side (vertically) if not enough space at bottom - ...and not at all if not enough room on side OR bottom (with warning) --- dominion_tabs.py | 151 ++++++++++++++++++++++++++++++++--------------- 1 file changed, 102 insertions(+), 49 deletions(-) diff --git a/dominion_tabs.py b/dominion_tabs.py index 5795f3e..27859db 100644 --- a/dominion_tabs.py +++ b/dominion_tabs.py @@ -142,7 +142,7 @@ class DominionTabs: self.canvas.line(-2*cmw,0,-cmw,0) self.canvas.line(-2*cmw,self.tabBaseHeight,-cmw,self.tabBaseHeight) if y > 0: - self.canvas.line(-2*cmw,self.tabTotalHeight,-cmw,self.tabTotalHeight) + self.canvas.line(-2*cmw,self.tabHeight,-cmw,self.tabHeight) if mirror: self.canvas.restoreState() if y == 0: @@ -151,14 +151,14 @@ class DominionTabs: if x == 0: self.canvas.line(0,-2*cmw,0,-cmw) elif y == self.numTabsVertical-1: - self.canvas.line(self.tabWidth,self.tabTotalHeight+cmw,self.tabWidth,self.tabTotalHeight+2*cmw) + self.canvas.line(self.tabWidth,self.tabHeight+cmw,self.tabWidth,self.tabHeight+2*cmw) self.canvas.line(self.tabWidth-self.tabLabelWidth, - self.tabTotalHeight+cmw, + self.tabHeight+cmw, self.tabWidth-self.tabLabelWidth, - self.tabTotalHeight+2*cmw) + self.tabHeight+2*cmw) if x == 0: - self.canvas.line(0,self.tabTotalHeight+cmw,0,self.tabTotalHeight+2*cmw) - + self.canvas.line(0,self.tabHeight+cmw,0,self.tabHeight+2*cmw) + self.canvas.restoreState() def drawTab(self, card, rightSide): @@ -166,17 +166,17 @@ class DominionTabs: self.canvas.saveState() if not rightSide or self.options.sameside: self.canvas.translate(self.tabWidth-self.tabLabelWidth, - self.tabTotalHeight-self.tabLabelHeight) + self.tabHeight-self.tabLabelHeight) else: - self.canvas.translate(0,self.tabTotalHeight-self.tabLabelHeight) - + self.canvas.translate(0,self.tabHeight-self.tabLabelHeight) + textWidth = 85 textHeight = self.tabLabelHeight/2-7+card.getType().getTabTextHeightOffset() self.canvas.drawImage(os.path.join(self.filedir,'images',card.getType().getNoCoinTabImageFile()),1,0, self.tabLabelWidth-2,self.tabLabelHeight-1, preserveAspectRatio=False,anchor='n',mask='auto') - + if card.getType().getTypeNames() != ('Expansion',): textInset = 22 @@ -203,12 +203,12 @@ class DominionTabs: else: textInset = 13 setImageHeight = 3 + card.getType().getTabTextHeightOffset() - + #set image setImage = DominionTabs.setImages.get(card.cardset, None) if not setImage: setImage = DominionTabs.promoImages.get(card.name.lower(), None) - + if setImage: self.canvas.drawImage(os.path.join(self.filedir,'images',setImage), self.tabLabelWidth-20, setImageHeight, 14, 12, mask='auto') elif setImage == None and card.cardset != 'base' and card.getType().getTypeNames() != ('Expansion',): @@ -223,7 +223,7 @@ class DominionTabs: name_parts = (name_parts[0] + ' /', name_parts[2]) else: name_parts = name.split() - + width = pdfmetrics.stringWidth(name,'MinionPro-Regular',fontSize) while width > textWidth and fontSize > 8: fontSize -= 1 @@ -262,7 +262,7 @@ class DominionTabs: if self.options.read_yaml: descriptions = re.split("\n",card.description) else: - descriptions = re.split("--+",card.description) + descriptions = re.split("--+",card.description) height = 0 for d in descriptions: @@ -270,9 +270,9 @@ class DominionTabs: s.fontName = "Times-Roman" dmod = self.add_inline_images(d,s.fontSize) p = Paragraph(dmod,s) - textHeight = self.tabTotalHeight - self.tabLabelHeight + 0.2*cm + textHeight = self.tabHeight - self.tabLabelHeight + 0.2*cm textWidth = self.tabWidth - cm - + w,h = p.wrap(textWidth,textHeight) while h > textHeight: s.fontSize -= 1 @@ -286,7 +286,7 @@ class DominionTabs: def drawDivider(self,card,x,y,useExtra=False): #figure out whether the tab should go on the right side or not - if self.numTabsHorizontal == 2: + if self.numTabsHorizontal % 2 == 0: rightSide = x%2 == 1 else: rightSide = useExtra @@ -295,7 +295,7 @@ class DominionTabs: self.canvas.translate(self.horizontalMargin,self.verticalMargin) if useExtra: self.canvas.translate(self.options.back_offset,0) - self.canvas.translate(x*(self.tabWidth+self.horizontalBorderSpace),y*(self.tabTotalHeight+self.verticalBorderSpace)) + self.canvas.translate(x*self.totalTabWidth,y*self.totalTabHeight) #actual drawing if not self.options.tabs_only: @@ -311,7 +311,7 @@ class DominionTabs: currentCard = "" extra = "" for line in f: - m = cardName.match(line) + m = cardName.match(line) if m: if currentCard: #print 'found',currentCard @@ -384,21 +384,64 @@ class DominionTabs: def drawSetNames(self, pageCards): #print sets for this page self.canvas.saveState() - self.canvas.setFont('MinionPro-Regular',12) - sets = [] - for c in pageCards: - setTitle = c.cardset.title() - if setTitle not in sets: - sets.append(setTitle) - self.canvas.drawCentredString(self.paperwidth/2,20,'/'.join(sets)) - self.canvas.restoreState() - + + try: + # calculate the text height, font size, and orientation + maxFontsize = 12 + minFontsize = 6 + fontname = 'MinionPro-Regular' + font = pdfmetrics.getFont(fontname) + fontHeightRelative = (font.face.ascent + font.face.descent) / 1000 + + canFit = False + + layouts = [{'rotation': 0, + 'minMarginHeight': self.minVerticalMargin, + 'totalMarginHeight': self.verticalMargin, + 'width': self.paperwidth}, + {'rotation': 90, + 'minMarginHeight': self.minHorizontalMargin, + 'totalMarginHeight': self.horizontalMargin, + 'width': self.paperheight}] + + for layout in layouts: + availableMargin = layout['totalMarginHeight'] - layout['minMarginHeight'] + fontsize = availableMargin / fontHeightRelative + fontsize = min(maxFontsize, fontsize) + if fontsize >= minFontsize: + canFit = True + break + + if not canFit: + import warnings + warnings.warn("Not enough space to display set names") + return + + self.canvas.setFont(fontname,fontsize) + + sets = [] + for c in pageCards: + setTitle = c.cardset.title() + if setTitle not in sets: + sets.append(setTitle) + + xPos = layout['width'] / 2 + yPos = layout['minMarginHeight'] + availableMargin / 2 + + if layout['rotation']: + self.canvas.rotate(layout['rotation']) + yPos = -yPos + + self.canvas.drawCentredString(xPos,yPos,'/'.join(sets)) + finally: + self.canvas.restoreState() + def drawDividers(self,cards): cards = split(cards,self.numTabsVertical*self.numTabsHorizontal) for pageCards in cards: if self.options.order != "global": self.drawSetNames(pageCards) - for i,card in enumerate(pageCards): + for i,card in enumerate(pageCards): #print card x = i % self.numTabsHorizontal y = i / self.numTabsHorizontal @@ -408,7 +451,7 @@ class DominionTabs: self.canvas.showPage() if self.options.order != "global": self.drawSetNames(pageCards) - for i,card in enumerate(pageCards): + for i,card in enumerate(pageCards): #print card x = (self.numTabsHorizontal-1-i) % self.numTabsHorizontal y = i / self.numTabsHorizontal @@ -447,9 +490,9 @@ class DominionTabs: help="sort order for the cards, whether by expansion or globally alphabetical") parser.add_option("--expansion_dividers", action="store_true", dest="expansion_dividers", help="add dividers describing each expansion set") - + return parser.parse_args(argstring) - + def main(self,argstring): options,args = DominionTabs.parse_opts(argstring) fname = None @@ -495,7 +538,7 @@ class DominionTabs: if self.options.tabs_only: #fixed for Avery 8867 for now minmarginwidth=0.76*cm - minmarginheight=1.27*cm + minmarginheight=1.27*cm self.tabLabelHeight = 1.27*cm self.tabLabelWidth = 4.44*cm self.tabBaseHeight = 0 @@ -506,44 +549,54 @@ class DominionTabs: else: minmarginwidth, minmarginheight = self.options.minmargin.split ("x", 1) minmarginwidth, minmarginheight = float (minmarginwidth) * cm, float (minmarginheight) * cm - + self.tabLabelHeight = 0.9*cm self.tabLabelWidth = 4*cm self.horizontalBorderSpace = 0*cm self.verticalBorderSpace = 0*cm - - self.tabTotalHeight = self.tabBaseHeight + self.tabLabelHeight - numTabsVerticalP = int ((self.paperheight - 2*minmarginheight) / self.tabTotalHeight) - numTabsHorizontalP = int ((self.paperwidth - 2*minmarginwidth) / self.tabWidth) - numTabsVerticalL = int ((self.paperwidth - 2*minmarginwidth) / self.tabWidth) - numTabsHorizontalL = int ((self.paperheight - 2*minmarginheight) / self.tabTotalHeight) + self.tabHeight = self.tabBaseHeight + self.tabLabelHeight - if numTabsVerticalL * numTabsHorizontalL > numTabsVerticalP * numTabsHorizontalP: + self.totalTabWidth = self.tabWidth + self.horizontalBorderSpace + self.totalTabHeight = self.tabHeight + self.verticalBorderSpace + + print "Paper dimensions: %fcm (w) x %fcm (h)" % (self.paperwidth / cm, self.paperheight / cm) + print "Tab dimensions: %fcm (w) x %fcm (h)" % (self.totalTabWidth / cm, self.totalTabHeight / cm) + + numTabsVerticalP = int ((self.paperheight - 2*minmarginheight) / self.totalTabHeight) + numTabsHorizontalP = int ((self.paperwidth - 2*minmarginwidth) / self.totalTabWidth) + numTabsVerticalL = int ((self.paperwidth - 2*minmarginwidth) / self.totalTabHeight) + numTabsHorizontalL = int ((self.paperheight - 2*minmarginheight) / self.totalTabWidth) + + if numTabsVerticalL * numTabsHorizontalL > numTabsVerticalP * numTabsHorizontalP and not fixedMargins: self.numTabsVertical, self.numTabsHorizontal\ = numTabsVerticalL, numTabsHorizontalL self.paperheight, self.paperwidth = self.paperwidth, self.paperheight + self.minHorizontalMargin = minmarginheight + self.minVerticalMargin = minmarginwidth else: self.numTabsVertical, self.numTabsHorizontal\ = numTabsVerticalP, numTabsHorizontalP + self.minHorizontalMargin = minmarginwidth + self.minVerticalMargin = minmarginheight if not fixedMargins: #dynamically max margins - self.horizontalMargin = (self.paperwidth-self.numTabsHorizontal*self.tabWidth)/2 - self.verticalMargin = (self.paperheight-self.numTabsVertical*self.tabTotalHeight)/2 + self.horizontalMargin = (self.paperwidth-self.numTabsHorizontal*self.totalTabWidth)/2 + self.verticalMargin = (self.paperheight-self.numTabsVertical*self.totalTabHeight)/2 else: self.horizontalMargin = minmarginwidth self.verticalMargin = minmarginheight - print "Margins: %fcm h, %fcm v\n" % (self.horizontalMargin / cm, + print "Margins: %fcm h, %fcm v\n" % (self.horizontalMargin / cm, self.verticalMargin / cm) self.tabOutline = [(0,0,self.tabWidth,0), - (self.tabWidth,0,self.tabWidth,self.tabTotalHeight), - (self.tabWidth,self.tabTotalHeight, - self.tabWidth-self.tabLabelWidth,self.tabTotalHeight), + (self.tabWidth,0,self.tabWidth,self.tabHeight), + (self.tabWidth,self.tabHeight, + self.tabWidth-self.tabLabelWidth,self.tabHeight), (self.tabWidth-self.tabLabelWidth, - self.tabTotalHeight,self.tabWidth-self.tabLabelWidth, + self.tabHeight,self.tabWidth-self.tabLabelWidth, self.tabBaseHeight), (self.tabWidth-self.tabLabelWidth, self.tabBaseHeight,0,self.tabBaseHeight), @@ -598,7 +651,7 @@ class DominionTabs: self.canvas = canvas.Canvas(f, pagesize=(self.paperwidth, self.paperheight)) self.drawDividers(cards) self.canvas.save() - + if __name__=='__main__': import sys tabs = DominionTabs() From e65061645bd8275bcfecba818777c80e3e78b680 Mon Sep 17 00:00:00 2001 From: Paul Molodowitch Date: Sat, 14 Dec 2013 13:36:10 -0800 Subject: [PATCH 02/11] add option for controlling line-width some printers have problems displaying .1 width lines --- dominion_tabs.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/dominion_tabs.py b/dominion_tabs.py index 27859db..fb896e6 100644 --- a/dominion_tabs.py +++ b/dominion_tabs.py @@ -122,7 +122,7 @@ class DominionTabs: def drawOutline(self, x, y, rightSide, isBack=False): #draw outline or cropmarks self.canvas.saveState() - self.canvas.setLineWidth(0.1) + self.canvas.setLineWidth(self.options.linewidth) cropmarksright = (x == self.numTabsHorizontal-1) cropmarksleft = (x == 0) if rightSide and not self.options.sameside: @@ -480,6 +480,8 @@ class DominionTabs: help="subset of dominion expansions to produce tabs for") parser.add_option("--cropmarks",action="store_true",dest="cropmarks", help="print crop marks on both sides, rather than tab outlines on one") + parser.add_option("--linewidth",type="float",default=.1, + help="width of lines for card outlines/crop marks") parser.add_option("--read_yaml", action="store_true",dest="read_yaml", help="read yaml version of card definitions and extras") parser.add_option("--write_yaml", action="store_true",dest="write_yaml", From 11ba180f79639d9997c72a299414f528a039df85 Mon Sep 17 00:00:00 2001 From: Paul Molodowitch Date: Sat, 14 Dec 2013 15:51:42 -0800 Subject: [PATCH 03/11] make expansions / expansions_dividers options work when using read_yaml --- dominion_tabs.py | 30 ++++++++++++++---------------- 1 file changed, 14 insertions(+), 16 deletions(-) diff --git a/dominion_tabs.py b/dominion_tabs.py index fb896e6..6bcd644 100644 --- a/dominion_tabs.py +++ b/dominion_tabs.py @@ -618,25 +618,23 @@ class DominionTabs: cards = yaml.load(cardfile) else: cards = self.read_card_defs(os.path.join(self.filedir,"dominion_cards.txt")) - if self.options.expansions: - self.options.expansions = [o.lower() for o in self.options.expansions] - cards=[c for c in cards if c.cardset in self.options.expansions] - self.read_card_extras(os.path.join(self.filedir,"dominion_card_extras.txt"),cards) - if options.expansion_dividers: - cardnamesByExpansion = {} - for c in cards: - cardnamesByExpansion.setdefault(c.cardset,[]).append(c.name.strip()) - for exp,names in cardnamesByExpansion.iteritems(): - c = Card(exp, exp, ("Expansion",), None, ' | '.join(sorted(names))) - cards.append(c) - #print '%d cards read' % len(cards) - sets = {} - types = {} for c in cards: - sets[c.cardset] = sets.get(c.cardset,0) + 1 - types[c.types] = types.get(c.types,0) + 1 c.description = re.sub('----+','\n',c.description) c.description = re.sub('\n\s*\n','\n',c.description) + self.read_card_extras(os.path.join(self.filedir,"dominion_card_extras.txt"),cards) + + if self.options.expansions: + self.options.expansions = [o.lower() for o in self.options.expansions] + cards=[c for c in cards if c.cardset in self.options.expansions] + + if options.expansion_dividers: + cardnamesByExpansion = {} + for c in cards: + cardnamesByExpansion.setdefault(c.cardset,[]).append(c.name.strip()) + for exp,names in cardnamesByExpansion.iteritems(): + c = Card(exp, exp, ("Expansion",), None, ' | '.join(sorted(names))) + cards.append(c) + if options.write_yaml: import yaml out = yaml.dump(cards) From 3d9b2add594412b60fbfc6333ebec2118956eeee Mon Sep 17 00:00:00 2001 From: Paul Molodowitch Date: Sat, 14 Dec 2013 16:42:02 -0800 Subject: [PATCH 04/11] fix description formatting when read from dominion_cards.txt --- dominion_tabs.py | 57 +++++++++++++++++++++++++++++------------------- 1 file changed, 34 insertions(+), 23 deletions(-) diff --git a/dominion_tabs.py b/dominion_tabs.py index 6bcd644..199c8df 100644 --- a/dominion_tabs.py +++ b/dominion_tabs.py @@ -259,10 +259,7 @@ class DominionTabs: if useExtra and card.extra: descriptions = (card.extra,) else: - if self.options.read_yaml: - descriptions = re.split("\n",card.description) - else: - descriptions = re.split("--+",card.description) + descriptions = re.split("\n",card.description) height = 0 for d in descriptions: @@ -333,25 +330,42 @@ class DominionTabs: c.extra = extras[c.name] #print c.name + ' ::: ' + extra + baseactionRE = re.compile("^\s*(\+\d+\s+\w+)(?:[,.;])") + def add_definition_line(self,card,line): + # Unfortunately, the way things are specified in the old card spec + # format is somewhat haphazard. In particular: + # 1) Sometimes "basic actions", which would be separated on the + # actual card text by separate lines, are instead only separated + # by punctuation ('.', ',', or ';') + # [Example: Intrigue - Courtyard] + # 2) When there is an actual horizontal line drawn on the card, this + # can be represented using either '____' or '-----' + # 3) There are sometimes random blank lines + + # To solve: + + # 1) #try to figure out if this a 'basic action' like +X Cards or +Y Actions - baseaction = re.compile("^\s*(\+\d+\s+\w+)(?:[,.;])") - m = baseaction.match(line) - prefix = '' - while m: - prefix += line[m.start(1):m.end(1)] + '----' + descriptions = [card.description] + while True: + m = self.baseactionRE.match(line) + if not m: + break + descriptions.append(m.group(1)) line = line[m.end():] - m = baseaction.match(line) - line = prefix + line - #this is a messy way to preserve the way the card spec file indicates separation - #and add our own via '---' sequences. Needs to be completely replaced, probably - #at the same time as changing the card spec file format. - if not card.description.strip().endswith(';')\ - and not card.description.strip().endswith('---')\ - and not line.startswith('-'): - card.description += '----' + line - else: - card.description += line + + # 2) Standardize on '____' as the format for a divider line + line = line.strip() + if not line.strip('-'): + line = line.replace('-', '_') + + # 3) get rid of blank lines + descriptions.append(line) + descriptions = [x.strip() for x in descriptions] + descriptions = [x for x in descriptions if x] + + card.description = '\n'.join(descriptions) def read_card_defs(self,fname,fileobject=None): cards = [] @@ -618,9 +632,6 @@ class DominionTabs: cards = yaml.load(cardfile) else: cards = self.read_card_defs(os.path.join(self.filedir,"dominion_cards.txt")) - for c in cards: - c.description = re.sub('----+','\n',c.description) - c.description = re.sub('\n\s*\n','\n',c.description) self.read_card_extras(os.path.join(self.filedir,"dominion_card_extras.txt"),cards) if self.options.expansions: From 852a469f5088524ee2b3cb08434edb3f4109d889 Mon Sep 17 00:00:00 2001 From: Paul Molodowitch Date: Sun, 15 Dec 2013 22:10:16 -0800 Subject: [PATCH 05/11] fix for custom papersize --- dominion_tabs.py | 1344 +++++++++++++++++++++++----------------------- 1 file changed, 675 insertions(+), 669 deletions(-) diff --git a/dominion_tabs.py b/dominion_tabs.py index 199c8df..748a115 100644 --- a/dominion_tabs.py +++ b/dominion_tabs.py @@ -1,669 +1,675 @@ -import re -from optparse import OptionParser -import os.path - -from reportlab.pdfgen import canvas -from reportlab.lib.pagesizes import LETTER,A4 -from reportlab.lib.units import cm -from reportlab.platypus import Paragraph -from reportlab.lib.styles import getSampleStyleSheet -from reportlab.pdfbase.ttfonts import TTFont -from reportlab.pdfbase import pdfmetrics - -def split(l,n): - i = 0 - while i < len(l) - n: - yield l[i:i+n] - i += n - yield l[i:] - -class Card: - def __init__(self,name,cardset,types,cost,description,potcost=0): - self.name = name - self.cardset = cardset - self.types = types - self.cost = cost - self.potcost = potcost - self.description = description - self.extra = "" - - def getType(self): - return DominionTabs.cardTypes[self.types] - - def __repr__(self): - return '"' + self.name + '"' - - def toString(self): - return self.name + ' ' + self.cardset + ' ' + '-'.join(self.types) + ' ' + `self.cost` + ' ' + self.description + ' ' + self.extra - -class CardType: - def __init__(self, typeNames, tabImageFile, tabTextHeightOffset=0, tabCostHeightOffset=-1): - self.typeNames = typeNames - self.tabImageFile = tabImageFile - self.tabTextHeightOffset = tabTextHeightOffset - self.tabCostHeightOffset = tabCostHeightOffset - - def getTypeNames(self): - return self.typeNames - - def getTabImageFile(self): - return self.tabImageFile - - def getNoCoinTabImageFile(self): - return ''.join(os.path.splitext(self.tabImageFile)[0] + '_nc' + os.path.splitext(self.tabImageFile)[1]) - - def getTabTextHeightOffset(self): - return self.tabTextHeightOffset - - def getTabCostHeightOffset(self): - return self.tabCostHeightOffset - -class DominionTabs: - cardTypes = [ - CardType(('Action',), 'action.png'), - CardType(('Action','Attack'), 'action.png'), - CardType(('Action','Attack','Prize'), 'action.png'), - CardType(('Action','Reaction'), 'reaction.png'), - CardType(('Action','Victory'), 'action-victory.png'), - CardType(('Action','Duration'), 'duration.png'), - CardType(('Action','Looter'), 'action.png'), - CardType(('Action','Prize'), 'action.png'), - CardType(('Action','Ruins'), 'ruins.png', 0, 1), - CardType(('Action','Shelter'), 'shelter.png', 0, 1), - CardType(('Action','Attack','Looter'), 'action.png'), - CardType(('Reaction',), 'reaction.png'), - CardType(('Reaction','Shelter'), 'shelter.png', 0, 1), - CardType(('Treasure',), 'treasure.png',3,0), - CardType(('Treasure','Victory'), 'treasure-victory.png'), - CardType(('Treasure','Prize'), 'treasure.png',3,0), - CardType(('Treasure','Reaction'), 'treasure-reaction.png', 0, 1), - CardType(('Victory',), 'victory.png'), - CardType(('Victory','Reaction'), 'victory-reaction.png', 0, 1), - CardType(('Victory','Shelter'), 'shelter.png', 0, 1), - CardType(('Curse',), 'curse.png',3), - CardType(('Expansion',), 'expansion.png',4) - ] - - cardTypes = dict(((c.getTypeNames(),c) for c in cardTypes)) - - setImages = { - 'dominion' : 'base_set.png', - 'intrigue' : 'intrigue_set.png', - 'seaside' : 'seaside_set.png', - 'prosperity' : 'prosperity_set.png', - 'alchemy' : 'alchemy_set.png', - 'cornucopia' : 'cornucopia_set.png', - 'hinterlands' : 'hinterlands_set.png', - 'dark ages' : 'dark_ages_set.png', - 'dark ages extras' : 'dark_ages_set.png', - 'guilds' : 'guilds_set.png' - } - promoImages = { - 'walled village' : 'walled_village_set.png', - 'stash' : 'stash_set.png', - 'governor' : 'governor_set.png', - 'black market' : 'black_market_set.png', - 'envoy' : 'envoy_set.png' - } - - def __init__(self): - self.filedir = os.path.dirname(__file__) - - def add_inline_images(self, text, fontsize): - path = os.path.join(self.filedir,'images') - replace = '' % (path,fontsize*1.2) - text = re.sub('(\d)\s(c|C)oin(s)?', replace,text) - replace = '' % (path,fontsize*1.2) - text = re.sub('\?\s(c|C)oin(s)?', replace,text) - replace = '' % (path,fontsize*1.5) - text = re.sub('\', replace,text) - return text - - def drawOutline(self, x, y, rightSide, isBack=False): - #draw outline or cropmarks - self.canvas.saveState() - self.canvas.setLineWidth(self.options.linewidth) - cropmarksright = (x == self.numTabsHorizontal-1) - cropmarksleft = (x == 0) - if rightSide and not self.options.sameside: - self.canvas.translate(self.tabWidth,0) - self.canvas.scale(-1,1) - if not self.options.cropmarks and not isBack: - #don't draw outline on back, in case lines don't line up with front - self.canvas.lines(self.tabOutline) - elif self.options.cropmarks: - cmw = 0.5*cm - mirror = cropmarksright and not rightSide or cropmarksleft and rightSide - if mirror: - self.canvas.saveState() - self.canvas.translate(self.tabWidth,0) - self.canvas.scale(-1,1) - if cropmarksleft or cropmarksright: - self.canvas.line(-2*cmw,0,-cmw,0) - self.canvas.line(-2*cmw,self.tabBaseHeight,-cmw,self.tabBaseHeight) - if y > 0: - self.canvas.line(-2*cmw,self.tabHeight,-cmw,self.tabHeight) - if mirror: - self.canvas.restoreState() - if y == 0: - self.canvas.line(self.tabWidth,-2*cmw,self.tabWidth,-cmw) - self.canvas.line(self.tabWidth-self.tabLabelWidth,-2*cmw,self.tabWidth-self.tabLabelWidth,-cmw) - if x == 0: - self.canvas.line(0,-2*cmw,0,-cmw) - elif y == self.numTabsVertical-1: - self.canvas.line(self.tabWidth,self.tabHeight+cmw,self.tabWidth,self.tabHeight+2*cmw) - self.canvas.line(self.tabWidth-self.tabLabelWidth, - self.tabHeight+cmw, - self.tabWidth-self.tabLabelWidth, - self.tabHeight+2*cmw) - if x == 0: - self.canvas.line(0,self.tabHeight+cmw,0,self.tabHeight+2*cmw) - - self.canvas.restoreState() - - def drawTab(self, card, rightSide): - #draw tab flap - self.canvas.saveState() - if not rightSide or self.options.sameside: - self.canvas.translate(self.tabWidth-self.tabLabelWidth, - self.tabHeight-self.tabLabelHeight) - else: - self.canvas.translate(0,self.tabHeight-self.tabLabelHeight) - - textWidth = 85 - textHeight = self.tabLabelHeight/2-7+card.getType().getTabTextHeightOffset() - - self.canvas.drawImage(os.path.join(self.filedir,'images',card.getType().getNoCoinTabImageFile()),1,0, - self.tabLabelWidth-2,self.tabLabelHeight-1, - preserveAspectRatio=False,anchor='n',mask='auto') - - if card.getType().getTypeNames() != ('Expansion',): - textInset = 22 - - - costHeight = textHeight + card.getType().getTabCostHeightOffset() - potHeight = 3 + card.getType().getTabTextHeightOffset() - potSize = 11 - - self.canvas.drawImage(os.path.join(self.filedir,'images','coin_small.png'),4,costHeight-5,16,16,preserveAspectRatio=True,mask='auto') - - if card.potcost: - self.canvas.drawImage(os.path.join(self.filedir,'images','potion.png'),21,potHeight,potSize,potSize,preserveAspectRatio=True,mask=[255,255,255,255,255,255]) - textInset += potSize - textWidth -= potSize - setImageHeight = potHeight - - self.canvas.setFont('MinionPro-Bold',12) - cost = str(card.cost) - if 'Prize' in card.types: - cost += '*' - costWidthOffset = 12 - self.canvas.drawCentredString(costWidthOffset,costHeight,cost) - - else: - textInset = 13 - setImageHeight = 3 + card.getType().getTabTextHeightOffset() - - #set image - setImage = DominionTabs.setImages.get(card.cardset, None) - if not setImage: - setImage = DominionTabs.promoImages.get(card.name.lower(), None) - - if setImage: - self.canvas.drawImage(os.path.join(self.filedir,'images',setImage), self.tabLabelWidth-20, setImageHeight, 14, 12, mask='auto') - elif setImage == None and card.cardset != 'base' and card.getType().getTypeNames() != ('Expansion',): - print 'warning, no set image for set "%s" card "%s"' % (card.cardset, card.name) - DominionTabs.setImages[card.cardset] = 0 - DominionTabs.promoImages[card.name.lower()] = 0 - - fontSize = 12 - name = card.name.upper() - name_parts = name.partition(' / ') - if name_parts[1]: - name_parts = (name_parts[0] + ' /', name_parts[2]) - else: - name_parts = name.split() - - width = pdfmetrics.stringWidth(name,'MinionPro-Regular',fontSize) - while width > textWidth and fontSize > 8: - fontSize -= 1 - #print 'decreasing font size for tab of',name,'now',fontSize - width = pdfmetrics.stringWidth(name,'MinionPro-Regular',fontSize) - tooLong = width > textWidth - #if tooLong: - # print name - - #self.canvas.drawString(tabLabelWidth/2+8,tabLabelHeight/2-7,name[0]) - w = 0 - for i,n in enumerate(name_parts): - self.canvas.setFont('MinionPro-Regular',fontSize) - h = textHeight - if tooLong: - if i == 0: - h += h/2 - else: - h -= h/2 - self.canvas.drawString(textInset+w,h,n[0]) - w += pdfmetrics.stringWidth(n[0],'MinionPro-Regular',fontSize) - #self.canvas.drawString(tabLabelWidth/2+8+w,tabLabelHeight/2-7,name[1:]) - self.canvas.setFont('MinionPro-Regular',fontSize-2) - self.canvas.drawString(textInset+w,h,n[1:]) - w += pdfmetrics.stringWidth(n[1:],'MinionPro-Regular',fontSize-2) - w += pdfmetrics.stringWidth(' ','MinionPro-Regular',fontSize) - if tooLong: - w = 0 - self.canvas.restoreState() - - def drawText(self, card, useExtra=False): - #draw text - if useExtra and card.extra: - descriptions = (card.extra,) - else: - descriptions = re.split("\n",card.description) - - height = 0 - for d in descriptions: - s = getSampleStyleSheet()['BodyText'] - s.fontName = "Times-Roman" - dmod = self.add_inline_images(d,s.fontSize) - p = Paragraph(dmod,s) - textHeight = self.tabHeight - self.tabLabelHeight + 0.2*cm - textWidth = self.tabWidth - cm - - w,h = p.wrap(textWidth,textHeight) - while h > textHeight: - s.fontSize -= 1 - s.leading -= 1 - #print 'decreasing fontsize on description for',card.name,'now',s.fontSize - dmod = self.add_inline_images(d,s.fontSize) - p = Paragraph(dmod,s) - w,h = p.wrap(textWidth,textHeight) - p.drawOn(self.canvas,cm/2.0,textHeight-height-h-0.5*cm) - height += h + 0.2*cm - - def drawDivider(self,card,x,y,useExtra=False): - #figure out whether the tab should go on the right side or not - if self.numTabsHorizontal % 2 == 0: - rightSide = x%2 == 1 - else: - rightSide = useExtra - #apply the transforms to get us to the corner of the current card - self.canvas.resetTransforms() - self.canvas.translate(self.horizontalMargin,self.verticalMargin) - if useExtra: - self.canvas.translate(self.options.back_offset,0) - self.canvas.translate(x*self.totalTabWidth,y*self.totalTabHeight) - - #actual drawing - if not self.options.tabs_only: - self.drawOutline(x, y, rightSide, useExtra) - self.drawTab(card, rightSide) - if not self.options.tabs_only: - self.drawText(card, useExtra) - - def read_card_extras(self,fname,cards): - f = open(fname) - cardName = re.compile("^:::(?P[ \w\-/']*)") - extras = {} - currentCard = "" - extra = "" - for line in f: - m = cardName.match(line) - if m: - if currentCard: - #print 'found',currentCard - #print extra - #print '------------------' - extras[currentCard] = extra - currentCard = m.groupdict()["name"] - extra = "" - if not self.options.expansions and currentCard and (currentCard not in (c.name for c in cards)): - print currentCard + ' has extra description, but is not in cards' - else: - extra += line.strip() - if currentCard and extra: - extras[currentCard] = extra.strip() - for c in cards: - if not c.name in extras: - print c.name + ' missing from extras' - else: - c.extra = extras[c.name] - #print c.name + ' ::: ' + extra - - baseactionRE = re.compile("^\s*(\+\d+\s+\w+)(?:[,.;])") - - def add_definition_line(self,card,line): - # Unfortunately, the way things are specified in the old card spec - # format is somewhat haphazard. In particular: - # 1) Sometimes "basic actions", which would be separated on the - # actual card text by separate lines, are instead only separated - # by punctuation ('.', ',', or ';') - # [Example: Intrigue - Courtyard] - # 2) When there is an actual horizontal line drawn on the card, this - # can be represented using either '____' or '-----' - # 3) There are sometimes random blank lines - - # To solve: - - # 1) - #try to figure out if this a 'basic action' like +X Cards or +Y Actions - descriptions = [card.description] - while True: - m = self.baseactionRE.match(line) - if not m: - break - descriptions.append(m.group(1)) - line = line[m.end():] - - # 2) Standardize on '____' as the format for a divider line - line = line.strip() - if not line.strip('-'): - line = line.replace('-', '_') - - # 3) get rid of blank lines - descriptions.append(line) - descriptions = [x.strip() for x in descriptions] - descriptions = [x for x in descriptions if x] - - card.description = '\n'.join(descriptions) - - def read_card_defs(self,fname,fileobject=None): - cards = [] - f = open(fname) - carddef = re.compile("^\d+\t+(?P[\w\-'/ ]+)\t+(?P[\w ]+)\t+(?P[-\w ]+)\t+\$(?P\d+)( (?P\d)+P)?\t+(?P.*)") - currentCard = None - for line in f: - line = line.strip() - m = carddef.match(line) - if m: - if m.groupdict()["potioncost"]: - potcost = int(m.groupdict()["potioncost"]) - else: - potcost = 0 - currentCard = Card(m.groupdict()["name"], - m.groupdict()["set"].lower(), - tuple([t.strip() for t in m.groupdict()["type"].split("-")]), - int(m.groupdict()["cost"]), - '', - potcost) - self.add_definition_line(currentCard,m.groupdict()["description"]) - cards.append(currentCard) - elif line: - assert currentCard - self.add_definition_line(currentCard,line) - #print currentCard - #print '----' - return cards - - def drawSetNames(self, pageCards): - #print sets for this page - self.canvas.saveState() - - try: - # calculate the text height, font size, and orientation - maxFontsize = 12 - minFontsize = 6 - fontname = 'MinionPro-Regular' - font = pdfmetrics.getFont(fontname) - fontHeightRelative = (font.face.ascent + font.face.descent) / 1000 - - canFit = False - - layouts = [{'rotation': 0, - 'minMarginHeight': self.minVerticalMargin, - 'totalMarginHeight': self.verticalMargin, - 'width': self.paperwidth}, - {'rotation': 90, - 'minMarginHeight': self.minHorizontalMargin, - 'totalMarginHeight': self.horizontalMargin, - 'width': self.paperheight}] - - for layout in layouts: - availableMargin = layout['totalMarginHeight'] - layout['minMarginHeight'] - fontsize = availableMargin / fontHeightRelative - fontsize = min(maxFontsize, fontsize) - if fontsize >= minFontsize: - canFit = True - break - - if not canFit: - import warnings - warnings.warn("Not enough space to display set names") - return - - self.canvas.setFont(fontname,fontsize) - - sets = [] - for c in pageCards: - setTitle = c.cardset.title() - if setTitle not in sets: - sets.append(setTitle) - - xPos = layout['width'] / 2 - yPos = layout['minMarginHeight'] + availableMargin / 2 - - if layout['rotation']: - self.canvas.rotate(layout['rotation']) - yPos = -yPos - - self.canvas.drawCentredString(xPos,yPos,'/'.join(sets)) - finally: - self.canvas.restoreState() - - def drawDividers(self,cards): - cards = split(cards,self.numTabsVertical*self.numTabsHorizontal) - for pageCards in cards: - if self.options.order != "global": - self.drawSetNames(pageCards) - for i,card in enumerate(pageCards): - #print card - x = i % self.numTabsHorizontal - y = i / self.numTabsHorizontal - self.canvas.saveState() - self.drawDivider(card,x,self.numTabsVertical-1-y) - self.canvas.restoreState() - self.canvas.showPage() - if self.options.order != "global": - self.drawSetNames(pageCards) - for i,card in enumerate(pageCards): - #print card - x = (self.numTabsHorizontal-1-i) % self.numTabsHorizontal - y = i / self.numTabsHorizontal - self.canvas.saveState() - self.drawDivider(card,x,self.numTabsVertical-1-y,useExtra=True) - self.canvas.restoreState() - self.canvas.showPage() - - @staticmethod - def parse_opts(argstring): - parser = OptionParser() - parser.add_option("--back_offset",type="int",dest="back_offset",default=0, - help="Points to offset the back page to the right; needed for some printers") - parser.add_option("--orientation",type="choice",choices=["horizontal","vertical"],dest="orientation",default="horizontal", - help="horizontal or vertical, default:horizontal") - parser.add_option("--sleeved",action="store_true",dest="sleeved",help="use --size=sleeved instead") - parser.add_option("--size",type="string",dest="size",default='normal', - help="'<%f>x<%f>' (size in cm), or 'normal' = '9.1x5.9', or 'sleeved' = '9.4x6.15'") - parser.add_option("--minmargin",type="string",dest="minmargin",default="1x1", - help="'<%f>x<%f>' (size in cm, left/right, top/bottom), default: 1x1") - parser.add_option("--papersize",type="string",dest="papersize",default=None, - help="'<%f>x<%f>' (size in cm), or 'A4', or 'LETTER'") - parser.add_option("--samesidelabels",action="store_true",dest="sameside", - help="force all label tabs to be on the same side") - parser.add_option("--expansions",action="append",type="string", - help="subset of dominion expansions to produce tabs for") - parser.add_option("--cropmarks",action="store_true",dest="cropmarks", - help="print crop marks on both sides, rather than tab outlines on one") - parser.add_option("--linewidth",type="float",default=.1, - help="width of lines for card outlines/crop marks") - parser.add_option("--read_yaml", action="store_true",dest="read_yaml", - help="read yaml version of card definitions and extras") - parser.add_option("--write_yaml", action="store_true",dest="write_yaml", - help="write yaml version of card definitions and extras") - parser.add_option("--tabs-only", action="store_true", dest="tabs_only", - help="draw only tabs to be printed on labels, no divider outlines") - parser.add_option("--order", type="choice", choices=["expansion","global"], dest="order", - help="sort order for the cards, whether by expansion or globally alphabetical") - parser.add_option("--expansion_dividers", action="store_true", dest="expansion_dividers", - help="add dividers describing each expansion set") - - return parser.parse_args(argstring) - - def main(self,argstring): - options,args = DominionTabs.parse_opts(argstring) - fname = None - if args: - fname = args[0] - return self.generate(options,fname) - - def generate(self,options,f): - self.options = options - size = self.options.size.upper() - if size == 'SLEEVED' or self.options.sleeved: - dominionCardWidth, dominionCardHeight = (9.4*cm, 6.15*cm) - print 'Using sleeved card size, %.2fcm x %.2fcm' % (dominionCardWidth/cm,dominionCardHeight/cm) - elif size in ['NORMAL','UNSLEEVED']: - dominionCardWidth, dominionCardHeight = (9.1*cm, 5.9*cm) - print 'Using normal card size, %.2fcm x%.2fcm' % (dominionCardWidth/cm,dominionCardHeight/cm) - else: - x, y = size.split ("X", 1) - dominionCardWidth, dominionCardHeight = (float (x) * cm, float (y) * cm) - print 'Using custom card size, %.2fcm x %.2fcm' % (dominionCardWidth/cm,dominionCardHeight/cm) - - papersize = None - if not self.options.papersize: - if os.path.exists("/etc/papersize"): - papersize = open ("/etc/papersize").readline().upper() - else: - papersize = self.options.papersize.upper() - - if papersize == 'A4': - print "Using A4 sized paper." - self.paperwidth, self.paperheight = A4 - else: - print "Using letter sized paper." - self.paperwidth, self.paperheight = LETTER - - - if self.options.orientation == "vertical": - self.tabWidth, self.tabBaseHeight = dominionCardHeight, dominionCardWidth - else: - self.tabWidth, self.tabBaseHeight = dominionCardWidth, dominionCardHeight - - fixedMargins = False - if self.options.tabs_only: - #fixed for Avery 8867 for now - minmarginwidth=0.76*cm - minmarginheight=1.27*cm - self.tabLabelHeight = 1.27*cm - self.tabLabelWidth = 4.44*cm - self.tabBaseHeight = 0 - self.tabWidth = self.tabLabelWidth - self.horizontalBorderSpace = 0.76*cm - self.verticalBorderSpace = 0.01*cm - fixedMargins = True - else: - minmarginwidth, minmarginheight = self.options.minmargin.split ("x", 1) - minmarginwidth, minmarginheight = float (minmarginwidth) * cm, float (minmarginheight) * cm - - self.tabLabelHeight = 0.9*cm - self.tabLabelWidth = 4*cm - self.horizontalBorderSpace = 0*cm - self.verticalBorderSpace = 0*cm - - self.tabHeight = self.tabBaseHeight + self.tabLabelHeight - - self.totalTabWidth = self.tabWidth + self.horizontalBorderSpace - self.totalTabHeight = self.tabHeight + self.verticalBorderSpace - - print "Paper dimensions: %fcm (w) x %fcm (h)" % (self.paperwidth / cm, self.paperheight / cm) - print "Tab dimensions: %fcm (w) x %fcm (h)" % (self.totalTabWidth / cm, self.totalTabHeight / cm) - - numTabsVerticalP = int ((self.paperheight - 2*minmarginheight) / self.totalTabHeight) - numTabsHorizontalP = int ((self.paperwidth - 2*minmarginwidth) / self.totalTabWidth) - numTabsVerticalL = int ((self.paperwidth - 2*minmarginwidth) / self.totalTabHeight) - numTabsHorizontalL = int ((self.paperheight - 2*minmarginheight) / self.totalTabWidth) - - if numTabsVerticalL * numTabsHorizontalL > numTabsVerticalP * numTabsHorizontalP and not fixedMargins: - self.numTabsVertical, self.numTabsHorizontal\ - = numTabsVerticalL, numTabsHorizontalL - self.paperheight, self.paperwidth = self.paperwidth, self.paperheight - self.minHorizontalMargin = minmarginheight - self.minVerticalMargin = minmarginwidth - else: - self.numTabsVertical, self.numTabsHorizontal\ - = numTabsVerticalP, numTabsHorizontalP - self.minHorizontalMargin = minmarginwidth - self.minVerticalMargin = minmarginheight - - if not fixedMargins: - #dynamically max margins - self.horizontalMargin = (self.paperwidth-self.numTabsHorizontal*self.totalTabWidth)/2 - self.verticalMargin = (self.paperheight-self.numTabsVertical*self.totalTabHeight)/2 - else: - self.horizontalMargin = minmarginwidth - self.verticalMargin = minmarginheight - - print "Margins: %fcm h, %fcm v\n" % (self.horizontalMargin / cm, - self.verticalMargin / cm) - - self.tabOutline = [(0,0,self.tabWidth,0), - (self.tabWidth,0,self.tabWidth,self.tabHeight), - (self.tabWidth,self.tabHeight, - self.tabWidth-self.tabLabelWidth,self.tabHeight), - (self.tabWidth-self.tabLabelWidth, - self.tabHeight,self.tabWidth-self.tabLabelWidth, - self.tabBaseHeight), - (self.tabWidth-self.tabLabelWidth, - self.tabBaseHeight,0,self.tabBaseHeight), - (0,self.tabBaseHeight,0,0)] - - try: - dirn = os.path.join(self.filedir,'fonts') - pdfmetrics.registerFont(TTFont('MinionPro-Regular',os.path.join(dirn,'MinionPro-Regular.ttf'))) - pdfmetrics.registerFont(TTFont('MinionPro-Bold',os.path.join(dirn,'MinionPro-Bold.ttf'))) - except: - raise - pdfmetrics.registerFont(TTFont('MinionPro-Regular','OptimusPrincepsSemiBold.ttf')) - pdfmetrics.registerFont(TTFont('MinionPro-Bold','OptimusPrinceps.ttf')) - if options.read_yaml: - import yaml - cardfile = open("cards.yaml","r") - cards = yaml.load(cardfile) - else: - cards = self.read_card_defs(os.path.join(self.filedir,"dominion_cards.txt")) - self.read_card_extras(os.path.join(self.filedir,"dominion_card_extras.txt"),cards) - - if self.options.expansions: - self.options.expansions = [o.lower() for o in self.options.expansions] - cards=[c for c in cards if c.cardset in self.options.expansions] - - if options.expansion_dividers: - cardnamesByExpansion = {} - for c in cards: - cardnamesByExpansion.setdefault(c.cardset,[]).append(c.name.strip()) - for exp,names in cardnamesByExpansion.iteritems(): - c = Card(exp, exp, ("Expansion",), None, ' | '.join(sorted(names))) - cards.append(c) - - if options.write_yaml: - import yaml - out = yaml.dump(cards) - open('cards.yaml','w').write(out) - - if options.order == "global": - sf = lambda x,y: cmp(x.name,y.name) - else: - sf = lambda x,y: cmp((x.cardset,x.name),(y.cardset,y.name)) - cards.sort(cmp=sf) - - if not f: - f = "dominion_tabs.pdf" - self.canvas = canvas.Canvas(f, pagesize=(self.paperwidth, self.paperheight)) - self.drawDividers(cards) - self.canvas.save() - -if __name__=='__main__': - import sys - tabs = DominionTabs() - tabs.main(sys.argv[1:]) +import re +from optparse import OptionParser +import os.path + +from reportlab.pdfgen import canvas +from reportlab.lib.pagesizes import LETTER,A4 +from reportlab.lib.units import cm +from reportlab.platypus import Paragraph +from reportlab.lib.styles import getSampleStyleSheet +from reportlab.pdfbase.ttfonts import TTFont +from reportlab.pdfbase import pdfmetrics + +def split(l,n): + i = 0 + while i < len(l) - n: + yield l[i:i+n] + i += n + yield l[i:] + +class Card: + def __init__(self,name,cardset,types,cost,description,potcost=0): + self.name = name + self.cardset = cardset + self.types = types + self.cost = cost + self.potcost = potcost + self.description = description + self.extra = "" + + def getType(self): + return DominionTabs.cardTypes[self.types] + + def __repr__(self): + return '"' + self.name + '"' + + def toString(self): + return self.name + ' ' + self.cardset + ' ' + '-'.join(self.types) + ' ' + `self.cost` + ' ' + self.description + ' ' + self.extra + +class CardType: + def __init__(self, typeNames, tabImageFile, tabTextHeightOffset=0, tabCostHeightOffset=-1): + self.typeNames = typeNames + self.tabImageFile = tabImageFile + self.tabTextHeightOffset = tabTextHeightOffset + self.tabCostHeightOffset = tabCostHeightOffset + + def getTypeNames(self): + return self.typeNames + + def getTabImageFile(self): + return self.tabImageFile + + def getNoCoinTabImageFile(self): + return ''.join(os.path.splitext(self.tabImageFile)[0] + '_nc' + os.path.splitext(self.tabImageFile)[1]) + + def getTabTextHeightOffset(self): + return self.tabTextHeightOffset + + def getTabCostHeightOffset(self): + return self.tabCostHeightOffset + +class DominionTabs: + cardTypes = [ + CardType(('Action',), 'action.png'), + CardType(('Action','Attack'), 'action.png'), + CardType(('Action','Attack','Prize'), 'action.png'), + CardType(('Action','Reaction'), 'reaction.png'), + CardType(('Action','Victory'), 'action-victory.png'), + CardType(('Action','Duration'), 'duration.png'), + CardType(('Action','Looter'), 'action.png'), + CardType(('Action','Prize'), 'action.png'), + CardType(('Action','Ruins'), 'ruins.png', 0, 1), + CardType(('Action','Shelter'), 'shelter.png', 0, 1), + CardType(('Action','Attack','Looter'), 'action.png'), + CardType(('Reaction',), 'reaction.png'), + CardType(('Reaction','Shelter'), 'shelter.png', 0, 1), + CardType(('Treasure',), 'treasure.png',3,0), + CardType(('Treasure','Victory'), 'treasure-victory.png'), + CardType(('Treasure','Prize'), 'treasure.png',3,0), + CardType(('Treasure','Reaction'), 'treasure-reaction.png', 0, 1), + CardType(('Victory',), 'victory.png'), + CardType(('Victory','Reaction'), 'victory-reaction.png', 0, 1), + CardType(('Victory','Shelter'), 'shelter.png', 0, 1), + CardType(('Curse',), 'curse.png',3), + CardType(('Expansion',), 'expansion.png',4) + ] + + cardTypes = dict(((c.getTypeNames(),c) for c in cardTypes)) + + setImages = { + 'dominion' : 'base_set.png', + 'intrigue' : 'intrigue_set.png', + 'seaside' : 'seaside_set.png', + 'prosperity' : 'prosperity_set.png', + 'alchemy' : 'alchemy_set.png', + 'cornucopia' : 'cornucopia_set.png', + 'hinterlands' : 'hinterlands_set.png', + 'dark ages' : 'dark_ages_set.png', + 'dark ages extras' : 'dark_ages_set.png', + 'guilds' : 'guilds_set.png' + } + promoImages = { + 'walled village' : 'walled_village_set.png', + 'stash' : 'stash_set.png', + 'governor' : 'governor_set.png', + 'black market' : 'black_market_set.png', + 'envoy' : 'envoy_set.png' + } + + def __init__(self): + self.filedir = os.path.dirname(__file__) + + def add_inline_images(self, text, fontsize): + path = os.path.join(self.filedir,'images') + replace = '' % (path,fontsize*1.2) + text = re.sub('(\d)\s(c|C)oin(s)?', replace,text) + replace = '' % (path,fontsize*1.2) + text = re.sub('\?\s(c|C)oin(s)?', replace,text) + replace = '' % (path,fontsize*1.5) + text = re.sub('\', replace,text) + return text + + def drawOutline(self, x, y, rightSide, isBack=False): + #draw outline or cropmarks + self.canvas.saveState() + self.canvas.setLineWidth(self.options.linewidth) + cropmarksright = (x == self.numTabsHorizontal-1) + cropmarksleft = (x == 0) + if rightSide and not self.options.sameside: + self.canvas.translate(self.tabWidth,0) + self.canvas.scale(-1,1) + if not self.options.cropmarks and not isBack: + #don't draw outline on back, in case lines don't line up with front + self.canvas.lines(self.tabOutline) + elif self.options.cropmarks: + cmw = 0.5*cm + mirror = cropmarksright and not rightSide or cropmarksleft and rightSide + if mirror: + self.canvas.saveState() + self.canvas.translate(self.tabWidth,0) + self.canvas.scale(-1,1) + if cropmarksleft or cropmarksright: + self.canvas.line(-2*cmw,0,-cmw,0) + self.canvas.line(-2*cmw,self.tabBaseHeight,-cmw,self.tabBaseHeight) + if y > 0: + self.canvas.line(-2*cmw,self.tabHeight,-cmw,self.tabHeight) + if mirror: + self.canvas.restoreState() + if y == 0: + self.canvas.line(self.tabWidth,-2*cmw,self.tabWidth,-cmw) + self.canvas.line(self.tabWidth-self.tabLabelWidth,-2*cmw,self.tabWidth-self.tabLabelWidth,-cmw) + if x == 0: + self.canvas.line(0,-2*cmw,0,-cmw) + elif y == self.numTabsVertical-1: + self.canvas.line(self.tabWidth,self.tabHeight+cmw,self.tabWidth,self.tabHeight+2*cmw) + self.canvas.line(self.tabWidth-self.tabLabelWidth, + self.tabHeight+cmw, + self.tabWidth-self.tabLabelWidth, + self.tabHeight+2*cmw) + if x == 0: + self.canvas.line(0,self.tabHeight+cmw,0,self.tabHeight+2*cmw) + + self.canvas.restoreState() + + def drawTab(self, card, rightSide): + #draw tab flap + self.canvas.saveState() + if not rightSide or self.options.sameside: + self.canvas.translate(self.tabWidth-self.tabLabelWidth, + self.tabHeight-self.tabLabelHeight) + else: + self.canvas.translate(0,self.tabHeight-self.tabLabelHeight) + + textWidth = 85 + textHeight = self.tabLabelHeight/2-7+card.getType().getTabTextHeightOffset() + + self.canvas.drawImage(os.path.join(self.filedir,'images',card.getType().getNoCoinTabImageFile()),1,0, + self.tabLabelWidth-2,self.tabLabelHeight-1, + preserveAspectRatio=False,anchor='n',mask='auto') + + if card.getType().getTypeNames() != ('Expansion',): + textInset = 22 + + + costHeight = textHeight + card.getType().getTabCostHeightOffset() + potHeight = 3 + card.getType().getTabTextHeightOffset() + potSize = 11 + + self.canvas.drawImage(os.path.join(self.filedir,'images','coin_small.png'),4,costHeight-5,16,16,preserveAspectRatio=True,mask='auto') + + if card.potcost: + self.canvas.drawImage(os.path.join(self.filedir,'images','potion.png'),21,potHeight,potSize,potSize,preserveAspectRatio=True,mask=[255,255,255,255,255,255]) + textInset += potSize + textWidth -= potSize + setImageHeight = potHeight + + self.canvas.setFont('MinionPro-Bold',12) + cost = str(card.cost) + if 'Prize' in card.types: + cost += '*' + costWidthOffset = 12 + self.canvas.drawCentredString(costWidthOffset,costHeight,cost) + + else: + textInset = 13 + setImageHeight = 3 + card.getType().getTabTextHeightOffset() + + #set image + setImage = DominionTabs.setImages.get(card.cardset, None) + if not setImage: + setImage = DominionTabs.promoImages.get(card.name.lower(), None) + + if setImage: + self.canvas.drawImage(os.path.join(self.filedir,'images',setImage), self.tabLabelWidth-20, setImageHeight, 14, 12, mask='auto') + elif setImage == None and card.cardset != 'base' and card.getType().getTypeNames() != ('Expansion',): + print 'warning, no set image for set "%s" card "%s"' % (card.cardset, card.name) + DominionTabs.setImages[card.cardset] = 0 + DominionTabs.promoImages[card.name.lower()] = 0 + + fontSize = 12 + name = card.name.upper() + name_parts = name.partition(' / ') + if name_parts[1]: + name_parts = (name_parts[0] + ' /', name_parts[2]) + else: + name_parts = name.split() + + width = pdfmetrics.stringWidth(name,'MinionPro-Regular',fontSize) + while width > textWidth and fontSize > 8: + fontSize -= 1 + #print 'decreasing font size for tab of',name,'now',fontSize + width = pdfmetrics.stringWidth(name,'MinionPro-Regular',fontSize) + tooLong = width > textWidth + #if tooLong: + # print name + + #self.canvas.drawString(tabLabelWidth/2+8,tabLabelHeight/2-7,name[0]) + w = 0 + for i,n in enumerate(name_parts): + self.canvas.setFont('MinionPro-Regular',fontSize) + h = textHeight + if tooLong: + if i == 0: + h += h/2 + else: + h -= h/2 + self.canvas.drawString(textInset+w,h,n[0]) + w += pdfmetrics.stringWidth(n[0],'MinionPro-Regular',fontSize) + #self.canvas.drawString(tabLabelWidth/2+8+w,tabLabelHeight/2-7,name[1:]) + self.canvas.setFont('MinionPro-Regular',fontSize-2) + self.canvas.drawString(textInset+w,h,n[1:]) + w += pdfmetrics.stringWidth(n[1:],'MinionPro-Regular',fontSize-2) + w += pdfmetrics.stringWidth(' ','MinionPro-Regular',fontSize) + if tooLong: + w = 0 + self.canvas.restoreState() + + def drawText(self, card, useExtra=False): + #draw text + if useExtra and card.extra: + descriptions = (card.extra,) + else: + descriptions = re.split("\n",card.description) + + height = 0 + for d in descriptions: + s = getSampleStyleSheet()['BodyText'] + s.fontName = "Times-Roman" + dmod = self.add_inline_images(d,s.fontSize) + p = Paragraph(dmod,s) + textHeight = self.tabHeight - self.tabLabelHeight + 0.2*cm + textWidth = self.tabWidth - cm + + w,h = p.wrap(textWidth,textHeight) + while h > textHeight: + s.fontSize -= 1 + s.leading -= 1 + #print 'decreasing fontsize on description for',card.name,'now',s.fontSize + dmod = self.add_inline_images(d,s.fontSize) + p = Paragraph(dmod,s) + w,h = p.wrap(textWidth,textHeight) + p.drawOn(self.canvas,cm/2.0,textHeight-height-h-0.5*cm) + height += h + 0.2*cm + + def drawDivider(self,card,x,y,useExtra=False): + #figure out whether the tab should go on the right side or not + if self.numTabsHorizontal % 2 == 0: + rightSide = x%2 == 1 + else: + rightSide = useExtra + #apply the transforms to get us to the corner of the current card + self.canvas.resetTransforms() + self.canvas.translate(self.horizontalMargin,self.verticalMargin) + if useExtra: + self.canvas.translate(self.options.back_offset,0) + self.canvas.translate(x*self.totalTabWidth,y*self.totalTabHeight) + + #actual drawing + if not self.options.tabs_only: + self.drawOutline(x, y, rightSide, useExtra) + self.drawTab(card, rightSide) + if not self.options.tabs_only: + self.drawText(card, useExtra) + + def read_card_extras(self,fname,cards): + f = open(fname) + cardName = re.compile("^:::(?P[ \w\-/']*)") + extras = {} + currentCard = "" + extra = "" + for line in f: + m = cardName.match(line) + if m: + if currentCard: + #print 'found',currentCard + #print extra + #print '------------------' + extras[currentCard] = extra + currentCard = m.groupdict()["name"] + extra = "" + if not self.options.expansions and currentCard and (currentCard not in (c.name for c in cards)): + print currentCard + ' has extra description, but is not in cards' + else: + extra += line.strip() + if currentCard and extra: + extras[currentCard] = extra.strip() + for c in cards: + if not c.name in extras: + print c.name + ' missing from extras' + else: + c.extra = extras[c.name] + #print c.name + ' ::: ' + extra + + baseactionRE = re.compile("^\s*(\+\d+\s+\w+)(?:[,.;])") + + def add_definition_line(self,card,line): + # Unfortunately, the way things are specified in the old card spec + # format is somewhat haphazard. In particular: + # 1) Sometimes "basic actions", which would be separated on the + # actual card text by separate lines, are instead only separated + # by punctuation ('.', ',', or ';') + # [Example: Intrigue - Courtyard] + # 2) When there is an actual horizontal line drawn on the card, this + # can be represented using either '____' or '-----' + # 3) There are sometimes random blank lines + + # To solve: + + # 1) + #try to figure out if this a 'basic action' like +X Cards or +Y Actions + descriptions = [card.description] + while True: + m = self.baseactionRE.match(line) + if not m: + break + descriptions.append(m.group(1)) + line = line[m.end():] + + # 2) Standardize on '____' as the format for a divider line + line = line.strip() + if not line.strip('-'): + line = line.replace('-', '_') + + # 3) get rid of blank lines + descriptions.append(line) + descriptions = [x.strip() for x in descriptions] + descriptions = [x for x in descriptions if x] + + card.description = '\n'.join(descriptions) + + def read_card_defs(self,fname,fileobject=None): + cards = [] + f = open(fname) + carddef = re.compile("^\d+\t+(?P[\w\-'/ ]+)\t+(?P[\w ]+)\t+(?P[-\w ]+)\t+\$(?P\d+)( (?P\d)+P)?\t+(?P.*)") + currentCard = None + for line in f: + line = line.strip() + m = carddef.match(line) + if m: + if m.groupdict()["potioncost"]: + potcost = int(m.groupdict()["potioncost"]) + else: + potcost = 0 + currentCard = Card(m.groupdict()["name"], + m.groupdict()["set"].lower(), + tuple([t.strip() for t in m.groupdict()["type"].split("-")]), + int(m.groupdict()["cost"]), + '', + potcost) + self.add_definition_line(currentCard,m.groupdict()["description"]) + cards.append(currentCard) + elif line: + assert currentCard + self.add_definition_line(currentCard,line) + #print currentCard + #print '----' + return cards + + def drawSetNames(self, pageCards): + #print sets for this page + self.canvas.saveState() + + try: + # calculate the text height, font size, and orientation + maxFontsize = 12 + minFontsize = 6 + fontname = 'MinionPro-Regular' + font = pdfmetrics.getFont(fontname) + fontHeightRelative = (font.face.ascent + font.face.descent) / 1000 + + canFit = False + + layouts = [{'rotation': 0, + 'minMarginHeight': self.minVerticalMargin, + 'totalMarginHeight': self.verticalMargin, + 'width': self.paperwidth}, + {'rotation': 90, + 'minMarginHeight': self.minHorizontalMargin, + 'totalMarginHeight': self.horizontalMargin, + 'width': self.paperheight}] + + for layout in layouts: + availableMargin = layout['totalMarginHeight'] - layout['minMarginHeight'] + fontsize = availableMargin / fontHeightRelative + fontsize = min(maxFontsize, fontsize) + if fontsize >= minFontsize: + canFit = True + break + + if not canFit: + import warnings + warnings.warn("Not enough space to display set names") + return + + self.canvas.setFont(fontname,fontsize) + + sets = [] + for c in pageCards: + setTitle = c.cardset.title() + if setTitle not in sets: + sets.append(setTitle) + + xPos = layout['width'] / 2 + yPos = layout['minMarginHeight'] + availableMargin / 2 + + if layout['rotation']: + self.canvas.rotate(layout['rotation']) + yPos = -yPos + + self.canvas.drawCentredString(xPos,yPos,'/'.join(sets)) + finally: + self.canvas.restoreState() + + def drawDividers(self,cards): + cards = split(cards,self.numTabsVertical*self.numTabsHorizontal) + for pageCards in cards: + if self.options.order != "global": + self.drawSetNames(pageCards) + for i,card in enumerate(pageCards): + #print card + x = i % self.numTabsHorizontal + y = i / self.numTabsHorizontal + self.canvas.saveState() + self.drawDivider(card,x,self.numTabsVertical-1-y) + self.canvas.restoreState() + self.canvas.showPage() + if self.options.order != "global": + self.drawSetNames(pageCards) + for i,card in enumerate(pageCards): + #print card + x = (self.numTabsHorizontal-1-i) % self.numTabsHorizontal + y = i / self.numTabsHorizontal + self.canvas.saveState() + self.drawDivider(card,x,self.numTabsVertical-1-y,useExtra=True) + self.canvas.restoreState() + self.canvas.showPage() + + @staticmethod + def parse_opts(argstring): + parser = OptionParser() + parser.add_option("--back_offset",type="int",dest="back_offset",default=0, + help="Points to offset the back page to the right; needed for some printers") + parser.add_option("--orientation",type="choice",choices=["horizontal","vertical"],dest="orientation",default="horizontal", + help="horizontal or vertical, default:horizontal") + parser.add_option("--sleeved",action="store_true",dest="sleeved",help="use --size=sleeved instead") + parser.add_option("--size",type="string",dest="size",default='normal', + help="'<%f>x<%f>' (size in cm), or 'normal' = '9.1x5.9', or 'sleeved' = '9.4x6.15'") + parser.add_option("--minmargin",type="string",dest="minmargin",default="1x1", + help="'<%f>x<%f>' (size in cm, left/right, top/bottom), default: 1x1") + parser.add_option("--papersize",type="string",dest="papersize",default=None, + help="'<%f>x<%f>' (size in cm), or 'A4', or 'LETTER'") + parser.add_option("--samesidelabels",action="store_true",dest="sameside", + help="force all label tabs to be on the same side") + parser.add_option("--expansions",action="append",type="string", + help="subset of dominion expansions to produce tabs for") + parser.add_option("--cropmarks",action="store_true",dest="cropmarks", + help="print crop marks on both sides, rather than tab outlines on one") + parser.add_option("--linewidth",type="float",default=.1, + help="width of lines for card outlines/crop marks") + parser.add_option("--read_yaml", action="store_true",dest="read_yaml", + help="read yaml version of card definitions and extras") + parser.add_option("--write_yaml", action="store_true",dest="write_yaml", + help="write yaml version of card definitions and extras") + parser.add_option("--tabs-only", action="store_true", dest="tabs_only", + help="draw only tabs to be printed on labels, no divider outlines") + parser.add_option("--order", type="choice", choices=["expansion","global"], dest="order", + help="sort order for the cards, whether by expansion or globally alphabetical") + parser.add_option("--expansion_dividers", action="store_true", dest="expansion_dividers", + help="add dividers describing each expansion set") + + return parser.parse_args(argstring) + + def main(self,argstring): + options,args = DominionTabs.parse_opts(argstring) + fname = None + if args: + fname = args[0] + return self.generate(options,fname) + + def parseDimensions(self, dimensionsStr): + x, y = dimensionsStr.upper().split('X', 1) + return (float (x) * cm, float (y) * cm) + + def generate(self,options,f): + self.options = options + size = self.options.size.upper() + if size == 'SLEEVED' or self.options.sleeved: + dominionCardWidth, dominionCardHeight = (9.4*cm, 6.15*cm) + print 'Using sleeved card size, %.2fcm x %.2fcm' % (dominionCardWidth/cm,dominionCardHeight/cm) + elif size 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 = self.parseDimensions(size) + print 'Using custom card size, %.2fcm x %.2fcm' % (dominionCardWidth/cm,dominionCardHeight/cm) + + papersize = None + if not self.options.papersize: + if os.path.exists("/etc/papersize"): + papersize = open ("/etc/papersize").readline().upper() + else: + papersize = 'LETTER' + else: + papersize = self.options.papersize.upper() + + if papersize == 'A4': + print "Using A4 sized paper." + self.paperwidth, self.paperheight = A4 + elif papersize == 'LETTER': + print "Using letter sized paper." + self.paperwidth, self.paperheight = LETTER + else: + self.paperwidth, self.paperheight = self.parseDimensions(papersize) + print 'Using custom paper size, %.2fcm x %.2fcm' % (self.paperwidth/cm,self.paperheight/cm) + + if self.options.orientation == "vertical": + self.tabWidth, self.tabBaseHeight = dominionCardHeight, dominionCardWidth + else: + self.tabWidth, self.tabBaseHeight = dominionCardWidth, dominionCardHeight + + fixedMargins = False + if self.options.tabs_only: + #fixed for Avery 8867 for now + minmarginwidth=0.76*cm + minmarginheight=1.27*cm + self.tabLabelHeight = 1.27*cm + self.tabLabelWidth = 4.44*cm + self.tabBaseHeight = 0 + self.tabWidth = self.tabLabelWidth + self.horizontalBorderSpace = 0.76*cm + self.verticalBorderSpace = 0.01*cm + fixedMargins = True + else: + minmarginwidth, minmarginheight = self.parseDimensions(self.options.minmargin) + + self.tabLabelHeight = 0.9*cm + self.tabLabelWidth = 4*cm + self.horizontalBorderSpace = 0*cm + self.verticalBorderSpace = 0*cm + + self.tabHeight = self.tabBaseHeight + self.tabLabelHeight + + self.totalTabWidth = self.tabWidth + self.horizontalBorderSpace + self.totalTabHeight = self.tabHeight + self.verticalBorderSpace + + print "Paper dimensions: %fcm (w) x %fcm (h)" % (self.paperwidth / cm, self.paperheight / cm) + print "Tab dimensions: %fcm (w) x %fcm (h)" % (self.totalTabWidth / cm, self.totalTabHeight / cm) + + numTabsVerticalP = int ((self.paperheight - 2*minmarginheight) / self.totalTabHeight) + numTabsHorizontalP = int ((self.paperwidth - 2*minmarginwidth) / self.totalTabWidth) + numTabsVerticalL = int ((self.paperwidth - 2*minmarginwidth) / self.totalTabHeight) + numTabsHorizontalL = int ((self.paperheight - 2*minmarginheight) / self.totalTabWidth) + + if numTabsVerticalL * numTabsHorizontalL > numTabsVerticalP * numTabsHorizontalP and not fixedMargins: + self.numTabsVertical, self.numTabsHorizontal\ + = numTabsVerticalL, numTabsHorizontalL + self.paperheight, self.paperwidth = self.paperwidth, self.paperheight + self.minHorizontalMargin = minmarginheight + self.minVerticalMargin = minmarginwidth + else: + self.numTabsVertical, self.numTabsHorizontal\ + = numTabsVerticalP, numTabsHorizontalP + self.minHorizontalMargin = minmarginwidth + self.minVerticalMargin = minmarginheight + + if not fixedMargins: + #dynamically max margins + self.horizontalMargin = (self.paperwidth-self.numTabsHorizontal*self.totalTabWidth)/2 + self.verticalMargin = (self.paperheight-self.numTabsVertical*self.totalTabHeight)/2 + else: + self.horizontalMargin = minmarginwidth + self.verticalMargin = minmarginheight + + print "Margins: %fcm h, %fcm v\n" % (self.horizontalMargin / cm, + self.verticalMargin / cm) + + self.tabOutline = [(0,0,self.tabWidth,0), + (self.tabWidth,0,self.tabWidth,self.tabHeight), + (self.tabWidth,self.tabHeight, + self.tabWidth-self.tabLabelWidth,self.tabHeight), + (self.tabWidth-self.tabLabelWidth, + self.tabHeight,self.tabWidth-self.tabLabelWidth, + self.tabBaseHeight), + (self.tabWidth-self.tabLabelWidth, + self.tabBaseHeight,0,self.tabBaseHeight), + (0,self.tabBaseHeight,0,0)] + + try: + dirn = os.path.join(self.filedir,'fonts') + pdfmetrics.registerFont(TTFont('MinionPro-Regular',os.path.join(dirn,'MinionPro-Regular.ttf'))) + pdfmetrics.registerFont(TTFont('MinionPro-Bold',os.path.join(dirn,'MinionPro-Bold.ttf'))) + except: + raise + pdfmetrics.registerFont(TTFont('MinionPro-Regular','OptimusPrincepsSemiBold.ttf')) + pdfmetrics.registerFont(TTFont('MinionPro-Bold','OptimusPrinceps.ttf')) + if options.read_yaml: + import yaml + cardfile = open("cards.yaml","r") + cards = yaml.load(cardfile) + else: + cards = self.read_card_defs(os.path.join(self.filedir,"dominion_cards.txt")) + self.read_card_extras(os.path.join(self.filedir,"dominion_card_extras.txt"),cards) + + if self.options.expansions: + self.options.expansions = [o.lower() for o in self.options.expansions] + cards=[c for c in cards if c.cardset in self.options.expansions] + + if options.expansion_dividers: + cardnamesByExpansion = {} + for c in cards: + cardnamesByExpansion.setdefault(c.cardset,[]).append(c.name.strip()) + for exp,names in cardnamesByExpansion.iteritems(): + c = Card(exp, exp, ("Expansion",), None, ' | '.join(sorted(names))) + cards.append(c) + + if options.write_yaml: + import yaml + out = yaml.dump(cards) + open('cards.yaml','w').write(out) + + if options.order == "global": + sf = lambda x,y: cmp(x.name,y.name) + else: + sf = lambda x,y: cmp((x.cardset,x.name),(y.cardset,y.name)) + cards.sort(cmp=sf) + + if not f: + f = "dominion_tabs.pdf" + self.canvas = canvas.Canvas(f, pagesize=(self.paperwidth, self.paperheight)) + self.drawDividers(cards) + self.canvas.save() + +if __name__=='__main__': + import sys + tabs = DominionTabs() + tabs.main(sys.argv[1:]) From 605368c8ed18d9bcb68e352ddc022262479510ae Mon Sep 17 00:00:00 2001 From: Paul Molodowitch Date: Sun, 15 Dec 2013 22:14:49 -0800 Subject: [PATCH 06/11] support for custom tab width --- dominion_tabs.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/dominion_tabs.py b/dominion_tabs.py index 748a115..3d9a49c 100644 --- a/dominion_tabs.py +++ b/dominion_tabs.py @@ -170,7 +170,7 @@ class DominionTabs: else: self.canvas.translate(0,self.tabHeight-self.tabLabelHeight) - textWidth = 85 + textWidth = self.tabLabelWidth - 6 # allow for 3 pt border on each side textHeight = self.tabLabelHeight/2-7+card.getType().getTabTextHeightOffset() self.canvas.drawImage(os.path.join(self.filedir,'images',card.getType().getNoCoinTabImageFile()),1,0, @@ -180,7 +180,6 @@ class DominionTabs: if card.getType().getTypeNames() != ('Expansion',): textInset = 22 - costHeight = textHeight + card.getType().getTabCostHeightOffset() potHeight = 3 + card.getType().getTabTextHeightOffset() potSize = 11 @@ -190,7 +189,6 @@ class DominionTabs: if card.potcost: self.canvas.drawImage(os.path.join(self.filedir,'images','potion.png'),21,potHeight,potSize,potSize,preserveAspectRatio=True,mask=[255,255,255,255,255,255]) textInset += potSize - textWidth -= potSize setImageHeight = potHeight self.canvas.setFont('MinionPro-Bold',12) @@ -204,6 +202,8 @@ class DominionTabs: textInset = 13 setImageHeight = 3 + card.getType().getTabTextHeightOffset() + textWidth -= textInset + #set image setImage = DominionTabs.setImages.get(card.cardset, None) if not setImage: @@ -488,6 +488,8 @@ class DominionTabs: help="'<%f>x<%f>' (size in cm, left/right, top/bottom), default: 1x1") parser.add_option("--papersize",type="string",dest="papersize",default=None, help="'<%f>x<%f>' (size in cm), or 'A4', or 'LETTER'") + parser.add_option("--tabwidth",type="float",default=4, + help="width in cm of stick-up tab (ignored if tabs-only used)") parser.add_option("--samesidelabels",action="store_true",dest="sameside", help="force all label tabs to be on the same side") parser.add_option("--expansions",action="append",type="string", @@ -571,9 +573,8 @@ class DominionTabs: fixedMargins = True else: minmarginwidth, minmarginheight = self.parseDimensions(self.options.minmargin) - - self.tabLabelHeight = 0.9*cm - self.tabLabelWidth = 4*cm + self.tabLabelWidth = self.options.tabwidth * cm + self.tabLabelHeight = .9*cm self.horizontalBorderSpace = 0*cm self.verticalBorderSpace = 0*cm From 3b3d9ff1284f73b2437f6a6467f44133b1d0e919 Mon Sep 17 00:00:00 2001 From: Paul Molodowitch Date: Mon, 16 Dec 2013 07:33:21 -0800 Subject: [PATCH 07/11] fix missing spaces in card extras (wasn't inserting space in place of line break) --- dominion_tabs.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/dominion_tabs.py b/dominion_tabs.py index 3d9a49c..743e647 100644 --- a/dominion_tabs.py +++ b/dominion_tabs.py @@ -320,7 +320,7 @@ class DominionTabs: if not self.options.expansions and currentCard and (currentCard not in (c.name for c in cards)): print currentCard + ' has extra description, but is not in cards' else: - extra += line.strip() + extra += ' ' + line.strip() if currentCard and extra: extras[currentCard] = extra.strip() for c in cards: @@ -659,10 +659,10 @@ class DominionTabs: open('cards.yaml','w').write(out) if options.order == "global": - sf = lambda x,y: cmp(x.name,y.name) + sortKey = lambda x: x.name else: - sf = lambda x,y: cmp((x.cardset,x.name),(y.cardset,y.name)) - cards.sort(cmp=sf) + sortKey = lambda x: (x.cardset,x.name) + cards.sort(key=sortKey) if not f: f = "dominion_tabs.pdf" From 70e8a513e5d225c8ef4c4f4e97f4f7909e71985a Mon Sep 17 00:00:00 2001 From: Paul Molodowitch Date: Mon, 16 Dec 2013 07:51:40 -0800 Subject: [PATCH 08/11] fix for 2-line names with more than 2 parts --- dominion_tabs.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dominion_tabs.py b/dominion_tabs.py index 743e647..d73fd00 100644 --- a/dominion_tabs.py +++ b/dominion_tabs.py @@ -250,7 +250,7 @@ class DominionTabs: self.canvas.drawString(textInset+w,h,n[1:]) w += pdfmetrics.stringWidth(n[1:],'MinionPro-Regular',fontSize-2) w += pdfmetrics.stringWidth(' ','MinionPro-Regular',fontSize) - if tooLong: + if tooLong and i == 0: w = 0 self.canvas.restoreState() From 651eafe78a95e0a01b806eb9bcb10ff53ea15e08 Mon Sep 17 00:00:00 2001 From: Paul Molodowitch Date: Tue, 17 Dec 2013 21:08:18 -0800 Subject: [PATCH 09/11] add edge-align-name option to make names more visible --- dominion_tabs.py | 105 +++++++++++++++++++++++++++++++++-------------- 1 file changed, 75 insertions(+), 30 deletions(-) diff --git a/dominion_tabs.py b/dominion_tabs.py index d73fd00..56ee925 100644 --- a/dominion_tabs.py +++ b/dominion_tabs.py @@ -19,8 +19,8 @@ def split(l,n): class Card: def __init__(self,name,cardset,types,cost,description,potcost=0): - self.name = name - self.cardset = cardset + self.name = name.strip() + self.cardset = cardset.strip() self.types = types self.cost = cost self.potcost = potcost @@ -161,6 +161,17 @@ class DominionTabs: self.canvas.restoreState() + @classmethod + def nameWidth(self, name, fontSize): + w = 0 + name_parts = name.split() + for i, part in enumerate(name_parts): + if i != 0: + w += pdfmetrics.stringWidth(' ','MinionPro-Regular',fontSize) + w += pdfmetrics.stringWidth(part[0],'MinionPro-Regular',fontSize) + w += pdfmetrics.stringWidth(part[1:],'MinionPro-Regular',fontSize-2) + return w + def drawTab(self, card, rightSide): #draw tab flap self.canvas.saveState() @@ -202,15 +213,17 @@ class DominionTabs: textInset = 13 setImageHeight = 3 + card.getType().getTabTextHeightOffset() - textWidth -= textInset - #set image setImage = DominionTabs.setImages.get(card.cardset, None) if not setImage: setImage = DominionTabs.promoImages.get(card.name.lower(), None) + # always need to offset from right edge, to make sure it stays on + # banner + textInsetRight = 6 if setImage: self.canvas.drawImage(os.path.join(self.filedir,'images',setImage), self.tabLabelWidth-20, setImageHeight, 14, 12, mask='auto') + textInsetRight = 20 elif setImage == None and card.cardset != 'base' and card.getType().getTypeNames() != ('Expansion',): print 'warning, no set image for set "%s" card "%s"' % (card.cardset, card.name) DominionTabs.setImages[card.cardset] = 0 @@ -218,40 +231,61 @@ class DominionTabs: fontSize = 12 name = card.name.upper() - name_parts = name.partition(' / ') - if name_parts[1]: - name_parts = (name_parts[0] + ' /', name_parts[2]) - else: - name_parts = name.split() - width = pdfmetrics.stringWidth(name,'MinionPro-Regular',fontSize) + textWidth -= textInset + textWidth -= textInsetRight + + width = self.nameWidth(name, fontSize) while width > textWidth and fontSize > 8: fontSize -= 1 #print 'decreasing font size for tab of',name,'now',fontSize - width = pdfmetrics.stringWidth(name,'MinionPro-Regular',fontSize) + width = self.nameWidth(name, fontSize) tooLong = width > textWidth + if tooLong: + name_lines = name.partition(' / ') + if name_lines[1]: + name_lines = (name_lines[0] + ' /', name_lines[2]) + else: + name_lines = name.split(None, 1) + else: + name_lines = [name] #if tooLong: # print name - #self.canvas.drawString(tabLabelWidth/2+8,tabLabelHeight/2-7,name[0]) - w = 0 - for i,n in enumerate(name_parts): - self.canvas.setFont('MinionPro-Regular',fontSize) + for linenum, line in enumerate(name_lines): h = textHeight - if tooLong: - if i == 0: + if tooLong and len(name_lines) > 1: + if linenum == 0: h += h/2 else: h -= h/2 - self.canvas.drawString(textInset+w,h,n[0]) - w += pdfmetrics.stringWidth(n[0],'MinionPro-Regular',fontSize) - #self.canvas.drawString(tabLabelWidth/2+8+w,tabLabelHeight/2-7,name[1:]) - self.canvas.setFont('MinionPro-Regular',fontSize-2) - self.canvas.drawString(textInset+w,h,n[1:]) - w += pdfmetrics.stringWidth(n[1:],'MinionPro-Regular',fontSize-2) - w += pdfmetrics.stringWidth(' ','MinionPro-Regular',fontSize) - if tooLong and i == 0: - w = 0 + + if rightSide or not self.options.edge_align_name: + w = textInset + name_parts = line.split() + def drawWordPiece(text, fontSize): + self.canvas.setFont('MinionPro-Regular',fontSize) + if text != ' ': + self.canvas.drawString(w,h,text) + return pdfmetrics.stringWidth(text,'MinionPro-Regular',fontSize) + for word in name_parts: + w += drawWordPiece(word[0], fontSize) + w += drawWordPiece(word[1:], fontSize-2) + else: + # align text to the right if tab is on right side, to make + # tabs easier to read when grouped together extra 3pt is for + # space between text + set symbol + + w = self.tabLabelWidth - textInsetRight - 3 + name_parts = reversed(line.split()) + def drawWordPiece(text, fontSize): + self.canvas.setFont('MinionPro-Regular',fontSize) + if text != ' ': + self.canvas.drawRightString(w,h,text) + return -pdfmetrics.stringWidth(text,'MinionPro-Regular',fontSize) + for word in name_parts: + w += drawWordPiece(word[1:], fontSize-2) + w += drawWordPiece(word[0], fontSize) self.canvas.restoreState() def drawText(self, card, useExtra=False): @@ -283,7 +317,7 @@ class DominionTabs: def drawDivider(self,card,x,y,useExtra=False): #figure out whether the tab should go on the right side or not - if self.numTabsHorizontal % 2 == 0: + if not self.options.sameside: rightSide = x%2 == 1 else: rightSide = useExtra @@ -380,8 +414,8 @@ class DominionTabs: potcost = int(m.groupdict()["potioncost"]) else: potcost = 0 - currentCard = Card(m.groupdict()["name"], - m.groupdict()["set"].lower(), + currentCard = Card(m.groupdict()["name"].strip(), + m.groupdict()["set"].lower().strip(), tuple([t.strip() for t in m.groupdict()["type"].split("-")]), int(m.groupdict()["cost"]), '', @@ -491,7 +525,14 @@ class DominionTabs: parser.add_option("--tabwidth",type="float",default=4, help="width in cm of stick-up tab (ignored if tabs-only used)") parser.add_option("--samesidelabels",action="store_true",dest="sameside", - help="force all label tabs to be on the same side") + help="force all label tabs to be on the same side" + " (this will be forced on if there is an uneven" + " number of cards horizontally across the page)") + parser.add_option("--edge-align-name",action="store_true", + help="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; ignored if samesidelabels is on") parser.add_option("--expansions",action="append",type="string", help="subset of dominion expansions to produce tabs for") parser.add_option("--cropmarks",action="store_true",dest="cropmarks", @@ -603,6 +644,10 @@ class DominionTabs: self.minHorizontalMargin = minmarginwidth self.minVerticalMargin = minmarginheight + if self.numTabsHorizontal % 2 != 0: + # force on sameside if an uneven # of tabs horizontally + self.options.sameside = True + if not fixedMargins: #dynamically max margins self.horizontalMargin = (self.paperwidth-self.numTabsHorizontal*self.totalTabWidth)/2 From b38eec7816ed548e4953de79478d9e8ca027bb7b Mon Sep 17 00:00:00 2001 From: Paul Molodowitch Date: Wed, 18 Dec 2013 07:52:03 -0800 Subject: [PATCH 10/11] fix alignment of tabs on back of cards when using sameside option --- dominion_tabs.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dominion_tabs.py b/dominion_tabs.py index 56ee925..bd07261 100644 --- a/dominion_tabs.py +++ b/dominion_tabs.py @@ -125,7 +125,7 @@ class DominionTabs: self.canvas.setLineWidth(self.options.linewidth) cropmarksright = (x == self.numTabsHorizontal-1) cropmarksleft = (x == 0) - if rightSide and not self.options.sameside: + if rightSide: self.canvas.translate(self.tabWidth,0) self.canvas.scale(-1,1) if not self.options.cropmarks and not isBack: @@ -175,7 +175,7 @@ class DominionTabs: def drawTab(self, card, rightSide): #draw tab flap self.canvas.saveState() - if not rightSide or self.options.sameside: + if not rightSide: self.canvas.translate(self.tabWidth-self.tabLabelWidth, self.tabHeight-self.tabLabelHeight) else: From 63dce01edb8b12b91a3bdc740391e05e566be038 Mon Sep 17 00:00:00 2001 From: Paul Molodowitch Date: Wed, 18 Dec 2013 08:22:49 -0800 Subject: [PATCH 11/11] fix some missing vertical-line cropmarks --- dominion_tabs.py | 42 ++++++++++++++++++++++++++++++------------ 1 file changed, 30 insertions(+), 12 deletions(-) diff --git a/dominion_tabs.py b/dominion_tabs.py index bd07261..794dc93 100644 --- a/dominion_tabs.py +++ b/dominion_tabs.py @@ -133,6 +133,8 @@ class DominionTabs: self.canvas.lines(self.tabOutline) elif self.options.cropmarks: cmw = 0.5*cm + + # Horizontal-line cropmarks mirror = cropmarksright and not rightSide or cropmarksleft and rightSide if mirror: self.canvas.saveState() @@ -145,19 +147,35 @@ class DominionTabs: self.canvas.line(-2*cmw,self.tabHeight,-cmw,self.tabHeight) if mirror: self.canvas.restoreState() + + # Vertical-line cropmarks + + # want to always draw the right-edge and middle-label-edge lines.. + # ...and draw the left-edge if this is the first card on the left + + # ...but we need to take mirroring into account, to know "where" + # to draw the left / right lines... + if rightSide: + leftLine = self.tabWidth + rightLine = 0 + else: + leftLine = 0 + rightLine = self.tabWidth + middleLine = self.tabWidth-self.tabLabelWidth + if y == 0: - self.canvas.line(self.tabWidth,-2*cmw,self.tabWidth,-cmw) - self.canvas.line(self.tabWidth-self.tabLabelWidth,-2*cmw,self.tabWidth-self.tabLabelWidth,-cmw) - if x == 0: - self.canvas.line(0,-2*cmw,0,-cmw) - elif y == self.numTabsVertical-1: - self.canvas.line(self.tabWidth,self.tabHeight+cmw,self.tabWidth,self.tabHeight+2*cmw) - self.canvas.line(self.tabWidth-self.tabLabelWidth, - self.tabHeight+cmw, - self.tabWidth-self.tabLabelWidth, - self.tabHeight+2*cmw) - if x == 0: - self.canvas.line(0,self.tabHeight+cmw,0,self.tabHeight+2*cmw) + self.canvas.line(rightLine,-2*cmw,rightLine,-cmw) + self.canvas.line(middleLine,-2*cmw,middleLine,-cmw) + if cropmarksleft: + self.canvas.line(leftLine,-2*cmw,leftLine,-cmw) + if y == self.numTabsVertical-1: + self.canvas.line(rightLine,self.tabHeight+cmw, + rightLine,self.tabHeight+2*cmw) + self.canvas.line(middleLine, self.tabHeight+cmw, + middleLine, self.tabHeight+2*cmw) + if cropmarksleft: + self.canvas.line(leftLine,self.tabHeight+cmw, + leftLine,self.tabHeight+2*cmw) self.canvas.restoreState()