forked from DerElam/inkscape-extension-playing-cards
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathPlayingCards.py
executable file
·868 lines (717 loc) · 31.5 KB
/
PlayingCards.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
from math import ceil, floor
from lxml import etree
import re
import inkex
# Constants
# =========
# A unit is represented as a conversion factor relative to the pixel unit. The
# keys must be identical to the optiongroup options defined in the .inx file.
UNITS = {
"px": 1.0,
"pt": 96.0 / 72.0,
"in": 96.0,
"cm": 96.0 / 2.54,
"mm": 96.0 / 25.4
}
# EPSILON is used as a threshold by the rounding functions
EPSILON = 1e-3
# FOLD_LINE_TYPES defines the accepted values for horizontal and vertical
# fold lines that can be set on the command line.
NO_FOLD_LINE = "NoFoldLine"
HORIZONTAL_FOLD_LINE = "HorizontalFoldLine"
VERTICAL_FOLD_LINE = "VerticalFoldLine"
FOLD_LINE_TYPES = [NO_FOLD_LINE, HORIZONTAL_FOLD_LINE, VERTICAL_FOLD_LINE]
# Functions that change positions in some way
# ===========================================
def round_up(value, grid_size):
"""
Return the smallest grid point that is greater or equal to the value.
:type value: float
:type grid_size: float
:rtype: float
"""
try:
return ceil(value / grid_size - EPSILON) * grid_size
except ZeroDivisionError:
return value
def round_down(value, grid_size):
"""
Return the greatest grid point that is less or equal to the value.
:type value: float
:type grid_size: float
:rtype: float
"""
try:
return floor(value / grid_size + EPSILON) * grid_size
except ZeroDivisionError:
return value
def mirror_at(value, at):
"""
Reflect the value at a given point.
:type value: float
:type at: float
:rtype: float
"""
return 2.0 * at - value
# Functions related to quantities and units
# =========================================
def convert_unit(source_unit, target_unit):
"""
Returns a factor that converts from one unit to another.
:type source_unit: str | float
:type target_unit: str | float
:rtype: float
"""
# If the units are the same the conversion factor is obviously 1
if source_unit == target_unit:
return 1.0
# If the unit is given as a float nothing needs to be done. Otherwise we
# try to find the unit and its float value in the dictionary of valid
# units.
if not isinstance(source_unit, float):
if source_unit not in UNITS.keys():
raise ValueError("unexpected unit \"" + source_unit + "\"")
source_unit = UNITS[source_unit]
if not isinstance(target_unit, float):
if target_unit not in UNITS.keys():
raise ValueError("unexpected unit \"" + target_unit + "\"")
target_unit = UNITS[target_unit]
return source_unit / target_unit
def make_quantity(magnitude, unit):
"""
Create a quantity from a magnitude and a unit.
:type magnitude: float
:type unit: str
"""
return "{0}{1}".format(magnitude, unit)
def split_quantity(quantity):
"""
Split a quantity into its magnitude and unit and return them as a tuple.
:type quantity: str
:rtype: (float, str) | (float, NoneType)
"""
# Matches a floating point number optionally followed by letters. The
# floating point number is the magnitude and the letters are the unit.
pattern = re.compile(r'(?P<magnitude>[+-]?(\d+(\.\d*)?|\.\d+)([eE][+-]?\d+)?)'
r'(?P<unit>[a-zA-Z]+)?')
match = re.match(pattern, quantity)
if match:
return (float(match.group("magnitude")), match.group("unit"))
else:
return (0, None)
def convert_quantity(quantity, target_unit):
"""
Convert the unit of a quantity to another unit.
:type quantity: str
:type taget_unit: str | float
:rtype: str
"""
return "{0}{1}".format(convert_magnitude(quantity, target_unit), target_unit)
def convert_magnitude(quantity, target_unit):
"""
Convert the unit of a quantity to another unit and return only the new magnitude.
:type quantity: str
:type target_unit: str | float
:rtype: float
"""
magnitude, source_unit = split_quantity(quantity)
new_magnitude = magnitude * convert_unit(source_unit, target_unit)
return new_magnitude
# Functions related to the placement of cards
# ===========================================
def calculate_positions_without_fold_line(page_size, margin_size, card_size,
bleed_size, grid_size, min_spacing,
grid_aligned):
"""
Position cards along one direction of the page without a fold line.
The calculated positions are the positions of the right edges or bottom
edges of the cards. The other edges and the positions of the bleeds can be
easily derived by adding card_size, -bleed_size, and card_size+bleed_size.
All sizes and spacings must be given as magnitudes, i.e. without units.
Their units are assumed to be identical but can be arbitrary.
:param page_size: The width or height of the page.
:type page_size: float
:param margin_size: The empty margin of the page. Nothing will be placed in
the margin except for the frame.
:type margin_size: float
:param card_size: The width or height of each card.
:type card_size: float
:param bleed_size: The bleed around each card. This can be zero.
:type bleed_size: float
:param grid_size: The size of the alignment grid. The value is ignored if
grid_aligned is False.
:type grid_size: float
:param min_spacing: The minimum distance between two cards.
:type min_spacing: float
:param grid_aligned: Whether or not the beginning of a card should be on a
grid point.
:type grid_aligned: bool
:return: A list containing the beginnings of each card
:rtype: [float]
"""
# The bleed of the first card begins where the page margin ends. The card
# is then moved to the next grid point if grid_aligned is True.
card_begin = margin_size + bleed_size
if grid_aligned:
card_begin = round_up(card_begin, grid_size)
card_end = card_begin + card_size
# There are to bleeds between the end of the first card and the beginning
# of the next. The spacing between two cards is two bleeds or min_spacing,
# whichever is greater. If grid_aligned is True the next card is moved even
# farther away so that it begins at the next grid point.
spacing = max(min_spacing, 2.0 * bleed_size)
if grid_aligned:
spacing = round_up(card_end + spacing, grid_size) - card_end
# We add cards and spacings until we run out of enough empty space.
cards = []
remaining = 0
while True:
card_end = card_begin + card_size
next_remaining = page_size - margin_size - card_end - bleed_size
if next_remaining < 0:
break
remaining = next_remaining
cards.append(card_begin)
card_begin = card_end + spacing
# Shift everything towards the center of the page.
shift = remaining / 2.0
if grid_aligned:
shift = round_down(shift, grid_size)
cards = [card + shift for card in cards]
return cards
def calculate_positions_with_fold_line(page_size, margin_size, card_size,
bleed_size, grid_size, min_spacing,
min_fold_line_spacing, grid_aligned):
"""
Position the cards along one direction of the page with a central fold line.
The calculated positions are the positions of the right edges or bottom
edges of the cards. The other edges and the positions of the bleeds can be
easily derived by adding card_size, -bleed_size, and card_size+bleed_size.
All sizes and spacings must be given as magnitudes, i.e. without units.
Their units are assumed to be identical but can be arbitrary.
:param page_size: The width or height of the page.
:type page_size: float
:param margin_size: The empty margin of the page. Nothing will be placed in
the margin.
:type margin_size: float
:param card_size: The width or height of each card.
:type card_size: float
:param bleed_size: The bleed around each card. This can be zero.
:type bleed_size: float
:param grid_size: The size of the alignment grid. The value is ignored if
grid_aligned is False.
:type grid_size: float
:param min_spacing: The minimum distance between two cards.
:type min_spacing: float
:param min_fold_line_spacing: The minimum distance between a card and the
fold line.
:type min_fold_line_spacing: float
:param grid_aligned: Whether or not the beginning of a card should be on a
grid point.
:type grid_aligned: bool
:return: A tuple with a list containing the beginnings of each card and the
position of the fold line.
:rtype: ([float], float)
"""
# The spacing between the two central cards at the fold line must be at
# at least 2*bleed_size or 2*min_fold_line_spacing or min_spacing,
# whichever is the greatest.
central_spacing = max(2.0 * min_fold_line_spacing,
max(min_spacing, 2.0 * bleed_size))
# First we assume that the fold line is at the center of the page. This
# might change a bit later if we want grid alignment. We then place the
# first card before the fold line so that there is an empty space of
# central_spacing/2 between the card and the fold line.
card_begin = (page_size - central_spacing) / 2.0 - card_size
if grid_aligned:
card_begin = round_down(card_begin, grid_size)
card_end = card_begin + card_size
# The card on the other side can be placed by mirroring the first card at
# the fold line. But this card is not neccessarily grid aligned. We fix that
# by increasing the central spacing so that the first card on the other side
# of the fold line is also grid aligned.
if grid_aligned:
central_spacing = round_up(
card_end + central_spacing, grid_size) - card_end
# The fold line should not be at the center of the page but in the middle
# between the two central cards. If we don't use grid alignment then this
# is also the center of the page.
fold_line = card_end + central_spacing / 2.0
# The spacing between all remaining cards might be different because we
# don't use min_fold_line_spacing. But the calculation remains the same as
# for the two central cards.
spacing = max(min_spacing, 2.0 * bleed_size)
if grid_aligned:
spacing = round_up(card_end + spacing, grid_size) - card_end
# Now that we have calculated all spacings we start adding cards to both
# sides of the fold line beginning at the center and moving outwards.
cards = []
while True:
if card_begin < margin_size:
break
cards.append(card_begin)
cards.append(mirror_at(card_end, fold_line))
card_begin -= card_size + spacing
card_end = card_begin + card_size
# We sort the positions of the cards so that the positions start with the
# lowest and end with the highest value.
cards.sort()
return (cards, fold_line)
class PlayingCardsExtension(inkex.Effect):
"""
Implements the interface for Inkscape addons.
An instance of this class is created in main(). __init__() sets up the
OptionParser provided by the base class to recognize all needed command
line parameters. Then in main() inkex.Effect.run() is called which then
parses the command line and calls effect(). This is where we do our work.
"""
# Constants passed from the command line
PAGE_WIDTH = None
PAGE_HEIGHT = None
CARD_WIDTH = None
CARD_HEIGHT = None
BLEED_SIZE = None
MIN_CARD_SPACING = None
CROP_MARK_SPACING = None
MIN_FOLD_LINE_SPACING = None
PAGE_MARGIN = None
GRID_SIZE = None
ALIGN_TO_GRID = None
FOLD_LINE_TYPE = None
FRAME_SPACING = None
DRAW_GUIDES = None
DRAW_CARDS = None
DRAW_BLEEDS = None
DRAW_CROP_LINES = None
DRAW_FOLD_LINE = None
DRAW_PAGE_MARGIN = None
DRAW_FRAME = None
USER_UNIT = None # The unit used in the document
horizontal_card_positions = None # Calculated horizontal positions
vertical_card_positions = None # Calculated vertical positions
fold_line_position = None # Calculated position of the fold line
def __init__(self):
"""Initialize base class and its arg_parser."""
inkex.Effect.__init__(self)
self.init_arg_parser()
def init_arg_parser(self):
"""
Initialize the OptionParser with recognized parameters.
The option names must be identical to those defined in the .inx file.
The option values are later used to initialize the class constants.
"""
self.arg_parser.add_argument("--pageName", type=str)
self.arg_parser.add_argument("--cardWidth", type=float)
self.arg_parser.add_argument("--cardWidthUnit", choices=UNITS.keys())
self.arg_parser.add_argument("--cardHeight", type=float)
self.arg_parser.add_argument("--cardHeightUnit", choices=UNITS.keys())
self.arg_parser.add_argument("--bleedSize", type=float, action="store")
self.arg_parser.add_argument("--bleedSizeUnit", choices=UNITS.keys())
self.arg_parser.add_argument("--minCardSpacing", type=float)
self.arg_parser.add_argument("--minCardSpacingUnit", choices=UNITS.keys())
self.arg_parser.add_argument("--cropMarkSpacing", type=float)
self.arg_parser.add_argument("--cropMarkSpacingUnit", choices=UNITS.keys())
self.arg_parser.add_argument("--minFoldLineSpacing", type=float)
self.arg_parser.add_argument("--minFoldLineSpacingUnit", choices=UNITS.keys())
self.arg_parser.add_argument("--pageMargin", type=float)
self.arg_parser.add_argument("--pageMarginUnit", choices=UNITS.keys())
self.arg_parser.add_argument("--frameSpacing", type=float)
self.arg_parser.add_argument("--frameSpacingUnit", choices=UNITS.keys())
self.arg_parser.add_argument("--gridSize", type=float)
self.arg_parser.add_argument("--gridSizeUnit", choices=UNITS.keys())
self.arg_parser.add_argument("--gridAligned", type=inkex.Boolean)
self.arg_parser.add_argument("--foldLineType", choices=FOLD_LINE_TYPES)
self.arg_parser.add_argument("--drawGuides", type=inkex.Boolean)
self.arg_parser.add_argument("--drawCards", type=inkex.Boolean)
self.arg_parser.add_argument("--drawBleeds", type=inkex.Boolean)
self.arg_parser.add_argument("--drawCropLines", type=inkex.Boolean)
self.arg_parser.add_argument("--drawFoldLine", type=inkex.Boolean)
self.arg_parser.add_argument("--drawPageMargin", type=inkex.Boolean)
self.arg_parser.add_argument("--drawFrame", type=inkex.Boolean)
def init_user_unit(self):
"""
Determine the user unit from the document contents.
"""
root = self.document.getroot()
view_box = root.get("viewBox")
# If the document has a valid viewBox we try to derive the user unit
# from that.
valid_view_box = view_box and len(view_box.split()) == 4
if valid_view_box:
view_box = root.get("viewBox").split()
view_box_width, view_box_width_unit = split_quantity(view_box[2])
# If the viewBox has a unit use that.
if view_box_width_unit:
self.USER_UNIT = view_box_width_unit
# If the viewBox has no unit derive the unit from the ratio between
# the document width and the viewBox width.
else:
document_width, document_width_unit = split_quantity(
self.document_width())
self.USER_UNIT = document_width / view_box_width
if document_width_unit:
self.USER_UNIT *= UNITS[document_width_unit]
# If the document has no valid viewBox we try to derive the user unit
# from the document width.
else:
document_width, document_width_unit = split_quantity(
self.document_width())
if document_width_unit:
self.USER_UNIT = UNITS[document_width_unit]
else:
# This might be problematic because v0.91 uses 90dpi and v0.92
# uses 96dpi
self.USER_UNIT = UNITS["px"]
def init_constants(self):
"""
Initialize the class constants from the OptionParser values and the
document contents.
This converts all quantities from the unit given on the command line to
the user unit.
"""
self.PAGE_WIDTH = self.to_user_unit(self.document_width())
self.PAGE_HEIGHT = self.to_user_unit(self.document_height())
self.CARD_WIDTH = self.to_user_unit(
make_quantity(self.options.cardWidth,
self.options.cardWidthUnit))
self.CARD_HEIGHT = self.to_user_unit(
make_quantity(self.options.cardHeight,
self.options.cardHeightUnit))
self.BLEED_SIZE = self.to_user_unit(
make_quantity(self.options.bleedSize,
self.options.bleedSizeUnit))
self.GRID_SIZE = self.to_user_unit(
make_quantity(self.options.gridSize,
self.options.gridSizeUnit))
self.MIN_CARD_SPACING = self.to_user_unit(
make_quantity(self.options.minCardSpacing,
self.options.minCardSpacingUnit))
self.CROP_MARK_SPACING = self.to_user_unit(
make_quantity(self.options.cropMarkSpacing,
self.options.cropMarkSpacingUnit))
self.MIN_FOLD_LINE_SPACING = self.to_user_unit(
make_quantity(self.options.minFoldLineSpacing,
self.options.minFoldLineSpacingUnit))
self.PAGE_MARGIN = self.to_user_unit(
make_quantity(self.options.pageMargin,
self.options.pageMarginUnit))
self.FRAME_SPACING = self.to_user_unit(
make_quantity(self.options.frameSpacing,
self.options.frameSpacingUnit))
self.ALIGN_TO_GRID = self.options.gridAligned
self.FOLD_LINE_TYPE = self.options.foldLineType
self.DRAW_GUIDES = self.options.drawGuides
self.DRAW_CARDS = self.options.drawCards
self.DRAW_BLEEDS = self.options.drawBleeds
self.DRAW_CROP_LINES = self.options.drawCropLines
self.DRAW_FOLD_LINE = self.options.drawFoldLine
self.DRAW_PAGE_MARGIN = self.options.drawPageMargin
self.DRAW_FRAME = self.options.drawFrame
def effect(self):
self.init_user_unit()
self.init_constants()
self.calculate_positions()
# Create one layer for the things that we want to print and another
# layer for things that we don't want to print but are useful while
# working on the cards.
non_printing_layer = self.create_layer("(template) non printing")
printing_layer = self.create_layer("(template) printing")
if self.DRAW_GUIDES:
self.create_guides()
if self.DRAW_CARDS:
self.create_cards(non_printing_layer)
if self.DRAW_BLEEDS:
self.create_bleeds(non_printing_layer)
if self.DRAW_CROP_LINES:
self.create_crop_lines(printing_layer)
if self.DRAW_FOLD_LINE:
self.create_fold_line(printing_layer)
if self.DRAW_PAGE_MARGIN:
self.create_margin(non_printing_layer)
if self.DRAW_FRAME:
self.create_frame(printing_layer)
def to_user_unit(self, quantity):
"""
Convert a quantity to the user unit and return its magnitude.
:type quantity: str
:rtype: float
"""
return convert_magnitude(quantity, self.USER_UNIT)
def document_width(self):
"""
Return the document width.
The width is read from the document. It may or may not contain a unit.
"""
return self.document.getroot().get("width")
def document_height(self):
"""
Return the document height.
The height is read from the document. It may or may not contain a unit.
"""
return self.document.getroot().get("height")
def calculate_positions(self):
"""
Calculate the horizontal and vertical positions of all cards.
The results are stored in self.horizontal_card_positions,
self.vertical_card_positions, and self.fold_line_position.
"""
if self.FOLD_LINE_TYPE == VERTICAL_FOLD_LINE:
self.horizontal_card_positions, self.fold_line_position = \
calculate_positions_with_fold_line(
self.PAGE_WIDTH,
self.PAGE_MARGIN,
self.CARD_WIDTH,
self.BLEED_SIZE,
self.GRID_SIZE,
self.MIN_CARD_SPACING,
self.MIN_FOLD_LINE_SPACING,
self.ALIGN_TO_GRID)
else:
self.horizontal_card_positions = \
calculate_positions_without_fold_line(
self.PAGE_WIDTH,
self.PAGE_MARGIN,
self.CARD_WIDTH,
self.BLEED_SIZE,
self.GRID_SIZE,
self.MIN_CARD_SPACING,
self.ALIGN_TO_GRID)
if self.FOLD_LINE_TYPE == HORIZONTAL_FOLD_LINE:
self.vertical_card_positions, self.fold_line_position = \
calculate_positions_with_fold_line(
self.PAGE_HEIGHT,
self.PAGE_MARGIN,
self.CARD_HEIGHT,
self.BLEED_SIZE,
self.GRID_SIZE,
self.MIN_CARD_SPACING,
self.MIN_FOLD_LINE_SPACING,
self.ALIGN_TO_GRID)
else:
self.vertical_card_positions = \
calculate_positions_without_fold_line(
self.PAGE_HEIGHT,
self.PAGE_MARGIN,
self.CARD_HEIGHT,
self.BLEED_SIZE,
self.GRID_SIZE,
self.MIN_CARD_SPACING,
self.ALIGN_TO_GRID)
# Functions related to the structure of the document
# ==================================================
def create_group(self, parent, label):
"""
Create a new group in the svg document.
:type parent: lxml.etree._Element
:type label: str
:rtype: lxml.etree._Element
"""
group = etree.SubElement(parent, "g")
group.set(inkex.addNS("label", "inkscape"), label)
return group
def create_layer(self, label, is_visible=True, is_locked=True):
"""
Create a new layer in the svg document.
:type label: str
:rtype: lxml.etree._Element
"""
layer = self.create_group(self.document.getroot(), label)
layer.set(inkex.addNS("groupmode", "inkscape"), "layer")
# The Inkscape y-axis runs from bottom to top, the SVG y-axis runs from
# top to bottom. Therefore we need to transform all y coordinates.
layer.set(
"transform", "matrix(1 0 0 -1 0 {0})".format(self.PAGE_HEIGHT))
# Don't show the layer contents
if not is_visible:
layer.set("style", "display:none")
# Lock the layer
if is_locked:
layer.set(inkex.addNS("insensitive", "sodipodi"), "true")
return layer
# Functions related to the contents of the document
# =================================================
def create_guide(self, x, y, orientation):
"""
Create an arbitrary guide.
:type x: float
:type y: float
:type orientation: str
"""
view = self.document.getroot().find(inkex.addNS("namedview", "sodipodi"))
guide = etree.SubElement(view, inkex.addNS("guide", "sodipodi"))
guide.set("orientation", orientation)
guide.set("position", "{0},{1}".format(x, y))
def create_horizontal_guide(self, y):
"""
Create a horizontal guide.
"""
self.create_guide(0, y, "0,1")
def create_vertical_guide(self, x):
"""
Create a vertical guide.
"""
self.create_guide(x, 0, "1,0")
def create_guides(self):
"""
Create guides at all sides of all cards and bleeds.
"""
for x in self.horizontal_card_positions:
self.create_vertical_guide(x)
self.create_vertical_guide(x + self.CARD_WIDTH)
if self.BLEED_SIZE > 0:
self.create_vertical_guide(x - self.BLEED_SIZE)
self.create_vertical_guide(x + self.CARD_WIDTH + self.BLEED_SIZE)
for y in self.vertical_card_positions:
self.create_horizontal_guide(y)
self.create_horizontal_guide(y + self.CARD_HEIGHT)
if self.BLEED_SIZE > 0:
self.create_horizontal_guide(y - self.BLEED_SIZE)
self.create_horizontal_guide(y + self.CARD_HEIGHT + self.BLEED_SIZE)
def create_bleeds(self, parent):
"""
Creates a rectangle for each bleed.
:type parent: lxml.etree._Element
"""
if self.BLEED_SIZE <= 0:
return
attributes = {"x": str(-self.BLEED_SIZE),
"y": str(-self.BLEED_SIZE),
"width": str(self.CARD_WIDTH + 2.0 * self.BLEED_SIZE),
"height": str(self.CARD_HEIGHT + 2.0 * self.BLEED_SIZE),
"stroke": "black",
"stroke-width": str(self.to_user_unit("0.25pt")),
"fill": "none"}
for y in self.vertical_card_positions:
for x in self.horizontal_card_positions:
attributes["transform"] = "translate({0},{1})".format(x, y)
etree.SubElement(parent, "rect", attributes)
def create_cards(self, parent):
"""
Create a rectangle for each card.
:type parent: lxml.etree._Element
"""
attributes = {"x": str(0),
"y": str(0),
"width": str(self.CARD_WIDTH),
"height": str(self.CARD_HEIGHT),
"stroke": "black",
"stroke-width": str(self.to_user_unit("0.25pt")),
"fill": "none"}
for y in self.vertical_card_positions:
for x in self.horizontal_card_positions:
attributes["transform"] = "translate({0},{1})".format(x, y)
etree.SubElement(parent, "rect", attributes)
def create_fold_line(self, parent):
"""
Create a horizontal or vertical fold line.
:type parent: lxml.etree._Element
"""
attributes = {"stroke": "black",
"stroke-width": str(self.to_user_unit("0.25pt")),
"fill": "none"}
if self.FOLD_LINE_TYPE == HORIZONTAL_FOLD_LINE:
attributes["d"] = "M 0,{0} H {1}".format(
self.fold_line_position, self.PAGE_WIDTH)
elif self.FOLD_LINE_TYPE == VERTICAL_FOLD_LINE:
attributes["d"] = "M {0},0 V {1}".format(
self.fold_line_position, self.PAGE_HEIGHT)
else:
return
etree.SubElement(parent, "path", attributes)
def create_crop_lines(self, parent):
"""
Create horizontal and vertical crop lines.
:type parent: lxml.etree._Element
"""
attributes = {"stroke": "black",
"stroke-width": str(self.to_user_unit("0.25pt")),
"fill": "none"}
# (begin, end) pairs for vertical crop line between bleeds
pairs = []
begin = 0
for y in self.vertical_card_positions:
end = y - self.BLEED_SIZE - self.CROP_MARK_SPACING
# Only add lines if they fit between two bleeds
if end - begin >= EPSILON:
pairs.append((begin, end))
begin = end + self.CARD_HEIGHT \
+ 2.0 * self.BLEED_SIZE \
+ 2.0 * self.CROP_MARK_SPACING
pairs.append((begin, self.PAGE_HEIGHT))
# One crop line consists of many short strokes
attributes["d"] = " ".join(["M 0,{0} 0,{1}".format(begin, end)
for (begin, end) in pairs])
# Shifted copies of the crop line
for x in self.horizontal_card_positions:
attributes["transform"] = "translate({0},0)".format(x)
etree.SubElement(parent, "path", attributes)
attributes["transform"] = "translate({0},0)".format(
x + self.CARD_WIDTH)
etree.SubElement(parent, "path", attributes)
# (begin, end) pairs for horizontal crop line between bleeds
pairs = []
begin = 0
for x in self.horizontal_card_positions:
end = x - self.BLEED_SIZE - self.CROP_MARK_SPACING
# Only add lines if they fit between two bleeds
if end - begin >= EPSILON:
pairs.append((begin, end))
begin = end + self.CARD_WIDTH \
+ 2.0 * self.BLEED_SIZE \
+ 2.0 * self.CROP_MARK_SPACING
pairs.append((begin, self.PAGE_WIDTH))
# One crop line consists of many short strokes
attributes["d"] = " ".join(["M {0},0 {1},0".format(begin, end)
for (begin, end) in pairs])
# Shifted copies of the crop line
for y in self.vertical_card_positions:
attributes["transform"] = "translate(0,{0})".format(y)
etree.SubElement(parent, "path", attributes)
attributes["transform"] = "translate(0,{0})".format(
y + self.CARD_HEIGHT)
etree.SubElement(parent, "path", attributes)
def create_margin(self, parent):
"""
Create a rectangle for the page margin.
:type parent: lxml.etree._Element
"""
if self.PAGE_MARGIN <= 0:
return
attributes = {"x": str(self.PAGE_MARGIN),
"y": str(self.PAGE_MARGIN),
"width": str(self.PAGE_WIDTH - 2.0 * self.PAGE_MARGIN),
"height": str(self.PAGE_HEIGHT - 2.0 * self.PAGE_MARGIN),
"stroke": "black",
"stroke-width": str(self.to_user_unit("0.25pt")),
"stroke-dasharray": "0.5,0.5",
"fill": "none"}
etree.SubElement(parent, "rect", attributes)
def create_frame(self, parent):
"""
Create a frame around the cards.
"""
# If we don't have any cards we can't draw a frame around them
if len(self.horizontal_card_positions) == 0 or \
len(self.vertical_card_positions) == 0:
return
XMIN = min(self.horizontal_card_positions)
XMAX = max(self.horizontal_card_positions)
YMIN = min(self.vertical_card_positions)
YMAX = max(self.vertical_card_positions)
LEFT = XMIN - self.FRAME_SPACING
BOTTOM = YMIN - self.FRAME_SPACING
WIDTH = XMAX - XMIN + self.CARD_WIDTH + 2 * self.FRAME_SPACING
HEIGHT = YMAX - YMIN + self.CARD_HEIGHT + 2 * self.FRAME_SPACING
attributes = {"x": str(LEFT),
"y": str(BOTTOM),
"width": str(WIDTH),
"height": str(HEIGHT),
"stroke": "black",
"stroke-width": str(self.to_user_unit("0.25pt")),
"fill": "none"}
etree.SubElement(parent, "rect", attributes)
def main():
extension = PlayingCardsExtension()
extension.run()
if __name__ == '__main__':
main()