├── requirements.txt ├── .gitignore ├── get_bounding_boxes.js ├── Makefile ├── README.md └── extract_icons.py /requirements.txt: -------------------------------------------------------------------------------- 1 | lxml 2 | cssselect 3 | pycairo 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | .DS_Store 3 | __MACOSX 4 | rendered 5 | extracted 6 | bounding_boxes.json 7 | glyphicons.svg 8 | glyphicons@*.svg 9 | -------------------------------------------------------------------------------- /get_bounding_boxes.js: -------------------------------------------------------------------------------- 1 | var page = require('webpage').create(), 2 | system = require('system'); 3 | 4 | page.open(system.args[1], function (status) { 5 | if (status !== 'success') { console.log('FAIL'); phantom.exit(); return; } 6 | var boxes = page.evaluate(function () { 7 | var paths = document.querySelectorAll('#glyphicons > g'); 8 | var i, boxes = []; 9 | for (i = 0; i < paths.length; i++) { 10 | var bbox = paths[i].getBBox(), 11 | bboxObj = {x: bbox.x, y: bbox.y, 12 | width: bbox.width, height: bbox.height}; 13 | boxes.push([paths[i].id, bboxObj]); 14 | } 15 | return boxes; 16 | }); 17 | console.log(JSON.stringify(boxes)); 18 | phantom.exit(); 19 | }); 20 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | PARALLEL = 4 2 | 3 | # This is where py2cairo was installed on my Mac; YMMV. 4 | export PYTHONPATH:=/usr/local/lib/python2.7/site-packages:${PYTHONPATH} 5 | 6 | # Default target: this will extract all individual SVGs and render them as 7 | # individual PDFs. 8 | render-all: extracted/4square.svg 9 | ls extracted/*.svg | sed 's/^extracted/rendered/;s/svg$$/pdf/' | xargs -P $(PARALLEL) $(MAKE) 10 | 11 | clean: 12 | rm -Rf extracted rendered bounding_boxes.json 13 | 14 | glyphicons.pdf: glyphicons.svg 15 | cairosvg glyphicons.svg --format=pdf --dpi=300 --output=glyphicons.pdf 16 | 17 | bounding_boxes.json: get_bounding_boxes.js glyphicons.svg 18 | phantomjs get_bounding_boxes.js glyphicons.svg > bounding_boxes.json 19 | 20 | extracted: 21 | mkdir -p extracted 22 | 23 | extracted/4square.svg: extracted bounding_boxes.json 24 | ./extract_icons.py glyphicons.svg bounding_boxes.json extracted 25 | 26 | rendered: 27 | mkdir -p rendered 28 | 29 | rendered/%.pdf: extracted/%.svg rendered 30 | cairosvg $< --format=pdf --output=$@ 31 | 32 | .PHONY: render-all clean 33 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # glyphicon splitter 2 | 3 | I needed individual [Glyphicons][] in vector format for an upcoming 4 | presentation, so I built a little toolchain with Python, PhantomJS and Make to 5 | extract individual icons from the `glyphicons.svg` file you get when you [buy 6 | the ‘PRO’ package][full-glyphicons]. 7 | 8 | [glyphicons]: http://glyphicons.com/ 9 | [full-glyphicons]: http://pul.ly/b/12171 10 | 11 | 12 | ## instructions 13 | 14 | You need Python, Cairo, PhantomJS and Make installed on your system. I ran this 15 | on a Mac; your mileage may vary on other operating systems. 16 | 17 | Install the necessary Python dependencies: 18 | 19 | pip install -r requirements.txt 20 | 21 | This will install lxml, cssselect and PyCairo. On my computer (running OS X 22 | Mountain Lion), I actually installed `py2cairo` from Homebrew, and pointed to 23 | it from the `PYTHONPATH` variable (see the `Makefile`). The `cairosvg` binary 24 | from pycairo must be on your `PATH` for this to work, too. 25 | 26 | You then need to take the `glyphicons.svg` file from 27 | `glyphicons_pro/glyphicons/svg` (which you can download when you buy it) and 28 | move/copy it into this directory. It will only be read, not written, so you can 29 | even symlink it. 30 | 31 | The next step is to simply run `make`; this will leave individual SVGs in the 32 | `extracted/` directory (because individual SVGs have been extracted from the 33 | large SVG file) and individual PDFs in the `rendered/` directory (because those 34 | PDFs were rendered from the extracted SVGs). Enjoy, and pull requests/bug 35 | reports are welcome! 36 | 37 | 38 | ## (un)license 39 | 40 | **N.B.**: The Glyphicon icons themselves are released under their own license; 41 | this covers the software I've written and added to this repo **only**. 42 | 43 | This is free and unencumbered software released into the public domain. 44 | 45 | Anyone is free to copy, modify, publish, use, compile, sell, or 46 | distribute this software, either in source code form or as a compiled 47 | binary, for any purpose, commercial or non-commercial, and by any 48 | means. 49 | 50 | In jurisdictions that recognize copyright laws, the author or authors 51 | of this software dedicate any and all copyright interest in the 52 | software to the public domain. We make this dedication for the benefit 53 | of the public at large and to the detriment of our heirs and 54 | successors. We intend this dedication to be an overt act of 55 | relinquishment in perpetuity of all present and future rights to this 56 | software under copyright law. 57 | 58 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 59 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 60 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 61 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 62 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 63 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 64 | OTHER DEALINGS IN THE SOFTWARE. 65 | 66 | For more information, please refer to 67 | -------------------------------------------------------------------------------- /extract_icons.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | import argparse 5 | import collections 6 | import json 7 | import os 8 | import re 9 | 10 | import lxml.cssselect 11 | import lxml.etree 12 | 13 | 14 | NS = { 15 | 'svg': 'http://www.w3.org/2000/svg' 16 | } 17 | SVG_DOCTYPE = '' 18 | 19 | 20 | parser = argparse.ArgumentParser() 21 | parser.add_argument('svg_source', 22 | help="The glyphicons.svg file to extract the icons from") 23 | parser.add_argument('bounding_boxes', 24 | help="A JSON file with bounding box values for each glyphicon (generated by get_bounding_boxes.js)") 25 | parser.add_argument('extraction_dir', 26 | help="The directory to which icons should be extracted") 27 | 28 | 29 | def make_pretty_id_mapping(glyphicons): 30 | # First pass is a simple 'prettification', but this is not guaranteed to give unique results. 31 | def prettify_id(g_id): 32 | pretty_id = g_id.lower().replace('_x34_', '4').replace('_x5f_', '-').replace('_', ' ').strip() 33 | terminating_number_match = re.search(r'[ \-]*([\d]+)$', pretty_id) 34 | if terminating_number_match: 35 | pretty_id = pretty_id[:terminating_number_match.start()] 36 | return pretty_id 37 | pretty_ids = dict((x.attrib['id'], prettify_id(x.attrib['id'])) for x in glyphicons) 38 | 39 | # Then we just disambiguate between g_ids that prettify to the same pretty_id. 40 | unambig_pretty_ids = pretty_ids.copy() 41 | pretty_id_counters = collections.defaultdict(int) 42 | for g_id, pretty_id in pretty_ids.items(): 43 | if pretty_id_counters[pretty_id] != 0: 44 | unambig_pretty_ids[g_id] = '%s-%d' % (pretty_id, pretty_id_counters[pretty_id]) 45 | pretty_id_counters[pretty_id] += 1 46 | return unambig_pretty_ids 47 | 48 | 49 | def main(): 50 | args = parser.parse_args() 51 | with open(args.svg_source) as source_file: 52 | svg = lxml.etree.parse(source_file) 53 | if not os.path.isdir(args.extraction_dir): 54 | os.makedirs(args.extraction_dir) 55 | with open(args.bounding_boxes) as bbox_file: 56 | bounding_boxes = dict(json.load(bbox_file)) 57 | 58 | glyphicons = lxml.cssselect.CSSSelector('svg|g#glyphicons > svg|g', 59 | namespaces=NS)(svg) 60 | 61 | pretty_ids = make_pretty_id_mapping(glyphicons) 62 | get_root_clone = lambda: lxml.etree.Element('svg', 63 | attrib=svg.getroot().attrib, 64 | nsmap=svg.getroot().nsmap) 65 | 66 | for glyphicon in glyphicons: 67 | pretty_id = pretty_ids[glyphicon.attrib['id']] 68 | bbox = bounding_boxes[glyphicon.attrib['id']] 69 | svg_root = get_root_clone() 70 | del svg_root.attrib['viewBox'] 71 | del svg_root.attrib['enable-background'] 72 | svg_root.attrib['width'] = repr(bbox['width']) 73 | svg_root.attrib['height'] = repr(bbox['height']) 74 | glyphicon.attrib['transform'] = 'translate(-%r,-%r)' % (bbox['x'], bbox['y']) 75 | svg_root.append(glyphicon) 76 | doc = lxml.etree.ElementTree(element=svg_root) 77 | doc_src = lxml.etree.tostring(doc, xml_declaration=True, doctype=SVG_DOCTYPE, encoding='utf-8') 78 | with open(os.path.join(args.extraction_dir, pretty_id + '.svg'), 'wb') as doc_fp: 79 | doc_fp.write(doc_src) 80 | 81 | 82 | if __name__ == '__main__': 83 | main() 84 | --------------------------------------------------------------------------------