* consolidate code for drawing backsides * drawSetNames: no need to calculate xPos / yPos per card * Account for back_offset when calculating set name placement Without this, if you had a negative back_offset_height (or positive back_offset), it was possible to print set names that would intrude onto the divider area on the backs * draw setNames along side (left or bottom) with more space This both provides more cushion, and makes it less likely that location will change between front + back (due to back_offset_height)
2014 lines
80 KiB
Python
2014 lines
80 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 -= 0.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 -= 0.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 = 0.5 * cm
|
|
textVerticalMargin = 0.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, backside=False):
|
|
# 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
|
|
|
|
layouts = [
|
|
{
|
|
"rotation": 0,
|
|
"minMarginHeight": self.options.minVerticalMargin,
|
|
"totalMarginHeight": self.options.verticalMargin
|
|
+ (self.options.back_offset_height if backside else 0),
|
|
"width": self.options.paperwidth,
|
|
},
|
|
{
|
|
"rotation": 90,
|
|
"minMarginHeight": self.options.minHorizontalMargin,
|
|
"totalMarginHeight": self.options.horizontalMargin
|
|
+ (-self.options.back_offset if backside else 0),
|
|
"width": self.options.paperheight,
|
|
},
|
|
]
|
|
|
|
# Pick whether to print setnames horizontally along bottom
|
|
# (i=0) or vertically along left (i=1). We pick whichever has more
|
|
# space.
|
|
fontsize = 0
|
|
maxAvailableMargin = 0
|
|
layoutIndex = -1
|
|
for i, layout in enumerate(layouts):
|
|
availableMargin = (
|
|
layout["totalMarginHeight"] - layout["minMarginHeight"]
|
|
)
|
|
if availableMargin > maxAvailableMargin:
|
|
maxAvailableMargin = availableMargin
|
|
fontsize = availableMargin / fontHeightRelative
|
|
fontsize = min(maxFontsize, fontsize)
|
|
layoutIndex = i
|
|
|
|
if fontsize < minFontsize:
|
|
import warnings
|
|
|
|
warnings.warn("Not enough space to display set names")
|
|
return
|
|
|
|
layout = layouts[layoutIndex]
|
|
|
|
self.canvas.setFont(fontname, fontsize)
|
|
|
|
# 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
|
|
|
|
sets = []
|
|
for item in pageItems:
|
|
setTitle = item.card.cardset.title()
|
|
if setTitle not in sets:
|
|
sets.append(setTitle)
|
|
|
|
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 = 0.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
|
|
|
|
drawFooter = not self.options.no_page_footer and (
|
|
not self.options.tabs_only and self.options.order != "global"
|
|
)
|
|
|
|
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
|
|
backSides = [False]
|
|
else:
|
|
backSides = [False, True]
|
|
|
|
for isBack in backSides:
|
|
# Page footer
|
|
if drawFooter:
|
|
self.drawSetNames(page, isBack)
|
|
|
|
# Page
|
|
for item in page:
|
|
# print the dividor
|
|
self.drawDivider(
|
|
item,
|
|
isBack=isBack,
|
|
horizontalMargin=hMargin,
|
|
verticalMargin=vMargin,
|
|
)
|
|
self.canvas.showPage()
|
|
|
|
if pageNum + 1 == self.options.num_pages:
|
|
break
|