├── doc
├── thumb.png
├── label_output_demo.png
└── label_options_demo.png
├── .gitignore
├── Makefile
├── README.md
├── down_spec.py
├── label_guides.inx
├── COPYING
└── label_guides.py
/doc/thumb.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/johnbeard/inkscape-label-guides/HEAD/doc/thumb.png
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | *.pyc
2 |
3 | # download cache
4 | *.sqlite
5 |
6 | # packaged ZIP files
7 | *.zip
8 |
--------------------------------------------------------------------------------
/doc/label_output_demo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/johnbeard/inkscape-label-guides/HEAD/doc/label_output_demo.png
--------------------------------------------------------------------------------
/doc/label_options_demo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/johnbeard/inkscape-label-guides/HEAD/doc/label_options_demo.png
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | NAME=label-guides
2 | VERSION=1.1.2
3 |
4 | DESTDIR ?= /usr/share/inkscape/extensions
5 |
6 | ZIP=$(NAME)-$(VERSION).zip
7 |
8 | SRC_FILES=label_guides.py label_guides.inx
9 |
10 | $(ZIP): $(SRC_FILES)
11 | zip -r $(ZIP) $(SRC_FILES)
12 |
13 | .PHONY: clean zip install
14 |
15 | clean:
16 | rm -f $(NAME)-*.zip
17 |
18 | zip: $(ZIP)
19 |
20 | install:
21 | mkdir -p $(DESTDIR)
22 | install -m 755 -t $(DESTDIR) label_guides.py
23 | install -m 644 -t $(DESTDIR) label_guides.inx
24 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Label Guide extension
2 |
3 | This is an extension to draw guides and outline for printable label sheets.
4 | There is a fairly large range of labels, mostly from LabelPlanet and Avery.
5 |
6 | Once installed, find the extension menu under
7 | Extensions->Render->Label Guides...
8 |
9 | The InkSpace extension page is
10 | [here][inkscape_ext_page].
11 |
12 | This is what the output can look like:
13 |
14 | 
15 |
16 | And the options dialog:
17 |
18 | 
19 |
20 | ## Features
21 |
22 | * List of around 100 preset label templates
23 | * Custom rectangular and elliptical grid-based templates
24 | * Various guide options. Any combination of:
25 | * Guides at label edges
26 | * Guides at label centres
27 | * Guides inset from edges by a set amount
28 | * Can draw label outline shapes for visualisation before printing
29 | * Can draw inset shapes to aid layout or as borders
30 |
31 | ## Installation
32 |
33 | ### Manual installation
34 |
35 | Copy the `label_guides.py` and `label_guides.inx` files to the relevant
36 | Inkscape extension directory.
37 |
38 | On Linux, this is `~/.config/inkscape/extensions` for user extensions or
39 | `/usr/share/inkscape/extensions` for system extensions.
40 |
41 | ### Makefile
42 |
43 | You can clone this repository and use `make` to install the extension to
44 | your user extension directory (Linux only, maybe OSX):
45 |
46 | make install
47 |
48 | or
49 |
50 | make install DESTDIR=/path/to/install/directory
51 |
52 | ### Arch Linux
53 |
54 | There is an AUR package called
55 | [`inkscape-label-guides`][aur_page].
56 |
57 | ## Development loop
58 |
59 | When developing this extension, you can symlink the directory like this:
60 |
61 | ln -s /path/to/inkscape-label-guides ~/.config/inkscape/extensions/inkscape-label-guides
62 |
63 | Then you can edit the files in the repository and reload Inkscape to see the
64 | changes.
65 |
66 | As far as I know, there's no way to reload the extension without
67 | restarting Inkscape.
68 |
69 | # Packaging
70 |
71 | To package this extension for distribution, you can use the `make` target:
72 |
73 | make zip
74 |
75 | Then upload the package to the Inkscape extension website.
76 |
77 | [inkscape_ext_page]: https://inkscape.org/~jjbeard/%E2%98%85label-guides
78 | [aur_page]: https://aur.archlinux.org/packages/inkscape-label-guides/
--------------------------------------------------------------------------------
/down_spec.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # -*- coding: utf-8 -*-
3 | """
4 | Simple download tool for scraping LabelPlanet.co.uk pages for label
5 | template specifications. Can output the descriptions for use in an INX file
6 | enum, as well as the definitions for use in the label_guides.py enxtension's
7 | database.
8 |
9 | Licenced under the GNU General Public License v2.0
10 | """
11 |
12 | from lxml.html import fromstring
13 | import requests
14 | import requests_cache
15 | import re
16 |
17 | import logging
18 | from pprint import pformat
19 | import argparse
20 |
21 |
22 | class FormatFinder(object):
23 | """
24 | Gets a list of known formats from a template list page
25 | """
26 |
27 | def _nth_cell_text(self, row, nth):
28 |
29 | selector = "td:nth-child({})".format(nth)
30 | return row.cssselect(selector)[0].text_content()
31 |
32 | def _get_xy_size_from_celltext(self, txt):
33 |
34 | parts = re.findall(r"[\d.,]+", txt)
35 |
36 | return parts
37 |
38 | def _get_codes_from_celltext(self, txt):
39 |
40 | lpcode = re.search("LP[^\s]+", txt)
41 |
42 | avery = re.search("(?<=Avery )[A-Z\d]+", txt)
43 |
44 | return (lpcode.group(), avery.group() if avery else None)
45 |
46 | def _get_link_from_row(self, row):
47 |
48 | link = row.cssselect("a")[0].attrib['href']
49 |
50 | return link
51 |
52 | def _get_item_from_row(self, row):
53 |
54 | num_per_sheet = int(self._nth_cell_text(row, 1))
55 |
56 | lab_size = self._nth_cell_text(row, 2)
57 |
58 | # some lable sizes aren't supported
59 | if any(s in lab_size for s in ['/']):
60 | return None
61 |
62 | lab_size = self._get_xy_size_from_celltext(lab_size)
63 |
64 | codes = self._get_codes_from_celltext(self._nth_cell_text(row, 3))
65 |
66 | link = self._get_link_from_row(row)
67 |
68 | item = {
69 | 'size': lab_size,
70 | 'avery': codes[1],
71 | 'lpcode': codes[0],
72 | 'persheet': num_per_sheet,
73 | 'prodlink': link
74 | }
75 |
76 | return item
77 |
78 | def get_list(self, list_page):
79 |
80 | url = ("https://www.labelplanet.co.uk/label-templates/" +
81 | list_page + ".php")
82 |
83 | shape = {
84 | "rectangular-rounded-corners": "rrect",
85 | "rectangular-square-corners": "rect",
86 | "square": "rrect",
87 | "round": "circle",
88 | "oval": "circle"
89 | }[list_page]
90 |
91 | r = requests.get(url)
92 |
93 | doc = fromstring(r.text)
94 |
95 | items = []
96 |
97 | for prod_row in doc.cssselect(".templatetable tbody tr"):
98 |
99 | # product rows have 3 cells
100 | if (len(prod_row.getchildren()) == 3):
101 | item = self._get_item_from_row(prod_row)
102 |
103 | if (item):
104 | item['shape'] = shape
105 | items.append(item)
106 |
107 | return items
108 |
109 |
110 | class SpecRipper(object):
111 | """
112 | Gets the full spec for a label from the template page
113 |
114 | Updates the given item with description and label spec
115 | """
116 |
117 | def __init__(self, item):
118 | self.item = item
119 |
120 | def _get_desc_text(self, doc):
121 |
122 | e = doc.xpath('.//td'
123 | '/strong[starts-with(text(), "Notes")]'
124 | '/../..'
125 | '/following-sibling::tr[1]/td'
126 | '//li[1]')
127 |
128 | parts = e[0].text_content().split("–")
129 |
130 | if len(parts) > 1:
131 | return parts[0].strip()
132 |
133 | return None
134 |
135 | def _get_cell_by_xy(self, table, x, y):
136 |
137 | nxt = table.xpath(".//tr[2]/following-sibling::*[1]")[0]
138 |
139 | # handle broken table formatting
140 | # (missing
on third row)
141 | if nxt.tag == "td" and y > 3:
142 | y += 3
143 |
144 | selector = "tr:nth-child({y}) > td:nth-child({x})".format(x=x, y=y)
145 | return table.cssselect(selector)[0]
146 |
147 | def _get_dim_from_text(self, txt):
148 |
149 | return txt.replace("mm", "").replace("(diameter)", "").strip()
150 |
151 | def _get_xy_template_spec(self, doc):
152 |
153 | table = doc.cssselect('.templatetable')[0]
154 |
155 | # cell x, cell y, data
156 | data_cells = [
157 | (2, 3, 'size_x', 'dim'),
158 | (3, 3, 'size_y', 'dim'),
159 | (4, 3, 'count_x', 'int'),
160 | (5, 3, 'count_y', 'int'),
161 | (1, 5, 'margin_t', 'dim'),
162 | (3, 5, 'margin_l', 'dim'),
163 | (2, 7, 'pitch_x', 'dim'),
164 | (1, 7, 'pitch_y', 'dim'),
165 | ]
166 |
167 | spec = {}
168 |
169 | for c in data_cells:
170 |
171 | txt = self._get_cell_by_xy(table, c[0], c[1]).text_content()
172 |
173 | if c[3] == 'dim':
174 | txt = self._get_dim_from_text(txt)
175 |
176 | spec[c[2]] = txt
177 |
178 | return spec
179 |
180 | def scrape(self):
181 |
182 | logging.debug("Scraping template: %s", self.item['lpcode'])
183 |
184 | url = self.item['prodlink']
185 |
186 | r = requests.get(url)
187 |
188 | doc = fromstring(r.text)
189 |
190 | self.item['desc'] = self._get_desc_text(doc)
191 |
192 | spec = self._get_xy_template_spec(doc)
193 |
194 | logging.debug(pformat(spec))
195 |
196 | self.item['layout'] = spec
197 |
198 |
199 | class InxFormatter(object):
200 |
201 | def format_inx(self, item):
202 |
203 | idcode = item['avery'] if item['avery'] else item['lpcode'].replace("/", "_")
204 |
205 | size = " x ".join(item['size'])
206 |
207 | sheet = "A4"
208 |
209 | codes = []
210 |
211 | if item['avery']:
212 | codes.append(item['avery'])
213 |
214 | codes.append(item['lpcode'])
215 |
216 | codes = ", ".join(codes)
217 |
218 | desc = "Labels" if not item['desc'] else item['desc']
219 |
220 | s = "<_item value=\"{code}\">{size}mm {desc} ({per}/sheet, {sheet}) [{allcodes}]".format(
221 | code=idcode,
222 | size=size,
223 | per=item['persheet'],
224 | sheet=sheet,
225 | allcodes=codes,
226 | desc=desc
227 | )
228 |
229 | return s
230 |
231 |
232 | class SpecFormatter(object):
233 |
234 | def format_spec(self, item):
235 |
236 | idcode = item['avery'] if item['avery'] else item['lpcode'].replace("/", "_")
237 |
238 | sheet = 'a4'
239 |
240 | layout = item['layout']
241 |
242 | s = "{indent}{idcode:16}['reg', 'mm', '{sheet}', {ml}, {mt}, {sx}, {sy}, {px}, {py}, {nx}, {ny}, '{shape}'],".format(
243 | indent=" " * 4,
244 | idcode="'{}':".format(idcode),
245 | sheet=sheet,
246 | ml=layout['margin_l'],
247 | mt=layout['margin_t'],
248 | sx=layout['size_x'],
249 | sy=layout['size_y'],
250 | px=layout['pitch_x'],
251 | py=layout['pitch_y'],
252 | nx=layout['count_x'],
253 | ny=layout['count_y'],
254 | shape=item['shape'],
255 | )
256 |
257 | return s
258 |
259 |
260 | if __name__ == "__main__":
261 |
262 | parser = argparse.ArgumentParser(
263 | description='Download label template specifications from '
264 | 'LabelPlanet.co.uk')
265 | parser.add_argument('-v', '--verbose', action='store_true',
266 | help='verbose mode')
267 | parser.add_argument('-t', '--type', action='store', required=True,
268 | choices=['rrect', 'rect', 'circ', 'oval', 'square'],
269 | help='label type')
270 | parser.add_argument('--inx', action='store_true',
271 | help='print INX items')
272 | parser.add_argument('--spec', action='store_true',
273 | help='print specification items')
274 |
275 | args = parser.parse_args()
276 |
277 | # avoid re-downloading pages
278 | requests_cache.install_cache('demo_cache')
279 |
280 | # convert type
281 | label_type = {
282 | 'rrect': 'rectangular-rounded-corners',
283 | 'rect': 'rectangular-square-corners',
284 | 'circ': 'round',
285 | 'oval': 'oval',
286 | 'square': 'square'
287 | }[args.type]
288 |
289 | if args.verbose:
290 | logging.basicConfig(level=logging.DEBUG)
291 |
292 | ff = FormatFinder()
293 | spec_list = ff.get_list(label_type)
294 |
295 | logging.debug("Got list of specs: ")
296 | logging.debug(pformat(spec_list))
297 |
298 | # get spec layouts + descs etc
299 | for spec in spec_list:
300 |
301 | spec_ripper = SpecRipper(spec)
302 |
303 | spec_ripper.scrape()
304 |
305 | if args.inx:
306 | inx_f = InxFormatter()
307 |
308 | for s in spec_list:
309 |
310 | inx = inx_f.format_inx(s)
311 | print(inx)
312 |
313 | if args.spec:
314 |
315 | spec_f = SpecFormatter()
316 |
317 | for s in spec_list:
318 |
319 | spec = spec_f.format_spec(s)
320 | print(spec)
321 |
--------------------------------------------------------------------------------
/label_guides.inx:
--------------------------------------------------------------------------------
1 |
2 |
3 | Label Guides
4 | org.inkscape.effect.labelguides
5 |
6 | - mm
7 | - inch
8 |
9 |
10 |
11 |
12 | - 199.6 x 289.1mm Labels (1/sheet, A4) [L7167, LP1/199]
13 | - 199.6 x 143.5mm Labels (2/sheet, A4) [L7168, LP2/199]
14 | - 99.1 x 139mm Labels (4/sheet, A4) [L7169, LP4/99]
15 | - 192 x 62mm Lever Arch File Labels (4/sheet, A4) [L7701, LP4/192]
16 | - 200 x 60mm Lever Arch File Labels (4/sheet, A4) [L7171, LP4/200]
17 | - 99.1 x 93.1mm Labels (6/sheet, A4) [L7166, LP6/99]
18 | - 192 x 39mm Lever Arch File Labels (7/sheet, A4) [L4760, LP7/192]
19 | - 99.1 x 67.7mm Labels (8/sheet, A4) [L7165, LP8/99]
20 | - 70 x 71.8mm Diskette Labels (8/sheet, A4) [L7664, LP8/71]
21 | - 133 x 29.6mm Data Cartridge Labels (9/sheet, A4) [L7667, LP9/133]
22 | - 99.1 x 57mm Labels (10/sheet, A4) [L7173, LP10/99]
23 | - 38.1 x 135mm Smartstamp “Logo” Labels (10/sheet, A4) [J5103, LP10/38]
24 | - 70 x 52mm Labels (10/sheet, A4) [L7666, LP10/70]
25 | - 95.8 x 50.7mm Labels (10/sheet, A4) [L7783, LP10/96]
26 | - 63.5 x 72mm Labels (12/sheet, A4) [L7164, LP12/63]
27 | - 76.2 x 46.4mm Labels (12/sheet, A4) [L7671, LP12/76]
28 | - 99.1 x 42.3mm Labels (12/sheet, A4) [L7177, LP12/99]
29 | - 99.1 x 38.1mm Labels (14/sheet, A4) [L7163, LP14/99]
30 | - 59 x 50.9mm Labels (15/sheet, A4) [L7668, LP15/59]
31 | - 99.1 x 33.9mm Labels (16/sheet, A4) [L7162, LP16/99]
32 | - 145 x 17mm Labels (16/sheet, A4) [L7674, LP16/145]
33 | - 63.5 x 46.6mm Labels (18/sheet, A4) [L7161, LP18/63]
34 | - 100 x 30mm Labels (18/sheet, A4) [L7172, LP18/100]
35 | - 38.1 x 69mm Smartstamp “Bulk Print” Labels (20/sheet, A4) [J5101, LP20/38]
36 | - 63.5 x 38.1mm Labels (21/sheet, A4) [L7160, LP21/63]
37 | - 63.5 x 33.9mm Labels (24/sheet, A4) [L7159, LP24/63]
38 | - 72 x 21.15mm Labels (24/sheet, A4) [L7665, LP24/72]
39 | - 134 x 11mm Eurofolio Labels (24/sheet, A4) [L7170, LP24/134]
40 | - 63.5 x 29.6mm Labels (27/sheet, A4) [L6011, LP27/63]
41 | - 54 x 22mm Labels (33/sheet, A4) [LP33/53]
42 | - 48.9 x 29.6mm Labels (36/sheet, A4) [LP36/49]
43 | - 45.7 x 25.4mm Labels (40/sheet, A4) [L7654, LP40/45]
44 | - 45.7 x 21.2mm Labels (48/sheet, A4) [L7636, LP48/45]
45 | - 89 x 10mm Labels (56/sheet, A4) [LP56/89]
46 | - 38.1 x 21.2mm Labels (65/sheet, A4) [L7651, LP65/38]
47 | - 46 x 11.1mm Labels (84/sheet, A4) [L7656, LP84/46]
48 | - 25.4 x 10mm Labels (189/sheet, A4) [L7658, LP189/25]
49 | - 17.8 x 10mm Labels (270/sheet, A4) [L7657, LP270/18]
50 | - 95 x 95mm Labels (6/sheet, A4) [LP6/95SQ]
51 | - 65 x 65mm Labels (12/sheet, A4) [LP12/65SQ]
52 | - 51 x 51mm Labels (15/sheet, A4) [LP15/51SQ]
53 | - 37 x 37mm Labels (35/sheet, A4) [LP35/37SQ]
54 | - 25 x 25mm Labels (70/sheet, A4) [LP70/25SQ]
55 |
56 | 1
57 |
58 |
59 |
60 | - 210 x 297mm Labels (1/sheet, A4) [L7784, LP1/210H,J,V]
61 | - 105 x 297mm Labels (2/sheet, A4) [LP2/105]
62 | - 210 x 148.5mm Labels (2/sheet, A4) [3655, LP2/210]
63 | - 210 x 99mm Labels (3/sheet, A4) [LP3/210]
64 | - 105 x 148.5mm Labels (4/sheet, A4) [3483, LP4/105]
65 | - 210 x 74.25mm Labels (4/sheet, A4) [LP4/210]
66 | - 70 x 148.5mm Labels (6/sheet, A4) [LP6/70]
67 | - 105 x 99mm Labels (6/sheet, A4) [LP6/105]
68 | - 105 x 74.25mm Labels (8/sheet, A4) [3427, LP8/105]
69 | - 105 x 70.7mm Labels (8/sheet, A4) [LP8/105S]
70 | - 105 x 59.4mm Labels (10/sheet, A4) [LP10/105]
71 | - 105 x 57.55mm Labels (10/sheet, A4) [3425, LP10/105S]
72 | - 105 x 49.5mm Labels (12/sheet, A4) [LP12/105]
73 | - 105 x 47.9mm Labels (12/sheet, A4) [3424, LP12/105S]
74 | - 105 x 42.42mm Labels (14/sheet, A4) [3653, LP14/105]
75 | - 70 x 59.4mm Labels (15/sheet, A4) [LP15/70]
76 | - 70 x 50.7mm Labels (15/sheet, A4) [LP15/70S]
77 | - 105 x 37.12mm Labels (16/sheet, A4) [3484, LP16/105]
78 | - 105 x 34.95mm Labels (16/sheet, A4) [3423, LP16/105S]
79 | - 70 x 42.42mm Labels (21/sheet, A4) [3652, LP21/70]
80 | - 70 x 38.1mm Labels (21/sheet, A4) [LP21/70S]
81 | - 70 x 37.12mm Labels (24/sheet, A4) [3474, LP24/70]
82 | - 70 x 34.95mm Labels (24/sheet, A4) [3422, LP24/70S]
83 | - 70 x 34mm Labels (24/sheet, A4) [LP24/70LS]
84 | - 70 x 36mm Labels (24/sheet, A4) [3475, LP24/70SS]
85 | - 70 x 31.95mm Labels (27/sheet, A4) [LP27/70S]
86 | - 70 x 29.7mm Labels (30/sheet, A4) [3489, LP30/70]
87 | - 70 x 25.4mm Labels (33/sheet, A4) [3421, LP33/70S]
88 | - 57 x 15mm Labels (51/sheet, A4) [L7409, LP51/57]
89 | - 52.5 x 21.21mm Labels (56/sheet, A4) [LP56/52]
90 |
91 |
92 |
93 |
94 | - 114.5mm Labels (2/sheet, A4) [LP2/115R]
95 | - 88mm Labels (6/sheet, A4) [LP6/88R]
96 | - 85mm Labels (6/sheet, A4) [LP6/85R]
97 | - 76mm Labels (6/sheet, A4) [LP6/76R]
98 | - 72mm Labels (6/sheet, A4) [C2244, LP6/72R]
99 | - 69mm Labels (8/sheet, A4) [LP8/69R]
100 | - 63.5mm Labels (12/sheet, A4) [L7670, LP12/64R]
101 | - 51mm Labels (15/sheet, A4) [LP15/51R]
102 | - 45mm Labels (24/sheet, A4) [LP24/45R]
103 | - 40mm Labels (24/sheet, A4) [L7780, LP24/40R]
104 | - 37mm Labels (35/sheet, A4) [LP35/37R]
105 | - 35mm Labels (35/sheet, A4) [LP35/35R]
106 | - 32mm Labels (40/sheet, A4) [LP40/32R]
107 | - 29mm Labels (54/sheet, A4) [LP54/29R]
108 | - 25mm Labels (70/sheet, A4) [LP70/25R]
109 | - 19mm Labels (117/sheet, A4) [LP117/19R]
110 | - 13mm Labels (216/sheet, A4) [LP216/13R]
111 | - 195 x 138mm Labels (2/sheet, A4) [LP2/195OV]
112 | - 90 x 135mm Labels (4/sheet, A4) [LP4/90OV]
113 | - 90 x 62mm Labels (8/sheet, A4) [LP8/90OV]
114 | - 95 x 53mm Labels (10/sheet, A4) [LP10/95OV]
115 | - 95 x 34mm Labels (14/sheet, A4) [LP14/95OV]
116 | - 60 x 34mm Labels (21/sheet, A4) [LP21/60OV]
117 | - 40 x 30mm Labels (32/sheet, A4) [LP32/40OV]
118 | - 35.05 x 16mm Labels (65/sheet, A4) [LP65/35OV]
119 |
120 |
121 |
122 | Custom Label Options
123 | 8.5
124 | 13>
125 | 37
126 | 37
127 | 39
128 | 39
129 | 5
130 | 7
131 |
132 | - Rectangle
133 | - Circle/Ellipse
134 |
135 |
136 |
137 | Drawing Options
138 | false
139 | true
140 | true
141 | 5
142 | true
143 | true
144 | 5
145 | true
146 | false
147 |
148 | all
149 |
150 |
151 |
152 |
153 |
156 |
157 |
--------------------------------------------------------------------------------
/COPYING:
--------------------------------------------------------------------------------
1 | GNU GENERAL PUBLIC LICENSE
2 | Version 2, June 1991
3 |
4 | Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
5 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
6 | Everyone is permitted to copy and distribute verbatim copies
7 | of this license document, but changing it is not allowed.
8 |
9 | Preamble
10 |
11 | The licenses for most software are designed to take away your
12 | freedom to share and change it. By contrast, the GNU General Public
13 | License is intended to guarantee your freedom to share and change free
14 | software--to make sure the software is free for all its users. This
15 | General Public License applies to most of the Free Software
16 | Foundation's software and to any other program whose authors commit to
17 | using it. (Some other Free Software Foundation software is covered by
18 | the GNU Lesser General Public License instead.) You can apply it to
19 | your programs, too.
20 |
21 | When we speak of free software, we are referring to freedom, not
22 | price. Our General Public Licenses are designed to make sure that you
23 | have the freedom to distribute copies of free software (and charge for
24 | this service if you wish), that you receive source code or can get it
25 | if you want it, that you can change the software or use pieces of it
26 | in new free programs; and that you know you can do these things.
27 |
28 | To protect your rights, we need to make restrictions that forbid
29 | anyone to deny you these rights or to ask you to surrender the rights.
30 | These restrictions translate to certain responsibilities for you if you
31 | distribute copies of the software, or if you modify it.
32 |
33 | For example, if you distribute copies of such a program, whether
34 | gratis or for a fee, you must give the recipients all the rights that
35 | you have. You must make sure that they, too, receive or can get the
36 | source code. And you must show them these terms so they know their
37 | rights.
38 |
39 | We protect your rights with two steps: (1) copyright the software, and
40 | (2) offer you this license which gives you legal permission to copy,
41 | distribute and/or modify the software.
42 |
43 | Also, for each author's protection and ours, we want to make certain
44 | that everyone understands that there is no warranty for this free
45 | software. If the software is modified by someone else and passed on, we
46 | want its recipients to know that what they have is not the original, so
47 | that any problems introduced by others will not reflect on the original
48 | authors' reputations.
49 |
50 | Finally, any free program is threatened constantly by software
51 | patents. We wish to avoid the danger that redistributors of a free
52 | program will individually obtain patent licenses, in effect making the
53 | program proprietary. To prevent this, we have made it clear that any
54 | patent must be licensed for everyone's free use or not licensed at all.
55 |
56 | The precise terms and conditions for copying, distribution and
57 | modification follow.
58 |
59 | GNU GENERAL PUBLIC LICENSE
60 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
61 |
62 | 0. This License applies to any program or other work which contains
63 | a notice placed by the copyright holder saying it may be distributed
64 | under the terms of this General Public License. The "Program", below,
65 | refers to any such program or work, and a "work based on the Program"
66 | means either the Program or any derivative work under copyright law:
67 | that is to say, a work containing the Program or a portion of it,
68 | either verbatim or with modifications and/or translated into another
69 | language. (Hereinafter, translation is included without limitation in
70 | the term "modification".) Each licensee is addressed as "you".
71 |
72 | Activities other than copying, distribution and modification are not
73 | covered by this License; they are outside its scope. The act of
74 | running the Program is not restricted, and the output from the Program
75 | is covered only if its contents constitute a work based on the
76 | Program (independent of having been made by running the Program).
77 | Whether that is true depends on what the Program does.
78 |
79 | 1. You may copy and distribute verbatim copies of the Program's
80 | source code as you receive it, in any medium, provided that you
81 | conspicuously and appropriately publish on each copy an appropriate
82 | copyright notice and disclaimer of warranty; keep intact all the
83 | notices that refer to this License and to the absence of any warranty;
84 | and give any other recipients of the Program a copy of this License
85 | along with the Program.
86 |
87 | You may charge a fee for the physical act of transferring a copy, and
88 | you may at your option offer warranty protection in exchange for a fee.
89 |
90 | 2. You may modify your copy or copies of the Program or any portion
91 | of it, thus forming a work based on the Program, and copy and
92 | distribute such modifications or work under the terms of Section 1
93 | above, provided that you also meet all of these conditions:
94 |
95 | a) You must cause the modified files to carry prominent notices
96 | stating that you changed the files and the date of any change.
97 |
98 | b) You must cause any work that you distribute or publish, that in
99 | whole or in part contains or is derived from the Program or any
100 | part thereof, to be licensed as a whole at no charge to all third
101 | parties under the terms of this License.
102 |
103 | c) If the modified program normally reads commands interactively
104 | when run, you must cause it, when started running for such
105 | interactive use in the most ordinary way, to print or display an
106 | announcement including an appropriate copyright notice and a
107 | notice that there is no warranty (or else, saying that you provide
108 | a warranty) and that users may redistribute the program under
109 | these conditions, and telling the user how to view a copy of this
110 | License. (Exception: if the Program itself is interactive but
111 | does not normally print such an announcement, your work based on
112 | the Program is not required to print an announcement.)
113 |
114 | These requirements apply to the modified work as a whole. If
115 | identifiable sections of that work are not derived from the Program,
116 | and can be reasonably considered independent and separate works in
117 | themselves, then this License, and its terms, do not apply to those
118 | sections when you distribute them as separate works. But when you
119 | distribute the same sections as part of a whole which is a work based
120 | on the Program, the distribution of the whole must be on the terms of
121 | this License, whose permissions for other licensees extend to the
122 | entire whole, and thus to each and every part regardless of who wrote it.
123 |
124 | Thus, it is not the intent of this section to claim rights or contest
125 | your rights to work written entirely by you; rather, the intent is to
126 | exercise the right to control the distribution of derivative or
127 | collective works based on the Program.
128 |
129 | In addition, mere aggregation of another work not based on the Program
130 | with the Program (or with a work based on the Program) on a volume of
131 | a storage or distribution medium does not bring the other work under
132 | the scope of this License.
133 |
134 | 3. You may copy and distribute the Program (or a work based on it,
135 | under Section 2) in object code or executable form under the terms of
136 | Sections 1 and 2 above provided that you also do one of the following:
137 |
138 | a) Accompany it with the complete corresponding machine-readable
139 | source code, which must be distributed under the terms of Sections
140 | 1 and 2 above on a medium customarily used for software interchange; or,
141 |
142 | b) Accompany it with a written offer, valid for at least three
143 | years, to give any third party, for a charge no more than your
144 | cost of physically performing source distribution, a complete
145 | machine-readable copy of the corresponding source code, to be
146 | distributed under the terms of Sections 1 and 2 above on a medium
147 | customarily used for software interchange; or,
148 |
149 | c) Accompany it with the information you received as to the offer
150 | to distribute corresponding source code. (This alternative is
151 | allowed only for noncommercial distribution and only if you
152 | received the program in object code or executable form with such
153 | an offer, in accord with Subsection b above.)
154 |
155 | The source code for a work means the preferred form of the work for
156 | making modifications to it. For an executable work, complete source
157 | code means all the source code for all modules it contains, plus any
158 | associated interface definition files, plus the scripts used to
159 | control compilation and installation of the executable. However, as a
160 | special exception, the source code distributed need not include
161 | anything that is normally distributed (in either source or binary
162 | form) with the major components (compiler, kernel, and so on) of the
163 | operating system on which the executable runs, unless that component
164 | itself accompanies the executable.
165 |
166 | If distribution of executable or object code is made by offering
167 | access to copy from a designated place, then offering equivalent
168 | access to copy the source code from the same place counts as
169 | distribution of the source code, even though third parties are not
170 | compelled to copy the source along with the object code.
171 |
172 | 4. You may not copy, modify, sublicense, or distribute the Program
173 | except as expressly provided under this License. Any attempt
174 | otherwise to copy, modify, sublicense or distribute the Program is
175 | void, and will automatically terminate your rights under this License.
176 | However, parties who have received copies, or rights, from you under
177 | this License will not have their licenses terminated so long as such
178 | parties remain in full compliance.
179 |
180 | 5. You are not required to accept this License, since you have not
181 | signed it. However, nothing else grants you permission to modify or
182 | distribute the Program or its derivative works. These actions are
183 | prohibited by law if you do not accept this License. Therefore, by
184 | modifying or distributing the Program (or any work based on the
185 | Program), you indicate your acceptance of this License to do so, and
186 | all its terms and conditions for copying, distributing or modifying
187 | the Program or works based on it.
188 |
189 | 6. Each time you redistribute the Program (or any work based on the
190 | Program), the recipient automatically receives a license from the
191 | original licensor to copy, distribute or modify the Program subject to
192 | these terms and conditions. You may not impose any further
193 | restrictions on the recipients' exercise of the rights granted herein.
194 | You are not responsible for enforcing compliance by third parties to
195 | this License.
196 |
197 | 7. If, as a consequence of a court judgment or allegation of patent
198 | infringement or for any other reason (not limited to patent issues),
199 | conditions are imposed on you (whether by court order, agreement or
200 | otherwise) that contradict the conditions of this License, they do not
201 | excuse you from the conditions of this License. If you cannot
202 | distribute so as to satisfy simultaneously your obligations under this
203 | License and any other pertinent obligations, then as a consequence you
204 | may not distribute the Program at all. For example, if a patent
205 | license would not permit royalty-free redistribution of the Program by
206 | all those who receive copies directly or indirectly through you, then
207 | the only way you could satisfy both it and this License would be to
208 | refrain entirely from distribution of the Program.
209 |
210 | If any portion of this section is held invalid or unenforceable under
211 | any particular circumstance, the balance of the section is intended to
212 | apply and the section as a whole is intended to apply in other
213 | circumstances.
214 |
215 | It is not the purpose of this section to induce you to infringe any
216 | patents or other property right claims or to contest validity of any
217 | such claims; this section has the sole purpose of protecting the
218 | integrity of the free software distribution system, which is
219 | implemented by public license practices. Many people have made
220 | generous contributions to the wide range of software distributed
221 | through that system in reliance on consistent application of that
222 | system; it is up to the author/donor to decide if he or she is willing
223 | to distribute software through any other system and a licensee cannot
224 | impose that choice.
225 |
226 | This section is intended to make thoroughly clear what is believed to
227 | be a consequence of the rest of this License.
228 |
229 | 8. If the distribution and/or use of the Program is restricted in
230 | certain countries either by patents or by copyrighted interfaces, the
231 | original copyright holder who places the Program under this License
232 | may add an explicit geographical distribution limitation excluding
233 | those countries, so that distribution is permitted only in or among
234 | countries not thus excluded. In such case, this License incorporates
235 | the limitation as if written in the body of this License.
236 |
237 | 9. The Free Software Foundation may publish revised and/or new versions
238 | of the General Public License from time to time. Such new versions will
239 | be similar in spirit to the present version, but may differ in detail to
240 | address new problems or concerns.
241 |
242 | Each version is given a distinguishing version number. If the Program
243 | specifies a version number of this License which applies to it and "any
244 | later version", you have the option of following the terms and conditions
245 | either of that version or of any later version published by the Free
246 | Software Foundation. If the Program does not specify a version number of
247 | this License, you may choose any version ever published by the Free Software
248 | Foundation.
249 |
250 | 10. If you wish to incorporate parts of the Program into other free
251 | programs whose distribution conditions are different, write to the author
252 | to ask for permission. For software which is copyrighted by the Free
253 | Software Foundation, write to the Free Software Foundation; we sometimes
254 | make exceptions for this. Our decision will be guided by the two goals
255 | of preserving the free status of all derivatives of our free software and
256 | of promoting the sharing and reuse of software generally.
257 |
258 | NO WARRANTY
259 |
260 | 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
261 | FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
262 | OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
263 | PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
264 | OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
265 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
266 | TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
267 | PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
268 | REPAIR OR CORRECTION.
269 |
270 | 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
271 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
272 | REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
273 | INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
274 | OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
275 | TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
276 | YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
277 | PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
278 | POSSIBILITY OF SUCH DAMAGES.
279 |
280 | END OF TERMS AND CONDITIONS
281 |
282 | How to Apply These Terms to Your New Programs
283 |
284 | If you develop a new program, and you want it to be of the greatest
285 | possible use to the public, the best way to achieve this is to make it
286 | free software which everyone can redistribute and change under these terms.
287 |
288 | To do so, attach the following notices to the program. It is safest
289 | to attach them to the start of each source file to most effectively
290 | convey the exclusion of warranty; and each file should have at least
291 | the "copyright" line and a pointer to where the full notice is found.
292 |
293 |
294 | Copyright (C)
295 |
296 | This program is free software; you can redistribute it and/or modify
297 | it under the terms of the GNU General Public License as published by
298 | the Free Software Foundation; either version 2 of the License, or
299 | (at your option) any later version.
300 |
301 | This program is distributed in the hope that it will be useful,
302 | but WITHOUT ANY WARRANTY; without even the implied warranty of
303 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
304 | GNU General Public License for more details.
305 |
306 | You should have received a copy of the GNU General Public License along
307 | with this program; if not, write to the Free Software Foundation, Inc.,
308 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
309 |
310 | Also add information on how to contact you by electronic and paper mail.
311 |
312 | If the program is interactive, make it output a short notice like this
313 | when it starts in an interactive mode:
314 |
315 | Gnomovision version 69, Copyright (C) year name of author
316 | Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
317 | This is free software, and you are welcome to redistribute it
318 | under certain conditions; type `show c' for details.
319 |
320 | The hypothetical commands `show w' and `show c' should show the appropriate
321 | parts of the General Public License. Of course, the commands you use may
322 | be called something other than `show w' and `show c'; they could even be
323 | mouse-clicks or menu items--whatever suits your program.
324 |
325 | You should also get your employer (if you work as a programmer) or your
326 | school, if any, to sign a "copyright disclaimer" for the program, if
327 | necessary. Here is a sample; alter the names:
328 |
329 | Yoyodyne, Inc., hereby disclaims all copyright interest in the program
330 | `Gnomovision' (which makes passes at compilers) written by James Hacker.
331 |
332 | , 1 April 1989
333 | Ty Coon, President of Vice
334 |
335 | This General Public License does not permit incorporating your program into
336 | proprietary programs. If your program is a subroutine library, you may
337 | consider it more useful to permit linking proprietary applications with the
338 | library. If this is what you want to do, use the GNU Lesser General
339 | Public License instead of this License.
340 |
341 |
--------------------------------------------------------------------------------
/label_guides.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 | '''
3 | Label Guides Creator
4 |
5 | Copyright (C) 2018 John Beard - john.j.beard **guesswhat** gmail.com
6 |
7 | ## Simple Extension to draw guides and outlines for common paper labels
8 |
9 | This program is free software; you can redistribute it and/or modify
10 | it under the terms of the GNU General Public License as published by
11 | the Free Software Foundation; version 2 of the License.
12 |
13 | This program is distributed in the hope that it will be useful,
14 | but WITHOUT ANY WARRANTY; without even the implied warranty of
15 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 | GNU General Public License for more details.
17 |
18 | You should have received a copy of the GNU General Public License
19 | along with this program; if not, write to the Free Software
20 | Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
21 | '''
22 |
23 | import inkex
24 | from lxml import etree
25 |
26 | # Colours to use for the guides
27 | GUIDE_COLOURS = {
28 | 'edge': '#00A000',
29 | 'centre': '#A00000',
30 | 'inset': '#0000A0'
31 | }
32 |
33 | # Preset list
34 | # Regular grids defined as:
35 | # 'reg', unit, page_szie, l marg, t marg, X size, Y size,
36 | # X pitch, Y pitch, Number across, Number down, shapes
37 | PRESETS = {
38 | # Rounded rectangular labels in grid layout
39 | 'L7167': ['reg', 'mm', 'a4', 5.2, 3.95, 199.6, 289.1, 199.6, 289.1, 1, 1, 'rrect'],
40 | 'L7168': ['reg', 'mm', 'a4', 5.2, 5, 199.6, 143.5, 199.6, 143.5, 1, 2, 'rrect'],
41 | 'L7169': ['reg', 'mm', 'a4', 4.65, 9.5, 99.1, 139, 101.6, 139, 2, 2, 'rrect'],
42 | 'L7701': ['reg', 'mm', 'a4', 9, 24.5, 192, 62, 192, 62, 1, 4, 'rrect'],
43 | 'L7171': ['reg', 'mm', 'a4', 5, 28.5, 200, 60, 200, 60, 1, 4, 'rrect'],
44 | 'L7166': ['reg', 'mm', 'a4', 4.65, 8.85, 99.1, 93.1, 101.6, 93.1, 2, 3, 'rrect'],
45 | 'L4760': ['reg', 'mm', 'a4', 9, 12, 192, 39, 192, 39, 1, 7, 'rrect'],
46 | 'L7165': ['reg', 'mm', 'a4', 4.65, 13.1, 99.1, 67.7, 101.6, 67.7, 2, 4, 'rrect'],
47 | 'L7664': ['reg', 'mm', 'a4', 18, 4.9, 70, 71.8, 104, 71.8, 2, 4, 'rrect'],
48 | 'L7667': ['reg', 'mm', 'a4', 38.5, 15.3, 133, 29.6, 133, 29.6, 1, 9, 'rrect'],
49 | 'L7173': ['reg', 'mm', 'a4', 4.65, 6, 99.1, 57, 101.6, 57, 2, 5, 'rrect'],
50 | 'J5103': ['reg', 'mm', 'a4', 4.75, 13.5, 38.1, 135, 40.6, 135, 5, 2, 'rrect'],
51 | 'L7666': ['reg', 'mm', 'a4', 23, 18.5, 70, 52, 94, 52, 2, 5, 'rrect'],
52 | 'L7783': ['reg', 'mm', 'a4', 7.85, 21.75, 95.8, 50.7, 98.5, 50.7, 2, 5, 'rrect'],
53 | 'L7164': ['reg', 'mm', 'a4', 7.25, 4.5, 63.5, 72, 66, 72, 3, 4, 'rrect'],
54 | 'L7671': ['reg', 'mm', 'a4', 27.55, 9.3, 76.2, 46.4, 78.7, 46.4, 2, 6, 'rrect'],
55 | 'L7177': ['reg', 'mm', 'a4', 4.65, 21.6, 99.1, 42.3, 101.6, 42.3, 2, 6, 'rrect'],
56 | 'L7163': ['reg', 'mm', 'a4', 4.65, 15.15, 99.1, 38.1, 101.6, 38.1, 2, 7, 'rrect'],
57 | 'L7668': ['reg', 'mm', 'a4', 13.5, 21.25, 59, 50.9, 62, 50.9, 3, 5, 'rrect'],
58 | 'L7162': ['reg', 'mm', 'a4', 4.65, 12.9, 99.1, 33.9, 101.6, 33.9, 2, 8, 'rrect'],
59 | 'L7674': ['reg', 'mm', 'a4', 32.5, 12.5, 145, 17, 145, 17, 1, 16, 'rrect'],
60 | 'L7161': ['reg', 'mm', 'a4', 7.25, 8.7, 63.5, 46.6, 66, 46.6, 3, 6, 'rrect'],
61 | 'L7172': ['reg', 'mm', 'a4', 3.75, 13.5, 100, 30, 102.5, 30, 2, 9, 'rrect'],
62 | 'J5101': ['reg', 'mm', 'a4', 4.75, 10.5, 38.1, 69, 40.6, 69, 5, 4, 'rrect'],
63 | 'L7160': ['reg', 'mm', 'a4', 7.25, 15.15, 63.5, 38.1, 66, 38.1, 3, 7, 'rrect'],
64 | 'L7159': ['reg', 'mm', 'a4', 7.25, 12.9, 63.5, 33.9, 66, 33.9, 3, 8, 'rrect'],
65 | 'L7665': ['reg', 'mm', 'a4', 22, 21.6, 72, 21.15, 94, 21.15, 2, 12, 'rrect'],
66 | 'L7170': ['reg', 'mm', 'a4', 38, 16.5, 134, 11, 134, 11, 1, 24, 'rrect'],
67 | 'L6011': ['reg', 'mm', 'a4', 7.25, 15.3, 63.5, 29.6, 66, 29.6, 3, 9, 'rrect'],
68 | 'LP33_53': ['reg', 'mm', 'a4', 21, 17.5, 54, 22, 57, 24, 3, 11, 'rrect'],
69 | 'LP36_49': ['reg', 'mm', 'a4', 4.8, 15.3, 48.9, 29.6, 50.5, 29.6, 4, 9, 'rrect'],
70 | 'L7654': ['reg', 'mm', 'a4', 9.7, 21.5, 45.7, 25.4, 48.3, 25.4, 4, 10, 'rrect'],
71 | 'L7636': ['reg', 'mm', 'a4', 9.85, 21.3, 45.7, 21.2, 48.2, 21.2, 4, 12, 'rrect'],
72 | 'LP56_89': ['reg', 'mm', 'a4', 8, 8.5, 89, 10, 105, 10, 2, 28, 'rrect'],
73 | 'L7651': ['reg', 'mm', 'a4', 4.75, 10.7, 38.1, 21.2, 40.6, 21.2, 5, 13, 'rrect'],
74 | 'L7656': ['reg', 'mm', 'a4', 5.95, 15.95, 46, 11.1, 50.7, 12.7, 4, 21, 'rrect'],
75 | 'L7658': ['reg', 'mm', 'a4', 8.6, 13.5, 25.4, 10, 27.9, 10, 7, 27, 'rrect'],
76 | 'L7657': ['reg', 'mm', 'a4', 4.75, 13.5, 17.8, 10, 20.3, 10, 10, 27, 'rrect'],
77 |
78 | # Rect labels
79 | 'L7784': ['reg', 'mm', 'a4', 0, 0, 210, 297, 210, 297, 1, 1, 'rect'],
80 | 'LP2_105': ['reg', 'mm', 'a4', 0, 0, 105, 297, 105, 297, 2, 1, 'rect'],
81 | '3655': ['reg', 'mm', 'a4', 0, 0, 210, 148.5, 210, 148.5, 1, 2, 'rect'],
82 | 'LP3_210': ['reg', 'mm', 'a4', 0, 0, 210, 99, 210, 99, 1, 3, 'rect'],
83 | '3483': ['reg', 'mm', 'a4', 0, 0, 105, 148.5, 105, 148.5, 2, 2, 'rect'],
84 | 'LP4_210': ['reg', 'mm', 'a4', 0, 0, 210, 74.25, 210, 74.25, 1, 4, 'rect'],
85 | 'LP6_70': ['reg', 'mm', 'a4', 0, 0, 70, 148.5, 70, 148.5, 3, 2, 'rect'],
86 | 'LP6_105': ['reg', 'mm', 'a4', 0, 0, 105, 99, 105, 99, 2, 3, 'rect'],
87 | '3427': ['reg', 'mm', 'a4', 0, 0, 105, 74.25, 105, 74.25, 2, 4, 'rect'],
88 | 'LP8_105S': ['reg', 'mm', 'a4', 0, 7.1, 105, 70.7, 105, 70.7, 2, 4, 'rect'],
89 | 'LP10_105': ['reg', 'mm', 'a4', 0, 0, 105, 59.4, 105, 59.4, 2, 5, 'rect'],
90 | '3425': ['reg', 'mm', 'a4', 0, 4.625, 105, 57.55, 105, 57.55, 2, 5, 'rect'],
91 | 'LP12_105': ['reg', 'mm', 'a4', 0, 0, 105, 49.5, 105, 49.5, 2, 6, 'rect'],
92 | '3424': ['reg', 'mm', 'a4', 0, 4.8, 105, 47.9, 105, 47.9, 2, 6, 'rect'],
93 | '3653': ['reg', 'mm', 'a4', 0, 0, 105, 42.42, 105, 42.42, 2, 7, 'rect'],
94 | 'LP15_70': ['reg', 'mm', 'a4', 0, 0, 70, 59.4, 70, 59.4, 3, 5, 'rect'],
95 | 'LP15_70S': ['reg', 'mm', 'a4', 0, 21.75, 70, 50.7, 70, 50.7, 3, 5, 'rect'],
96 | '3484': ['reg', 'mm', 'a4', 0, 0, 105, 37.12, 105, 37.12, 2, 8, 'rect'],
97 | '3423': ['reg', 'mm', 'a4', 0, 8.7, 105, 34.95, 105, 34.95, 2, 8, 'rect'],
98 | '3652': ['reg', 'mm', 'a4', 0, 0, 70, 42.42, 70, 42.42, 3, 7, 'rect'],
99 | 'LP21_70S': ['reg', 'mm', 'a4', 0, 15.15, 70, 38.1, 70, 38.1, 3, 7, 'rect'],
100 | '3474': ['reg', 'mm', 'a4', 0, 0, 70, 37.12, 70, 37.12, 3, 8, 'rect'],
101 | '3422': ['reg', 'mm', 'a4', 0, 8.7, 70, 34.95, 70, 34.95, 3, 8, 'rect'],
102 | 'LP24_70LS': ['reg', 'mm', 'a4', 0, 12.5, 70, 34, 70, 34, 3, 8, 'rect'],
103 | '3475': ['reg', 'mm', 'a4', 0, 4.5, 70, 36, 70, 36, 3, 8, 'rect'],
104 | 'LP27_70S': ['reg', 'mm', 'a4', 0, 4.725, 70, 31.95, 70, 31.95, 3, 9, 'rect'],
105 | '3489': ['reg', 'mm', 'a4', 0, 0, 70, 29.7, 70, 29.7, 3, 10, 'rect'],
106 | '3421': ['reg', 'mm', 'a4', 0, 8.8, 70, 25.4, 70, 25.4, 3, 11, 'rect'],
107 | 'L7409': ['reg', 'mm', 'a4', 19.5, 21, 57, 15, 57, 15, 3, 17, 'rect'],
108 | 'LP56_52': ['reg', 'mm', 'a4', 0, 0, 52.5, 21.21, 52.5, 21.21, 4, 14, 'rect'],
109 |
110 | # Round labels
111 | 'LP2_115R': ['reg', 'mm', 'a4', 47.75, 16.65, 114.5, 114.5, 114.5, 149.2, 1, 2, 'circle'],
112 | 'LP6_88R': ['reg', 'mm', 'a4', 16, 14.5, 88, 88, 90, 90, 2, 3, 'circle'],
113 | 'LP6_85R': ['reg', 'mm', 'a4', 17.5, 16, 85, 85, 90, 90, 2, 3, 'circle'],
114 | 'LP6_76R': ['reg', 'mm', 'a4', 27, 31, 76, 76, 80, 79.5, 2, 3, 'circle'],
115 | 'C2244': ['reg', 'mm', 'a4', 29.7, 33.9, 72, 72, 78.6, 78.6, 2, 3, 'circle'],
116 | 'LP8_69R': ['reg', 'mm', 'a4', 34.5, 6, 69, 69, 72, 72, 2, 4, 'circle'],
117 | 'L7670': ['reg', 'mm', 'a4', 5.25, 14.75, 63.5, 63.5, 68, 68, 3, 4, 'circle'],
118 | 'LP15_51R': ['reg', 'mm', 'a4', 26.5, 17, 51, 51, 53, 53, 3, 5, 'circle'],
119 | 'LP24_45R': ['reg', 'mm', 'a4', 9, 6, 45, 45, 49, 48, 4, 6, 'circle'],
120 | 'L7780': ['reg', 'mm', 'a4', 16, 13.5, 40, 40, 46, 46, 4, 6, 'circle'],
121 | 'LP35_37R': ['reg', 'mm', 'a4', 8.5, 13, 37, 37, 39, 39, 5, 7, 'circle'],
122 | 'LP35_35R': ['reg', 'mm', 'a4', 9.5, 17, 35, 35, 39, 38, 5, 7, 'circle'],
123 | 'LP40_32R': ['reg', 'mm', 'a4', 19, 10.35, 32, 32, 35, 34.9, 5, 8, 'circle'],
124 | 'LP54_29R': ['reg', 'mm', 'a4', 8, 6, 29, 29, 33, 32, 6, 9, 'circle'],
125 | 'LP70_25R': ['reg', 'mm', 'a4', 11.5, 14.5, 25, 25, 27, 27, 7, 10, 'circle'],
126 | 'LP117_19R': ['reg', 'mm', 'a4', 11.5, 13, 19, 19, 21, 21, 9, 13, 'circle'],
127 | 'LP216_13R': ['reg', 'mm', 'a4', 13.25, 10.25, 13, 13, 15.5, 15.5, 12, 18, 'circle'],
128 |
129 | # Oval labels
130 | 'LP2_195OV': ['reg', 'mm', 'a4', 7.5, 8.5, 195, 138, 195, 142, 1, 2, 'circle'],
131 | 'LP4_90OV': ['reg', 'mm', 'a4', 14, 12.5, 90, 135, 92, 137, 2, 2, 'circle'],
132 | 'LP8_90OV': ['reg', 'mm', 'a4', 9.25, 15.95, 90, 62, 101.5, 67.7, 2, 4, 'circle'],
133 | 'LP10_95OV': ['reg', 'mm', 'a4', 7, 8, 95, 53, 101, 57, 2, 5, 'circle'],
134 | 'LP14_95OV': ['reg', 'mm', 'a4', 7, 17.5, 95, 34, 101, 38, 2, 7, 'circle'],
135 | 'LP21_60OV': ['reg', 'mm', 'a4', 11, 10, 60, 34, 64, 40.5, 3, 7, 'circle'],
136 | 'LP32_40OV': ['reg', 'mm', 'a4', 22, 21.5, 40, 30, 42, 32, 4, 8, 'circle'],
137 | 'LP65_35OV': ['reg', 'mm', 'a4', 5.975, 13.3, 35.05, 16, 40.75, 21.2, 5, 13, 'circle'],
138 |
139 | # Square labels
140 | 'LP6_95SQ': ['reg', 'mm', 'a4', 6.5, 3, 95, 95, 98, 98, 2, 3, 'rrect'],
141 | 'LP12_65SQ': ['reg', 'mm', 'a4', 5, 15.5, 65, 65, 67.5, 67, 3, 4, 'rrect'],
142 | 'LP15_51SQ': ['reg', 'mm', 'a4', 26.6, 17.2, 51, 51, 52.9, 52.9, 3, 5, 'rrect'],
143 | 'LP35_37SQ': ['reg', 'mm', 'a4', 8.5 / 12, 13.3, 37, 37, 39, 38.9, 5, 7, 'rrect'],
144 | 'LP70_25SQ': ['reg', 'mm', 'a4', 11.5, 14.5, 25, 25, 27, 27, 7, 10, 'rrect'],
145 | }
146 |
147 |
148 | def add_SVG_guide(x, y, orientation, colour, parent):
149 | """ Create a sodipodi:guide node on the given parent
150 | """
151 |
152 | try:
153 | # convert mnemonics to actual orientations
154 | orientation = {
155 | 'vert': '1,0',
156 | 'horz': '0,1'
157 | }[orientation]
158 | except KeyError:
159 | pass
160 |
161 | attribs = {
162 | 'position': str(x) + "," + str(y),
163 | 'orientation': orientation
164 | }
165 |
166 | if colour is not None:
167 | attribs[inkex.addNS('color', 'inkscape')] = colour
168 |
169 | etree.SubElement(
170 | parent,
171 | inkex.addNS('guide', 'sodipodi'),
172 | attribs)
173 |
174 |
175 | def delete_all_guides(document):
176 | # getting the parent's tag of the guides
177 | nv = document.xpath(
178 | '/svg:svg/sodipodi:namedview', namespaces=inkex.NSS)[0]
179 |
180 | # getting all the guides
181 | children = document.xpath('/svg:svg/sodipodi:namedview/sodipodi:guide',
182 | namespaces=inkex.NSS)
183 |
184 | # removing each guides
185 | for element in children:
186 | nv.remove(element)
187 |
188 |
189 | def draw_SVG_ellipse(rx, ry, cx, cy, style, parent):
190 |
191 | attribs = {
192 | 'style': str(inkex.Style(style)),
193 | inkex.addNS('cx', 'sodipodi'): str(cx),
194 | inkex.addNS('cy', 'sodipodi'): str(cy),
195 | inkex.addNS('rx', 'sodipodi'): str(rx),
196 | inkex.addNS('ry', 'sodipodi'): str(ry),
197 | inkex.addNS('type', 'sodipodi'): 'arc',
198 | }
199 |
200 | etree.SubElement(parent, inkex.addNS('path', 'svg'), attribs)
201 |
202 |
203 | def draw_SVG_rect(x, y, w, h, round, style, parent):
204 |
205 | attribs = {
206 | 'style': str(inkex.Style(style)),
207 | 'height': str(h),
208 | 'width': str(w),
209 | 'x': str(x),
210 | 'y': str(y)
211 | }
212 |
213 | if round:
214 | attribs['ry'] = str(round)
215 |
216 | etree.SubElement(parent, inkex.addNS('rect', 'svg'), attribs)
217 |
218 |
219 | def add_SVG_layer(parent, gid, label):
220 |
221 | layer = etree.SubElement(parent, 'g', {
222 | 'id': gid,
223 | inkex.addNS('groupmode', 'inkscape'): 'layer',
224 | inkex.addNS('label', 'inkscape'): label
225 | })
226 |
227 | return layer
228 |
229 |
230 | class LabelGuides(inkex.Effect):
231 |
232 | def __init__(self):
233 |
234 | inkex.Effect.__init__(self)
235 | self.arg_parser.add_argument(
236 | '--units', default="mm",
237 | help='The units to use for custom label sizing')
238 | self.arg_parser.add_argument(
239 | '--preset_tab', default='rrect',
240 | help='The preset section that is selected (other sections will be ignored)')
241 |
242 | # ROUNDED RECTANGLE PRESET OPTIONS
243 | self.arg_parser.add_argument(
244 | '--rrect_preset',default='L7167',
245 | help='Use the given rounded rectangle preset template')
246 | self.arg_parser.add_argument(
247 | '--rrect_radius', type=float, default=1,
248 | help='Rectangle corner radius')
249 |
250 | # RECTANGULAR PRESET OPTIONS
251 | self.arg_parser.add_argument(
252 | '--rect_preset', default='L7784',
253 | help='Use the given square-corner rectangle template')
254 | # CIRCULAR PRESET OPTIONS
255 | self.arg_parser.add_argument('--circ_preset', default='LP2_115R',
256 | help='Use the given circular template')
257 |
258 | # CUSTOM LABEL OPTIONS
259 | self.arg_parser.add_argument(
260 | '--margin_l', type=float, default=8.5,
261 | help='Left page margin')
262 | self.arg_parser.add_argument(
263 | '--margin_t', type=float, default=13,
264 | help='Top page margin')
265 | self.arg_parser.add_argument(
266 | '--size_x', type=float, default=37,
267 | help='Label X size')
268 | self.arg_parser.add_argument(
269 | '--size_y', type=float, default=37,
270 | help='Label Y size')
271 | self.arg_parser.add_argument(
272 | '--pitch_x', type=float, default=39,
273 | help='Label X pitch')
274 | self.arg_parser.add_argument(
275 | '--pitch_y', type=float, default=39,
276 | help='Label Y pitch')
277 | self.arg_parser.add_argument(
278 | '--count_x', type=int, default=5,
279 | help='Number of labels across')
280 | self.arg_parser.add_argument(
281 | '--count_y', type=int, default=7,
282 | help='Number of labels down')
283 | self.arg_parser.add_argument(
284 | '--shapes', default='rect',
285 | help='Label shapes to draw')
286 |
287 | # GENERAL DRAWING OPTIONS
288 | self.arg_parser.add_argument(
289 | '--delete_existing_guides', type=inkex.Boolean, default=False,
290 | help='Delete existing guides from document')
291 | self.arg_parser.add_argument(
292 | '--draw_edge_guides', type=inkex.Boolean, default=True,
293 | help='Draw guides at label edges')
294 | self.arg_parser.add_argument(
295 | '--draw_centre_guides', type=inkex.Boolean, default=True,
296 | help='Draw guides at label centres')
297 | self.arg_parser.add_argument(
298 | '--inset', type=float, default=5,
299 | help='Inset to use for inset guides')
300 | self.arg_parser.add_argument(
301 | '--draw_inset_guides', type=inkex.Boolean, default=True,
302 | help='Draw guides inset to label edges')
303 | self.arg_parser.add_argument(
304 | '--draw_shapes', type=inkex.Boolean, default=True,
305 | help='Draw label outline shapes')
306 | self.arg_parser.add_argument(
307 | '--shape_inset', default=5,
308 | help='Inset to use for inset shapes')
309 | self.arg_parser.add_argument(
310 | '--draw_inset_shapes', type=inkex.Boolean, default=True,
311 | help='Draw shapes inset in the label outline')
312 | self.arg_parser.add_argument(
313 | '--set_page_size', type=inkex.Boolean, default=True,
314 | help='Set page size (presets only)')
315 |
316 | def _to_uu(self, val, unit):
317 | """
318 | Transform a value in given units to User Units
319 | """
320 | return self.svg.unittouu(str(val) + unit)
321 |
322 | def _get_page_size(self, size):
323 | """
324 | Get a page size from a definition entry - can be in the form
325 | [x, y], or a string (one of ['a4'])
326 | """
327 |
328 | if isinstance(size, (list,)):
329 | # Explicit size
330 | return size
331 | elif size == "a4":
332 | return [210, 297]
333 |
334 | # Failed to find a useful size, None will inhibit setting the size
335 | return None
336 |
337 | def _set_SVG_page_size(self, document, x, y, unit):
338 | """
339 | Set the SVG page size to the given absolute size. The viewbox is
340 | also rescaled as needed to maintain the scale factor.
341 | """
342 |
343 | svg = document.getroot()
344 |
345 | # Re-calculate viewbox in terms of User Units
346 | new_uu_w = self._to_uu(x, unit)
347 | new_uu_h = self._to_uu(y, unit)
348 |
349 | # set SVG page size
350 | svg.attrib['width'] = str(x) + unit
351 | svg.attrib['height'] = str(y) + unit
352 |
353 | svg.attrib['viewBox'] = "0 0 %f %f" % (new_uu_w, new_uu_h)
354 |
355 | def _read_custom_options(self, options):
356 | """
357 | Read custom label geometry options and produce
358 | a dictionary of parameters for ingestion
359 | """
360 | unit = options.units
361 |
362 | custom_opts = {
363 | 'units': options.units,
364 | 'page_size': None,
365 | 'margin': {
366 | 'l': self._to_uu(options.margin_l, unit),
367 | 't': self._to_uu(options.margin_t, unit)
368 | },
369 | 'size': {
370 | 'x': self._to_uu(options.size_x, unit),
371 | 'y': self._to_uu(options.size_y, unit)
372 | },
373 | 'pitch': {
374 | 'x': self._to_uu(options.pitch_x, unit),
375 | 'y': self._to_uu(options.pitch_y, unit)
376 | },
377 | 'count': {
378 | 'x': options.count_x,
379 | 'y': options.count_y
380 | },
381 | 'shapes': options.shapes,
382 | 'corner_rad': None,
383 | }
384 |
385 | return custom_opts
386 |
387 | def _construct_preset_opts(self, preset_type, preset_id, options):
388 | """Construct an options object for a preset label template
389 | """
390 | preset = PRESETS[preset_id]
391 |
392 | unit = preset[1]
393 |
394 | opts = {
395 | 'units': unit,
396 | 'page_size': self._get_page_size(preset[2]),
397 | 'margin': {
398 | 'l': self._to_uu(preset[3], unit),
399 | 't': self._to_uu(preset[4], unit)
400 | },
401 | 'size': {
402 | 'x': self._to_uu(preset[5], unit),
403 | 'y': self._to_uu(preset[6], unit)
404 | },
405 | 'pitch': {
406 | 'x': self._to_uu(preset[7], unit),
407 | 'y': self._to_uu(preset[8], unit)
408 | },
409 | 'count': {
410 | 'x': preset[9],
411 | 'y': preset[10]
412 | },
413 | 'shapes': preset[11],
414 | 'corner_rad': None,
415 | }
416 |
417 | # add addtional options by preset type
418 | if preset_type == "rrect":
419 | opts["corner_rad"] = self._to_uu(options.rrect_radius, unit)
420 |
421 | return opts
422 |
423 | def _get_regular_guides(self, label_opts, inset):
424 | """
425 | Get the guides for a set of labels defined by a regular grid
426 |
427 | This is done so that irregular-grid presets can be defined if
428 | needed
429 | """
430 |
431 | guides = {'v': [], 'h': []}
432 |
433 | x = label_opts['margin']['l']
434 |
435 | # Vertical guides, left to right
436 | for x_idx in range(label_opts['count']['x']):
437 |
438 | l_pos = x + inset
439 | r_pos = x + label_opts['size']['x'] - inset
440 |
441 | guides['v'].extend([l_pos, r_pos])
442 |
443 | # Step over to next label
444 | x += label_opts['pitch']['x']
445 |
446 | # Horizontal guides, bottom to top
447 | y = label_opts['margin']['t']
448 |
449 | for y_idx in range(label_opts['count']['y']):
450 |
451 | t_pos = y + inset
452 | b_pos = y + label_opts['size']['y'] - inset
453 |
454 | guides['h'].extend([t_pos, b_pos])
455 |
456 | # Step over to next label
457 | y += label_opts['pitch']['y']
458 |
459 | return guides
460 |
461 | def _draw_label_guides(self, document, label_opts, inset, colour):
462 | """
463 | Draws label guides from a regular guide description object
464 | """
465 | # convert to UU
466 | inset = self._to_uu(inset, label_opts['units'])
467 |
468 | guides = self._get_regular_guides(label_opts, inset)
469 |
470 | # Get parent tag of the guides
471 | nv = self.svg.namedview
472 |
473 | # Draw vertical guides
474 | for g in guides['v']:
475 | add_SVG_guide(g, 0, 'vert', colour, nv)
476 |
477 | # Draw horizontal guides
478 | for g in guides['h']:
479 | add_SVG_guide(0, self.svg.viewbox_height - g, 'horz', colour, nv)
480 |
481 | def _draw_centre_guides(self, document, label_opts, colour):
482 | """
483 | Draw guides in the centre of labels defined by the given options
484 | """
485 |
486 | guides = self._get_regular_guides(label_opts, 0)
487 | nv = self.svg.namedview
488 |
489 | for g in range(0, len(guides['v']), 2):
490 | pos = (guides['v'][g] + guides['v'][g + 1]) / 2
491 | add_SVG_guide(pos, 0, 'vert', colour, nv)
492 |
493 | for g in range(0, len(guides['h']), 2):
494 | pos = (guides['h'][g] + guides['h'][g + 1]) / 2
495 | add_SVG_guide(0, self.svg.viewbox_height - pos, 'horz', colour, nv)
496 |
497 | def _draw_shapes(self, document, label_opts, inset):
498 | """
499 | Draw label shapes from a regular grid
500 | """
501 |
502 | style = {
503 | 'stroke': '#000000',
504 | 'stroke-width': self._to_uu(1, "px"),
505 | 'fill': "none"
506 | }
507 |
508 | inset = self._to_uu(inset, label_opts['units'])
509 |
510 | guides = self._get_regular_guides(label_opts, 0)
511 | shape = label_opts['shapes']
512 |
513 | shapeLayer = add_SVG_layer(
514 | self.document.getroot(),
515 | self.svg.get_unique_id("outlineLayer"),
516 | "Label outlines")
517 |
518 | # draw shapes between every set of two guides
519 | for xi in range(0, len(guides['v']), 2):
520 |
521 | for yi in range(0, len(guides['h']), 2):
522 |
523 | if shape == 'circle':
524 | cx = (guides['v'][xi] + guides['v'][xi + 1]) / 2
525 | cy = (guides['h'][yi] + guides['h'][yi + 1]) / 2
526 |
527 | rx = cx - guides['v'][xi] - inset
528 | ry = cy - guides['h'][yi] - inset
529 |
530 | draw_SVG_ellipse(rx, ry, cx, cy, style, shapeLayer)
531 |
532 | elif shape in ["rect", "rrect"]:
533 |
534 | x = guides['v'][xi] + inset
535 | w = guides['v'][xi + 1] - x - inset
536 |
537 | y = guides['h'][yi] + inset
538 | h = guides['h'][yi + 1] - y - inset
539 |
540 | rnd = self._to_uu(label_opts['corner_rad'],
541 | label_opts['units'])
542 |
543 | draw_SVG_rect(x, y, w, h, rnd, style, shapeLayer)
544 | def _set_page_size(self, document, label_opts):
545 | """
546 | Set the SVG page size from the given label template definition
547 | """
548 |
549 | size = label_opts['page_size']
550 | unit = label_opts['units']
551 |
552 | if size is not None:
553 | self._set_SVG_page_size(document, size[0], size[1], unit)
554 |
555 | def effect(self):
556 | """
557 | Perform the label template generation effect
558 | """
559 |
560 | preset_type = self.options.preset_tab.strip('"')
561 |
562 | if preset_type == "custom":
563 | # construct from parameters
564 | label_opts = self._read_custom_options(self.options)
565 | else:
566 | # construct from a preset
567 |
568 | # get the preset ID from the relevant enum entry
569 | preset_id = {
570 | "rrect": self.options.rrect_preset,
571 | "rect": self.options.rect_preset,
572 | "circ": self.options.circ_preset,
573 | }[preset_type]
574 |
575 | label_opts = self._construct_preset_opts(preset_type, preset_id,
576 | self.options)
577 |
578 | if self.options.delete_existing_guides:
579 | delete_all_guides(self.document)
580 |
581 | # Resize page first, otherwise guides won't be in the right places
582 | if self.options.set_page_size:
583 | self._set_page_size(self.document, label_opts)
584 |
585 | if self.options.draw_edge_guides:
586 | self._draw_label_guides(self.document, label_opts, 0,
587 | GUIDE_COLOURS['edge'])
588 |
589 | if self.options.draw_centre_guides:
590 | self._draw_centre_guides(self.document, label_opts,
591 | GUIDE_COLOURS['centre'])
592 |
593 | if self.options.draw_inset_guides and self.options.inset > 0.0:
594 | self._draw_label_guides(self.document, label_opts,
595 | self.options.inset,
596 | GUIDE_COLOURS['inset'])
597 |
598 | if self.options.draw_shapes:
599 | self._draw_shapes(self.document, label_opts, 0)
600 |
601 | if self.options.draw_inset_shapes:
602 | self._draw_shapes(self.document, label_opts,
603 | self.options.shape_inset)
604 |
605 | if __name__ == '__main__':
606 | LabelGuides().run()
607 |
--------------------------------------------------------------------------------