1714 lines
79 KiB
Python
1714 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
|
|
and self.options.tab_name_align == "edge")):
|
|
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
|