* Major refactor of draw.py * Also addresses Issue #70, Issue #85, Issue #120, partial to Issue #6 * Make the label information more available so it can be picked up easier by the website frontend * Add Avery Presta 94211 labels, label names * Add tab-height to make easier calculations * height is now the full height of the label. tab-height is the portion used for the tab. (By doing this, adding labels is more intuitive and requires fewer calculations.) * Fix for issue #239 The fix changes this to now include the original expansion in this case. So now this results in the original expansion expansion being printed with the upgrade expansion cards included as well.
1713 lines
79 KiB
Python
1713 lines
79 KiB
Python
from __future__ import print_function
|
|
|
|
import os
|
|
import re
|
|
import sys
|
|
|
|
import pkg_resources
|
|
|
|
from reportlab.lib.units import cm
|
|
from reportlab.pdfbase import pdfmetrics
|
|
from reportlab.lib.styles import getSampleStyleSheet
|
|
from reportlab.platypus import Paragraph, XPreformatted
|
|
from reportlab.lib.enums import TA_JUSTIFY, TA_CENTER, TA_LEFT
|
|
from reportlab.pdfgen import canvas
|
|
from reportlab.pdfbase.ttfonts import TTFont
|
|
from reportlab.pdfbase.pdfmetrics import stringWidth
|
|
from .cards import Card
|
|
|
|
|
|
def split(l, n):
|
|
i = 0
|
|
while i < len(l) - n:
|
|
yield l[i:i + n]
|
|
i += n
|
|
yield l[i:]
|
|
|
|
|
|
class CardPlot(object):
|
|
# This object contains information needed to print a divider on a page.
|
|
# It goes beyond information about the general card/divider to include page specific drawing information.
|
|
# It also includes helpful methods used in manipulating the object and keeping up with tab locations.
|
|
|
|
LEFT, CENTRE, RIGHT, TOP, BOTTOM = range(100, 105) # location & directional constants
|
|
|
|
tabNumber = 1 # Number of different tab locations
|
|
tabIncrement = 0 # Either 1, 0, or -1. Used to select next tab. This can change if tabSerpentine.
|
|
tabIncrementStart = 0 # Starting value of tabIncrement
|
|
tabStart = 1 # The starting tab location.
|
|
tabStartSide = LEFT # The starting side for the tabs
|
|
tabSerpentine = False # What to do at the end of a line of tabs. False = start over. True = reverses direction.
|
|
lineType = 'line' # Type of outline to use: line, dot, none
|
|
cardWidth = 0 # Width of just the divider, with no extra padding/spacing. NEEDS TO BE SET.
|
|
cardHeight = 0 # Height of just the divider, with no extra padding/spacing or tab. NEEDS TO BE SET.
|
|
tabWidth = 0 # Width of the tab. NEEDS TO BE SET.
|
|
tabHeight = 0 # Height of the tab. NEEDS TO BE SET.
|
|
wrapper = False # If the divider is a sleeve/wrapper.
|
|
|
|
@staticmethod
|
|
def tabSetup(tabNumber=None, cardWidth=None, cardHeight=None, tabWidth=None, tabHeight=None,
|
|
lineType=None, start=None, serpentine=None, wrapper=None):
|
|
# Set up the basic tab information used in calculations when a new CardPlot object is created.
|
|
# This needs to be called at least once before the first CardPlot object is created and then it
|
|
# needs to be called any time one of the above parameters needs to change.
|
|
CardPlot.tabNumber = tabNumber if tabNumber is not None else CardPlot.tabNumber
|
|
CardPlot.cardWidth = cardWidth if cardWidth is not None else CardPlot.cardWidth
|
|
CardPlot.cardHeight = cardHeight if cardHeight is not None else CardPlot.cardHeight
|
|
CardPlot.tabWidth = tabWidth if tabWidth is not None else CardPlot.tabWidth
|
|
CardPlot.tabHeight = tabHeight if tabHeight is not None else CardPlot.tabHeight
|
|
CardPlot.lineType = lineType if lineType is not None else CardPlot.lineType
|
|
CardPlot.tabStartSide = start if start is not None else CardPlot.tabStartSide
|
|
CardPlot.tabSerpentine = serpentine if serpentine is not None else CardPlot.tabSerpentine
|
|
CardPlot.wrapper = wrapper if wrapper is not None else CardPlot.wrapper
|
|
# LEFT tabs RIGHT
|
|
# +---+ +---+ +---+ +---+ +---+
|
|
# | 1 | | 2 | | 3 | |...| | N | Note: tabNumber = N, N >=1, 0 is for centred tabs
|
|
# + +-+ +-+ +-+ +-+ +
|
|
|
|
# Setup first tab as well as starting point and direction of increment for tabs.
|
|
if CardPlot.tabStartSide == CardPlot.RIGHT:
|
|
CardPlot.tabStart = CardPlot.tabNumber
|
|
CardPlot.tabIncrementStart = -1
|
|
elif CardPlot.tabStartSide == CardPlot.CENTRE:
|
|
# Get as close to centre as possible
|
|
CardPlot.tabStart = (CardPlot.tabNumber + 1) // 2
|
|
CardPlot.tabIncrementStart = 1
|
|
else:
|
|
# LEFT and anything else
|
|
CardPlot.tabStartSide = CardPlot.LEFT
|
|
CardPlot.tabStart = 1
|
|
CardPlot.tabIncrementStart = 1
|
|
|
|
if CardPlot.tabNumber == 1:
|
|
CardPlot.tabIncrementStart = 0
|
|
CardPlot.tabIncrement = CardPlot.tabIncrementStart
|
|
|
|
@staticmethod
|
|
def tabRestart():
|
|
# Resets the tabIncrement to the starting value and returns the starting tabIndex number.
|
|
CardPlot.tabIncrement = CardPlot.tabIncrementStart
|
|
return CardPlot.tabStart
|
|
|
|
def __init__(self, card, x=0, y=0, rotation=0, stackHeight=0, tabIndex=None, page=0,
|
|
textTypeFront="card", textTypeBack="rules",
|
|
cropOnTop=False, cropOnBottom=False, cropOnLeft=False, cropOnRight=False):
|
|
self.card = card
|
|
self.x = x # x location of the lower left corner of the card on the page
|
|
self.y = y # y location of the lower left corner of the card on the page
|
|
self.rotation = rotation # of the card. 0, 90, 180, 270
|
|
self.stackHeight = stackHeight # The height of a stack of these cards. Used for interleaving.
|
|
self.tabIndex = tabIndex # Tab location index. Starts at 1 and goes up to CardPlot.tabNumber
|
|
self.page = page # holds page number of this printed card
|
|
self.textTypeFront = textTypeFront # What card text to put on the front of the divider
|
|
self.textTypeBack = textTypeBack # What card text to put on the back of the divider
|
|
self.cropOnTop = cropOnTop # When true, cropmarks needed along TOP *printed* edge of the card
|
|
self.cropOnBottom = cropOnBottom # When true, cropmarks needed along BOTTOM *printed* edge of the card
|
|
self.cropOnLeft = cropOnLeft # When true, cropmarks needed along LEFT *printed* edge of the card
|
|
self.cropOnRight = cropOnRight # When true, cropmarks needed along RIGHT *printed* edge of the card
|
|
|
|
# And figure out the backside index
|
|
if self.tabIndex == 0:
|
|
self.tabIndexBack = 0 # Exact Centre special case, so swapping is still exact centre
|
|
elif CardPlot.tabNumber == 1:
|
|
self.tabIndex = self.tabIndexBack = 1 # There is only one tab, so can only use 1 for both sides
|
|
elif 1 <= self.tabIndex <= CardPlot.tabNumber:
|
|
self.tabIndexBack = CardPlot.tabNumber + 1 - self.tabIndex
|
|
else:
|
|
# For anything else, just start at 1
|
|
self.tabIndex = self.tabIndexBack = 1
|
|
|
|
# Now set the offsets and the closest edge to the tab
|
|
if self.tabIndex == 0:
|
|
# Special case for centred tabs
|
|
self.tabOffset = self.tabOffsetBack = (CardPlot.cardWidth - CardPlot.tabWidth) / 2
|
|
self.closestSide = CardPlot.CENTRE
|
|
elif CardPlot.tabNumber <= 1:
|
|
# If just one tab, then can be right, centre, or left
|
|
self.closestSide = CardPlot.tabStartSide
|
|
if CardPlot.tabStartSide == CardPlot.RIGHT:
|
|
self.tabOffset = CardPlot.cardWidth - CardPlot.tabWidth
|
|
self.tabOffsetBack = 0
|
|
elif CardPlot.tabStartSide == CardPlot.CENTRE:
|
|
self.tabOffset = (CardPlot.cardWidth - CardPlot.tabWidth) / 2
|
|
self.tabOffsetBack = (CardPlot.cardWidth - CardPlot.tabWidth) / 2
|
|
else:
|
|
# LEFT and anything else
|
|
self.tabOffset = 0
|
|
self.tabOffsetBack = CardPlot.cardWidth - CardPlot.tabWidth
|
|
else:
|
|
# More than 1 tabs
|
|
self.tabOffset = (self.tabIndex - 1) * (
|
|
(CardPlot.cardWidth - CardPlot.tabWidth) / (CardPlot.tabNumber - 1))
|
|
self.tabOffsetBack = CardPlot.cardWidth - CardPlot.tabWidth - self.tabOffset
|
|
|
|
# Set which edge is closest to the tab
|
|
if self.tabIndex <= CardPlot.tabNumber / 2:
|
|
self.closestSide = CardPlot.LEFT
|
|
else:
|
|
self.closestSide = CardPlot.RIGHT if self.tabIndex > (CardPlot.tabNumber + 1) / 2 else CardPlot.CENTRE
|
|
|
|
def setXY(self, x, y, rotation=None):
|
|
# set the card to the given x,y and optional rotation
|
|
self.x = x
|
|
self.y = y
|
|
if rotation is not None:
|
|
self.rotation = rotation
|
|
|
|
def rotate(self, delta):
|
|
# rotate the card by amount delta
|
|
self.rotation = (self.rotation + delta) % 360
|
|
|
|
def getTabOffset(self, backside=False):
|
|
# Get the tab offset (from the left edge) of the tab given
|
|
if backside:
|
|
return self.tabOffsetBack
|
|
else:
|
|
return self.tabOffset
|
|
|
|
def nextTab(self, tab=None):
|
|
# For a given tab, calculate the next tab in the sequence
|
|
tab = tab if tab is not None else self.tabIndex
|
|
if CardPlot.tabNumber == 1:
|
|
return 1 # it is the same, nothing else to do
|
|
|
|
# Increment if in range
|
|
if 1 <= tab <= CardPlot.tabNumber:
|
|
tab += CardPlot.tabIncrement
|
|
|
|
# Now check for wrap around
|
|
if tab > CardPlot.tabNumber:
|
|
tab = 1
|
|
elif tab < 1:
|
|
tab = CardPlot.tabNumber
|
|
|
|
if CardPlot.tabSerpentine and CardPlot.tabNumber > 2:
|
|
if (tab == 1) or (tab == CardPlot.tabNumber):
|
|
# reverse direction for next tab
|
|
CardPlot.tabIncrement *= -1
|
|
return tab
|
|
|
|
def getClosestSide(self, backside=False):
|
|
# Get the closest side for this tab.
|
|
# Used when wanting text to be aligned towards the outer edge.
|
|
side = self.closestSide
|
|
if backside:
|
|
# Need to flip
|
|
if side == CardPlot.LEFT:
|
|
side = CardPlot.RIGHT
|
|
elif side == CardPlot.RIGHT:
|
|
side = CardPlot.LEFT
|
|
return side
|
|
|
|
def flipFront2Back(self):
|
|
# Flip a card from front to back. i.e., print the front of the divider on the page's back
|
|
# and print the back of the divider on the page's front. So what does that mean...
|
|
# If it is a wrapper / slipcover, then it is rotated 180 degrees.
|
|
# Otherwise, the tab moves from right(left) to left(right). If centre, it stays the same.
|
|
# And then the divider's text is moved to the other side of the page.
|
|
if self.wrapper:
|
|
self.rotate(180)
|
|
else:
|
|
self.tabIndex, self.tabIndexBack = self.tabIndexBack, self.tabIndex
|
|
self.tabOffset, self.tabOffsetBack = self.tabOffsetBack, self.tabOffset
|
|
self.textTypeFront, self.textTypeBack = self.textTypeBack, self.textTypeFront
|
|
self.closestSide = self.getClosestSide(backside=True)
|
|
|
|
def translate(self, canvas, page_width, backside=False):
|
|
# Translate the page x,y of the lower left of item, taking into account the rotation,
|
|
# and set up the canvas so that (0,0) is now at the lower lower left of the item
|
|
# and the item can be drawn as if it is in the "standard" orientation.
|
|
# So when done, the canvas is set and ready to draw the divider
|
|
x = self.x
|
|
y = self.y
|
|
rotation = self.rotation
|
|
|
|
# set width and height for this card
|
|
width = self.cardWidth
|
|
height = self.cardHeight + self.tabHeight
|
|
if self.wrapper:
|
|
height = 2 * (height + self.stackHeight)
|
|
|
|
if backside:
|
|
x = page_width - x - width
|
|
|
|
if self.rotation == 180:
|
|
x += width
|
|
y += height
|
|
elif self.rotation == 90:
|
|
if backside:
|
|
x += width
|
|
rotation = 270
|
|
else:
|
|
y += width
|
|
elif self.rotation == 270:
|
|
if backside:
|
|
x += width - height
|
|
y += width
|
|
rotation = 90
|
|
else:
|
|
x += height
|
|
|
|
rotation = 360 - rotation % 360 # ReportLab rotates counter clockwise, not clockwise.
|
|
canvas.translate(x, y)
|
|
canvas.rotate(rotation)
|
|
|
|
def translateCropmarkEnable(self, side):
|
|
# Returns True if a cropmark is needed on that side of the card
|
|
# Takes into account the card's rotation, if the tab is flipped, if the card is next to an edge, etc.
|
|
|
|
# First the rotation. The page does not change even if the card is rotated.
|
|
# So need to translate page side to the actual drawn card edge
|
|
if self.rotation == 0:
|
|
sideTop = self.cropOnTop
|
|
sideBottom = self.cropOnBottom
|
|
sideRight = self.cropOnRight
|
|
sideLeft = self.cropOnLeft
|
|
elif self.rotation == 90:
|
|
sideTop = self.cropOnRight
|
|
sideBottom = self.cropOnLeft
|
|
sideRight = self.cropOnBottom
|
|
sideLeft = self.cropOnTop
|
|
elif self.rotation == 180:
|
|
sideTop = self.cropOnBottom
|
|
sideBottom = self.cropOnTop
|
|
sideRight = self.cropOnLeft
|
|
sideLeft = self.cropOnRight
|
|
elif self.rotation == 270:
|
|
sideTop = self.cropOnLeft
|
|
sideBottom = self.cropOnRight
|
|
sideRight = self.cropOnTop
|
|
sideLeft = self.cropOnBottom
|
|
|
|
# Now can return the proper value based upon what side is requested
|
|
if side == self.TOP:
|
|
return sideTop
|
|
elif side == self.BOTTOM:
|
|
return sideBottom
|
|
elif side == self.RIGHT:
|
|
return sideRight
|
|
elif side == self.LEFT:
|
|
return sideLeft
|
|
else:
|
|
return False # just in case
|
|
|
|
|
|
class Plotter(object):
|
|
# Creates a simple plotting object that goes from point to point.
|
|
# This makes outline drawing easier since calculations only need to be the delta from
|
|
# one point to the next. The default plotting in reportlab requires both
|
|
# ends of the line in absolute sense. Thus calculations can become increasingly more
|
|
# complicated given various options. Using this object simplifies the calculations significantly.
|
|
|
|
def __init__(self, canvas, x=0, y=0, cropmarkLength=-1, cropmarkSpacing=-1):
|
|
self.canvas = canvas
|
|
self.x = x
|
|
self.y = y
|
|
self.LEFT, self.RIGHT, self.TOP, self.BOTTOM, self.LINE, self.NO_LINE, self.DOT = range(1, 8) # Constants
|
|
if cropmarkLength < 0:
|
|
cropmarkLength = 0.2
|
|
if cropmarkSpacing < 0:
|
|
cropmarkSpacing = 0.1
|
|
self.CropMarkLength = cropmarkLength * cm # The length of a cropmark
|
|
self.CropMarkSpacing = cropmarkSpacing * cm # The spacing between the cut point and the start of the cropmark
|
|
self.DotSize = 0.2 # Size of dot marks
|
|
self.CropEnable = {self.LEFT: False, self.RIGHT: False, self.TOP: False, self.BOTTOM: False}
|
|
|
|
def setXY(self, x, y):
|
|
self.x = x
|
|
self.y = y
|
|
|
|
def getXY(self):
|
|
return (self.x, self.y)
|
|
|
|
def setCropEnable(self, mark, enable=False):
|
|
if mark in self.CropEnable:
|
|
self.CropEnable[mark] = enable
|
|
|
|
def plot(self, delta_x=0, delta_y=0, pen=False, cropmarks=[]):
|
|
# Move the pen, drawing along the way
|
|
if pen is False:
|
|
pen = self.NO_LINE
|
|
x, y = self.getXY() # get current point
|
|
new_x = x + delta_x # calculate new point from delta
|
|
new_y = y + delta_y
|
|
if pen == self.LINE:
|
|
self.canvas.line(x, y, new_x, new_y)
|
|
if pen == self.DOT:
|
|
self.canvas.circle(new_x, new_y, self.DotSize)
|
|
self.setXY(new_x, new_y) # save the new point
|
|
|
|
# Make sure cropmarks is a list
|
|
cropmarks = cropmarks if isinstance(cropmarks, list) else [cropmarks]
|
|
# Now add any cropmarks
|
|
for mark in cropmarks:
|
|
# setCropEnable must be called for each direction ahead of time (once per divider).
|
|
# Cropmarks are only drawn for directions that are enabled (as set above).
|
|
# Each crop mark given is either:
|
|
# 1. A tuple of direction and a boolean of additional enablement criteria
|
|
# 2. A direction to draw a drop mark
|
|
if isinstance(mark, tuple):
|
|
direction, enable = mark
|
|
enable = enable and self.CropEnable[direction] if direction in self.CropEnable else False
|
|
else:
|
|
direction = mark
|
|
enable = self.CropEnable[direction] if direction in self.CropEnable else False
|
|
if direction in self.CropEnable:
|
|
self.cropmark(direction, enable)
|
|
|
|
def cropmark(self, direction, enabled=False):
|
|
# From current point, draw a cropmark in the correct direction and return to starting point
|
|
if enabled:
|
|
x, y = self.getXY() # Saving for later
|
|
|
|
if direction == self.TOP:
|
|
self.plot(0, self.CropMarkSpacing)
|
|
self.plot(0, self.CropMarkLength, self.LINE)
|
|
if direction == self.BOTTOM:
|
|
self.plot(0, -self.CropMarkSpacing)
|
|
self.plot(0, -self.CropMarkLength, self.LINE)
|
|
if direction == self.RIGHT:
|
|
self.plot(self.CropMarkSpacing, 0)
|
|
self.plot(self.CropMarkLength, 0, self.LINE)
|
|
if direction == self.LEFT:
|
|
self.plot(-self.CropMarkSpacing, 0)
|
|
self.plot(-self.CropMarkLength, 0, self.LINE)
|
|
self.setXY(x, y) # Restore to starting point
|
|
|
|
|
|
class DividerDrawer(object):
|
|
def __init__(self, options=None):
|
|
self.canvas = None
|
|
self.pages = None
|
|
self.options = options
|
|
|
|
@staticmethod
|
|
def get_image_filepath(fname):
|
|
return pkg_resources.resource_filename('domdiv', os.path.join('images', fname))
|
|
|
|
def draw(self, cards=[], options=None):
|
|
if options is not None:
|
|
self.options = options
|
|
|
|
self.registerFonts()
|
|
self.canvas = canvas.Canvas(
|
|
self.options.outfile,
|
|
pagesize=(self.options.paperwidth, self.options.paperheight))
|
|
self.drawDividers(cards)
|
|
if self.options.info or self.options.info_all:
|
|
self.drawInfo()
|
|
self.canvas.save()
|
|
|
|
def registerFonts(self):
|
|
# the following are filenames from both an Adobe Reader install and a download from fontsgeek
|
|
fontfilenames = ['MinionPro-Regular.ttf',
|
|
'MinionPro-Bold.ttf',
|
|
'MinionPro-It.ttf',
|
|
'Minion Pro Regular.ttf',
|
|
'Minion Pro Bold.ttf',
|
|
'Minion Pro Italic.ttf']
|
|
# first figure out which, if any, are present
|
|
fontpaths = [os.path.join('fonts', fname) for fname in fontfilenames]
|
|
fontpaths = [fpath for fpath in fontpaths if pkg_resources.resource_exists('domdiv', fpath)]
|
|
self.font_mapping = {'Regular': [fpath for fpath in fontpaths if 'Regular' in fpath],
|
|
'Bold': [fpath for fpath in fontpaths if 'Bold' in fpath],
|
|
'Italic': [fpath for fpath in fontpaths if 'It' in fpath]}
|
|
# then make sure that we have at least one for each type
|
|
for fonttype in self.font_mapping:
|
|
if not len(self.font_mapping[fonttype]):
|
|
print(("Warning, Minion Pro ttf file for {} missing from domdiv/fonts!"
|
|
" Falling back on Times font for everything.").format(fonttype), file=sys.stderr)
|
|
self.font_mapping = {'Regular': 'Times-Roman',
|
|
'Bold': 'Times-Bold',
|
|
'Italic': 'Times-Oblique'}
|
|
break
|
|
else:
|
|
# and finally register and tag one for each type
|
|
ftag = 'MinionPro-{}'.format(fonttype)
|
|
pdfmetrics.registerFont(TTFont(ftag,
|
|
pkg_resources.resource_filename('domdiv',
|
|
self.font_mapping[fonttype][0])))
|
|
self.font_mapping[fonttype] = ftag
|
|
self.font_mapping['Monospaced'] = 'Courier'
|
|
|
|
def drawTextPages(self, pages, margin=1.0, fontsize=10, leading=10, spacer=0.05):
|
|
s = getSampleStyleSheet()['BodyText']
|
|
s.fontName = self.font_mapping['Monospaced']
|
|
s.alignment = TA_LEFT
|
|
|
|
textHorizontalMargin = margin * cm
|
|
textVerticalMargin = margin * cm
|
|
textBoxWidth = self.options.paperwidth - 2 * textHorizontalMargin
|
|
textBoxHeight = self.options.paperheight - 2 * textVerticalMargin
|
|
minSpacerHeight = 0.05 * cm
|
|
|
|
for page in pages:
|
|
s.fontsize = fontsize
|
|
s.leading = leading
|
|
spacerHeight = spacer * cm
|
|
text = re.split("\n", page)
|
|
while True:
|
|
paragraphs = []
|
|
# this accounts for the spacers we insert between paragraphs
|
|
h = (len(text) - 1) * spacerHeight
|
|
for line in text:
|
|
p = XPreformatted(line, s)
|
|
h += p.wrap(textBoxWidth, textBoxHeight)[1]
|
|
paragraphs.append(p)
|
|
|
|
if h <= textBoxHeight or s.fontSize <= 1 or s.leading <= 1:
|
|
break
|
|
else:
|
|
s.fontSize -= 0.2
|
|
s.leading -= 0.2
|
|
spacerHeight = max(spacerHeight - 1, minSpacerHeight)
|
|
|
|
h = self.options.paperheight - textVerticalMargin
|
|
for p in paragraphs:
|
|
h -= p.height
|
|
p.drawOn(self.canvas, textHorizontalMargin, h)
|
|
h -= spacerHeight
|
|
self.canvas.showPage()
|
|
|
|
def drawInfo(self, printIt=True):
|
|
# Keep track of the number of pages
|
|
pageCount = 0
|
|
# A unique separator that will not be found in any normal text. Was '@@@***!!!***@@@' at one time.
|
|
sep = chr(30) + chr(31)
|
|
# Generic space. Other options are ' ', ' ', ' '
|
|
space = ' '
|
|
tab_spaces = 4
|
|
blank_line = (space + '\n') * 2
|
|
|
|
if self.options.info or self.options.info_all:
|
|
text = "<para alignment='center'><font size=18><b>"
|
|
text += "Sumpfork's Dominion Tabbed Divider Generator"
|
|
text += "</b></font></para>\n"
|
|
text += blank_line
|
|
text += "Online generator at: "
|
|
text += "<a href='http://domtabs.sandflea.org/' color='blue'>http://domtabs.sandflea.org</a>\n\n"
|
|
text += "Source code on GitHub at: "
|
|
text += "<a href='https://github.com/sumpfork/dominiontabs' color='blue'>"
|
|
text += "https://github.com/sumpfork/dominiontabs</a>\n\n"
|
|
text += "Options for this file:\n"
|
|
|
|
cmd = " ".join(self.options.argv)
|
|
cmd = cmd.replace(' --', sep + '--')
|
|
cmd = cmd.replace(' -', sep + '-')
|
|
cmd = cmd.replace(sep, '\n' + space * tab_spaces)
|
|
|
|
text += cmd
|
|
text += blank_line
|
|
|
|
if printIt:
|
|
self.drawTextPages([text], margin=1.0, fontsize=10, leading=10, spacer=0.05)
|
|
pageCount += 1
|
|
|
|
if self.options.info_all:
|
|
linesPerPage = 80
|
|
lines = self.options.help.replace('\n\n', blank_line).replace(' ', space).split('\n')
|
|
pages = []
|
|
lineCount = 0
|
|
text = ""
|
|
for line in lines:
|
|
lineCount += 1
|
|
text += line + '\n'
|
|
if lineCount >= linesPerPage:
|
|
pages.append(text)
|
|
pageCount += 1
|
|
lineCount = 0
|
|
text = ""
|
|
if text:
|
|
pages.append(text)
|
|
pageCount += 1
|
|
if printIt:
|
|
self.drawTextPages(pages, margin=0.75, fontsize=6, leading=7, spacer=0.1)
|
|
|
|
return pageCount
|
|
|
|
def wantCentreTab(self, card):
|
|
return (card.isExpansion() and self.options.centre_expansion_dividers) or self.options.tab_side == "centre"
|
|
|
|
def drawOutline(self, item, isBack=False):
|
|
# draw outline or cropmarks
|
|
if isBack and not self.options.cropmarks:
|
|
return
|
|
if self.options.linewidth <= 0.0:
|
|
return
|
|
self.canvas.saveState()
|
|
self.canvas.setLineWidth(self.options.linewidth)
|
|
|
|
# The back is flipped
|
|
if isBack:
|
|
self.canvas.translate(item.cardWidth, 0)
|
|
self.canvas.scale(-1, 1)
|
|
|
|
plotter = Plotter(self.canvas,
|
|
cropmarkLength=self.options.cropmarkLength,
|
|
cropmarkSpacing=self.options.cropmarkSpacing)
|
|
|
|
dividerWidth = item.cardWidth
|
|
dividerHeight = item.cardHeight + item.tabHeight
|
|
dividerBaseHeight = item.cardHeight
|
|
tabLabelWidth = item.tabWidth
|
|
theTabWidth = item.tabWidth
|
|
theTabHeight = item.tabHeight
|
|
|
|
left2tab = item.getTabOffset(backside=isBack)
|
|
right2tab = dividerWidth - tabLabelWidth - left2tab
|
|
nearZero = 0.01
|
|
left2tab = left2tab if left2tab > nearZero else 0
|
|
right2tab = right2tab if right2tab > nearZero else 0
|
|
|
|
if item.lineType.lower() == 'line':
|
|
lineType = plotter.LINE
|
|
lineTypeNoDot = plotter.LINE
|
|
elif item.lineType.lower() == 'dot':
|
|
lineType = plotter.DOT
|
|
lineTypeNoDot = plotter.NO_LINE
|
|
else:
|
|
lineType = plotter.NO_LINE
|
|
lineTypeNoDot = plotter.NO_LINE
|
|
|
|
# Setup bare minimum lineStyle's
|
|
lineStyle = [lineType for i in range(0, 10)]
|
|
lineStyle[0] = lineTypeNoDot
|
|
lineStyle[7] = lineType
|
|
lineStyle[8] = lineType if left2tab > 0 else lineTypeNoDot
|
|
lineStyle[9] = lineType if right2tab > 0 else lineTypeNoDot
|
|
|
|
RIGHT = plotter.RIGHT
|
|
LEFT = plotter.LEFT
|
|
BOTTOM = plotter.BOTTOM
|
|
TOP = plotter.TOP
|
|
NO_LINE = plotter.NO_LINE
|
|
|
|
plotter.setCropEnable(RIGHT, self.options.cropmarks and item.translateCropmarkEnable(item.RIGHT))
|
|
plotter.setCropEnable(LEFT, self.options.cropmarks and item.translateCropmarkEnable(item.LEFT))
|
|
plotter.setCropEnable(TOP, self.options.cropmarks and item.translateCropmarkEnable(item.TOP))
|
|
plotter.setCropEnable(BOTTOM, self.options.cropmarks and item.translateCropmarkEnable(item.BOTTOM))
|
|
|
|
if not item.wrapper:
|
|
# Normal Card Outline
|
|
# <-left2tab-> <--tabLabelWidth--> <-right2tab->
|
|
# | | | |
|
|
# Z-+ F7-------------------7E +-Y
|
|
# | |
|
|
# H-8------------8 9-------------9-C
|
|
# | G D |
|
|
# | Generic Divider |
|
|
# | Tab Centered or to the Side |
|
|
# | |
|
|
# A-7------------0-------------------0-------------7-B
|
|
# | V| W| |
|
|
#
|
|
plotter.plot(0, 0, NO_LINE, [LEFT, BOTTOM]) # ? to A
|
|
plotter.plot(left2tab, 0, lineStyle[0], BOTTOM) # A to V
|
|
plotter.plot(theTabWidth, 0, lineStyle[0], BOTTOM) # V to W
|
|
plotter.plot(right2tab, 0, lineStyle[7], [BOTTOM, RIGHT]) # W to B
|
|
plotter.plot(0, dividerBaseHeight, lineStyle[9], RIGHT) # B to C
|
|
plotter.plot(-right2tab, 0, lineStyle[9]) # C to D
|
|
plotter.plot(0, theTabHeight, lineStyle[7], TOP) # D to E
|
|
plotter.plot(right2tab, 0, NO_LINE, [TOP, RIGHT]) # E to Y
|
|
plotter.plot(-right2tab, 0, NO_LINE) # Y to E
|
|
plotter.plot(-theTabWidth, 0, lineStyle[7], TOP) # E to F
|
|
plotter.plot(0, -theTabHeight, lineStyle[8]) # F to G
|
|
plotter.plot(-left2tab, 0, lineStyle[8], LEFT) # G to H
|
|
plotter.plot(0, theTabHeight, NO_LINE, [TOP, LEFT]) # H to Z
|
|
plotter.plot(0, -theTabHeight, NO_LINE) # Z to H
|
|
plotter.plot(0, -dividerBaseHeight, lineStyle[7]) # H to A
|
|
|
|
else:
|
|
# Card Wrapper Outline
|
|
|
|
# Set up values used in the outline
|
|
minNotch = 0.1 * cm # Don't really want notches that are smaller than this.
|
|
if self.options.notch_length * cm > minNotch:
|
|
# A notch length was given, so notches are wanted
|
|
notch_height = self.options.notch_height * cm # thumb notch height
|
|
notch1 = notch2 = notch3 = notch4 = self.options.notch_length * cm # thumb notch width
|
|
notch1used = notch2used = notch3used = notch4used = True # For now
|
|
else:
|
|
# No notches are wanted
|
|
notch_height = 0
|
|
notch1 = notch2 = notch3 = notch4 = 0
|
|
notch1used = notch2used = notch3used = notch4used = False
|
|
|
|
# Even if wanted, there may not be room, and limit to one pair of notches
|
|
if (right2tab - minNotch < notch1) or not notch1used:
|
|
notch1 = 0
|
|
notch1used = False
|
|
if (left2tab - minNotch < notch4) or not notch4used or notch1used:
|
|
notch4 = notch2 = 0
|
|
notch4used = notch2used = False
|
|
else:
|
|
notch3 = 0
|
|
notch3used = False
|
|
|
|
# Setup the rest of the lineStyle's
|
|
lineStyle[1] = lineType if notch1used else lineTypeNoDot
|
|
lineStyle[2] = lineType if notch2used else lineTypeNoDot
|
|
lineStyle[3] = lineType if notch3used else lineTypeNoDot
|
|
lineStyle[4] = lineType if notch4used else lineTypeNoDot
|
|
lineStyle[5] = lineType if notch1used and right2tab > 0 else lineTypeNoDot
|
|
lineStyle[6] = lineType if notch4used and left2tab > 0 else lineTypeNoDot
|
|
|
|
stackHeight = item.stackHeight
|
|
body_minus_notches = dividerBaseHeight - (2.0 * notch_height)
|
|
tab2notch1 = right2tab - notch1
|
|
tab2notch4 = left2tab - notch4
|
|
|
|
# <-----left2tab----------> <--tabLabelWidth--> <-----right2tab-------->
|
|
# | | | | | |
|
|
# Zb-+ Va+ V7-------------------7U +Ua +-Ub
|
|
# <--tab2notch4->| |<--tab2notch1->
|
|
# + W0...................0T
|
|
# Y | | R
|
|
# Za-+ 8---------------8...................9---------------9 +-Pa
|
|
# <notch4 >| X S |<notch1>
|
|
# Z-6---------4Ya Q1--------5-P
|
|
# | |
|
|
# | Generic Wrapper |
|
|
# | Normal Side |
|
|
# | |
|
|
# AA-2--------2BB N3--------3-O
|
|
# <notch2>| |<notch3>
|
|
# + 0CC.................................................M0 +
|
|
# | |
|
|
# + 0DD.................................................L0 +
|
|
# <notch2>| |<notch3>
|
|
# FF-2--------2EE K3--------3-J
|
|
# | |
|
|
# | Reverse Side |
|
|
# | rotated 180 |
|
|
# | Ca H |
|
|
# GG-6---------4<--tab2notch4-> <--tab2notch1->1--------5-I
|
|
# <notch4 >| C F |<notch1>
|
|
# B-+ Cb8---------------8 9---------------1G +-Ia
|
|
# | |
|
|
# -+A Cc+ D7-------------------7E +Ga +-Ib
|
|
# | | | | | |
|
|
# <-----left2tab----------> <--tabLabelWidth--> <-----right2tab-------->
|
|
|
|
plotter.plot(0, 0, NO_LINE, [BOTTOM, LEFT]) # ? to A
|
|
plotter.plot(0, theTabHeight, NO_LINE, LEFT) # A to B
|
|
plotter.plot(0, notch_height, NO_LINE, (LEFT, notch4used or notch1used)) # B to GG
|
|
plotter.plot(notch4, 0, lineStyle[4]) # GG to Ca
|
|
plotter.plot(0, -notch_height, lineStyle[8]) # Ca to Cb
|
|
plotter.plot(0, -theTabHeight, NO_LINE, (BOTTOM, notch4used or notch2used)) # Cb to Cc
|
|
plotter.plot(0, theTabHeight, NO_LINE) # Cc to Cb
|
|
plotter.plot(tab2notch4, 0, lineStyle[8]) # Cb to C
|
|
plotter.plot(0, -theTabHeight, lineStyle[7], BOTTOM) # C to D
|
|
plotter.plot(tabLabelWidth, 0, lineStyle[7], BOTTOM) # D to E
|
|
plotter.plot(0, theTabHeight, lineStyle[9]) # E to F
|
|
plotter.plot(tab2notch1, 0, lineStyle[1]) # F to G
|
|
plotter.plot(0, -theTabHeight, NO_LINE, (BOTTOM, notch1used or notch3used)) # G to Ga
|
|
plotter.plot(0, theTabHeight, NO_LINE) # Ga to G
|
|
plotter.plot(0, notch_height, lineStyle[1]) # G to H
|
|
plotter.plot(notch1, 0, lineStyle[5], (RIGHT, notch1used or notch4used)) # H to I
|
|
plotter.plot(0, -notch_height, NO_LINE, RIGHT) # I to Ia
|
|
plotter.plot(0, -theTabHeight, NO_LINE, [RIGHT, BOTTOM]) # Ia to Ib
|
|
plotter.plot(0, theTabHeight, NO_LINE) # Ib to Ia
|
|
plotter.plot(0, notch_height, NO_LINE) # Ia to I
|
|
plotter.plot(0, body_minus_notches, lineStyle[3], (RIGHT, notch2used or notch3used)) # I to J
|
|
plotter.plot(-notch3, 0, lineStyle[3]) # J to K
|
|
plotter.plot(0, notch_height, lineStyle[0]) # K to L
|
|
plotter.plot(0, stackHeight, lineStyle[0]) # L to M
|
|
plotter.plot(0, notch_height, lineStyle[3]) # M to N
|
|
plotter.plot(notch3, 0, lineStyle[3], (RIGHT, notch2used or notch3used)) # N to O
|
|
plotter.plot(0, body_minus_notches, lineStyle[5], (RIGHT, notch1used or notch4used)) # O to P
|
|
plotter.plot(0, notch_height, NO_LINE, RIGHT) # P to Pa
|
|
plotter.plot(0, -notch_height, NO_LINE) # Pa to P
|
|
plotter.plot(-notch1, 0, lineStyle[1]) # P to Q
|
|
plotter.plot(0, notch_height, lineStyle[9]) # Q to R
|
|
plotter.plot(-tab2notch1, 0, lineStyle[9]) # R to S
|
|
plotter.plot(0, stackHeight, lineStyle[0]) # S to T
|
|
plotter.plot(0, theTabHeight, lineStyle[7], TOP) # S to U
|
|
plotter.plot(tab2notch1, 0, NO_LINE, (TOP, notch1used or notch3used)) # U to Ua
|
|
plotter.plot(notch1, 0, NO_LINE, [TOP, RIGHT]) # Ua to Ub
|
|
plotter.plot(-notch1, 0, NO_LINE) # Ub to Ua
|
|
plotter.plot(-tab2notch1, 0, NO_LINE) # Ua to U
|
|
plotter.plot(-theTabWidth, 0, lineStyle[7], TOP) # U to V
|
|
plotter.plot(-tab2notch4, 0, NO_LINE, (TOP, notch4used or notch2used)) # V to Va
|
|
plotter.plot(tab2notch4, 0, NO_LINE) # Va to V
|
|
plotter.plot(0, -theTabHeight, lineStyle[0]) # V to W
|
|
plotter.plot(0, -stackHeight, lineStyle[8]) # W to X
|
|
plotter.plot(-tab2notch4, 0, lineStyle[8]) # X to Y
|
|
plotter.plot(0, -notch_height, lineStyle[4]) # Y to Ya
|
|
plotter.plot(-notch4, 0, lineStyle[6], (LEFT, notch1used or notch4used)) # Ya to Z
|
|
plotter.plot(0, notch_height, NO_LINE, LEFT) # Z to Za
|
|
plotter.plot(0, theTabHeight + stackHeight, NO_LINE, [TOP, LEFT]) # Za to Zb
|
|
plotter.plot(0, -theTabHeight - stackHeight, NO_LINE) # Zb to Za
|
|
plotter.plot(0, -notch_height, NO_LINE) # Za to Z
|
|
plotter.plot(0, -body_minus_notches, lineStyle[2], (LEFT, notch2used or notch3used)) # Z to AA
|
|
plotter.plot(notch2, 0, lineStyle[2]) # AA to BB
|
|
plotter.plot(0, -notch_height, lineStyle[0]) # BB to CC
|
|
plotter.plot(0, -stackHeight, lineStyle[0]) # CC to DD
|
|
plotter.plot(0, -notch_height, lineStyle[2]) # DD to EE
|
|
plotter.plot(-notch2, 0, lineStyle[2], (LEFT, notch2used or notch3used)) # EE to FF
|
|
plotter.plot(0, -body_minus_notches, lineStyle[6]) # FF to GG
|
|
|
|
# Add fold lines
|
|
self.canvas.setStrokeGray(0.9)
|
|
plotter.setXY(left2tab, dividerHeight + stackHeight + dividerBaseHeight) # ? to X
|
|
plotter.plot(theTabWidth, 0, plotter.LINE) # X to S
|
|
plotter.plot(0, stackHeight) # S to T
|
|
plotter.plot(-theTabWidth, 0, plotter.LINE) # V to S
|
|
|
|
plotter.setXY(notch2, dividerHeight) # ? to DD
|
|
plotter.plot(dividerWidth - notch2 - notch3, 0, plotter.LINE) # DD to L
|
|
plotter.plot(0, stackHeight) # L to M
|
|
plotter.plot(-dividerWidth + notch2 + notch3, 0, plotter.LINE) # M to CC
|
|
|
|
self.canvas.restoreState()
|
|
|
|
def add_inline_images(self, text, fontsize):
|
|
def replace_image_tag(text,
|
|
fontsize,
|
|
tag_pattern,
|
|
fname_replace,
|
|
fontsize_multiplier,
|
|
height_percent,
|
|
text_fontsize_multiplier=None):
|
|
replace_template = '<img src="{fpath}" width={width} height="{height_percent}%" valign="middle" />'
|
|
offset = 0
|
|
for match in re.finditer(tag_pattern, text):
|
|
replace = replace_template
|
|
tag = match.group(0)
|
|
fname = re.sub(tag_pattern, fname_replace, tag)
|
|
if text_fontsize_multiplier is not None:
|
|
font_replace = re.sub(tag_pattern,
|
|
'<font size={}>\\1</font>'.format(fontsize * text_fontsize_multiplier),
|
|
tag)
|
|
replace = font_replace + replace
|
|
replace = replace.format(fpath=DividerDrawer.get_image_filepath(fname),
|
|
width=fontsize * fontsize_multiplier,
|
|
height_percent=height_percent)
|
|
text = text[:match.start() + offset] + replace + text[match.end() + offset:]
|
|
offset += len(replace) - len(match.group(0))
|
|
return text
|
|
# Coins
|
|
replace_specs = [
|
|
# Coins
|
|
(r'(\d+)\s\<\*COIN\*\>', 'coin_small_\\1.png', 2.4, 200),
|
|
(r'(\d+)\s(c|C)oin(s)?', 'coin_small_\\1.png', 1.2, 100),
|
|
(r'\?\s(c|C)oin(s)?', 'coin_small_question.png', 1.2, 100),
|
|
(r'(empty|\_)\s(c|C)oin(s)?', 'coin_small_empty.png', 1.2, 100),
|
|
|
|
# VP
|
|
(r'(?:\s+|\<)VP(?:\s+|\>|\.|$)', 'victory_emblem.png', 1.25, 100),
|
|
(r'(\d+)\s*\<\*VP\*\>', 'victory_emblem.png', 2, 160, 1.3),
|
|
|
|
# Debt
|
|
(r'(\d+)\sDebt', 'debt_\\1.png', 1.2, 105),
|
|
(r'Debt', 'debt.png', 1.2, 105),
|
|
|
|
# Potion
|
|
(r'(\d+)\s*\<\*POTION\*\>', 'potion_small.png', 2, 140, 1.5),
|
|
(r'Potion', 'potion_small.png', 1.2, 100)
|
|
|
|
]
|
|
for args in replace_specs:
|
|
text = replace_image_tag(text, fontsize, *args)
|
|
|
|
return text.strip()
|
|
|
|
def add_inline_text(self, card, text):
|
|
# Bonuses
|
|
text = card.getBonusBoldText(text)
|
|
|
|
# <line>
|
|
replace = "<center>{}</center>\n".format("–" * 22)
|
|
text = re.sub(r"\<line\>", replace, text)
|
|
# <tab> and \t
|
|
text = re.sub(r"\<tab\>", '\t', text)
|
|
text = re.sub(r"\<t\>", '\t', text)
|
|
text = re.sub(r"\t", " " * 4, text)
|
|
|
|
# various breaks
|
|
text = re.sub(r"\<br\>", "<br />", text)
|
|
text = re.sub(r"\<n\>", "\n", text)
|
|
|
|
# alignments
|
|
text = re.sub(r"\<c\>", "<center>", text)
|
|
text = re.sub(r"\<center\>", "\n<para alignment='center'>", text)
|
|
text = re.sub(r"\</c\>", "</center>", text)
|
|
text = re.sub(r"\</center\>", "</para>", text)
|
|
|
|
text = re.sub(r"\<l\>", "<left>", text)
|
|
text = re.sub(r"\<left\>", "\n<para alignment='left'>", text)
|
|
text = re.sub(r"\</l\>", "</left>", text)
|
|
text = re.sub(r"\</left\>", "</para>", text)
|
|
|
|
text = re.sub(r"\<r\>", "<right>", text)
|
|
text = re.sub(r"\<right\>", "\n<para alignment='right'>", text)
|
|
text = re.sub(r"\</r\>", "</right>", text)
|
|
text = re.sub(r"\</right\>", "</para>", text)
|
|
|
|
text = re.sub(r"\<j\>", "<justify>", text)
|
|
text = re.sub(r"\<justify\>", "\n<para alignment='justify'>", text)
|
|
text = re.sub(r"\</j\>", "</justify>", text)
|
|
text = re.sub(r"\</justify\>", "</para>", text)
|
|
|
|
return text.strip().strip('\n')
|
|
|
|
def drawCardCount(self, card, x, y, offset=-1):
|
|
# Note that this is right justified.
|
|
# x represents the right most for the image (image grows to the left)
|
|
if card.getCardCount() < 1:
|
|
return 0
|
|
|
|
# draw_list = [(card.getCardCount(), 1)]
|
|
draw_list = sorted([(i, card.count.count(i)) for i in set(card.count)])
|
|
|
|
cardIconHeight = y + offset
|
|
countHeight = cardIconHeight - 4
|
|
width = 0
|
|
|
|
for value, count in draw_list:
|
|
# draw the image set with the number of cards inside it
|
|
width += 16
|
|
x -= 16
|
|
self.canvas.drawImage(
|
|
DividerDrawer.get_image_filepath('card.png'),
|
|
x,
|
|
countHeight,
|
|
16,
|
|
16,
|
|
preserveAspectRatio=True,
|
|
mask='auto')
|
|
self.canvas.setFont(self.font_mapping['Bold'], 10)
|
|
self.canvas.drawCentredString(x + 8, countHeight + 4, str(value))
|
|
|
|
# now draw the number of sets
|
|
if count > 1:
|
|
count_string = u"{}\u00d7".format(count)
|
|
width_string = stringWidth(count_string, self.font_mapping['Regular'], 10)
|
|
width_string -= 1 # adjust to make it closer to image
|
|
width += width_string
|
|
x -= width_string
|
|
self.canvas.setFont(self.font_mapping['Regular'], 10)
|
|
self.canvas.drawString(x, countHeight + 4, count_string)
|
|
|
|
return width + 1
|
|
|
|
def drawCost(self, card, x, y, costOffset=-1):
|
|
# width starts at 2 (1 pt border on each side)
|
|
width = 2
|
|
|
|
costHeight = y + costOffset
|
|
coinHeight = costHeight - 5
|
|
potHeight = y - 3
|
|
potSize = 11
|
|
|
|
if (not(card.cost == "" or
|
|
(card.debtcost and int(card.cost) == 0) or
|
|
(card.potcost and int(card.cost) == 0))):
|
|
|
|
self.canvas.drawImage(
|
|
DividerDrawer.get_image_filepath('coin_small.png'),
|
|
x,
|
|
coinHeight,
|
|
16,
|
|
16,
|
|
preserveAspectRatio=True,
|
|
mask='auto')
|
|
self.canvas.setFont(self.font_mapping['Bold'], 12)
|
|
self.canvas.drawCentredString(x + 8, costHeight, str(card.cost))
|
|
self.canvas.setFillColorRGB(0, 0, 0)
|
|
x += 17
|
|
width += 16
|
|
|
|
if card.debtcost:
|
|
self.canvas.drawImage(
|
|
DividerDrawer.get_image_filepath('debt.png'),
|
|
x,
|
|
coinHeight,
|
|
16,
|
|
16,
|
|
preserveAspectRatio=True,
|
|
mask=[170, 255, 170, 255, 170, 255])
|
|
self.canvas.setFillColorRGB(1, 1, 1)
|
|
self.canvas.setFont(self.font_mapping['Bold'], 12)
|
|
self.canvas.drawCentredString(x + 8, costHeight, str(card.debtcost))
|
|
self.canvas.setFillColorRGB(0, 0, 0)
|
|
x += 17
|
|
width += 16
|
|
|
|
if card.potcost:
|
|
self.canvas.drawImage(
|
|
DividerDrawer.get_image_filepath('potion.png'),
|
|
x,
|
|
potHeight,
|
|
potSize,
|
|
potSize,
|
|
preserveAspectRatio=True,
|
|
mask='auto')
|
|
width += potSize
|
|
|
|
return width
|
|
|
|
def drawSetIcon(self, setImage, x, y):
|
|
# set image
|
|
w = 2
|
|
self.canvas.drawImage(
|
|
DividerDrawer.get_image_filepath(setImage),
|
|
x,
|
|
y,
|
|
14,
|
|
12,
|
|
mask='auto')
|
|
return w + 14
|
|
|
|
def nameWidth(self, name, fontSize):
|
|
w = 0
|
|
name_parts = name.split()
|
|
for i, part in enumerate(name_parts):
|
|
if i != 0:
|
|
w += pdfmetrics.stringWidth(' ', self.font_mapping['Regular'],
|
|
fontSize)
|
|
w += pdfmetrics.stringWidth(part[0], self.font_mapping['Regular'],
|
|
fontSize)
|
|
w += pdfmetrics.stringWidth(part[1:], self.font_mapping['Regular'],
|
|
fontSize - 2)
|
|
return w
|
|
|
|
def drawTab(self, item, wrapper="no", backside=False):
|
|
card = item.card
|
|
# Skip blank cards
|
|
if card.isBlank():
|
|
return
|
|
|
|
# draw tab flap
|
|
self.canvas.saveState()
|
|
|
|
translate_y = item.cardHeight
|
|
if self.wantCentreTab(card):
|
|
translate_x = item.cardWidth / 2 - item.tabWidth / 2
|
|
else:
|
|
translate_x = item.getTabOffset(backside=backside)
|
|
|
|
if wrapper == "back":
|
|
translate_y = item.tabHeight
|
|
if self.wantCentreTab(card):
|
|
translate_x = item.cardWidth / 2 + item.tabWidth / 2
|
|
else:
|
|
translate_x = item.getTabOffset(backside=False) + item.tabWidth
|
|
|
|
if wrapper == "front":
|
|
translate_y = translate_y + item.cardHeight + item.tabHeight + 2.0 * item.stackHeight
|
|
|
|
self.canvas.translate(translate_x, translate_y)
|
|
|
|
if wrapper == "back":
|
|
self.canvas.rotate(180)
|
|
|
|
if self.options.black_tabs:
|
|
self.canvas.saveState()
|
|
self.canvas.setFillColorRGB(0, 0, 0)
|
|
self.canvas.rect(0, 0, item.tabWidth, item.tabHeight, fill=True)
|
|
self.canvas.restoreState()
|
|
|
|
# allow for 3 pt border on each side
|
|
textWidth = item.tabWidth - 6
|
|
textHeight = 7
|
|
if self.options.no_tab_artwork:
|
|
textHeight = 4
|
|
textHeight = item.tabHeight / 2 - textHeight + \
|
|
card.getType().getTabTextHeightOffset()
|
|
|
|
# draw banner
|
|
img = card.getType().getTabImageFile()
|
|
if not self.options.no_tab_artwork and img:
|
|
self.canvas.drawImage(
|
|
DividerDrawer.get_image_filepath(img),
|
|
1,
|
|
0,
|
|
item.tabWidth - 2,
|
|
item.tabHeight - 1,
|
|
preserveAspectRatio=False,
|
|
anchor='n',
|
|
mask='auto')
|
|
|
|
# draw cost
|
|
if not card.isExpansion() and not card.isBlank(
|
|
) and not card.isLandmark() and not card.isType('Trash'):
|
|
if 'tab' in self.options.cost:
|
|
textInset = 4
|
|
textInset += self.drawCost(
|
|
card, textInset, textHeight,
|
|
card.getType().getTabCostHeightOffset())
|
|
else:
|
|
textInset = 6
|
|
else:
|
|
textInset = 13
|
|
|
|
# draw set image
|
|
# always need to offset from right edge, to make sure it stays on
|
|
# banner
|
|
textInsetRight = 6
|
|
if self.options.use_text_set_icon:
|
|
setImageHeight = card.getType().getTabTextHeightOffset()
|
|
setText = card.setTextIcon()
|
|
self.canvas.setFont(self.font_mapping['Italic'], 8)
|
|
if setText is None:
|
|
setText = ""
|
|
|
|
self.canvas.drawCentredString(item.tabWidth - 10,
|
|
textHeight + 2, setText)
|
|
textInsetRight = 15
|
|
else:
|
|
setImage = card.setImage()
|
|
if setImage and 'tab' in self.options.set_icon:
|
|
setImageHeight = 3 + card.getType().getTabTextHeightOffset()
|
|
|
|
self.drawSetIcon(setImage, item.tabWidth - 20,
|
|
setImageHeight)
|
|
|
|
textInsetRight = 20
|
|
|
|
# draw name
|
|
fontSize = 12
|
|
name = card.name.upper()
|
|
|
|
textWidth -= textInset
|
|
textWidth -= textInsetRight
|
|
|
|
width = self.nameWidth(name, fontSize)
|
|
while width > textWidth and fontSize > 8:
|
|
fontSize -= .01
|
|
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]
|
|
|
|
for linenum, line in enumerate(name_lines):
|
|
h = textHeight
|
|
if tooLong and len(name_lines) > 1:
|
|
if linenum == 0:
|
|
h += h / 2
|
|
else:
|
|
h -= h / 2
|
|
|
|
words = line.split()
|
|
NotRightEdge = (
|
|
not self.options.tab_name_align == "right" and
|
|
(self.options.tab_name_align == "centre" or
|
|
item.getClosestSide(backside=backside) != CardPlot.RIGHT or
|
|
not self.options.tab_name_align == "edge"))
|
|
if wrapper == "back" and not self.options.tab_name_align == "centre":
|
|
NotRightEdge = not NotRightEdge
|
|
if NotRightEdge:
|
|
if (self.options.tab_name_align == "centre" or self.wantCentreTab(card)
|
|
or (item.getClosestSide(backside=backside) == CardPlot.CENTRE)):
|
|
w = item.tabWidth / 2 - self.nameWidth(line, fontSize) / 2
|
|
else:
|
|
w = textInset
|
|
|
|
def drawWordPiece(text, fontSize):
|
|
self.canvas.setFont(self.font_mapping['Regular'], fontSize)
|
|
if text != ' ':
|
|
self.canvas.drawString(w, h, text)
|
|
return pdfmetrics.stringWidth(text, self.font_mapping['Regular'],
|
|
fontSize)
|
|
|
|
for i, word in enumerate(words):
|
|
if i != 0:
|
|
w += drawWordPiece(' ', fontSize)
|
|
w += drawWordPiece(word[0], fontSize)
|
|
w += drawWordPiece(word[1:], fontSize - 2)
|
|
else:
|
|
# align text to the right if tab is on right side
|
|
if self.options.tab_name_align == "centre" or self.wantCentreTab(card):
|
|
w = item.tabWidth / 2 - self.nameWidth(line, fontSize) / 2
|
|
w = item.tabWidth - w
|
|
else:
|
|
w = item.tabWidth - textInsetRight
|
|
|
|
# to make tabs easier to read when grouped together extra 3pt is for
|
|
# space between text + set symbol
|
|
w -= 3
|
|
|
|
words.reverse()
|
|
|
|
def drawWordPiece(text, fontSize):
|
|
self.canvas.setFont(self.font_mapping['Regular'], fontSize)
|
|
if text != ' ':
|
|
self.canvas.drawRightString(w, h, text)
|
|
return -pdfmetrics.stringWidth(text, self.font_mapping['Regular'],
|
|
fontSize)
|
|
|
|
for i, word in enumerate(words):
|
|
w += drawWordPiece(word[1:], fontSize - 2)
|
|
w += drawWordPiece(word[0], fontSize)
|
|
if i != len(words) - 1:
|
|
w += drawWordPiece(' ', fontSize)
|
|
|
|
if wrapper == "front" and card.getCardCount() >= 5:
|
|
# Print smaller version of name on the top wrapper edge
|
|
self.canvas.translate(0, -item.stackHeight) # move into area used by the wrapper
|
|
fontSize = 8 # use the smallest font
|
|
self.canvas.setFont(self.font_mapping['Regular'], fontSize)
|
|
|
|
textHeight = fontSize - 2
|
|
textHeight = item.stackHeight / 2 - textHeight / 2
|
|
h = textHeight
|
|
words = name.split()
|
|
w = item.tabWidth / 2 - self.nameWidth(name, fontSize) / 2
|
|
|
|
def drawWordPiece(text, fontSize):
|
|
self.canvas.setFont(self.font_mapping['Regular'], fontSize)
|
|
if text != ' ':
|
|
self.canvas.drawString(w, h, text)
|
|
return pdfmetrics.stringWidth(text, self.font_mapping['Regular'],
|
|
fontSize)
|
|
|
|
for i, word in enumerate(words):
|
|
if i != 0:
|
|
w += drawWordPiece(' ', fontSize)
|
|
w += drawWordPiece(word[0], fontSize)
|
|
w += drawWordPiece(word[1:], fontSize - 2)
|
|
|
|
self.canvas.restoreState()
|
|
|
|
def drawText(self, item, divider_text="card", wrapper="no"):
|
|
card = item.card
|
|
# Skip blank cards
|
|
if card.isBlank():
|
|
return
|
|
|
|
self.canvas.saveState()
|
|
usedHeight = 0
|
|
totalHeight = item.cardHeight
|
|
|
|
# Figure out if any translation needs to be done
|
|
if wrapper == "back":
|
|
self.canvas.translate(item.cardWidth, item.cardHeight + item.tabHeight)
|
|
self.canvas.rotate(180)
|
|
|
|
if wrapper == "front":
|
|
self.canvas.translate(0, item.cardHeight + item.tabHeight + item.stackHeight)
|
|
|
|
if wrapper == "front" or wrapper == "back":
|
|
if self.options.notch_length > 0:
|
|
usedHeight += self.options.notch_height * cm
|
|
|
|
# Add 'body-top' items
|
|
drewTopIcon = False
|
|
Image_x_left = 4
|
|
if 'body-top' in self.options.cost and not card.isExpansion():
|
|
Image_x_left += self.drawCost(card, Image_x_left, totalHeight - usedHeight - 0.5 * cm)
|
|
drewTopIcon = True
|
|
|
|
Image_x_right = item.cardWidth - 4
|
|
if 'body-top' in self.options.set_icon and not card.isExpansion():
|
|
setImage = card.setImage()
|
|
if setImage:
|
|
Image_x_right -= 16
|
|
self.drawSetIcon(setImage, Image_x_right,
|
|
totalHeight - usedHeight - 0.5 * cm - 3)
|
|
drewTopIcon = True
|
|
|
|
if self.options.count:
|
|
Image_x_right -= self.drawCardCount(card, Image_x_right,
|
|
totalHeight - usedHeight - 0.5 * cm)
|
|
drewTopIcon = True
|
|
|
|
if (self.options.types and not card.isExpansion()):
|
|
|
|
# Calculate how much width have for printing
|
|
# Want centered, but number of other items can limit
|
|
left_margin = Image_x_left
|
|
right_margin = item.cardWidth - Image_x_right
|
|
worst_margin = max(left_margin, right_margin)
|
|
w = item.cardWidth / 2
|
|
textWidth = item.cardWidth - 2 * worst_margin
|
|
textWidth2 = item.cardWidth - left_margin - right_margin
|
|
|
|
# Calculate font size that will fit in the area
|
|
# Start with centering type. But if the fontSize gets too small
|
|
# use all the available space, even if it is not centered on the card
|
|
fontSize = 8
|
|
failover = False
|
|
width = stringWidth(card.types_name, self.font_mapping['Regular'], fontSize)
|
|
while width > textWidth:
|
|
fontSize -= .01
|
|
if fontSize < 6 and not failover:
|
|
# Start over using all available space left on line
|
|
textWidth = textWidth2
|
|
w = left_margin + (textWidth2 / 2)
|
|
fontSize = 8
|
|
failover = True
|
|
width = stringWidth(card.types_name, self.font_mapping['Regular'], fontSize)
|
|
|
|
# Print out the text in the right spot
|
|
h = totalHeight - usedHeight - 0.5 * cm
|
|
self.canvas.setFont(self.font_mapping['Regular'], fontSize)
|
|
if card.types_name != ' ':
|
|
self.canvas.drawCentredString(w, h, card.types_name)
|
|
drewTopIcon = True
|
|
|
|
if drewTopIcon:
|
|
usedHeight += 15
|
|
|
|
# Figure out what text is to be printed on this divider
|
|
descriptions = None
|
|
if divider_text == "card" and card.description:
|
|
# Add the card text to the divider
|
|
descriptions = card.description
|
|
elif divider_text == "rules" and card.extra:
|
|
# Add the extra rules text to the divider
|
|
descriptions = card.extra
|
|
|
|
if descriptions is None:
|
|
# No text to print, so exit early and cleanly
|
|
self.canvas.restoreState()
|
|
return
|
|
|
|
s = getSampleStyleSheet()['BodyText']
|
|
s.fontName = "Times-Roman"
|
|
if divider_text == "card" and not card.isExpansion():
|
|
s.alignment = TA_CENTER
|
|
else:
|
|
s.alignment = TA_JUSTIFY
|
|
|
|
textHorizontalMargin = .5 * cm
|
|
textVerticalMargin = .3 * cm
|
|
textBoxWidth = item.cardWidth - 2 * textHorizontalMargin
|
|
textBoxHeight = totalHeight - usedHeight - 2 * textVerticalMargin
|
|
spacerHeight = 0.2 * cm
|
|
minSpacerHeight = 0.05 * cm
|
|
|
|
if not card.isExpansion():
|
|
descriptions = self.add_inline_text(card, descriptions)
|
|
descriptions = re.split("\n", descriptions)
|
|
while True:
|
|
paragraphs = []
|
|
# this accounts for the spacers we insert between paragraphs
|
|
h = (len(descriptions) - 1) * spacerHeight
|
|
for d in descriptions:
|
|
if card.isExpansion():
|
|
dmod = d
|
|
else:
|
|
dmod = self.add_inline_images(d, s.fontSize)
|
|
try:
|
|
p = Paragraph(dmod, s)
|
|
except ValueError as e:
|
|
raise ValueError(u'Error rendering text from "{}": {} ("{}")'.format(card.name, e, dmod))
|
|
h += p.wrap(textBoxWidth, textBoxHeight)[1]
|
|
paragraphs.append(p)
|
|
|
|
if h <= textBoxHeight or s.fontSize <= 1 or s.leading <= 1:
|
|
break
|
|
else:
|
|
s.fontSize -= 1
|
|
s.leading -= 1
|
|
spacerHeight = max(spacerHeight - 1, minSpacerHeight)
|
|
|
|
h = totalHeight - usedHeight - textVerticalMargin
|
|
for p in paragraphs:
|
|
h -= p.height
|
|
p.drawOn(self.canvas, textHorizontalMargin, h)
|
|
h -= spacerHeight
|
|
|
|
self.canvas.restoreState()
|
|
|
|
def drawDivider(self, item, isBack=False, horizontalMargin=-1, verticalMargin=-1):
|
|
# First save canvas state
|
|
self.canvas.saveState()
|
|
|
|
# Make sure we use the right margins
|
|
if horizontalMargin < 0:
|
|
horizontalMargin = self.options.horizontalMargin
|
|
if verticalMargin < 0:
|
|
verticalMargin = self.options.verticalMargin
|
|
|
|
# apply the transforms to get us to the corner of the current card
|
|
self.canvas.resetTransforms()
|
|
pageWidth = self.options.paperwidth - (2 * horizontalMargin)
|
|
self.canvas.translate(horizontalMargin, verticalMargin)
|
|
if isBack:
|
|
self.canvas.translate(self.options.back_offset,
|
|
self.options.back_offset_height)
|
|
pageWidth -= 2 * self.options.back_offset
|
|
|
|
item.translate(self.canvas, pageWidth, isBack)
|
|
|
|
# actual drawing
|
|
if not self.options.tabs_only:
|
|
self.drawOutline(item, isBack)
|
|
|
|
if self.options.wrapper:
|
|
wrap = "front"
|
|
isBack = False # Safety. If a wrapper, there is no backside
|
|
else:
|
|
wrap = "no"
|
|
|
|
cardText = item.textTypeFront
|
|
if isBack:
|
|
cardText = item.textTypeBack
|
|
|
|
self.drawTab(item, wrapper=wrap, backside=isBack)
|
|
if not self.options.tabs_only:
|
|
self.drawText(item, cardText, wrapper=wrap)
|
|
if item.wrapper:
|
|
self.drawTab(item, wrapper="back", backside=True)
|
|
self.drawText(item, item.textTypeBack, wrapper="back")
|
|
|
|
# retore the canvas state to the way we found it
|
|
self.canvas.restoreState()
|
|
|
|
def drawSetNames(self, pageItems):
|
|
# print sets for this page
|
|
self.canvas.saveState()
|
|
|
|
try:
|
|
# calculate the text height, font size, and orientation
|
|
maxFontsize = 12
|
|
minFontsize = 6
|
|
fontname = self.font_mapping['Regular']
|
|
font = pdfmetrics.getFont(fontname)
|
|
fontHeightRelative = (
|
|
font.face.ascent + abs(font.face.descent)) / 1000.0
|
|
|
|
canFit = False
|
|
|
|
layouts = [{'rotation': 0,
|
|
'minMarginHeight': self.options.minVerticalMargin,
|
|
'totalMarginHeight': self.options.verticalMargin,
|
|
'width': self.options.paperwidth},
|
|
{'rotation': 90,
|
|
'minMarginHeight': self.options.minHorizontalMargin,
|
|
'totalMarginHeight': self.options.horizontalMargin,
|
|
'width': self.options.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)
|
|
|
|
xPos = layout['width'] / 2
|
|
# Place at the very edge of the margin
|
|
yPos = layout['minMarginHeight']
|
|
|
|
sets = []
|
|
for item in pageItems:
|
|
setTitle = item.card.cardset.title()
|
|
if setTitle not in sets:
|
|
sets.append(setTitle)
|
|
|
|
# Centered on page
|
|
xPos = layout['width'] / 2
|
|
# Place at the very edge of the margin
|
|
yPos = layout['minMarginHeight']
|
|
|
|
if layout['rotation']:
|
|
self.canvas.rotate(layout['rotation'])
|
|
yPos = -yPos
|
|
|
|
self.canvas.drawCentredString(xPos, yPos, '/'.join(sets))
|
|
finally:
|
|
self.canvas.restoreState()
|
|
|
|
def calculatePages(self, cards):
|
|
options = self.options
|
|
|
|
# Adjust for Vertical vs Horizontal
|
|
if options.orientation == "vertical":
|
|
options.dividerWidth, options.dividerBaseHeight = options.dominionCardHeight, options.dominionCardWidth
|
|
else:
|
|
options.dividerWidth, options.dividerBaseHeight = options.dominionCardWidth, options.dominionCardHeight
|
|
|
|
options.fixedMargins = False
|
|
options.spin = 0
|
|
options.label = options.label if 'label' in options else None
|
|
if options.label is not None:
|
|
# Set Margins
|
|
options.minmarginheight = (options.label['margin-top'] + options.label['pad-vertical']) * cm
|
|
options.minmarginwidth = (options.label['margin-left'] + options.label['pad-horizontal']) * cm
|
|
# Set Label size
|
|
options.labelHeight = (options.label['tab-height'] - 2 * options.label['pad-vertical']) * cm
|
|
options.labelWidth = (options.label['width'] - 2 * options.label['pad-horizontal']) * cm
|
|
# Set spacing between labels
|
|
options.verticalBorderSpace = (options.label['gap-vertical'] + 2 * options.label['pad-vertical']) * cm
|
|
options.horizontalBorderSpace = (options.label['gap-horizontal'] + 2 * options.label['pad-horizontal']) * cm
|
|
# Fix up other settings
|
|
options.fixedMargins = True
|
|
options.dividerBaseHeight = options.label['body-height'] * cm
|
|
options.dividerWidth = options.labelWidth
|
|
options.rotate = 0
|
|
options.dominionCardWidth = options.dividerWidth
|
|
options.dominionCardHeight = options.dividerBaseHeight
|
|
if options.orientation == "vertical":
|
|
# Spin the card. This is similar to a rotate, but given a label has a fixed location on the page
|
|
# the divider must change shape and rotation. Rotate can't be used directly,
|
|
# since that is used in the calculation of where to place the dividers on the page.
|
|
# This 'spins' the divider only, but keeps all the other calcuations the same.
|
|
options.spin = 270
|
|
# Now fix up the card dimentions.
|
|
options.dominionCardWidth = options.labelHeight + options.label['body-height'] * cm
|
|
options.dominionCardHeight = options.labelWidth - options.label['tab-height'] * cm
|
|
options.labelWidth = options.dominionCardWidth
|
|
# Need to swap now because they will be swapped again later because "vertical"
|
|
options.dominionCardWidth, options.dominionCardHeight = (options.dominionCardHeight,
|
|
options.dominionCardWidth)
|
|
|
|
# Fix up the label dimentions
|
|
if options.tab_side != "full":
|
|
options.labelWidth = options.tabwidth * cm
|
|
|
|
else:
|
|
# Margins already set
|
|
# Set Label size
|
|
options.labelHeight = .9 * cm
|
|
options.labelWidth = options.tabwidth * cm
|
|
if options.tab_side == "full" or options.labelWidth > options.dividerWidth:
|
|
options.labelWidth = options.dividerWidth
|
|
# Set spacing between labels
|
|
options.verticalBorderSpace = options.vertical_gap * cm
|
|
options.horizontalBorderSpace = options.horizontal_gap * cm
|
|
|
|
# Set Height
|
|
options.dividerHeight = options.dividerBaseHeight + options.labelHeight
|
|
|
|
# Start building up the space reserved for each divider
|
|
options.dividerWidthReserved = options.dividerWidth
|
|
options.dividerHeightReserved = options.dividerHeight
|
|
|
|
if options.wrapper:
|
|
# Adjust height for wrapper. Use the maximum thickness of any divider so we know anything will fit.
|
|
maxStackHeight = max(c.getStackHeight(options.thickness) for c in cards)
|
|
options.dividerHeightReserved = 2 * (options.dividerHeightReserved + maxStackHeight)
|
|
print("Max Card Stack Height: {:.2f}cm ".format(maxStackHeight/10.0))
|
|
|
|
# Adjust for rotation
|
|
if options.rotate == 90 or options.rotate == 270:
|
|
# for page calculations, this just means switching horizontal and vertical for these rotations.
|
|
options.dividerWidth, options.dividerHeight = options.dividerHeight, options.dividerWidth
|
|
options.dividerWidthReserved, options.dividerHeightReserved = (options.dividerHeightReserved,
|
|
options.dividerWidthReserved)
|
|
|
|
options.dividerWidthReserved += options.horizontalBorderSpace
|
|
options.dividerHeightReserved += options.verticalBorderSpace
|
|
|
|
# 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(
|
|
(options.paperheight - 2 * options.minmarginheight + options.verticalBorderSpace) /
|
|
options.dividerHeightReserved)
|
|
numDividersHorizontalP = int(
|
|
(options.paperwidth - 2 * options.minmarginwidth + options.horizontalBorderSpace) /
|
|
options.dividerWidthReserved)
|
|
numDividersVerticalL = int(
|
|
(options.paperwidth - 2 * options.minmarginwidth + options.verticalBorderSpace) /
|
|
options.dividerHeightReserved)
|
|
numDividersHorizontalL = int(
|
|
(options.paperheight - 2 * options.minmarginheight + options.horizontalBorderSpace) /
|
|
options.dividerWidthReserved)
|
|
|
|
if ((numDividersVerticalL * numDividersHorizontalL > numDividersVerticalP *
|
|
numDividersHorizontalP) and not options.fixedMargins) and options.rotate == 0:
|
|
options.numDividersVertical = numDividersVerticalL
|
|
options.numDividersHorizontal = numDividersHorizontalL
|
|
options.minHorizontalMargin = options.minmarginheight
|
|
options.minVerticalMargin = options.minmarginwidth
|
|
options.paperheight, options.paperwidth = options.paperwidth, options.paperheight
|
|
else:
|
|
options.numDividersVertical = numDividersVerticalP
|
|
options.numDividersHorizontal = numDividersHorizontalP
|
|
options.minHorizontalMargin = options.minmarginheight
|
|
options.minVerticalMargin = options.minmarginwidth
|
|
|
|
assert options.numDividersVertical > 0, "Could not vertically fit the divider on the page"
|
|
assert options.numDividersHorizontal > 0, "Could not horizontally fit the divider on the page"
|
|
|
|
if not options.fixedMargins:
|
|
# dynamically max margins
|
|
options.horizontalMargin = (options.paperwidth - options.numDividersHorizontal *
|
|
options.dividerWidthReserved + options.horizontalBorderSpace) / 2
|
|
options.verticalMargin = (options.paperheight - options.numDividersVertical *
|
|
options.dividerHeightReserved + options.verticalBorderSpace) / 2
|
|
else:
|
|
options.horizontalMargin = options.minmarginwidth
|
|
options.verticalMargin = options.minmarginheight
|
|
|
|
items = self.setupCardPlots(options, cards) # Turn cards into items to plot
|
|
self.pages = self.convert2pages(options, items) # plot items into pages
|
|
|
|
def setupCardPlots(self, options, cards=[]):
|
|
# First, set up common information for the dividers
|
|
# Doing a lot of this up front, while the cards are ordered
|
|
# just in case the dividers need to be reordered on the page.
|
|
# By setting up first, any tab or text flipping will be correct,
|
|
# even if the divider moves around a bit on the pages.
|
|
|
|
# Drawing line type
|
|
if options.cropmarks:
|
|
if 'dot' in options.linetype.lower():
|
|
lineType = 'dot' # Allow the DOTs if requested
|
|
else:
|
|
lineType = 'no_line'
|
|
else:
|
|
lineType = options.linetype.lower()
|
|
|
|
# Starting with tabs on the left, right, or centre?
|
|
if "right" in options.tab_side:
|
|
tabSideStart = CardPlot.RIGHT # right, right-alternate, right-flip
|
|
elif "left" in options.tab_side:
|
|
tabSideStart = CardPlot.LEFT # left, left-alternate, left-flip
|
|
elif "centre" in options.tab_side:
|
|
tabSideStart = CardPlot.CENTRE # centre
|
|
elif "full" == options.tab_side:
|
|
tabSideStart = CardPlot.CENTRE # full
|
|
else:
|
|
tabSideStart = CardPlot.LEFT # catch anything else
|
|
|
|
cardWidth = options.dominionCardWidth
|
|
cardHeight = options.dominionCardHeight
|
|
|
|
# Adjust for Vertical
|
|
if options.orientation == "vertical":
|
|
cardWidth, cardHeight = cardHeight, cardWidth
|
|
|
|
# Initialized CardPlot tabs
|
|
CardPlot.tabSetup(tabNumber=options.tab_number,
|
|
cardWidth=cardWidth,
|
|
cardHeight=cardHeight,
|
|
lineType=lineType,
|
|
tabWidth=options.labelWidth,
|
|
tabHeight=options.labelHeight,
|
|
start=tabSideStart,
|
|
serpentine=options.tab_serpentine,
|
|
wrapper=options.wrapper)
|
|
|
|
# Now go through all the cards and create their plotter information record...
|
|
items = []
|
|
nextTabIndex = CardPlot.tabRestart()
|
|
lastCardSet = None
|
|
reset_expansion_tabs = options.expansion_dividers and options.expansion_reset_tabs
|
|
|
|
for card in cards:
|
|
# Check if tab needs to be reset to the start
|
|
if reset_expansion_tabs and not card.isExpansion():
|
|
if lastCardSet != card.cardset_tag:
|
|
# In a new expansion, so reset the tabs to start over
|
|
nextTabIndex = CardPlot.tabRestart()
|
|
if options.tab_number > Card.sets[card.cardset_tag]['count']:
|
|
# Limit to the number of tabs to the number of dividers in the expansion
|
|
CardPlot.tabSetup(tabNumber=Card.sets[card.cardset_tag]['count'])
|
|
elif CardPlot.tabNumber != options.tab_number:
|
|
# Make sure tabs are set back to the original
|
|
CardPlot.tabSetup(tabNumber=options.tab_number)
|
|
lastCardSet = card.cardset_tag
|
|
|
|
if self.wantCentreTab(card):
|
|
# If we want centred expansion cards, then force this divider to centre
|
|
thisTabIndex = 0
|
|
else:
|
|
thisTabIndex = nextTabIndex
|
|
|
|
item = CardPlot(card,
|
|
rotation=options.spin if options.spin != 0 else options.rotate,
|
|
tabIndex=thisTabIndex,
|
|
textTypeFront=options.text_front,
|
|
textTypeBack=options.text_back,
|
|
stackHeight=card.getStackHeight(options.thickness)
|
|
)
|
|
if options.flip and (options.tab_number == 2) and (thisTabIndex != CardPlot.tabStart):
|
|
item.flipFront2Back() # Instead of flipping the tab, flip the whole divider front to back
|
|
|
|
# Before moving on, setup the tab for the next item if this tab slot was used
|
|
if thisTabIndex == nextTabIndex:
|
|
nextTabIndex = item.nextTab(nextTabIndex) # already used, so move on to the next tab
|
|
|
|
items.append(item)
|
|
return items
|
|
|
|
def convert2pages(self, options, items=[]):
|
|
# Take the layout and all the items and separate the items into pages.
|
|
# Each item will have all its plotting information filled in.
|
|
rows = options.numDividersVertical
|
|
columns = options.numDividersHorizontal
|
|
numPerPage = rows * columns
|
|
# Calculate if there is always enough room for horizontal and vertical crop marks
|
|
RoomForCropH = options.horizontalBorderSpace > 2*(options.cropmarkLength + options.cropmarkSpacing) * cm
|
|
RoomForCropV = options.verticalBorderSpace > 2*(options.cropmarkLength + options.cropmarkSpacing) * cm
|
|
|
|
items = split(items, numPerPage)
|
|
pages = []
|
|
for pageNum, pageItems in enumerate(items):
|
|
page = []
|
|
for i in range(numPerPage):
|
|
if pageItems and i < len(pageItems):
|
|
# Given a CardPlot object called item, its number on the page, and the page number
|
|
# Return/set the items x,y,rotation, crop mark settings, and page number
|
|
# For x,y assume the canvas has already been adjusted for the margins
|
|
x = i % columns
|
|
y = (rows - 1) - (i // columns)
|
|
pageItems[i].x = x * options.dividerWidthReserved
|
|
pageItems[i].y = y * options.dividerHeightReserved
|
|
pageItems[i].cropOnTop = (y == rows - 1) or RoomForCropV
|
|
pageItems[i].cropOnBottom = (y == 0) or RoomForCropV
|
|
pageItems[i].cropOnLeft = (x == 0) or RoomForCropH
|
|
pageItems[i].cropOnRight = (x == columns - 1) or RoomForCropH
|
|
# pageItems[i].rotation = 0
|
|
pageItems[i].page = pageNum + 1
|
|
page.append(pageItems[i])
|
|
|
|
pages.append((options.horizontalMargin, options.verticalMargin, page))
|
|
return pages
|
|
|
|
def drawDividers(self, cards=[]):
|
|
if not self.pages:
|
|
self.calculatePages(cards)
|
|
|
|
# Now go page by page and print the dividers
|
|
for pageNum, pageInfo in enumerate(self.pages):
|
|
hMargin, vMargin, page = pageInfo
|
|
|
|
# Front page footer
|
|
if not self.options.no_page_footer and (
|
|
not self.options.tabs_only and
|
|
self.options.order != "global"):
|
|
self.drawSetNames(page)
|
|
|
|
# Front page
|
|
for item in page:
|
|
# print the dividor
|
|
self.drawDivider(item, isBack=False, horizontalMargin=hMargin, verticalMargin=vMargin)
|
|
self.canvas.showPage()
|
|
if pageNum + 1 == self.options.num_pages:
|
|
break
|
|
if self.options.tabs_only or self.options.text_back == "none" or self.options.wrapper:
|
|
# Don't print the sheets with the back of the dividers
|
|
continue
|
|
|
|
# back page footer
|
|
if not self.options.no_page_footer and self.options.order != "global":
|
|
self.drawSetNames(page)
|
|
|
|
# Back page
|
|
for item in page:
|
|
# print the dividor
|
|
self.drawDivider(item, isBack=True, horizontalMargin=hMargin, verticalMargin=vMargin)
|
|
self.canvas.showPage()
|
|
if pageNum + 1 == self.options.num_pages:
|
|
break
|