Skip to content
Snippets Groups Projects
Commit e37e57b6 authored by ezsh's avatar ezsh
Browse files

Update vector art split scripts

1. Repair identation (my python refused to execute them as they were).
2. Teach normilize_svg to remove explicit "style=fill:" attributes from
elements, controlled by color classes like skin, hair, etc.
3. vector_layer_split moved from inserting vars with sigil for SugarCube
to using "data-transformation" attribute.
4. vector_layer_split output for the 'tw' format became an SVG too, but
with special attributes.
parent 8825fe6d
No related branches found
No related tags found
No related merge requests found
...@@ -17,71 +17,95 @@ python3 inkscape_svg_fixup.py vector_source.svg ...@@ -17,71 +17,95 @@ python3 inkscape_svg_fixup.py vector_source.svg
import lxml.etree as etree import lxml.etree as etree
import sys import sys
import re
color_classes = {
'skin', 'head', 'torso', 'boob', 'penis', 'scrotum', 'belly',
'areola', 'bellybutton', 'labia', 'hair', 'pubic_hair', 'underarm_hair',
'eyebrow_hair', 'shoe', 'shoe_shadow', 'smart_piercing', 'steel_piercing',
'steel_chastity', 'outfit_base', 'gag', 'shadow', 'glasses', 'eye', 'sclera'
}
def fix(tree): def fix(tree):
# know namespaces # know namespaces
ns = { ns = {
'svg' : 'http://www.w3.org/2000/svg', 'svg': 'http://www.w3.org/2000/svg',
'inkscape' : 'http://www.inkscape.org/namespaces/inkscape' 'inkscape': 'http://www.inkscape.org/namespaces/inkscape'
} }
# find document global style definition # find document global style definition
# mangle it and interpret as python dictionary # mangle it and interpret as python dictionary
style_element = tree.find('./svg:style',namespaces=ns) style_element = tree.find('./svg:style', namespaces=ns)
style_definitions = style_element.text style_definitions = style_element.text
pythonic_style_definitions = '{'+style_definitions.\ pythonic_style_definitions = '{'+style_definitions.\
replace('\t.','"').replace('{','":"').replace('}','",').\ replace('\t.', '"').replace('{', '":"').replace('}', '",').\
replace('/*','#')+'}' replace('/*', '#')+'}'
styles = eval(pythonic_style_definitions) styles = eval(pythonic_style_definitions)
# go through all SVG elements # go through all SVG elements
for elem in tree.iter(): for elem in tree.iter():
if (elem.tag == etree.QName(ns['svg'], 'g')): if (elem.tag == etree.QName(ns['svg'], 'g')):
# compare inkscape label with group element ID # compare inkscape label with group element ID
l = elem.get(etree.QName(ns['inkscape'], 'label')) lbl = elem.get(etree.QName(ns['inkscape'], 'label'))
if l: if lbl:
i = elem.get('id') i = elem.get('id')
if (i != l): if (i != lbl):
print("Overwriting ID %s with Label %s..."%(i, l)) print("Overwriting ID %s with Label %s..." % (i, lbl))
elem.set('id', l) elem.set('id', lbl)
# clean styles (for easier manual merging) # clean styles (for easier manual merging)
style_string = elem.get('style') style_string = elem.get('style')
if style_string: if style_string:
split_styles = style_string.strip('; ').split(';') split_styles = style_string.strip('; ').split(';')
styles_pairs = [s.strip('; ').split(':') for s in split_styles] styles_pairs = [s.strip('; ').split(':') for s in split_styles]
filtered_pairs = [ (k,v) for k,v in styles_pairs if not ( filtered_pairs = [(k, v) for k, v in styles_pairs if not (
k.startswith('font-') or k.startswith('font-') or
k.startswith('text-') or k.startswith('text-') or
k.endswith('-spacing') or k.endswith('-spacing') or
k in ["line-height", " direction", " writing", " baseline-shift", " white-space", " writing-mode"] k in ["line-height", " direction", " writing", " baseline-shift", " white-space", " writing-mode"]
)] )]
split_styles = [':'.join(p) for p in filtered_pairs] split_styles = [':'.join(p) for p in filtered_pairs]
style_string = ';'.join(sorted(split_styles)) style_string = ';'.join(sorted(split_styles))
elem.attrib["style"] = style_string elem.attrib["style"] = style_string
# remove all style attributes offending class styles # remove all style attributes offending class styles
s = elem.get('style') s = elem.get('style')
c = elem.get('class') c = elem.get('class')
if (c and s): if (c and s):
s = s.lower() s = s.lower()
c = c.split(' ')[0] # regard main style only c = c.split(' ')[0] # regard main style only
cs = '' classes = c.split(' ')
if c in styles: hasColorClass = any(x in color_classes for x in classes)
cs = styles[c].strip('; ').lower() if hasColorClass:
if (c not in styles): s_new = re.sub('fill:#[0-9a-f]+;?', '', s)
print("Object id %s references unknown style class %s."%(i,c)) if s != s_new:
else: print("Explicit fill was removed from style string ({0}) for element with ID {1} "
if (cs != s.strip('; ')): "because its class ({2}) controls the fill color".format(s, i, c))
print("Style %s removed from object id %s differed from class %s style %s."%(s,i,c,cs)) s = s_new
del elem.attrib["style"] if s == 'style=""': # the style is empty now
del elem.attrib["style"]
continue
cs = ''
if c in styles:
cs = styles[c].strip('; ').lower()
if (c not in styles):
print("Object id %s references unknown style class %s." % (i, c))
else:
if (cs != s.strip('; ')):
print("Style %s removed from object id %s differed from class %s style %s." % (s, i, c, cs))
del elem.attrib["style"]
# remove explicit fill color if element class is one of the color_classes
if __name__ == "__main__": if __name__ == "__main__":
input_file = sys.argv[1] input_file = sys.argv[1]
tree = etree.parse(input_file) tree = etree.parse(input_file)
fix(tree) fix(tree)
# store SVG into file (input file is overwritten) # store SVG into file (input file is overwritten)
svg = etree.tostring(tree, pretty_print=True) svg = etree.tostring(tree, pretty_print=True)
with open(input_file, 'wb') as f: with open(input_file, 'wb') as f:
f.write('<?xml version="1.0" encoding="UTF-8" standalone="no"?>\n'.encode("utf-8")) f.write('<?xml version="1.0" encoding="UTF-8" standalone="no"?>\n'.encode("utf-8"))
f.write(svg) f.write(svg)
...@@ -16,109 +16,126 @@ import os ...@@ -16,109 +16,126 @@ import os
import copy import copy
import re import re
import normalize_svg import normalize_svg
import argparse
parser = argparse.ArgumentParser(
description='Application for splitting groups from one SVG file into separate files.')
parser.add_argument('-o', '--output', dest='output_dir', required=True,
help='output directory')
parser.add_argument('-f', '--format', dest='output_format',
choices=['svg', 'tw'], default='svg', help='output format.')
parser.add_argument('-p', '--prefix', dest='prefix', default='',
help='Prepend this string to result file names')
parser.add_argument('input_file', metavar='FILENAME', nargs='+',
help='Input SVG file with layers')
args = parser.parse_args()
output_format = args.output_format
output_directory = args.output_dir
def splitFile(inputFile):
tree = etree.parse(inputFile)
normalize_svg.fix(tree)
# prepare output template
template = copy.deepcopy(tree)
root = template.getroot()
# remove all svg root attributes except document size
for a in root.attrib:
if (a != "viewBox"):
del root.attrib[a]
# add placeholder for CSS class (needed for workaround for non HTML 5.1 compliant browser)
# if output_format == 'tw':
# root.attrib["class"] = "'+_art_display_class+'"
ns = {
'svg': 'http://www.w3.org/2000/svg',
'inkscape': 'http://www.inkscape.org/namespaces/inkscape',
'sodipodi': "http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd",
}
# remove all content, including metadata
# for twine output, style definitions are removed, too
defs = None
for e in root:
if (e.tag == etree.QName(ns['svg'], 'defs')):
defs = e
if (e.tag == etree.QName(ns['svg'], 'g') or
e.tag == etree.QName(ns['svg'], 'metadata') or
e.tag == etree.QName(ns['svg'], 'defs') or
e.tag == etree.QName(ns['sodipodi'], 'namedview') or
(output_format == 'tw' and e.tag == etree.QName(ns['svg'], 'style'))
):
root.remove(e)
# template preparation finished
# prepare regex for later use
regex_xmlns = re.compile(' xmlns[^ ]+')
regex_space = re.compile(r'[>]\s+[<]')
# find all groups
layers = tree.xpath('//svg:g', namespaces=ns)
for layer in layers:
i = layer.get('id')
if ( # disregard non-content groups
i.endswith("_") or # manually suppressed with underscore
i.startswith("XMLID") or # Illustrator generated group
i.startswith("g") # Inkscape generated group
):
continue
# create new canvas
output = copy.deepcopy(template)
# copy all shapes into template
canvas = output.getroot()
for e in layer:
canvas.append(e)
# represent template as SVG (binary string)
svg = etree.tostring(output, pretty_print=False)
# poor man's conditional defs insertion
# TODO: extract only referenced defs (filters, gradients, ...)
# TODO: detect necessity by traversing the elements. do not stupidly search in the string representation
if ("filter:" in svg.decode('utf-8')):
# it seems there is a filter referenced in the generated SVG, re-insert defs from main document
canvas.insert(0, defs)
# re-generate output
svg = etree.tostring(output, pretty_print=False)
if (output_format == 'tw'):
# remove unnecessary attributes
# TODO: never generate unnecessary attributes in the first place
svg = svg.decode('utf-8')
svg = regex_xmlns.sub('', svg)
svg = svg.replace(' inkscape:connector-curvature="0"', '') # this just saves space
svg = svg.replace('\n', '').replace('\r', '') # print cannot be multi-line
svg = regex_space.sub('><', svg) # remove indentation
svg = svg.replace('svg:', '') # svg namespace was removed
if ("Boob" in i): # internal groups are used for scaling
svg = svg.replace('<g ', '<g data-transform="boob" ') # boob art uses the boob scaling
elif ("Belly" in i):
svg = svg.replace('<g ', '<g data-transform="belly" ') # belly art uses the belly scaling
else:
svg = svg.replace('<g ', '<g data-transform="art" ') # otherwise use default scaling
if (not svg.endswith('\n')):
svg += '\n'
svg = svg.encode('utf-8')
# save SVG string to file
i = layer.get('id')
output_path = os.path.join(output_directory, "{0}{1}.svg".format(args.prefix, i))
with open(output_path, 'wb') as f:
if (output_format == 'svg'):
# Header for normal SVG (XML)
f.write('<?xml version="1.0" encoding="UTF-8" standalone="no"?>\n'.encode("utf-8"))
f.write(svg)
elif (output_format == 'tw'):
f.write(svg)
input_file = sys.argv[1]
output_format = sys.argv[2]
output_directory = sys.argv[3]
if not os.path.exists(output_directory): if not os.path.exists(output_directory):
os.makedirs(output_directory) os.makedirs(output_directory)
ns = { for f in args.input_file:
'svg' : 'http://www.w3.org/2000/svg', splitFile(f)
'inkscape' : 'http://www.inkscape.org/namespaces/inkscape',
'sodipodi':"http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd",
}
tree = etree.parse(input_file)
normalize_svg.fix(tree)
# prepare output template
template = copy.deepcopy(tree)
root = template.getroot()
# remove all svg root attributes except document size
for a in root.attrib:
if (a != "viewBox"):
del root.attrib[a]
# add placeholder for CSS class (needed for workaround for non HTML 5.1 compliant browser)
if output_format == 'tw':
root.attrib["class"] = "'+_art_display_class+'"
# remove all content, including metadata
# for twine output, style definitions are removed, too
defs = None
for e in root:
if (e.tag == etree.QName(ns['svg'], 'defs')):
defs = e
if (e.tag == etree.QName(ns['svg'], 'g') or
e.tag == etree.QName(ns['svg'], 'metadata') or
e.tag == etree.QName(ns['svg'], 'defs') or
e.tag == etree.QName(ns['sodipodi'], 'namedview') or
(output_format == 'tw' and e.tag == etree.QName(ns['svg'], 'style'))
):
root.remove(e)
# template preparation finished
# prepare regex for later use
regex_xmlns = re.compile(' xmlns[^ ]+',)
regex_space = re.compile('[>][ ]+[<]',)
# find all groups
layers = tree.xpath('//svg:g',namespaces=ns)
for layer in layers:
i = layer.get('id')
if ( # disregard non-content groups
i.endswith("_") or # manually suppressed with underscore
i.startswith("XMLID") or # Illustrator generated group
i.startswith("g") # Inkscape generated group
):
continue
# create new canvas
output = copy.deepcopy(template)
# copy all shapes into template
canvas = output.getroot()
for e in layer:
canvas.append(e)
# represent template as SVG (binary string)
svg = etree.tostring(output, pretty_print=False)
# poor man's conditional defs insertion
# TODO: extract only referenced defs (filters, gradients, ...)
# TODO: detect necessity by traversing the elements. do not stupidly search in the string representation
if ("filter:" in svg.decode('utf-8')):
# it seems there is a filter referenced in the generated SVG, re-insert defs from main document
canvas.insert(0,defs)
# re-generate output
svg = etree.tostring(output, pretty_print=False)
if (output_format == 'tw'):
# remove unnecessary attributes
# TODO: never generate unnecessary attributes in the first place
svg = svg.decode('utf-8')
svg = regex_xmlns.sub('',svg)
svg = svg.replace(' inkscape:connector-curvature="0"','') # this just saves space
svg = svg.replace('\n','').replace('\r','') # print cannot be multi-line
svg = regex_space.sub('><',svg) # remove indentation
svg = svg.replace('svg:','') # svg namespace was removed
if ("Boob" in i): # internal groups are used for scaling
svg = svg.replace('<g ','<g transform="\'+_artTransformBoob+\'"') # boob art uses the boob scaling
elif ("Belly" in i):
svg = svg.replace('<g ','<g transform="\'+_artTransformBelly+\'"') # belly art uses the belly scaling
else:
svg = svg.replace('<g ','<g transform="\'+_art_transform+\'"') # otherwise use default scaling
svg = svg.encode('utf-8')
# save SVG string to file
i = layer.get('id')
output_path = os.path.join(output_directory,i+'.'+output_format)
with open(output_path, 'wb') as f:
if (output_format == 'svg'):
# Header for normal SVG (XML)
f.write('<?xml version="1.0" encoding="UTF-8" standalone="no"?>\n'.encode("utf-8"))
f.write(svg)
elif (output_format == 'tw'):
# Header for SVG in Twine file (SugarCube print statement)
f.write((':: Art_Vector_%s [nobr]\n\n'%(i)).encode("utf-8"))
f.write("<<print '<html>".encode("utf-8"))
f.write(svg)
f.write("</html>' >>".encode("utf-8"))
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment