added playing cards extension
This commit is contained in:
parent
8be3f56e94
commit
5de17d9631
131
extensions/fablabchemnitz/playing_cards/playing_cards.inx
Executable file
131
extensions/fablabchemnitz/playing_cards/playing_cards.inx
Executable file
@ -0,0 +1,131 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<inkscape-extension xmlns="http://www.inkscape.org/namespace/inkscape/extension">
|
||||
<name>Playing Cards</name>
|
||||
<id>fablabchemnitz.de.playing_cards</id>
|
||||
<param name="pageName" type="notebook">
|
||||
<page name="pageCards" gui-text="Cards">
|
||||
<hbox>
|
||||
<label>Width</label>
|
||||
<spacer size="expand" />
|
||||
<param name="cardWidth" type="float" min="0" max="1000" precision="3" gui-text=" ">2.5</param>
|
||||
<param name="cardWidthUnit" type="optiongroup" appearance="combo" gui-text=" ">
|
||||
<option value="in">in</option>
|
||||
<option value="mm">mm</option>
|
||||
<option value="cm">cm</option>
|
||||
</param>
|
||||
</hbox>
|
||||
<hbox>
|
||||
<label>Height</label>
|
||||
<spacer size="expand" />
|
||||
<param name="cardHeight" type="float" min="0" max="1000" precision="3" gui-text=" ">3.5</param>
|
||||
<param name="cardHeightUnit" type="optiongroup" appearance="combo" gui-text=" ">
|
||||
<option value="in">in</option>
|
||||
<option value="mm">mm</option>
|
||||
<option value="cm">cm</option>
|
||||
</param>
|
||||
</hbox>
|
||||
<hbox>
|
||||
<label>Bleed size</label>
|
||||
<spacer size="expand" />
|
||||
<param name="bleedSize" type="float" min="0" max="1000" precision="3" gui-text=" ">1</param>
|
||||
<param name="bleedSizeUnit" type="optiongroup" appearance="combo" gui-text=" ">
|
||||
<option value="mm">mm</option>
|
||||
<option value="cm">cm</option>
|
||||
<option value="in">in</option>
|
||||
</param>
|
||||
</hbox>
|
||||
</page>
|
||||
<page name="pageMargins" gui-text="Margins">
|
||||
<hbox>
|
||||
<label>Minimal distance between cards</label>
|
||||
<spacer size="expand" />
|
||||
<param name="minCardSpacing" type="float" min="0" max="1000" precision="3" gui-text=" ">0</param>
|
||||
<param name="minCardSpacingUnit" type="optiongroup" appearance="combo" gui-text=" ">
|
||||
<option value="mm">mm</option>
|
||||
<option value="cm">cm</option>
|
||||
<option value="in">in</option>
|
||||
</param>
|
||||
</hbox>
|
||||
<hbox>
|
||||
<label>Minimal distance between cards and fold line</label>
|
||||
<spacer size="expand" />
|
||||
<param name="minFoldLineSpacing" type="float" min="0" max="1000" precision="3" gui-text=" ">5</param>
|
||||
<param name="minFoldLineSpacingUnit" type="optiongroup" appearance="combo" gui-text=" ">
|
||||
<option value="mm">mm</option>
|
||||
<option value="cm">cm</option>
|
||||
<option value="in">in</option>
|
||||
</param>
|
||||
</hbox>
|
||||
<hbox>
|
||||
<label>Distance between crop marks and bleed</label>
|
||||
<spacer size="expand" />
|
||||
<param name="cropMarkSpacing" type="float" min="0" max="1000" precision="3" gui-text=" ">1</param>
|
||||
<param name="cropMarkSpacingUnit" type="optiongroup" appearance="combo" gui-text=" ">
|
||||
<option value="mm">mm</option>
|
||||
<option value="cm">cm</option>
|
||||
<option value="in">in</option>
|
||||
</param>
|
||||
</hbox>
|
||||
<hbox>
|
||||
<label>Page margin</label>
|
||||
<spacer size="expand" />
|
||||
<param name="pageMargin" type="float" min="0" max="1000" precision="3" gui-text=" ">5</param>
|
||||
<param name="pageMarginUnit" type="optiongroup" appearance="combo" gui-text=" ">
|
||||
<option value="mm">mm</option>
|
||||
<option value="cm">cm</option>
|
||||
<option value="in">in</option>
|
||||
</param>
|
||||
</hbox>
|
||||
<hbox>
|
||||
<label>Distance between cards and frame</label>
|
||||
<spacer size="expand" />
|
||||
<param name="frameSpacing" type="float" min="0" max="1000" precision="3" gui-text=" ">0</param>
|
||||
<param name="frameSpacingUnit" type="optiongroup" appearance="combo" gui-text=" ">
|
||||
<option value="mm">mm</option>
|
||||
<option value="cm">cm</option>
|
||||
<option value="in">in</option>
|
||||
</param>
|
||||
</hbox>
|
||||
</page>
|
||||
<page name="pageMarks" gui-text="Fold line">
|
||||
<param name="foldLineType" gui-text="Fold line type" type="optiongroup" appearance="combo">
|
||||
<option value="HorizontalFoldLine">Horizontal fold line</option>
|
||||
<option value="VerticalFoldLine">Vertical fold line</option>
|
||||
<option value="NoFoldLine">No fold line</option>
|
||||
</param>
|
||||
</page>
|
||||
<page name="pageAlignment" gui-text="Alignment">
|
||||
<param name="gridAligned" type="bool" gui-text="Align cards to grid">true</param>
|
||||
<hbox>
|
||||
<label>Grid spacing</label>
|
||||
<spacer size="expand" />
|
||||
<param name="gridSize" type="float" min="0" max="1000" precision="3" gui-text=" ">5</param>
|
||||
<param name="gridSizeUnit" type="optiongroup" appearance="combo" gui-text=" ">
|
||||
<option value="mm">mm</option>
|
||||
<option value="cm">cm</option>
|
||||
<option value="in">in</option>
|
||||
</param>
|
||||
</hbox>
|
||||
</page>
|
||||
<page name="pageVisibility" gui-text="Visibility">
|
||||
<param name="drawGuides" type="bool" gui-text="Draw guides">true</param>
|
||||
<param name="drawCards" type="bool" gui-text="Draw cards">true</param>
|
||||
<param name="drawBleeds" type="bool" gui-text="Draw bleeds">true</param>
|
||||
<param name="drawCropLines" type="bool" gui-text="Draw crop lines">true</param>
|
||||
<param name="drawFoldLine" type="bool" gui-text="Draw fold line">true</param>
|
||||
<param name="drawPageMargin" type="bool" gui-text="Draw page margin">false</param>
|
||||
<param name="drawFrame" type="bool" gui-text="Draw frame">true</param>
|
||||
</page>
|
||||
</param>
|
||||
<effect>
|
||||
<object-type>all</object-type>
|
||||
<effects-menu>
|
||||
<submenu name="FabLab Chemnitz">
|
||||
<submenu name="Paper/Cardboard Boxes"/>
|
||||
</submenu>
|
||||
</effects-menu>
|
||||
</effect>
|
||||
<script>
|
||||
<command location="inx" interpreter="python">playing_cards.py</command>
|
||||
</script>
|
||||
</inkscape-extension>
|
862
extensions/fablabchemnitz/playing_cards/playing_cards.py
Executable file
862
extensions/fablabchemnitz/playing_cards/playing_cards.py
Executable file
@ -0,0 +1,862 @@
|
||||
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.EffectExtension):
|
||||
"""
|
||||
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 add_arguments(self, pars):
|
||||
"""
|
||||
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.
|
||||
"""
|
||||
pars.add_argument("--pageName", type=str)
|
||||
|
||||
pars.add_argument("--cardWidth", type=float)
|
||||
pars.add_argument("--cardWidthUnit", choices=UNITS.keys())
|
||||
|
||||
pars.add_argument("--cardHeight", type=float)
|
||||
pars.add_argument("--cardHeightUnit", choices=UNITS.keys())
|
||||
|
||||
pars.add_argument("--bleedSize", type=float, action="store")
|
||||
pars.add_argument("--bleedSizeUnit", choices=UNITS.keys())
|
||||
|
||||
pars.add_argument("--minCardSpacing", type=float)
|
||||
pars.add_argument("--minCardSpacingUnit", choices=UNITS.keys())
|
||||
|
||||
pars.add_argument("--cropMarkSpacing", type=float)
|
||||
pars.add_argument("--cropMarkSpacingUnit", choices=UNITS.keys())
|
||||
|
||||
pars.add_argument("--minFoldLineSpacing", type=float)
|
||||
pars.add_argument("--minFoldLineSpacingUnit", choices=UNITS.keys())
|
||||
|
||||
pars.add_argument("--pageMargin", type=float)
|
||||
pars.add_argument("--pageMarginUnit", choices=UNITS.keys())
|
||||
|
||||
pars.add_argument("--frameSpacing", type=float)
|
||||
pars.add_argument("--frameSpacingUnit", choices=UNITS.keys())
|
||||
|
||||
pars.add_argument("--gridSize", type=float)
|
||||
pars.add_argument("--gridSizeUnit", choices=UNITS.keys())
|
||||
|
||||
pars.add_argument("--gridAligned", type=inkex.Boolean)
|
||||
pars.add_argument("--foldLineType", choices=FOLD_LINE_TYPES)
|
||||
pars.add_argument("--drawGuides", type=inkex.Boolean)
|
||||
pars.add_argument("--drawCards", type=inkex.Boolean)
|
||||
pars.add_argument("--drawBleeds", type=inkex.Boolean)
|
||||
pars.add_argument("--drawCropLines", type=inkex.Boolean)
|
||||
pars.add_argument("--drawFoldLine", type=inkex.Boolean)
|
||||
pars.add_argument("--drawPageMargin", type=inkex.Boolean)
|
||||
pars.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():
|
||||
PlayingCardsExtension().run()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
Reference in New Issue
Block a user