├── 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 | ![labels_output_demo](doc/label_output_demo.png) 15 | 16 | And the options dialog: 17 | 18 | ![labels_options_demo](doc/label_options_demo.png) 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 | --------------------------------------------------------------------------------