From 7c4c010fba8ded49bc492e8bbe19738f41cae775 Mon Sep 17 00:00:00 2001 From: Wendel Voigt Date: Tue, 1 Jan 2019 22:55:35 -0600 Subject: [PATCH] Refactor of draw.py (#150) * Major refactor of draw.py * Also addresses Issue #70, Issue #85, Issue #120, partial to Issue #6 * Make the label information more available so it can be picked up easier by the website frontend * Add Avery Presta 94211 labels, label names * Add tab-height to make easier calculations * height is now the full height of the label. tab-height is the portion used for the tab. (By doing this, adding labels is more intuitive and requires fewer calculations.) * Fix for issue #239 The fix changes this to now include the original expansion in this case. So now this results in the original expansion expansion being printed with the upgrade expansion cards included as well. --- domdiv/card_db/labels_db.json | 26 +- domdiv/draw.py | 1280 +++++++++++++++++++++++--------- domdiv/main.py | 301 ++++---- domdiv/tests/text_tab_tests.py | 2 + 4 files changed, 1118 insertions(+), 491 deletions(-) diff --git a/domdiv/card_db/labels_db.json b/domdiv/card_db/labels_db.json index eeef435..0ed011e 100644 --- a/domdiv/card_db/labels_db.json +++ b/domdiv/card_db/labels_db.json @@ -4,14 +4,15 @@ "height": 1.27, "margin-left": 0.75, "margin-top": 1.27, + "name": "Label Avery 8867 Letter", "names": [ + "8867", "5167", "5267", "5667", "5967", "8167", "8667", - "8867", "8927", "15267", "15667", @@ -34,6 +35,7 @@ "height": 1.69, "margin-left": 1.02, "margin-top": 1.08, + "name": "Label Avery L4732 A4", "names": [ "L4732", "L7632" @@ -41,15 +43,16 @@ "pad-horizontal": 0.1, "pad-vertical": 0.1, "paper": "A4", + "tab-height": 1.2, "tab-only": true, "width": 3.56 },{ - "body-height": 1.055, "gap-horizontal": 0.295, "gap-vertical": 0.0, - "height": 1.06, + "height": 2.115, "margin-left": 0.95, "margin-top": 2.15, + "name": "Label Avery L4736 A4", "names": [ "L4736", "L6113" @@ -57,7 +60,24 @@ "pad-horizontal": 0.1, "pad-vertical": 0.1, "paper": "A4", + "tab-height": 1.0, "tab-only": false, "width": 4.53 +},{ + "gap-horizontal": 0.96, + "gap-vertical": 0.42, + "height": 5.93, + "margin-left": 1.74, + "margin-top": 1.47, + "name": "Label Avery Presta 94211 Letter", + "names": [ + "94211" + ], + "pad-horizontal": 0.1, + "pad-vertical": 0.1, + "paper": "LETTER", + "tab-height": 0.9, + "tab-only": false, + "width": 8.57 } ] diff --git a/domdiv/draw.py b/domdiv/draw.py index e59a95e..791c29b 100644 --- a/domdiv/draw.py +++ b/domdiv/draw.py @@ -14,6 +14,7 @@ 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): @@ -24,16 +25,379 @@ def split(l, 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): - self.odd = True + def __init__(self, options=None): self.canvas = None - self.options = 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', @@ -164,167 +528,239 @@ class DividerDrawer(object): def wantCentreTab(self, card): return (card.isExpansion() and self.options.centre_expansion_dividers) or self.options.tab_side == "centre" - def getOutline(self, card): - - dividerWidth = self.options.dividerWidth - dividerHeight = self.options.dividerHeight - dividerBaseHeight = self.options.dividerBaseHeight - tabLabelWidth = self.options.labelWidth - notch_height = self.options.notch_height # thumb notch height - notch_width1 = self.options.notch_width1 # thumb notch width: top away from tab - notch_width2 = self.options.notch_width2 # thumb notch width: bottom on side of tab - - theTabHeight = dividerHeight - dividerBaseHeight - theTabWidth = self.options.labelWidth - - if self.wantCentreTab(card): - side_2_tab = (dividerWidth - theTabWidth) / 2 - else: - side_2_tab = 0 - - nonTabWidth = dividerWidth - tabLabelWidth - side_2_tab - - def DeltaXYtoLines(delta): - result = [] - started = False - for x, y in delta: - if not started: - last_x = x - last_y = y - started = True - else: - result.append((last_x, last_y, last_x + x, last_y + y)) - last_x = last_x + x - last_y = last_y + y - return result - + 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) - if not self.options.wrapper: + # 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 - # + F+-------------------+E - # | | - # H+-----------------------+G D+-----+C - # | | - # | Generic Divider | - # | Tab Centered or to the Side | - # | | - # A+-------------------------------------------------+B - # delta x delta y - delta = [(0, 0), # to A - (dividerWidth, 0), # A to B - (0, dividerBaseHeight), # B to C - (-side_2_tab, 0), # C to D - (0, theTabHeight), # D to E - (-theTabWidth, 0), # E to F - (0, -theTabHeight), # F to G - (-nonTabWidth, 0), # G to H - (0, -dividerBaseHeight)] # H to A - self.canvas.lines(DeltaXYtoLines(delta)) + # <-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 - notch_width3 = notch_width1 # thumb notch width: bottom away from tab - stackHeight = card.getStackHeight(self.options.thickness) + + # 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) - tab_2_notch = dividerWidth - theTabWidth - side_2_tab - notch_width1 - if (tab_2_notch < 0): - tab_2_notch = dividerWidth - theTabWidth - side_2_tab - notch_width1 = 0 - # + U+-------------------+T + - # | | - # + V+. . . . . . . . . .+S + - # | | - # + X+---------------------+W . . . . . . . . R+----+Q - # | | - # Z+-------+Y +P - # | | - # | Generic Wrapper | - # | Normal Side | - # | | - # AA+-------+BB N+-------+O - # | | - # + +CC. . . . . . . . . . . . . . . . . .M+ + - # | | - # + +DD. . . . . . . . . . . . . . . . . .L+ + - # | | - # FF+-------+EE K+-------+J - # | | - # | Reverse Side | - # | rotated 180 | - # | | - # A+-------+B +I - # | | - # + C+---------------------+D G+----+H - # | | - # + E+-------------------+F + - # - # delta x delta y - delta = [(0, theTabHeight + notch_height), # to A - (notch_width1, 0), # A to B - (0, -notch_height), # B to C - (tab_2_notch, 0), # C to D - (0, -theTabHeight), # D to E - (theTabWidth, 0), # E to F - (0, theTabHeight), # F to G - (side_2_tab, 0), # G to H - (0, notch_height), # H to I - (0, body_minus_notches), # I to J - (-notch_width2, 0), # J to K - (0, notch_height), # K to L - (0, stackHeight), # L to M - (0, notch_height), # M to N - (notch_width2, 0), # N to O - (0, body_minus_notches), # O to P - (0, notch_height), # P to Q - (-side_2_tab, 0), # Q to R - (0, stackHeight), # R to S - (0, theTabHeight), # S to T - (-theTabWidth, 0), # T to U - (0, -theTabHeight), # U to V - (0, -stackHeight), # V to W - (-tab_2_notch, 0), # W to X - (0, -notch_height), # X to Y - (-notch_width1, 0), # Y to Z - (0, -body_minus_notches), # Z to AA - (notch_width3, 0), # AA to BB - (0, -notch_height), # BB to CC - (0, -stackHeight), # CC to DD - (0, -notch_height), # DD to EE - (-notch_width3, 0), # EE to FF - (0, -body_minus_notches)] # FF to A + tab2notch1 = right2tab - notch1 + tab2notch4 = left2tab - notch4 - self.canvas.lines(DeltaXYtoLines(delta)) + # <-----left2tab----------> <--tabLabelWidth--> <-----right2tab--------> + # | | | | | | + # Zb-+ Va+ V7-------------------7U +Ua +-Ub + # <--tab2notch4->| |<--tab2notch1-> + # + W0...................0T + # Y | | R + # Za-+ 8---------------8...................9---------------9 +-Pa + # | X S | + # Z-6---------4Ya Q1--------5-P + # | | + # | Generic Wrapper | + # | Normal Side | + # | | + # AA-2--------2BB N3--------3-O + # | | + # + 0CC.................................................M0 + + # | | + # + 0DD.................................................L0 + + # | | + # FF-2--------2EE K3--------3-J + # | | + # | Reverse Side | + # | rotated 180 | + # | Ca H | + # GG-6---------4<--tab2notch4-> <--tab2notch1->1--------5-I + # | C F | + # 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) - self.canvas.line(dividerWidth - side_2_tab, - dividerHeight + dividerBaseHeight + stackHeight, - dividerWidth - side_2_tab - theTabWidth, - dividerHeight + dividerBaseHeight + stackHeight) - self.canvas.line( - dividerWidth - side_2_tab, dividerHeight + dividerBaseHeight + - 2 * stackHeight, dividerWidth - side_2_tab - theTabWidth, - dividerHeight + dividerBaseHeight + 2 * stackHeight) - self.canvas.line(notch_width1, dividerHeight, - dividerWidth - notch_width2, dividerHeight) - self.canvas.line(notch_width1, dividerHeight + stackHeight, - dividerWidth - notch_width2, - dividerHeight + stackHeight) + 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 draw(self, cards, options): - self.options = options - - self.registerFonts() - self.canvas = canvas.Canvas( - options.outfile, - pagesize=(options.paperwidth, options.paperheight)) - self.drawDividers(cards) - if options.info or options.info_all: - self.drawInfo() - self.canvas.save() - def add_inline_images(self, text, fontsize): def replace_image_tag(text, fontsize, @@ -415,83 +851,6 @@ class DividerDrawer(object): return text.strip().strip('\n') - def drawOutline(self, - card, - x, - y, - rightSide, - isBack=False): - # draw outline or cropmarks - - # Don't draw anything if zero (or less) line width - if self.options.linewidth <= 0.0: - return - - self.canvas.saveState() - self.canvas.setLineWidth(self.options.linewidth) - cropmarksright = (x == self.options.numDividersHorizontal - 1) - cropmarksleft = (x == 0) - if rightSide: - self.canvas.translate(self.options.dividerWidth, 0) - self.canvas.scale(-1, 1) - if not self.options.cropmarks and not isBack: - # don't draw outline on back, in case lines don't line up with - # front - self.getOutline(card) - - elif self.options.cropmarks and not self.options.wrapper: - cmw = 0.5 * cm - - # Horizontal-line cropmarks - mirror = cropmarksright and not rightSide or cropmarksleft and rightSide - if mirror: - self.canvas.saveState() - self.canvas.translate(self.options.dividerWidth, 0) - self.canvas.scale(-1, 1) - if cropmarksleft or cropmarksright: - self.canvas.line(-2 * cmw, 0, -cmw, 0) - self.canvas.line(-2 * cmw, self.options.dividerBaseHeight, - -cmw, self.options.dividerBaseHeight) - if y > 0: - self.canvas.line(-2 * cmw, self.options.dividerHeight, - -cmw, self.options.dividerHeight) - if mirror: - self.canvas.restoreState() - - # Vertical-line cropmarks - - # want to always draw the right-edge and middle-label-edge lines.. - # ...and draw the left-edge if this is the first card on the left - - # ...but we need to take mirroring into account, to know "where" - # to draw the left / right lines... - if rightSide: - leftLine = self.options.dividerWidth - rightLine = 0 - else: - leftLine = 0 - rightLine = self.options.dividerWidth - middleLine = self.options.dividerWidth - self.options.labelWidth - - if y == 0: - self.canvas.line(rightLine, -2 * cmw, rightLine, -cmw) - self.canvas.line(middleLine, -2 * cmw, middleLine, -cmw) - if cropmarksleft: - self.canvas.line(leftLine, -2 * cmw, leftLine, -cmw) - if y == self.options.numDividersVertical - 1: - self.canvas.line(rightLine, self.options.dividerHeight + cmw, - rightLine, - self.options.dividerHeight + 2 * cmw) - self.canvas.line(middleLine, self.options.dividerHeight + cmw, - middleLine, - self.options.dividerHeight + 2 * cmw) - if cropmarksleft: - self.canvas.line( - leftLine, self.options.dividerHeight + cmw, leftLine, - self.options.dividerHeight + 2 * cmw) - - self.canvas.restoreState() - 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) @@ -613,35 +972,30 @@ class DividerDrawer(object): fontSize - 2) return w - def drawTab(self, card, rightSide, wrapper="no"): + 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 = self.options.dividerWidth / 2 - self.options.labelWidth / 2 - translate_y = self.options.dividerHeight - self.options.labelHeight - elif not rightSide: - translate_x = self.options.dividerWidth - self.options.labelWidth - translate_y = self.options.dividerHeight - self.options.labelHeight + translate_x = item.cardWidth / 2 - item.tabWidth / 2 else: - translate_x = 0 - translate_y = self.options.dividerHeight - self.options.labelHeight + translate_x = item.getTabOffset(backside=backside) if wrapper == "back": - translate_y = self.options.labelHeight + translate_y = item.tabHeight if self.wantCentreTab(card): - translate_x = self.options.dividerWidth / 2 + self.options.labelWidth / 2 - elif not rightSide: - translate_x = self.options.dividerWidth + translate_x = item.cardWidth / 2 + item.tabWidth / 2 else: - translate_x = self.options.labelWidth + translate_x = item.getTabOffset(backside=False) + item.tabWidth if wrapper == "front": - translate_y = translate_y + self.options.dividerHeight + 2.0 * card.getStackHeight( - self.options.thickness) + translate_y = translate_y + item.cardHeight + item.tabHeight + 2.0 * item.stackHeight self.canvas.translate(translate_x, translate_y) @@ -651,15 +1005,15 @@ class DividerDrawer(object): if self.options.black_tabs: self.canvas.saveState() self.canvas.setFillColorRGB(0, 0, 0) - self.canvas.rect(0, 0, self.options.labelWidth, self.options.labelHeight, fill=True) + self.canvas.rect(0, 0, item.tabWidth, item.tabHeight, fill=True) self.canvas.restoreState() # allow for 3 pt border on each side - textWidth = self.options.labelWidth - 6 + textWidth = item.tabWidth - 6 textHeight = 7 if self.options.no_tab_artwork: textHeight = 4 - textHeight = self.options.labelHeight / 2 - textHeight + \ + textHeight = item.tabHeight / 2 - textHeight + \ card.getType().getTabTextHeightOffset() # draw banner @@ -669,8 +1023,8 @@ class DividerDrawer(object): DividerDrawer.get_image_filepath(img), 1, 0, - self.options.labelWidth - 2, - self.options.labelHeight - 1, + item.tabWidth - 2, + item.tabHeight - 1, preserveAspectRatio=False, anchor='n', mask='auto') @@ -699,7 +1053,7 @@ class DividerDrawer(object): if setText is None: setText = "" - self.canvas.drawCentredString(self.options.labelWidth - 10, + self.canvas.drawCentredString(item.tabWidth - 10, textHeight + 2, setText) textInsetRight = 15 else: @@ -707,7 +1061,7 @@ class DividerDrawer(object): if setImage and 'tab' in self.options.set_icon: setImageHeight = 3 + card.getType().getTabTextHeightOffset() - self.drawSetIcon(setImage, self.options.labelWidth - 20, + self.drawSetIcon(setImage, item.tabWidth - 20, setImageHeight) textInsetRight = 20 @@ -744,14 +1098,15 @@ class DividerDrawer(object): words = line.split() NotRightEdge = ( not self.options.tab_name_align == "right" and - (self.options.tab_name_align == "centre" or rightSide or + (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): - w = self.options.labelWidth / 2 - self.nameWidth( - line, fontSize) / 2 + if (self.options.tab_name_align == "centre" or self.wantCentreTab(card) + or (item.getClosestSide(backside=backside) == CardPlot.CENTRE)): + w = item.tabWidth / 2 - self.nameWidth(line, fontSize) / 2 else: w = textInset @@ -770,11 +1125,10 @@ class DividerDrawer(object): else: # align text to the right if tab is on right side if self.options.tab_name_align == "centre" or self.wantCentreTab(card): - w = self.options.labelWidth / 2 - self.nameWidth( - line, fontSize) / 2 - w = self.options.labelWidth - w + w = item.tabWidth / 2 - self.nameWidth(line, fontSize) / 2 + w = item.tabWidth - w else: - w = self.options.labelWidth - textInsetRight + w = item.tabWidth - textInsetRight # to make tabs easier to read when grouped together extra 3pt is for # space between text + set symbol @@ -797,18 +1151,15 @@ class DividerDrawer(object): if wrapper == "front" and card.getCardCount() >= 5: # Print smaller version of name on the top wrapper edge - self.canvas.translate(0, -card.getStackHeight( - self.options.thickness)) # move into area used by the wrapper + 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 = card.getStackHeight( - self.options.thickness) / 2 - textHeight / 2 + textHeight = item.stackHeight / 2 - textHeight / 2 h = textHeight words = name.split() - w = self.options.labelWidth / 2 - self.nameWidth(name, - fontSize) / 2 + w = item.tabWidth / 2 - self.nameWidth(name, fontSize) / 2 def drawWordPiece(text, fontSize): self.canvas.setFont(self.font_mapping['Regular'], fontSize) @@ -825,28 +1176,27 @@ class DividerDrawer(object): self.canvas.restoreState() - def drawText(self, card, divider_text="card", wrapper="no"): + 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 = self.options.dividerHeight - self.options.labelHeight + totalHeight = item.cardHeight # Figure out if any translation needs to be done if wrapper == "back": - self.canvas.translate(self.options.dividerWidth, - self.options.dividerHeight) + self.canvas.translate(item.cardWidth, item.cardHeight + item.tabHeight) self.canvas.rotate(180) if wrapper == "front": - self.canvas.translate(0, self.options.dividerHeight + - card.getStackHeight(self.options.thickness)) + self.canvas.translate(0, item.cardHeight + item.tabHeight + item.stackHeight) if wrapper == "front" or wrapper == "back": - if self.options.notch_width1 > 0: - usedHeight += self.options.notch_height + if self.options.notch_length > 0: + usedHeight += self.options.notch_height * cm # Add 'body-top' items drewTopIcon = False @@ -855,7 +1205,7 @@ class DividerDrawer(object): Image_x_left += self.drawCost(card, Image_x_left, totalHeight - usedHeight - 0.5 * cm) drewTopIcon = True - Image_x_right = self.options.dividerWidth - 4 + Image_x_right = item.cardWidth - 4 if 'body-top' in self.options.set_icon and not card.isExpansion(): setImage = card.setImage() if setImage: @@ -874,11 +1224,11 @@ class DividerDrawer(object): # Calculate how much width have for printing # Want centered, but number of other items can limit left_margin = Image_x_left - right_margin = self.options.dividerWidth - Image_x_right + right_margin = item.cardWidth - Image_x_right worst_margin = max(left_margin, right_margin) - w = self.options.dividerWidth / 2 - textWidth = self.options.dividerWidth - 2 * worst_margin - textWidth2 = self.options.dividerWidth - 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 @@ -929,7 +1279,7 @@ class DividerDrawer(object): textHorizontalMargin = .5 * cm textVerticalMargin = .3 * cm - textBoxWidth = self.options.dividerWidth - 2 * textHorizontalMargin + textBoxWidth = item.cardWidth - 2 * textHorizontalMargin textBoxHeight = totalHeight - usedHeight - 2 * textVerticalMargin spacerHeight = 0.2 * cm minSpacerHeight = 0.05 * cm @@ -968,51 +1318,52 @@ class DividerDrawer(object): self.canvas.restoreState() - def drawDivider(self, - card, - x, - y, - isBack=False, - divider_text="card", - divider_text2="rules"): - # figure out whether the tab should go on the right side or not - if self.options.tab_side == "right": - rightSide = isBack - elif self.options.tab_side in ["left", "full"]: - rightSide = not isBack - else: - # alternate the cards - if not isBack: - rightSide = not self.odd - else: - rightSide = self.odd + 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() - self.canvas.translate(self.options.horizontalMargin, - self.options.verticalMargin) + 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) - self.canvas.translate(x * self.options.dividerWidthReserved, - y * self.options.dividerHeightReserved) + pageWidth -= 2 * self.options.back_offset + + item.translate(self.canvas, pageWidth, isBack) # actual drawing if not self.options.tabs_only: - self.drawOutline(card, x, y, rightSide, isBack) + self.drawOutline(item, isBack) if self.options.wrapper: wrap = "front" + isBack = False # Safety. If a wrapper, there is no backside else: wrap = "no" - self.drawTab(card, rightSide, wrapper=wrap) - if not self.options.tabs_only: - self.drawText(card, divider_text, wrapper=wrap) - if self.options.wrapper: - self.drawTab(card, rightSide, wrapper="back") - self.drawText(card, divider_text2, wrapper="back") - def drawSetNames(self, pageCards): + 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() @@ -1057,8 +1408,8 @@ class DividerDrawer(object): yPos = layout['minMarginHeight'] sets = [] - for c in pageCards: - setTitle = c.cardset + for item in pageItems: + setTitle = item.card.cardset.title() if setTitle not in sets: sets.append(setTitle) @@ -1075,64 +1426,287 @@ class DividerDrawer(object): finally: self.canvas.restoreState() - def drawDividers(self, cards): - # split into pages - cards = split(cards, self.options.numDividersVertical * - self.options.numDividersHorizontal) + def calculatePages(self, cards): + options = self.options - # Starting with tabs on the left or the right? - if self.options.tab_side in ["right-alternate", "right"]: - self.odd = True + # Adjust for Vertical vs Horizontal + if options.orientation == "vertical": + options.dividerWidth, options.dividerBaseHeight = options.dominionCardHeight, options.dominionCardWidth else: - # left-alternate, left, full - self.odd = False + options.dividerWidth, options.dividerBaseHeight = options.dominionCardWidth, options.dominionCardHeight - for pageNum, pageCards in enumerate(cards): - # remember whether we start with odd or even divider for tab - # location - pageStartOdd = self.odd + 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(pageCards) + self.drawSetNames(page) - for i, card in enumerate(pageCards): - # print card - x = i % self.options.numDividersHorizontal - y = i // self.options.numDividersHorizontal - self.canvas.saveState() - self.drawDivider(card, - x, - self.options.numDividersVertical - 1 - y, - isBack=False, - divider_text=self.options.text_front, - divider_text2=self.options.text_back) - self.canvas.restoreState() - self.odd = not self.odd + # 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(pageCards) - # start at same oddness - self.odd = pageStartOdd - for i, card in enumerate(pageCards): - # print card - x = (self.options.numDividersHorizontal - 1 - i - ) % self.options.numDividersHorizontal - y = i // self.options.numDividersHorizontal - self.canvas.saveState() - self.drawDivider(card, - x, - self.options.numDividersVertical - 1 - y, - isBack=True, - divider_text=self.options.text_back) - self.canvas.restoreState() - self.odd = not self.odd + 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 diff --git a/domdiv/main.py b/domdiv/main.py index 730d17b..5e3783e 100644 --- a/domdiv/main.py +++ b/domdiv/main.py @@ -21,10 +21,11 @@ from .draw import DividerDrawer LOCATION_CHOICES = ["tab", "body-top", "hide"] NAME_ALIGN_CHOICES = ["left", "right", "centre", "edge"] TAB_SIDE_CHOICES = ["left", "right", "left-alternate", "right-alternate", - "centre", "full"] + "left-flip", "right-flip", "centre", "full"] TEXT_CHOICES = ["card", "rules", "blank"] +LINE_CHOICES = ["line", "dot", "cropmarks", "dot-cropmarks"] + EDITION_CHOICES = ["1", "2", "latest", "all"] -LABEL_CHOICES = ["8867", "L4732", "L4736"] EXPANSION_CHOICES = ["adventures", "alchemy", "base", "cornucopia", "dark ages", "dominion1stEdition", "dominion2ndEdition", "dominion2ndEditionUpgrade", @@ -58,6 +59,26 @@ def get_languages(path): LANGUAGE_CHOICES = get_languages("card_db") +def get_resource_stream(path): + return codecs.EncodedFile(pkg_resources.resource_stream('domdiv', path), "utf-8") + + +# Load Label information +LABEL_INFO = None +LABEL_CHOICES = [] +LABEL_KEYS = [] +LABEL_SELECTIONS = [] +labels_db_filepath = os.path.join("card_db", "labels_db.json") +with get_resource_stream(labels_db_filepath) as labelfile: + LABEL_INFO = json.loads(labelfile.read().decode('utf-8')) +assert LABEL_INFO, "Could not load label information from database" +for label in LABEL_INFO: + if len(label['names']) > 0: + LABEL_KEYS.append(label['names'][0]) + LABEL_SELECTIONS.append(label['name'] if 'name' in label else label['names'][0]) + LABEL_CHOICES.extend(label['names']) + + def add_opt(options, option, value): assert not hasattr(options, option) setattr(options, option, value) @@ -165,11 +186,33 @@ def parse_opts(cmdline_args=None): dest="tab_side", default="right-alternate", help="Alignment of tab; " - "'left'/'right' forces all tabs to left/right side; " - "'left-alternate' will start on the left and then toggle between left and right for the tabs; " - "'right-alternate' will start on the right and then toggle between right and left for the tabs; " - "'centre' will force all label tabs to the centre; " - "'full' will force all label tabs to be full width of the divider.") + "'left'/'right'/'centre' sets the starting side of the tabs; " + "'full' will force all label tabs to be full width of the divider; sets --tab_number 1 " + "'left-alternate' will start on the left and then toggle between left and right for the tabs," + " sets --tab_number 2; " + "'right-alternate' will start on the right and then toggle between right and left for the tabs," + " sets --tab_number 2; " + "'left-flip' like left-alternate, but the right will be flipped front/back with tab on left," + " sets --tab_number 2; " + "'right-flip' like right-alternate, but the left will be flipped front/back with tab on right," + " sets --tab_number 2; ") + group_tab.add_argument( + "--tab-number", + type=int, + default=1, + help="The number of tabs. When set to 1, all tabs are on the same side (specified by --tab_side). " + "When set to 2, tabs will alternate between left and right. (starting side specified by --tab_side). " + "When set > 2, the first tab will be on left/right side specified by --tab_side, then the rest " + "of the tabs will be evenly spaced until ending on the opposite side. Then the cycle repeats. " + "May be overriden by some options of --tab_side.") + group_tab.add_argument( + "--tab-serpentine", + action="store_true", + help="Affects the order of tabs. When not selected, tabs will progress from the starting side (left/right) " + "to the opposite side (right/left), and then repeat (e.g., left to right, left to right, etc.). " + "When selected, the order is changed to smoothly alternate between the two sides " + "(e.g., left to right, to left, to right, etc.) " + "Only valid if --tab_number > 2.") group_tab.add_argument( "--tab-name-align", choices=NAME_ALIGN_CHOICES + ["center"], @@ -227,6 +270,12 @@ def parse_opts(cmdline_args=None): action="store_true", dest="centre_expansion_dividers", help='Centre the tabs on expansion dividers.') + group_expansion.add_argument( + "--expansion-reset-tabs", + action="store_true", + dest="expansion_reset_tabs", + help="When set, the tabs are restarted (left/right) at the beginning of each expansion. " + "If not set, the tab pattern will continue from one expansion to the next. ") group_expansion.add_argument( "--expansion-dividers-long-name", action="store_true", @@ -427,9 +476,37 @@ def parse_opts(cmdline_args=None): action="store_true", help="In tabs-only mode, draw tabs on black background" ) + group_printing.add_argument( + "--linetype", + choices=LINE_CHOICES, + dest="linetype", + default="line", + help="The divider outline type. " + "'line' will print a solid line outlining the divider; " + "'dot' will print a dot at each corner of the divider; " + "'cropmarks' will print cropmarks for the divider; " + "'dot-cropmarks' will combine 'dot' and 'cropmarks'") + group_printing.add_argument( + "--cropmarkLength", + type=float, + default=0.2, + help="Length of actual drawn cropmark in centimeters.") + group_printing.add_argument( + "--cropmarkSpacing", + type=float, + default=0.1, + help="Spacing between card and the start of the cropmark in centimeters.") + group_printing.add_argument( + "--rotate", + type=int, + choices=[0, 90, 180, 270], + default=0, + help="Divider degrees of rotation relative to the page edge. " + "No optimization will be done on the number of dividers per page.") group_printing.add_argument( "--label", dest="label_name", + choices=LABEL_CHOICES, default=None, help="Use preset label dimentions. Specify a label name. " "This will override settings that conflict with the preset label settings.") @@ -475,6 +552,43 @@ def parse_opts(cmdline_args=None): def clean_opts(options): + + if "center" in options.tab_side: + options.tab_side = str(options.tab_side).replace("center", "centre") + + if "center" in options.tab_name_align: + options.tab_name_align = str(options.tab_name_align).replace("center", "centre") + + if options.tab_side == "full" and options.tab_name_align == "edge": + # This case does not make sense since there are two tab edges in this case. So picking left edge. + print("** Warning: Aligning card name as 'left' for 'full' tabs **") + options.tab_name_align = "left" + + if options.tab_number < 1: + print("** Warning: --tab-number must be 1 or greater. Setting to 1. **") + options.tab_number = 1 + + if options.tab_side == "full" and options.tab_number != 1: + options.tab_number = 1 # Full is 1 big tab + + if "-alternate" in options.tab_side: + if options.tab_number != 2: + print("** Warning: --tab-side with 'alternate' implies 2 tabs. Setting --tab-number to 2 **") + options.tab_number = 2 # alternating left and right, so override tab_number + + if "-flip" in options.tab_side: + # for left and right tabs + if options.tab_number != 2: + print("** Warning: --tab-side with 'flip' implies 2 tabs. Setting --tab-number to 2 **") + options.tab_number = 2 # alternating left and right with a flip, so override tab_number + options.flip = True + else: + options.flip = False + + if options.tab_number < 3 and options.tab_serpentine: + print("** Warning: --tab-serpentine only valid if --tab-number > 2. **") + options.tab_serpentine = False + if options.sleeved_thick: options.thickness = 3.2 options.sleeved = True @@ -486,6 +600,19 @@ def clean_opts(options): if options.notch: options.notch_length = 1.5 + if options.notch_length > 0: + options.notch_height = 0.25 # thumb notch height + + if options.cropmarks and options.linetype == 'line': + options.linetype = 'cropmarks' + + if options.linetype == 'cropmarks': + options.cropmarks = True + + if options.linetype == 'dot-cropmarks': + options.linetype = 'dot' + options.cropmarks = True + if options.expansions is None: # No instance given, so default to all Official expansions options.expansions = ['*'] @@ -512,12 +639,7 @@ def clean_opts(options): options.label = None if options.label_name is not None: - # Load the Labels, and look for match - labels_db_filepath = os.path.join("card_db", "labels_db.json") - with get_resource_stream(labels_db_filepath) as labelfile: - label_info = json.loads(labelfile.read().decode('utf-8')) - assert label_info, "Could not load label information from database" - for label in label_info: + for label in LABEL_INFO: if options.label_name.upper() in [n.upper() for n in label['names']]: options.label = label break @@ -528,23 +650,38 @@ def clean_opts(options): label = options.label label['paper'] = label['paper'] if 'paper' in label else "LETTER" label['tab-only'] = label['tab-only'] if 'tab-only' in label else True - label['body-height'] = label['body-height'] if 'body-height' in label else 0 + label['tab-height'] = label['tab-height'] if 'tab-height' in label else label['height'] + label['body-height'] = label['body-height'] if 'body-height' in label else label['height'] - label['tab-height'] label['gap-vertical'] = label['gap-vertical'] if 'gap-vertical' in label else 0.0 label['gap-horizontal'] = label['gap-horizontal'] if 'gap-horizontal' in label else 0.0 label['pad-vertical'] = label['pad-vertical'] if 'pad-vertical' in label else 0.1 label['pad-horizontal'] = label['pad-horizontal'] if 'pad-horizontal' in label else 0.1 # Option Overrides when using labels + MIN_BODY_CM_FOR_COUNT = 0.6 + MIN_BODY_CM_FOR_TEXT = 4.0 + MIN_HEIGHT_CM_FOR_VERTICAL = 5.0 + MIN_WIDTH_CM_FOR_FULL = 5.0 + options.linewidth = 0.0 + options.cropmarks = False + options.wrapper = False options.papersize = label['paper'] if label['tab-only']: options.tabs_only = True - if label['body-height'] < 4.0: + if label['body-height'] < MIN_BODY_CM_FOR_TEXT: + # Not enough room for any text options.text_front = "blank" options.text_back = "blank" - if label['body-height'] < 1.0: + if label['body-height'] < MIN_BODY_CM_FOR_COUNT: + # Not enough room for count and type options.count = False options.types = False + if label['height'] < MIN_HEIGHT_CM_FOR_VERTICAL: + # Not enough room to make vertical + options.orientation = "horizontal" + if (options.label['width'] - 2 * options.label['pad-horizontal']) < MIN_WIDTH_CM_FOR_FULL: + options.tab_side = "full" options.label = label return options @@ -608,10 +745,6 @@ def parse_cardsize(spec, sleeved): return dominionCardWidth, dominionCardHeight -def get_resource_stream(path): - return codecs.EncodedFile(pkg_resources.resource_stream('domdiv', path), "utf-8") - - def find_index_of_object(lst=[], attributes={}): # Returns the index of the first object in lst that matches the given attributes. Otherwise returns None. # attributes is a dict of key: value pairs. Object attributes that are lists are checked to have value in them. @@ -957,8 +1090,10 @@ def filter_sort_cards(cards, options): for card in cards: if card.cardset_tag == 'dominion2ndEditionUpgrade': card.cardset_tag = 'dominion1stEdition' + options.expansions.append(card.cardset_tag.lower()) elif card.cardset_tag == 'intrigue2ndEditionUpgrade': card.cardset_tag = 'intrigue1stEdition' + options.expansions.append(card.cardset_tag.lower()) # Combine all Events across all expansions if options.exclude_events: @@ -1194,6 +1329,7 @@ def filter_sort_cards(cards, options): exp_name = exp count = randomizerCountByExpansion[exp] + Card.sets[set_tag]['count'] = count if 'no_randomizer' in set_values: if set_values['no_randomizer']: count = 0 @@ -1230,119 +1366,15 @@ def filter_sort_cards(cards, options): def calculate_layout(options, cards=[]): + # This is in place to allow for test cases to it call directly to get + options = clean_opts(options) + options.dominionCardWidth, options.dominionCardHeight = parse_cardsize(options.size, options.sleeved) + options.paperwidth, options.paperheight = parse_papersize(options.papersize) + options.minmarginwidth, options.minmarginheight = parseDimensions(options.minmargin) - dominionCardWidth, dominionCardHeight = parse_cardsize(options.size, - options.sleeved) - paperwidth, paperheight = parse_papersize(options.papersize) - - if options.orientation == "vertical": - dividerWidth, dividerBaseHeight = dominionCardHeight, dominionCardWidth - else: - dividerWidth, dividerBaseHeight = dominionCardWidth, dominionCardHeight - - if options.tab_name_align == "center": - options.tab_name_align = "centre" - - if options.tab_side == "full" and options.tab_name_align == "edge": - # This case does not make sense since there are two tab edges in this case. So picking left edge. - print("** Warning: Aligning card name as 'left' for 'full' tabs **", file=sys.stderr) - options.tab_name_align = "left" - - fixedMargins = False - options.label = options.label if 'label' in options else None - if options.label is not None: - # Set Margins - minmarginheight = (options.label['margin-top'] + options.label['pad-vertical']) * cm - minmarginwidth = (options.label['margin-left'] + options.label['pad-horizontal']) * cm - # Set Label size - labelHeight = (options.label['height'] - 2 * options.label['pad-vertical']) * cm - labelWidth = (options.label['width'] - 2 * options.label['pad-horizontal']) * cm - # Set spacing between labels - verticalBorderSpace = (options.label['gap-vertical'] + 2 * options.label['pad-vertical']) * cm - horizontalBorderSpace = (options.label['gap-horizontal'] + 2 * options.label['pad-horizontal']) * cm - # Fix up other settings - dividerBaseHeight = options.label['body-height'] * cm - dividerWidth = labelWidth - fixedMargins = True - else: - minmarginwidth, minmarginheight = parseDimensions(options.minmargin) - if options.tab_side == "full": - labelWidth = dividerWidth - else: - labelWidth = options.tabwidth * cm - labelHeight = .9 * cm - horizontalBorderSpace = options.horizontal_gap * cm - verticalBorderSpace = options.vertical_gap * cm - - dividerHeight = dividerBaseHeight + labelHeight - - dividerWidthReserved = dividerWidth + horizontalBorderSpace - dividerHeightReserved = dividerHeight + verticalBorderSpace - if options.wrapper: - max_card_stack_height = max(c.getStackHeight(options.thickness) - for c in cards) - dividerHeightReserved = (dividerHeightReserved * 2) + ( - max_card_stack_height * 2) - print("Max Card Stack Height: {:.2f}cm ".format(max_card_stack_height)) - - # Notch measurements - notch_height = 0.25 * cm # thumb notch height - notch_width1 = options.notch_length * cm # thumb notch width: top away from tab - notch_width2 = 0.00 * cm # thumb notch width: bottom on side of tab - - add_opt(options, 'dividerWidth', dividerWidth) - add_opt(options, 'dividerHeight', dividerHeight) - add_opt(options, 'dividerBaseHeight', dividerBaseHeight) - add_opt(options, 'dividerWidthReserved', dividerWidthReserved) - add_opt(options, 'dividerHeightReserved', dividerHeightReserved) - add_opt(options, 'labelWidth', labelWidth) - add_opt(options, 'labelHeight', labelHeight) - add_opt(options, 'notch_height', notch_height) - add_opt(options, 'notch_width1', notch_width1) - add_opt(options, 'notch_width2', notch_width2) - - # as we don't draw anything in the final border, it shouldn't count towards how many tabs we can fit - # so it gets added back in to the page size here - numDividersVerticalP = int( - (paperheight - 2 * minmarginheight + verticalBorderSpace) / - options.dividerHeightReserved) - numDividersHorizontalP = int( - (paperwidth - 2 * minmarginwidth + horizontalBorderSpace) / - options.dividerWidthReserved) - numDividersVerticalL = int( - (paperwidth - 2 * minmarginwidth + verticalBorderSpace) / - options.dividerHeightReserved) - numDividersHorizontalL = int( - (paperheight - 2 * minmarginheight + horizontalBorderSpace) / - options.dividerWidthReserved) - - if ((numDividersVerticalL * numDividersHorizontalL > numDividersVerticalP * - numDividersHorizontalP) and not fixedMargins): - add_opt(options, 'numDividersVertical', numDividersVerticalL) - add_opt(options, 'numDividersHorizontal', numDividersHorizontalL) - add_opt(options, 'paperheight', paperwidth) - add_opt(options, 'paperwidth', paperheight) - add_opt(options, 'minHorizontalMargin', minmarginheight) - add_opt(options, 'minVerticalMargin', minmarginwidth) - else: - add_opt(options, 'numDividersVertical', numDividersVerticalP) - add_opt(options, 'numDividersHorizontal', numDividersHorizontalP) - add_opt(options, 'paperheight', paperheight) - add_opt(options, 'paperwidth', paperwidth) - add_opt(options, 'minHorizontalMargin', minmarginheight) - add_opt(options, 'minVerticalMargin', minmarginwidth) - - if not fixedMargins: - # dynamically max margins - add_opt(options, 'horizontalMargin', - (options.paperwidth - options.numDividersHorizontal * - options.dividerWidthReserved + horizontalBorderSpace) / 2) - add_opt(options, 'verticalMargin', - (options.paperheight - options.numDividersVertical * - options.dividerHeightReserved + verticalBorderSpace) / 2) - else: - add_opt(options, 'horizontalMargin', minmarginwidth) - add_opt(options, 'verticalMargin', minmarginheight) + dd = DividerDrawer(options) + dd.calculatePages(cards) + return dd def generate(options): @@ -1352,7 +1384,7 @@ def generate(options): cards = filter_sort_cards(cards, options) assert cards, "No cards after filtering/sorting" - calculate_layout(options, cards) + dd = calculate_layout(options, cards) print("Paper dimensions: {:.2f}cm (w) x {:.2f}cm (h)".format( options.paperwidth / cm, options.paperheight / cm)) @@ -1363,8 +1395,7 @@ def generate(options): print("Margins: {:.2f}cm h, {:.2f}cm v\n".format( options.horizontalMargin / cm, options.verticalMargin / cm)) - dd = DividerDrawer() - dd.draw(cards, options) + dd.draw(cards) def main(): diff --git a/domdiv/tests/text_tab_tests.py b/domdiv/tests/text_tab_tests.py index dd67fee..ff42d8e 100644 --- a/domdiv/tests/text_tab_tests.py +++ b/domdiv/tests/text_tab_tests.py @@ -256,6 +256,7 @@ def test_tab_edge_full(): ['--tab-name-align', 'edge', '--tab-side', 'full']) assert options.tab_name_align == 'edge' assert options.tab_side == 'full' + options = main.clean_opts(options) main.calculate_layout(options) assert options.tab_name_align == 'left' # special check for odd condition assert options.tab_side == 'full' @@ -316,6 +317,7 @@ def test_tab_center_left(): options = main.parse_opts(['--tab-name-align', 'center', '--tab-side', 'left']) assert options.tab_name_align == 'center' assert options.tab_side == 'left' + options = main.clean_opts(options) main.calculate_layout(options) assert options.tab_name_align == 'centre' # check for change in value assert options.tab_side == 'left'