/
draw.py
1713 lines (1492 loc) · 79 KB
/
draw.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
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)