diff --git a/render_freestyle_svg.py b/render_freestyle_svg.py
index 492672429d3184fc867151a45b36dd9e8bfcae5a..9328f71383417809700f910f5d9aa44fed85d582 100644
--- a/render_freestyle_svg.py
+++ b/render_freestyle_svg.py
@@ -37,21 +37,47 @@ import os
 
 import xml.etree.cElementTree as et
 
+from bpy.app.handlers import persistent
+from collections import OrderedDict
+from functools import partial
+from mathutils import Vector
+
 from freestyle.types import (
         StrokeShader,
         Interface0DIterator,
         Operators,
+        Nature,
+        StrokeVertex,
         )
-from freestyle.utils import getCurrentScene
-from freestyle.functions import GetShapeF1D, CurveMaterialF0D
+from freestyle.utils import (
+    getCurrentScene,
+    BoundingBox,
+    is_poly_clockwise,
+    StrokeCollector,
+    material_from_fedge,
+    get_object_name,
+    )
+from freestyle.functions import (
+    GetShapeF1D, 
+    CurveMaterialF0D,
+    )
 from freestyle.predicates import (
+        AndBP1D,
         AndUP1D,
         ContourUP1D,
-        SameShapeIdBP1D,
+        ExternalContourUP1D,
+        MaterialBP1D,
+        NotBP1D,
         NotUP1D,
+        OrBP1D,
+        OrUP1D,
+        pyNatureUP1D,
+        pyZBP1D,
+        pyZDiscontinuityBP1D,
         QuantitativeInvisibilityUP1D,
+        SameShapeIdBP1D,
+        TrueBP1D,
         TrueUP1D,
-        pyZBP1D,
         )
 from freestyle.chainingiterators import ChainPredicateIterator
 from parameter_editor import get_dashed_pattern
@@ -61,14 +87,12 @@ from bpy.props import (
         EnumProperty,
         PointerProperty,
         )
-from bpy.app.handlers import persistent
-from collections import OrderedDict
-from functools import partial
-from mathutils import Vector
 
 
 # use utf-8 here to keep ElementTree happy, end result is utf-16
 svg_primitive = """<?xml version="1.0" encoding="ascii" standalone="no"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
+  "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
 <svg xmlns="http://www.w3.org/2000/svg" version="1.1" width="{:d}" height="{:d}">
 </svg>"""
 
@@ -77,8 +101,11 @@ svg_primitive = """<?xml version="1.0" encoding="ascii" standalone="no"?>
 namespaces = {
     "inkscape": "http://www.inkscape.org/namespaces/inkscape",
     "svg": "http://www.w3.org/2000/svg",
+    "sodipodi": "http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd",
+    "": "http://www.w3.org/2000/svg",
     }
 
+
 # wrap XMLElem.find, so the namespaces don't need to be given as an argument
 def find_xml_elem(obj, search, namespaces, *, all=False):
     if all:
@@ -98,6 +125,7 @@ def render_width(scene):
 
 # stores the state of the render, used to differ between animation and single frame renders.
 class RenderState:
+
     # Note that this flag is set to False only after the first frame
     # has been written to file.
     is_preview = True
@@ -288,6 +316,7 @@ class SVGPathShader(StrokeShader):
         # return instance
         return cls(name, style, filepath, res_y, split_at_invisible, frame_current)
 
+
     @staticmethod
     def pathgen(stroke, path, height, split_at_invisible, f=lambda v: not v.attribute.visible):
         """Generator that creates SVG paths (as strings) from the current stroke """
@@ -340,10 +369,12 @@ class SVGPathShader(StrokeShader):
         id = "frame_{:04n}".format(self.frame_current)
 
         stroke_group = et.XML("<g/>")
-        stroke_group.attrib = {'xmlns:inkscape': namespaces["inkscape"],
-                               'inkscape:groupmode': 'layer',
-                               'id': 'strokes',
-                               'inkscape:label': 'strokes'}
+        stroke_group.attrib = {
+            'xmlns:inkscape': namespaces["inkscape"],
+            'inkscape:groupmode': 'layer',
+            'id': 'strokes',
+            'inkscape:label': 'strokes'
+            }
         # nest the structure
         stroke_group.extend(self.elements)
         if scene.svg_export.mode == 'ANIMATION':
@@ -360,30 +391,11 @@ class SVGPathShader(StrokeShader):
         tree.write(self.filepath, encoding='ascii', xml_declaration=True)
 
 
-class SVGFillShader(StrokeShader):
-    """Creates SVG fills from the current stroke set"""
+class SVGFillBuilder:
     def __init__(self, filepath, height, name):
-        StrokeShader.__init__(self)
-        # use an ordered dict to maintain input and z-order
-        self.shape_map = OrderedDict()
         self.filepath = filepath
-        self.h = height
         self._name = name
-
-    def shade(self, stroke, func=GetShapeF1D(), curvemat=CurveMaterialF0D()):
-        shape = func(stroke)[0].id.first
-        item = self.shape_map.get(shape)
-        if len(stroke) > 2:
-            if item is not None:
-                item[0].append(stroke)
-            else:
-                # the shape is not yet present, let's create it.
-                material = curvemat(Interface0DIterator(stroke))
-                *color, alpha = material.diffuse
-                self.shape_map[shape] = ([stroke], color, alpha)
-        # make the strokes of the second drawing invisible
-        for v in stroke:
-            v.attribute.visible = False
+        self.stroke_to_fill = partial(self.stroke_to_svg, height=height)
 
     @staticmethod
     def pathgen(vertices, path, height):
@@ -391,48 +403,94 @@ class SVGFillShader(StrokeShader):
         for point in vertices:
             x, y = point
             yield '{:.3f}, {:.3f} '.format(x, height - y)
-        yield 'z" />'  # closes the path; connects the current to the first point
+        yield ' z" />'  # closes the path; connects the current to the first point
 
-    def write(self):
+
+    @staticmethod
+    def get_merged_strokes(strokes):
+        def extend_stroke(stroke, vertices):
+            for vert in map(StrokeVertex, vertices):
+                stroke.insert_vertex(vert, stroke.stroke_vertices_end())
+            return stroke
+
+        base_strokes = tuple(stroke for stroke in strokes if not is_poly_clockwise(stroke))
+        merged_strokes = OrderedDict((s, list()) for s in base_strokes)
+
+        for stroke in filter(is_poly_clockwise, strokes):
+            for base in base_strokes:
+                # don't merge when diffuse colors don't match
+                if diffuse_from_stroke(stroke) != diffuse_from_stroke(stroke):
+                    continue
+                # only merge when the 'hole' is inside the base
+                elif stroke_inside_stroke(stroke, base):
+                    merged_strokes[base].append(stroke)
+                    break
+                # if it isn't a hole, it is likely that there are two strokes belonging
+                # to the same object separated by another object. let's try to join them
+                elif (get_object_name(base) == get_object_name(stroke) and 
+                      diffuse_from_stroke(stroke) == diffuse_from_stroke(stroke)):
+                    base = extend_stroke(base, (sv for sv in stroke))
+                    break
+            else:
+                # if all else fails, treat this stroke as a base stroke
+                merged_strokes.update({stroke:  []})
+        return merged_strokes
+
+
+    def stroke_to_svg(self, stroke, height, parameters=None):
+        if parameters is None:
+            *color, alpha = diffuse_from_stroke(stroke)
+            color = tuple(int(255 * c) for c in color)
+            parameters = {
+                'fill_rule': 'evenodd',
+                'stroke': 'none',
+                'fill-opacity': alpha,
+                'fill': 'rgb' + repr(color),
+            }
+        param_str = " ".join('{}="{}"'.format(k, v) for k, v in parameters.items())
+        path = '<path {} d=" M '.format(param_str)
+        vertices = (svert.point for svert in stroke)
+        s = "".join(self.pathgen(vertices, path, height))
+        result = et.XML(s)
+        return result
+
+    def create_fill_elements(self, strokes):
+        """Creates ElementTree objects by merging stroke objects together and turning them into SVG paths."""
+        merged_strokes = self.get_merged_strokes(strokes)
+        for k, v in merged_strokes.items():
+            base = self.stroke_to_fill(k)
+            fills = (self.stroke_to_fill(stroke).get("d") for stroke in v)
+            merged_points = " ".join(fills)
+            base.attrib['d'] += merged_points
+            yield base 
+
+    def write(self, strokes):
         """Write SVG data tree to file """
-        # initialize SVG
+
         tree = et.parse(self.filepath)
         root = tree.getroot()
-        name = self._name
         scene = bpy.context.scene
-        lineset_group = find_svg_elem(tree, ".//svg:g[@id='{}']".format(name))
-
-        # create XML elements from the acquired data
-        elems = []
-        path = '<path fill-rule="evenodd" stroke="none" fill-opacity="{}" fill="rgb({}, {}, {})"  d=" M '
-        for strokes, col, alpha in self.shape_map.values():
-            p = path.format(alpha, *(int(255 * c) for c in col))
-            for stroke in strokes:
-                elems.append(et.XML("".join(self.pathgen((sv.point for sv in stroke), p, self.h))))
-
-        if scene.svg_export.mode == 'ANIMATION':
-            # add the fills to the <g> of the current frame
-            frame_group = find_svg_elem(lineset_group, ".//svg:g[@id='frame_{:04n}']".format(scene.frame_current))
-            if frame_group is None:
-                # something has gone very wrong
-                raise RuntimeError("SVGFillShader: frame_group is None")
-
+        lineset_group = find_svg_elem(tree, ".//svg:g[@id='{}']".format(self._name))
+        if lineset_group is None:
+            print("searched for {}, but could not find a <g> with that id".format(self._name))
+            return
 
         # <g> for the fills of the current frame
         fill_group = et.XML('<g/>')
         fill_group.attrib = {
             'xmlns:inkscape': namespaces["inkscape"],
-           'inkscape:groupmode': 'layer',
-           'inkscape:label': 'fills',
-           'id': 'fills'
+            'inkscape:groupmode': 'layer',
+            'inkscape:label': 'fills',
+            'id': 'fills'
            }
 
-        fill_group.extend(reversed(elems))
+        fill_elements = self.create_fill_elements(strokes)
+        fill_group.extend(reversed(tuple(fill_elements)))
         if scene.svg_export.mode == 'ANIMATION':
+            # add the fills to the <g> of the current frame
+            frame_group = find_svg_elem(lineset_group, ".//svg:g[@id='frame_{:04n}']".format(scene.frame_current))
             frame_group.insert(0, fill_group)
         else:
-            # get the current lineset group. if it's None we're in trouble, so may as well error hard.
-            lineset_group = tree.find(".//svg:g[@id='{}']".format(name), namespaces=namespaces)
             lineset_group.insert(0, fill_group)
 
         # write SVG to file
@@ -440,6 +498,16 @@ class SVGFillShader(StrokeShader):
         tree.write(self.filepath, encoding='ascii', xml_declaration=True)
 
 
+def stroke_inside_stroke(a, b):
+    box_a = BoundingBox.from_sequence(svert.point for svert in a)
+    box_b = BoundingBox.from_sequence(svert.point for svert in b)
+    return box_a.inside(box_b)
+
+
+def diffuse_from_stroke(stroke, curvemat=CurveMaterialF0D()):
+    material = curvemat(Interface0DIterator(stroke))
+    return material.diffuse
+
 # - Callbacks - #
 class ParameterEditorCallback(object):
     """Object to store callbacks for the Parameter Editor in"""
@@ -452,11 +520,19 @@ class ParameterEditorCallback(object):
     def lineset_post(self, scene, layer, lineset):
         raise NotImplementedError()
 
+    @classmethod
+    def evaluate(cls, scene):
+        'Evaluates whether these callbacks should run'
+        return (
+            scene.render.use_freestyle 
+            and scene.svg_export.use_svg_export 
+            )
+
 
 class SVGPathShaderCallback(ParameterEditorCallback):
     @classmethod
     def modifier_post(cls, scene, layer, lineset):
-        if not (scene.render.use_freestyle and scene.svg_export.use_svg_export):
+        if not cls.evaluate(scene):
             return []
 
         split = scene.svg_export.split_at_invisible
@@ -467,9 +543,8 @@ class SVGPathShaderCallback(ParameterEditorCallback):
 
     @classmethod
     def lineset_post(cls, scene, *args):
-        if not (scene.render.use_freestyle and scene.svg_export.use_svg_export):
+        if not cls.evaluate(scene):
             return
-
         cls.shader.write()
 
 
@@ -481,19 +556,33 @@ class SVGFillShaderCallback(ParameterEditorCallback):
 
         # reset the stroke selection (but don't delete the already generated strokes)
         Operators.reset(delete_strokes=False)
-        # shape detection
-        upred = AndUP1D(QuantitativeInvisibilityUP1D(0), ContourUP1D())
+        # Unary Predicates: visible and correct edge nature
+        upred = AndUP1D(
+            QuantitativeInvisibilityUP1D(0), 
+            OrUP1D(ExternalContourUP1D(), 
+                   pyNatureUP1D(Nature.BORDER)),
+            )
+        # select the new edges
         Operators.select(upred)
-        # chain when the same shape and visible
-        bpred = SameShapeIdBP1D()
-        Operators.bidirectional_chain(ChainPredicateIterator(upred, bpred), NotUP1D(QuantitativeInvisibilityUP1D(0)))
-        # sort according to the distance from camera
-        Operators.sort(pyZBP1D())
-        # render and write fills
-        shader = SVGFillShader(create_path(scene), render_height(scene), layer.name + '_' + lineset.name)
-        Operators.create(TrueUP1D(), [shader, ])
+        # Binary Predicates
+        bpred = AndBP1D(
+            MaterialBP1D(), 
+            NotBP1D(pyZDiscontinuityBP1D()),
+            )
+        bpred = OrBP1D(bpred, AndBP1D(NotBP1D(bpred), AndBP1D(SameShapeIdBP1D(), MaterialBP1D())))
+        # chain the edges
+        Operators.bidirectional_chain(ChainPredicateIterator(upred, bpred))
+        # export SVG
+        collector = StrokeCollector()
+        Operators.create(TrueUP1D(), [collector])
+
+        builder = SVGFillBuilder(create_path(scene), render_height(scene), layer.name + '_' + lineset.name)
+        builder.write(collector.strokes)
+        # make strokes used for filling invisible
+        for stroke in collector.strokes:
+            for svert in stroke:
+                svert.attribute.visible = False
 
-        shader.write()
 
 
 def indent_xml(elem, level=0, indentsize=4):
@@ -512,6 +601,12 @@ def indent_xml(elem, level=0, indentsize=4):
         elem.tail = i
 
 
+def register_namespaces(namespaces=namespaces):
+    for name, url in namespaces.items():
+        if name != 'svg': # creates invalid xml
+            et.register_namespace(name, url)
+
+
 classes = (
     SVGExporterPanel,
     SVGExport,
@@ -536,9 +631,7 @@ def register():
     parameter_editor.callbacks_lineset_post.append(SVGFillShaderCallback.lineset_post)
 
     # register namespaces
-    et.register_namespace("", "http://www.w3.org/2000/svg")
-    et.register_namespace("inkscape", "http://www.inkscape.org/namespaces/inkscape")
-    et.register_namespace("sodipodi", "http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd")
+    register_namespaces()
 
 
 def unregister():