diff --git a/io_export_paper_model.py b/io_export_paper_model.py
index f44038f10400723ad5e533c3a9b666ed7dcfceb7..5f8e70f5906e8b8f19130bf90d66ac998186a3b1 100644
--- a/io_export_paper_model.py
+++ b/io_export_paper_model.py
@@ -1,6 +1,6 @@
 # -*- coding: utf-8 -*-
 # This script is Free software. Please share and reuse.
-# ♡2010-2019 Adam Dominec <adominec@gmail.com>
+# ♡2010-2020 Adam Dominec <adominec@gmail.com>
 
 ## Code structure
 # This file consists of several components, in this order:
@@ -12,8 +12,8 @@
 bl_info = {
     "name": "Export Paper Model",
     "author": "Addam Dominec",
-    "version": (1, 1),
-    "blender": (2, 80, 0),
+    "version": (1, 2),
+    "blender": (2, 83, 0),
     "location": "File > Export > Paper Model",
     "warning": "",
     "description": "Export printable net of the active mesh",
@@ -22,21 +22,16 @@ bl_info = {
 }
 
 # Task: split into four files (SVG and PDF separately)
-    # does any portion of baking belong into the export module?
-    # sketch out the code for GCODE and two-sided export
+# * does any portion of baking belong into the export module?
+# * sketch out the code for GCODE and two-sided export
 
 # TODO:
-# sanitize the constructors Edge, Face, UVFace so that they don't edit their parent object
-# The Exporter classes should take parameters as a whole pack, and parse it themselves
-# remember objects selected before baking (except selected to active)
-# add 'estimated number of pages' to the export UI
 # QuickSweepline is very much broken -- it throws GeometryError for all nets > ~15 faces
 # rotate islands to minimize area -- and change that only if necessary to fill the page size
-# Sticker.vertices should be of type Vector
 
 # check conflicts in island naming and either:
-#  * append a number to the conflicting names or
-#  * enumerate faces uniquely within all islands of the same name (requires a check that both label and abbr. equals)
+# * append a number to the conflicting names or
+# * enumerate faces uniquely within all islands of the same name (requires a check that both label and abbr. equals)
 
 import bpy
 import bl_operators
@@ -127,7 +122,6 @@ def cage_fit(points, aspect):
             rot_polygon = [rot @ p for p in polygon]
             left, right = [fn(rot_polygon, key=lambda p: p.to_tuple()) for fn in (min, max)]
             bottom, top = [fn(rot_polygon, key=lambda p: p.yx.to_tuple()) for fn in (min, max)]
-            #print(f"{rot_polygon.index(left)}-{rot_polygon.index(right)}, {rot_polygon.index(bottom)}-{rot_polygon.index(top)}")
             horz, vert = right - left, top - bottom
             # solve (rot * a).y == (rot * b).y
             yield max(aspect * horz.x, vert.y), sinx, cosx
@@ -143,9 +137,7 @@ def cage_fit(points, aspect):
             rot = M.Matrix(((cosy, -siny), (siny, cosy)))
             for p in rot_polygon:
                 p[:] = rot @ p  # note: this also modifies left, right, bottom, top
-            #print(f"solve {aspect * (right - left).x} == {(top - bottom).y} with aspect = {aspect}")
             if left.x < right.x and bottom.y < top.y and all(left.x <= p.x <= right.x and bottom.y <= p.y <= top.y for p in rot_polygon):
-                #print(f"yield {max(aspect * (right - left).x, (top - bottom).y)}")
                 yield max(aspect * (right - left).x, (top - bottom).y), sinx*cosy + cosx*siny, cosx*cosy - sinx*siny
     polygon = [points[i] for i in M.geometry.convex_hull_2d(points)]
     height, sinx, cosx = min(guesses(polygon))
@@ -166,6 +158,16 @@ def create_blank_image(image_name, dimensions, alpha=1):
     return image
 
 
+def store_rna_properties(*datablocks):
+    return [{prop.identifier: getattr(data, prop.identifier) for prop in data.rna_type.properties if not prop.is_readonly} for data in datablocks]
+
+
+def apply_rna_properties(memory, *datablocks):
+    for recall, data in zip(memory, datablocks):
+        for key, value in recall.items():
+            setattr(data, key, value)
+
+
 class UnfoldError(ValueError):
     def mesh_select(self):
         if len(self.args) > 1:
@@ -217,7 +219,7 @@ class Unfolder:
         # Note about scale: input is directly in blender length
         # Mesh.scale_islands multiplies everything by a user-defined ratio
         # exporters (SVG or PDF) multiply everything by 1000 (output in millimeters)
-        Exporter = SVG if properties.file_format == 'SVG' else PDF
+        Exporter = Svg if properties.file_format == 'SVG' else Pdf
         filepath = properties.filepath
         extension = properties.file_format.lower()
         filepath = bpy.path.ensure_ext(filepath, "." + extension)
@@ -249,11 +251,9 @@ class Unfolder:
             sce = bpy.context.scene
             rd = sce.render
             bk = rd.bake
-            # TODO: do we really need all this recollection?
-            recall = rd.engine, sce.cycles.bake_type, sce.cycles.samples, bk.use_selected_to_active, bk.margin, bk.cage_extrusion, bk.use_cage, bk.use_clear
+            recall = store_rna_properties(rd, bk, sce.cycles)
             rd.engine = 'CYCLES'
-            recall_pass = {p: getattr(bk, f"use_pass_{p}") for p in ('ambient_occlusion', 'color', 'diffuse', 'direct', 'emit', 'glossy', 'indirect', 'transmission')}
-            for p in recall_pass:
+            for p in ('ambient_occlusion', 'color', 'diffuse', 'direct', 'emit', 'glossy', 'indirect', 'transmission'):
                 setattr(bk, f"use_pass_{p}", (properties.output_type != 'TEXTURE'))
             lookup = {'TEXTURE': 'DIFFUSE', 'AMBIENT_OCCLUSION': 'AO', 'RENDER': 'COMBINED', 'SELECTED_TO_ACTIVE': 'COMBINED'}
             sce.cycles.bake_type = lookup[properties.output_type]
@@ -276,13 +276,9 @@ class Unfolder:
             elif image_packing == 'ISLAND_EMBED':
                 self.mesh.save_separate_images(ppm, filepath, embed=Exporter.encode_image)
 
-            rd.engine, sce.cycles.bake_type, sce.cycles.samples, bk.use_selected_to_active, bk.margin, bk.cage_extrusion, bk.use_cage, bk.use_clear = recall
-            for p, v in recall_pass.items():
-                setattr(bk, f"use_pass_{p}", v)
+            apply_rna_properties(recall, rd, bk, sce.cycles)
 
-        exporter = Exporter(page_size, properties.style, properties.output_margin, (properties.output_type == 'NONE'), properties.angle_epsilon)
-        exporter.do_create_stickers = properties.do_create_stickers
-        exporter.text_size = properties.sticker_width
+        exporter = Exporter(properties)
         exporter.write(self.mesh, filepath)
 
 
@@ -336,8 +332,9 @@ class Mesh:
         if not (null_edges or null_faces or twisted_faces or inverted_scale):
             return True
         if inverted_scale:
-            raise UnfoldError("The object is flipped inside-out.\n"
-            "You can use Object -> Apply -> Scale to fix it. Export failed.")
+            raise UnfoldError(
+                "The object is flipped inside-out.\n"
+                "You can use Object -> Apply -> Scale to fix it. Export failed.")
         disease = [("Remove Doubles", null_edges or null_faces), ("Triangulate", twisted_faces)]
         cure = " and ".join(s for s, k in disease if k)
         raise UnfoldError(
@@ -514,14 +511,13 @@ class Mesh:
             angle, _ = cage_fit(points, (cage_size.y - title_height) / cage_size.x)
             rot = M.Matrix.Rotation(angle, 2)
             for point in points:
-                # note: we need an in-place operation, and Vector.rotate() seems to work for 3d vectors only
-                point[:] = rot @ point
+                point.rotate(rot)
             for marker in island.markers:
                 marker.rot = rot @ marker.rot
             bottom_left = M.Vector((min(v.x for v in points), min(v.y for v in points) - title_height))
-            #DEBUG
-            top_right = M.Vector((max(v.x for v in points), max(v.y for v in points) - title_height))
-            #print(f"fitted aspect: {(top_right.y - bottom_left.y) / (top_right.x - bottom_left.x)}")
+            # DEBUG
+            # top_right = M.Vector((max(v.x for v in points), max(v.y for v in points) - title_height))
+            # print(f"fitted aspect: {(top_right.y - bottom_left.y) / (top_right.x - bottom_left.x)}")
             for point in points:
                 point -= bottom_left
             island.bounding_box = M.Vector((max(v.x for v in points), max(v.y for v in points)))
@@ -666,7 +662,8 @@ class Mesh:
 
 class Edge:
     """Wrapper for BPy Edge"""
-    __slots__ = ('data', 'va', 'vb', 'main_faces', 'uvedges',
+    __slots__ = (
+        'data', 'va', 'vb', 'main_faces', 'uvedges',
         'vector', 'angle',
         'is_main_cut', 'force_cut', 'priority', 'freestyle')
 
@@ -689,10 +686,11 @@ class Edge:
 
     def choose_main_faces(self):
         """Choose two main faces that might get connected in an island"""
-        from itertools import combinations
-        loops = self.data.link_loops
+
         def score(pair):
             return abs(pair[0].face.normal.dot(pair[1].face.normal))
+
+        loops = self.data.link_loops
         if len(loops) == 2:
             self.main_faces = list(loops)
         elif len(loops) > 2:
@@ -709,7 +707,7 @@ class Edge:
             self.angle = -3  # just a very sharp angle
         else:
             s = normal_a.cross(normal_b).dot(self.vector.normalized())
-            s = max(min(s, 1.0), -1.0) # deal with rounding errors
+            s = max(min(s, 1.0), -1.0)  # deal with rounding errors
             self.angle = asin(s)
             if loop_a.link_loop_next.vert != loop_b.vert or loop_b.link_loop_next.vert != loop_a.vert:
                 self.angle = abs(self.angle)
@@ -741,7 +739,8 @@ class Edge:
 
 class Island:
     """Part of the net to be exported"""
-    __slots__ = ('mesh', 'faces', 'edges', 'vertices', 'fake_vertices', 'boundary', 'markers',
+    __slots__ = (
+        'mesh', 'faces', 'edges', 'vertices', 'fake_vertices', 'boundary', 'markers',
         'pos', 'bounding_box',
         'image_path', 'embedded_image',
         'number', 'label', 'abbreviation', 'title',
@@ -803,6 +802,7 @@ class Island:
         for loop, uvvertex in self.vertices.items():
             loop[tex].uv = uvvertex.co.x * scale_x, uvvertex.co.y * scale_y
 
+
 def join(uvedge_a, uvedge_b, size_limit=None, epsilon=1e-6):
     """
     Try to join other island on given edge
@@ -1081,7 +1081,7 @@ class Page:
 
     def __init__(self, num=1):
         self.islands = list()
-        self.name = "page{}".format(num)  # TODO delete me
+        self.name = "page{}".format(num)  # note: this is only used in svg files naming
         self.image_path = None
 
 
@@ -1098,7 +1098,8 @@ class UVEdge:
     """Edge in 2D"""
     # Every UVEdge is attached to only one UVFace
     # UVEdges are doubled as needed because they both have to point clockwise around their faces
-    __slots__ = ('va', 'vb', 'uvface', 'loop',
+    __slots__ = (
+        'va', 'vb', 'uvface', 'loop',
         'min', 'max', 'bottom', 'top',
         'neighbor_left', 'neighbor_right', 'sticker')
 
@@ -1172,7 +1173,7 @@ class Arrow:
 
 class Sticker:
     """Mark in the document: sticker tab"""
-    __slots__ = ('bounds', 'center', 'rot', 'text', 'width', 'vertices')
+    __slots__ = ('bounds', 'center', 'points', 'rot', 'text', 'width')
 
     def __init__(self, uvedge, default_width, index, other: UVEdge):
         """Sticker is directly attached to the given UVEdge"""
@@ -1217,12 +1218,12 @@ class Sticker:
         len_a = min(len_a, (edge.length * sin_b) / (sin_a * cos_b + sin_b * cos_a))
         len_b = 0 if sin_b == 0 else min(sticker_width / sin_b, (edge.length - len_a * cos_a) / cos_b)
 
-        v3 = UVVertex(second_vertex.co + M.Matrix(((cos_b, -sin_b), (sin_b, cos_b))) @ edge * len_b / edge.length)
-        v4 = UVVertex(first_vertex.co + M.Matrix(((-cos_a, -sin_a), (sin_a, -cos_a))) @ edge * len_a / edge.length)
-        if v3.co != v4.co:
-            self.vertices = [second_vertex, v3, v4, first_vertex]
+        v3 = second_vertex.co + M.Matrix(((cos_b, -sin_b), (sin_b, cos_b))) @ edge * len_b / edge.length
+        v4 = first_vertex.co + M.Matrix(((-cos_a, -sin_a), (sin_a, -cos_a))) @ edge * len_a / edge.length
+        if v3 != v4:
+            self.points = [second_vertex.co, v3, v4, first_vertex.co]
         else:
-            self.vertices = [second_vertex, v3, first_vertex]
+            self.points = [second_vertex.co, v3, first_vertex.co]
 
         sin, cos = edge.y / edge.length, edge.x / edge.length
         self.rot = M.Matrix(((cos, -sin), (sin, cos)))
@@ -1232,7 +1233,7 @@ class Sticker:
         else:
             self.text = index
         self.center = (uvedge.va.co + uvedge.vb.co) / 2 + self.rot @ M.Vector((0, self.width * 0.2))
-        self.bounds = [v3.co, v4.co, self.center] if v3.co != v4.co else [v3.co, self.center]
+        self.bounds = [v3, v4, self.center] if v3 != v4 else [v3, self.center]
 
 
 class NumberAlone:
@@ -1251,19 +1252,22 @@ class NumberAlone:
         self.bounds = [self.center]
 
 
-class SVG:
+def init_exporter(self, properties):
+    self.page_size = M.Vector((properties.output_size_x, properties.output_size_y))
+    self.style = properties.style
+    margin = properties.output_margin
+    self.margin = M.Vector((margin, margin))
+    self.pure_net = (properties.output_type == 'NONE')
+    self.do_create_stickers = properties.do_create_stickers
+    self.text_size = properties.sticker_width
+    self.angle_epsilon = properties.angle_epsilon
+
+
+class Svg:
     """Simple SVG exporter"""
 
-    def __init__(self, page_size: M.Vector, style, margin, pure_net=True, angle_epsilon=0.01):
-        """Initialize document settings.
-        page_size: document dimensions in meters
-        pure_net: if True, do not use image"""
-        self.page_size = page_size
-        self.pure_net = pure_net
-        self.style = style
-        self.margin = margin
-        self.text_size = 12
-        self.angle_epsilon = angle_epsilon
+    def __init__(self, properties):
+        init_exporter(self, properties)
 
     @classmethod
     def encode_image(cls, bpy_image):
@@ -1275,10 +1279,9 @@ class SVG:
             bpy_image.save()
             return base64.encodebytes(open(filename, "rb").read()).decode('ascii')
 
-    def format_vertex(self, vector, pos=M.Vector((0, 0))):
+    def format_vertex(self, vector):
         """Return a string with both coordinates of the given vertex."""
-        x, y = vector + pos
-        return "{:.6f} {:.6f}".format((x + self.margin) * 1000, (self.page_size.y - y - self.margin) * 1000)
+        return "{:.6f} {:.6f}".format((vector.x + self.margin.x) * 1000, (self.page_size.y - vector.y - self.margin.y) * 1000)
 
     def write(self, mesh, filename):
         """Write data to a file given by its name."""
@@ -1306,7 +1309,7 @@ class SVG:
         styleargs = {
             name: format_color(getattr(self.style, name)) for name in (
                 "outer_color", "outbg_color", "convex_color", "concave_color", "freestyle_color",
-                "inbg_color", "sticker_fill", "text_color")}
+                "inbg_color", "sticker_color", "text_color")}
         styleargs.update({
             name: format_style[getattr(self.style, name)] for name in
             ("outer_style", "convex_style", "concave_style", "freestyle_style")})
@@ -1315,7 +1318,7 @@ class SVG:
                 ("outer_alpha", "outer_color"), ("outbg_alpha", "outbg_color"),
                 ("convex_alpha", "convex_color"), ("concave_alpha", "concave_color"),
                 ("freestyle_alpha", "freestyle_color"),
-                ("inbg_alpha", "inbg_color"), ("sticker_alpha", "sticker_fill"),
+                ("inbg_alpha", "inbg_color"), ("sticker_alpha", "sticker_color"),
                 ("text_alpha", "text_color"))})
         styleargs.update({
             name: getattr(self.style, name) * self.style.line_width * 1000 for name in
@@ -1328,9 +1331,9 @@ class SVG:
                 if page.image_path:
                     print(
                         self.image_linked_tag.format(
-                            pos="{0:.6f} {0:.6f}".format(self.margin*1000),
-                            width=(self.page_size.x - 2 * self.margin)*1000,
-                            height=(self.page_size.y - 2 * self.margin)*1000,
+                            pos="{0:.6f} {0:.6f}".format(self.margin.x*1000),
+                            width=(self.page_size.x - 2 * self.margin.x)*1000,
+                            height=(self.page_size.y - 2 * self.margin.y)*1000,
                             path=path_convert(page.image_path)),
                         file=f)
                 if len(page.islands) > 1:
@@ -1359,20 +1362,20 @@ class SVG:
                         print(
                             self.text_tag.format(
                                 size=1000 * self.text_size,
-                                x=1000 * (island.bounding_box.x*0.5 + island.pos.x + self.margin),
-                                y=1000 * (self.page_size.y - island.pos.y - self.margin - 0.2 * self.text_size),
+                                x=1000 * (island.bounding_box.x*0.5 + island.pos.x + self.margin.x),
+                                y=1000 * (self.page_size.y - island.pos.y - self.margin.y - 0.2 * self.text_size),
                                 label=island.title),
                             file=f)
 
-                    data_markers, data_stickerfill, data_outer, data_convex, data_concave, data_freestyle = (list() for i in range(6))
+                    data_markers, data_stickerfill = list(), list()
                     for marker in island.markers:
                         if isinstance(marker, Sticker):
                             data_stickerfill.append("M {} Z".format(
-                                line_through(self.format_vertex(vertex.co, island.pos) for vertex in marker.vertices)))
+                                line_through(self.format_vertex(co + island.pos) for co in marker.points)))
                             if marker.text:
                                 data_markers.append(self.text_transformed_tag.format(
                                     label=marker.text,
-                                    pos=self.format_vertex(marker.center, island.pos),
+                                    pos=self.format_vertex(marker.center + island.pos),
                                     mat=format_matrix(marker.rot),
                                     size=marker.width * 1000))
                         elif isinstance(marker, Arrow):
@@ -1380,29 +1383,30 @@ class SVG:
                             position = marker.center + marker.size * marker.rot @ M.Vector((0, -0.9))
                             data_markers.append(self.arrow_marker_tag.format(
                                 index=marker.text,
-                                arrow_pos=self.format_vertex(marker.center, island.pos),
+                                arrow_pos=self.format_vertex(marker.center + island.pos),
                                 scale=size,
-                                pos=self.format_vertex(position, island.pos - marker.size*M.Vector((0, 0.4))),
+                                pos=self.format_vertex(position + island.pos - marker.size*M.Vector((0, 0.4))),
                                 mat=format_matrix(size * marker.rot)))
                         elif isinstance(marker, NumberAlone):
                             data_markers.append(self.text_transformed_tag.format(
                                 label=marker.text,
-                                pos=self.format_vertex(marker.center, island.pos),
+                                pos=self.format_vertex(marker.center + island.pos),
                                 mat=format_matrix(marker.rot),
                                 size=marker.size * 1000))
-                    if data_stickerfill and self.style.sticker_fill[3] > 0:
+                    if data_stickerfill and self.style.sticker_color[3] > 0:
                         print("<path class='sticker' d='", rows(data_stickerfill), "'/>", file=f)
 
+                    data_outer, data_convex, data_concave, data_freestyle = (list() for i in range(4))
                     outer_edges = set(island.boundary)
                     while outer_edges:
                         data_loop = list()
                         uvedge = outer_edges.pop()
                         while 1:
                             if uvedge.sticker:
-                                data_loop.extend(self.format_vertex(vertex.co, island.pos) for vertex in uvedge.sticker.vertices[1:])
+                                data_loop.extend(self.format_vertex(co + island.pos) for co in uvedge.sticker.points[1:])
                             else:
                                 vertex = uvedge.vb if uvedge.uvface.flipped else uvedge.va
-                                data_loop.append(self.format_vertex(vertex.co, island.pos))
+                                data_loop.append(self.format_vertex(vertex.co + island.pos))
                             uvedge = uvedge.neighbor_right
                             try:
                                 outer_edges.remove(uvedge)
@@ -1416,7 +1420,7 @@ class SVG:
                         if edge.is_cut(uvedge.uvface.face) and not uvedge.sticker:
                             continue
                         data_uvedge = "M {}".format(
-                            line_through(self.format_vertex(vertex.co, island.pos) for vertex in (uvedge.va, uvedge.vb)))
+                            line_through(self.format_vertex(v.co + island.pos) for v in (uvedge.va, uvedge.vb)))
                         if edge.freestyle:
                             data_freestyle.append(data_uvedge)
                         # each uvedge is in two opposite-oriented variants; we want to add each only once
@@ -1507,7 +1511,7 @@ class SVG:
         stroke-width: {inbg_width:.2}
     }}
     path.sticker {{
-        fill: {sticker_fill};
+        fill: {sticker_color};
         stroke: none;
         fill-opacity: {sticker_alpha:.2};
     }}
@@ -1526,7 +1530,7 @@ class SVG:
     </style>"""
 
 
-class PDF:
+class Pdf:
     """Simple PDF exporter"""
 
     mm_to_pt = 72 / 25.4
@@ -1536,16 +1540,34 @@ class PDF:
         667: '&ABEKPSVXY\x8a\x9fÀÁÂÃÄÅÈÉÊËÝÞ', 722: 'CDHNRUwÇÐÑÙÚÛÜ', 737: '©®', 778: 'GOQÒÓÔÕÖØ', 833: 'Mm¼½¾', 889: '%æ', 944: 'W\x9c', 1000: '\x85\x89\x8c\x97\x99Æ', 1015: '@', }
     character_width = {c: value for (value, chars) in character_width_packed.items() for c in chars}
 
-    def __init__(self, page_size: M.Vector, style, margin, pure_net=True, angle_epsilon=0.01):
-        self.page_size = page_size
-        self.style = style
-        self.margin = M.Vector((margin, margin))
-        self.pure_net = pure_net
-        self.angle_epsilon = angle_epsilon
+    def __init__(self, properties):
+        init_exporter(self, properties)
+        self.styles = dict()
 
     def text_width(self, text, scale=None):
         return (scale or self.text_size) * sum(self.character_width.get(c, 556) for c in text) / 1000
 
+    def styling(self, name, do_stroke=True):
+        s, m, l = (length * self.style.line_width * 1000 for length in (1, 4, 9))
+        format_style = {'SOLID': [], 'DOT': [s, m], 'DASH': [m, l], 'LONGDASH': [l, m], 'DASHDOT': [l, m, s, m]}
+        style, color, width = (getattr(self.style, f"{name}_{arg}", None) for arg in ("style", "color", "width"))
+        style = style or 'SOLID'
+        result = ["q"]
+        if do_stroke:
+            result += [
+                "[ " + " ".join("{:.3f}".format(num) for num in format_style[style]) + " ] 0 d",
+                "{0:.3f} {1:.3f} {2:.3f} RG".format(*color),
+                "{:.3f} w".format(self.style.line_width * 1000 * width),
+                ]
+        else:
+            result.append("{0:.3f} {1:.3f} {2:.3f} rg".format(*color))
+        if color[3] < 1:
+            style_name = "R{:03}".format(round(1000 * color[3]))
+            result.append("/{} gs".format(style_name))
+            if style_name not in self.styles:
+                self.styles[style_name] = {"CA": color[3], "ca": color[3]}
+        return result
+
     @classmethod
     def encode_image(cls, bpy_image):
         data = bytes(int(255 * px) for (i, px) in enumerate(bpy_image.pixels) if i % 4 != 3)
@@ -1557,10 +1579,12 @@ class PDF:
 
     def write(self, mesh, filename):
         def format_dict(obj, refs=tuple()):
-            return "<< " + "".join("/{} {}\n".format(key, format_value(value, refs)) for (key, value) in obj.items()) + ">>"
+            content = "".join("/{} {}\n".format(key, format_value(value, refs)) for (key, value) in obj.items())
+            return f"<< {content} >>"
 
         def line_through(seq):
-            return "".join("{0.x:.6f} {0.y:.6f} {1} ".format(1000*v.co, c) for (v, c) in zip(seq, chain("m", repeat("l"))))
+            fmt = "{0.x:.6f} {0.y:.6f} {1} ".format
+            return "".join(fmt(1000*co, cmd) for (co, cmd) in zip(seq, chain("m", repeat("l"))))
 
         def format_value(value, refs=tuple()):
             if value in refs:
@@ -1579,82 +1603,64 @@ class PDF:
                 return "/{}".format(value)  # this script can output only PDF names, no strings
 
         def write_object(index, obj, refs, f, stream=None):
-            byte_count = f.write("{} 0 obj\n".format(index))
+            byte_count = f.write("{} 0 obj\n".format(index).encode())
             if type(obj) is not dict:
                 stream, obj = obj, dict()
             elif "stream" in obj:
                 stream = obj.pop("stream")
             if stream:
-                if True or type(stream) is bytes:
-                    obj["Filter"] = ["ASCII85Decode", "FlateDecode"]
-                    stream = encode(stream)
+                obj["Filter"] = "FlateDecode"
+                stream = encode(stream)
                 obj["Length"] = len(stream)
-            byte_count += f.write(format_dict(obj, refs))
+            byte_count += f.write(format_dict(obj, refs).encode())
             if stream:
-                byte_count += f.write("\nstream\n")
+                byte_count += f.write(b"\nstream\n")
                 byte_count += f.write(stream)
-                byte_count += f.write("\nendstream")
-            return byte_count + f.write("\nendobj\n")
+                byte_count += f.write(b"\nendstream")
+            return byte_count + f.write(b"\nendobj\n")
 
         def encode(data):
-            from base64 import a85encode
             from zlib import compress
             if hasattr(data, "encode"):
                 data = data.encode()
-            return a85encode(compress(data), adobe=True, wrapcol=250)[2:].decode()
+            return compress(data)
 
         page_size_pt = 1000 * self.mm_to_pt * self.page_size
+        reset_style = ["Q"]  # graphic command for later use
         root = {"Type": "Pages", "MediaBox": [0, 0, page_size_pt.x, page_size_pt.y], "Kids": list()}
         catalog = {"Type": "Catalog", "Pages": root}
         font = {
             "Type": "Font", "Subtype": "Type1", "Name": "F1",
             "BaseFont": "Helvetica", "Encoding": "MacRomanEncoding"}
-
-        dl = [length * self.style.line_width * 1000 for length in (1, 4, 9)]
-        format_style = {
-            'SOLID': list(), 'DOT': [dl[0], dl[1]], 'DASH': [dl[1], dl[2]],
-            'LONGDASH': [dl[2], dl[1]], 'DASHDOT': [dl[2], dl[1], dl[0], dl[1]]}
-        styles = {
-            "Gtext": {"ca": self.style.text_color[3], "Font": [font, 1000 * self.text_size]},
-            "Gsticker": {"ca": self.style.sticker_fill[3]}}
-        for name in ("outer", "convex", "concave", "freestyle"):
-            gs = {
-                "LW": self.style.line_width * 1000 * getattr(self.style, name + "_width"),
-                "CA": getattr(self.style, name + "_color")[3],
-                "D": [format_style[getattr(self.style, name + "_style")], 0]}
-            styles["G" + name] = gs
-        for name in ("outbg", "inbg"):
-            gs = {
-                "LW": self.style.line_width * 1000 * getattr(self.style, name + "_width"),
-                "CA": getattr(self.style, name + "_color")[3],
-                "D": [format_style['SOLID'], 0]}
-            styles["G" + name] = gs
-
         objects = [root, catalog, font]
-        objects.extend(styles.values())
 
         for page in mesh.pages:
             commands = ["{0:.6f} 0 0 {0:.6f} 0 0 cm".format(self.mm_to_pt)]
-            resources = {"Font": {"F1": font}, "ExtGState": styles, "XObject": dict()}
+            resources = {"Font": {"F1": font}, "ExtGState": self.styles, "ProcSet": ["PDF"]}
+            if any(island.embedded_image for island in page.islands):
+                resources["XObject"] = dict()
+                resources["ProcSet"].append("ImageC")
             for island in page.islands:
                 commands.append("q 1 0 0 1 {0.x:.6f} {0.y:.6f} cm".format(1000*(self.margin + island.pos)))
                 if island.embedded_image:
-                    identifier = "Im{}".format(len(resources["XObject"]) + 1)
+                    identifier = "I{}".format(len(resources["XObject"]) + 1)
                     commands.append(self.command_image.format(1000 * island.bounding_box, identifier))
                     objects.append(island.embedded_image)
                     resources["XObject"][identifier] = island.embedded_image
 
                 if island.title:
+                    commands += self.styling("text", do_stroke=False)
                     commands.append(self.command_label.format(
                         size=1000*self.text_size,
                         x=500 * (island.bounding_box.x - self.text_width(island.title)),
                         y=1000 * 0.2 * self.text_size,
                         label=island.title))
+                    commands += reset_style
 
-                data_markers, data_stickerfill, data_outer, data_convex, data_concave, data_freestyle = (list() for i in range(6))
+                data_markers, data_stickerfill = list(), list()
                 for marker in island.markers:
                     if isinstance(marker, Sticker):
-                        data_stickerfill.append(line_through(marker.vertices) + "f")
+                        data_stickerfill.append(line_through(marker.points) + "f")
                         if marker.text:
                             data_markers.append(self.command_sticker.format(
                                 label=marker.text,
@@ -1678,16 +1684,17 @@ class PDF:
                             mat=marker.rot,
                             size=1000*marker.size))
 
+                data_outer, data_convex, data_concave, data_freestyle = (list() for i in range(4))
                 outer_edges = set(island.boundary)
                 while outer_edges:
                     data_loop = list()
                     uvedge = outer_edges.pop()
                     while 1:
                         if uvedge.sticker:
-                            data_loop.extend(uvedge.sticker.vertices[1:])
+                            data_loop.extend(uvedge.sticker.points[1:])
                         else:
                             vertex = uvedge.vb if uvedge.uvface.flipped else uvedge.va
-                            data_loop.append(vertex)
+                            data_loop.append(vertex.co)
                         uvedge = uvedge.neighbor_right
                         try:
                             outer_edges.remove(uvedge)
@@ -1699,7 +1706,7 @@ class PDF:
                     edge = mesh.edges[loop.edge]
                     if edge.is_cut(uvedge.uvface.face) and not uvedge.sticker:
                         continue
-                    data_uvedge = line_through((uvedge.va, uvedge.vb)) + "S"
+                    data_uvedge = line_through((uvedge.va.co, uvedge.vb.co)) + "S"
                     if edge.freestyle:
                         data_freestyle.append(data_uvedge)
                     # each uvedge exists in two opposite-oriented variants; we want to add each only once
@@ -1711,56 +1718,52 @@ class PDF:
                 if island.is_inside_out:
                     data_convex, data_concave = data_concave, data_convex
 
-                if data_stickerfill and self.style.sticker_fill[3] > 0:
-                    commands.append("/Gsticker gs {0[0]:.3f} {0[1]:.3f} {0[2]:.3f} rg".format(self.style.sticker_fill))
-                    commands.extend(data_stickerfill)
+                if data_stickerfill and self.style.sticker_color[3] > 0:
+                    commands += chain(self.styling("sticker", do_stroke=False), data_stickerfill, reset_style)
                 if data_freestyle:
-                    commands.append("/Gfreestyle gs {0[0]:.3f} {0[1]:.3f} {0[2]:.3f} RG".format(self.style.freestyle_color))
-                    commands.extend(data_freestyle)
+                    commands += chain(self.styling("freestyle"), data_freestyle, reset_style)
                 if (data_convex or data_concave) and not self.pure_net and self.style.use_inbg:
-                    commands.append("/Ginbg gs {0[0]:.3f} {0[1]:.3f} {0[2]:.3f} RG".format(self.style.inbg_color))
-                    commands.extend(chain(data_convex, data_concave))
+                    commands += chain(self.styling("inbg"), data_convex, data_concave, reset_style)
                 if data_convex:
-                    commands.append("/Gconvex gs {0[0]:.3f} {0[1]:.3f} {0[2]:.3f} RG".format(self.style.convex_color))
-                    commands.extend(data_convex)
+                    commands += chain(self.styling("convex"), data_convex, reset_style)
                 if data_concave:
-                    commands.append("/Gconcave gs {0[0]:.3f} {0[1]:.3f} {0[2]:.3f} RG".format(self.style.concave_color))
-                    commands.extend(data_concave)
+                    commands += chain(self.styling("concave"), data_concave, reset_style)
                 if data_outer:
                     if not self.pure_net and self.style.use_outbg:
-                        commands.append("/Goutbg gs {0[0]:.3f} {0[1]:.3f} {0[2]:.3f} RG".format(self.style.outbg_color))
-                        commands.extend(data_outer)
-                    commands.append("/Gouter gs {0[0]:.3f} {0[1]:.3f} {0[2]:.3f} RG".format(self.style.outer_color))
-                    commands.extend(data_outer)
-                commands.append("/Gtext gs {0[0]:.3f} {0[1]:.3f} {0[2]:.3f} rg".format(self.style.text_color))
-                commands.extend(data_markers)
-                commands.append("Q")
+                        commands += chain(self.styling("outbg"), data_outer, reset_style)
+                    commands += chain(self.styling("outer"), data_outer, reset_style)
+                if data_markers:
+                    commands += chain(self.styling("text", do_stroke=False), data_markers, reset_style)
+                commands += reset_style  # return from island to page coordinates
             content = "\n".join(commands)
             page = {"Type": "Page", "Parent": root, "Contents": content, "Resources": resources}
             root["Kids"].append(page)
-            objects.extend((page, content))
+            objects += page, content
+            objects.extend(self.styles.values())
 
         root["Count"] = len(root["Kids"])
-        with open(filename, "w+") as f:
+        with open(filename, "wb+") as f:
             xref_table = list()
-            position = f.write("%PDF-1.4\n")
+            position = 0
+            position += f.write(b"%PDF-1.4\n")
+            position += f.write(b"%\xde\xad\xbe\xef\n")
             for index, obj in enumerate(objects, 1):
                 xref_table.append(position)
                 position += write_object(index, obj, objects, f)
             xref_pos = position
-            f.write("xref_table\n0 {}\n".format(len(xref_table) + 1))
-            f.write("{:010} {:05} f\n".format(0, 65536))
+            f.write("xref\n0 {}\n".format(len(xref_table) + 1).encode())
+            f.write("{:010} {:05} f\r\n".format(0, 65535).encode())
             for position in xref_table:
-                f.write("{:010} {:05} n\n".format(position, 0))
-            f.write("trailer\n")
-            f.write(format_dict({"Size": len(xref_table), "Root": catalog}, objects))
-            f.write("\nstartxref\n{}\n%%EOF\n".format(xref_pos))
+                f.write("{:010} {:05} n\r\n".format(position, 0).encode())
+            f.write(b"trailer\n")
+            f.write(format_dict({"Size": len(xref_table) + 1, "Root": catalog}, objects).encode())
+            f.write("\nstartxref\n{}\n%%EOF\n".format(xref_pos).encode())
 
-    command_label = "/Gtext gs BT {x:.6f} {y:.6f} Td ({label}) Tj ET"
+    command_label = "q /F1 {size:.6f} Tf BT {x:.6f} {y:.6f} Td ({label}) Tj ET Q"
     command_image = "q {0.x:.6f} 0 0 {0.y:.6f} 0 0 cm 1 0 0 -1 0 1 cm /{1} Do Q"
-    command_sticker = "q {mat[0][0]:.6f} {mat[1][0]:.6f} {mat[0][1]:.6f} {mat[1][1]:.6f} {pos.x:.6f} {pos.y:.6f} cm BT {align:.6f} 0 Td /F1 {size:.6f} Tf ({label}) Tj ET Q"
-    command_arrow = "q BT {pos.x:.6f} {pos.y:.6f} Td /F1 {size:.6f} Tf ({index}) Tj ET {mat[0][0]:.6f} {mat[1][0]:.6f} {mat[0][1]:.6f} {mat[1][1]:.6f} {arrow_pos.x:.6f} {arrow_pos.y:.6f} cm 0 0 m 1 -1 l 0 -0.25 l -1 -1 l f Q"
-    command_number = "q {mat[0][0]:.6f} {mat[1][0]:.6f} {mat[0][1]:.6f} {mat[1][1]:.6f} {pos.x:.6f} {pos.y:.6f} cm BT /F1 {size:.6f} Tf ({label}) Tj ET Q"
+    command_sticker = "q /F1 {size:.6f} Tf {mat[0][0]:.6f} {mat[1][0]:.6f} {mat[0][1]:.6f} {mat[1][1]:.6f} {pos.x:.6f} {pos.y:.6f} cm BT {align:.6f} 0 Td ({label}) Tj ET Q"
+    command_arrow = "q /F1 {size:.6f} Tf BT {pos.x:.6f} {pos.y:.6f} Td ({index}) Tj ET {mat[0][0]:.6f} {mat[1][0]:.6f} {mat[0][1]:.6f} {mat[1][1]:.6f} {arrow_pos.x:.6f} {arrow_pos.y:.6f} cm 0 0 m 1 -1 l 0 -0.25 l -1 -1 l f Q"
+    command_number = "q /F1 {size:.6f} Tf {mat[0][0]:.6f} {mat[1][0]:.6f} {mat[0][1]:.6f} {mat[1][1]:.6f} {pos.x:.6f} {pos.y:.6f} cm BT ({label}) Tj ET Q"
 
 
 class Unfold(bpy.types.Operator):
@@ -1957,7 +1960,7 @@ class PaperModelStyle(bpy.types.PropertyGroup):
         name="Inner Highlight Thickness", description="Relative thickness of the highlighting lines",
         default=2, min=0, soft_max=10, precision=1, step=10, subtype='FACTOR')
 
-    sticker_fill: bpy.props.FloatVectorProperty(
+    sticker_color: bpy.props.FloatVectorProperty(
         name="Tabs Fill", description="Fill color of sticking tabs",
         default=(0.9, 0.9, 0.9, 1.0), min=0, max=1, subtype='COLOR', size=4)
     text_color: bpy.props.FloatVectorProperty(
@@ -1972,6 +1975,8 @@ class ExportPaperModel(bpy.types.Operator):
     bl_idname = "export_mesh.paper_model"
     bl_label = "Export Paper Model"
     bl_description = "Export the selected object's net and optionally bake its texture"
+    bl_options = {'PRESET'}
+
     filepath: bpy.props.StringProperty(
         name="File Path", description="Target file to save the SVG", options={'SKIP_SAVE'})
     filename: bpy.props.StringProperty(
@@ -2034,13 +2039,16 @@ class ExportPaperModel(bpy.types.Operator):
         name="Scale", description="Divisor of all dimensions when exporting",
         default=1, soft_min=1.0, soft_max=100.0, subtype='FACTOR', precision=1)
     do_create_uvmap: bpy.props.BoolProperty(
-        name="Create UVMap", description="Create a new UV Map showing the islands and page layout",
+        name="Create UVMap",
+        description="Create a new UV Map showing the islands and page layout",
         default=False, options={'SKIP_SAVE'})
     ui_expanded_document: bpy.props.BoolProperty(
-        name="Show Document Settings Expanded", description="Shows the box 'Document Settings' expanded in user interface",
+        name="Show Document Settings Expanded",
+        description="Shows the box 'Document Settings' expanded in user interface",
         default=True, options={'SKIP_SAVE'})
     ui_expanded_style: bpy.props.BoolProperty(
-        name="Show Style Settings Expanded", description="Shows the box 'Colors and Style' expanded in user interface",
+        name="Show Style Settings Expanded",
+        description="Shows the box 'Colors and Style' expanded in user interface",
         default=False, options={'SKIP_SAVE'})
     style: bpy.props.PointerProperty(type=PaperModelStyle)
 
@@ -2058,8 +2066,9 @@ class ExportPaperModel(bpy.types.Operator):
         self.object = context.active_object
         self.unfolder = Unfolder(self.object)
         cage_size = M.Vector((sce.paper_model.output_size_x, sce.paper_model.output_size_y))
-        self.unfolder.prepare(cage_size, scale=sce.unit_settings.scale_length/self.scale, limit_by_page=sce.paper_model.limit_by_page)
-        if self.scale == 1:
+        unfolder_scale = sce.unit_settings.scale_length/self.scale
+        self.unfolder.prepare(cage_size, scale=unfolder_scale, limit_by_page=sce.paper_model.limit_by_page)
+        if sce.paper_model.use_auto_scale:
             self.scale = ceil(self.get_scale_ratio(sce))
 
     def recall(self):
@@ -2106,14 +2115,7 @@ class ExportPaperModel(bpy.types.Operator):
 
     def draw(self, context):
         layout = self.layout
-
         layout.prop(self.properties, "do_create_uvmap")
-
-        row = layout.row(align=True)
-        row.menu("VIEW3D_MT_paper_model_presets", text=bpy.types.VIEW3D_MT_paper_model_presets.bl_label)
-        row.operator("export_mesh.paper_model_preset_add", text="", icon='ADD')
-        row.operator("export_mesh.paper_model_preset_add", text="", icon='REMOVE').remove_active = True
-
         layout.prop(self.properties, "scale", text="Scale: 1/")
         scale_ratio = self.get_scale_ratio(context.scene)
         if scale_ratio > 1:
@@ -2153,7 +2155,7 @@ class ExportPaperModel(bpy.types.Operator):
             box.prop(self.properties, "output_type")
             col = box.column()
             col.active = (self.output_type != 'NONE')
-            if len(self.object.data.uv_layers) == 8:
+            if len(self.object.data.uv_layers) >= 8:
                 col.label(text="No UV slots left, No Texture is the only option.", icon='ERROR')
             elif context.scene.render.engine != 'CYCLES' and self.output_type != 'NONE':
                 col.label(text="Cycles will be used for texture baking.", icon='ERROR')
@@ -2206,7 +2208,7 @@ class ExportPaperModel(bpy.types.Operator):
             sub.prop(self.style, "inbg_width", text="Relative width")
             col = box.column()
             col.active = self.do_create_stickers
-            col.prop(self.style, "sticker_fill")
+            col.prop(self.style, "sticker_color")
             box.prop(self.style, "text_color")
 
 
@@ -2271,31 +2273,6 @@ class SelectIsland(bpy.types.Operator):
         return {'FINISHED'}
 
 
-class VIEW3D_MT_paper_model_presets(bpy.types.Menu):
-    bl_label = "Paper Model Presets"
-    preset_subdir = "export_mesh"
-    preset_operator = "script.execute_preset"
-    draw = bpy.types.Menu.draw_preset
-
-
-class AddPresetPaperModel(bl_operators.presets.AddPresetBase, bpy.types.Operator):
-    """Add or remove a Paper Model Preset"""
-    bl_idname = "export_mesh.paper_model_preset_add"
-    bl_label = "Add Paper Model Preset"
-    preset_menu = "VIEW3D_MT_paper_model_presets"
-    preset_subdir = "export_mesh"
-    preset_defines = ["op = bpy.context.active_operator"]
-
-    @property
-    def preset_values(self):
-        op = bpy.ops.export_mesh.paper_model
-        properties = op.get_rna().bl_rna.properties.items()
-        blacklist = bpy.types.Operator.bl_rna.properties.keys()
-        return [
-            "op.{}".format(prop_id) for (prop_id, prop) in properties
-            if not (prop.is_hidden or prop.is_skip_save or prop_id in blacklist)]
-
-
 class VIEW3D_PT_paper_model_tools(bpy.types.Panel):
     bl_space_type = 'VIEW_3D'
     bl_region_type = 'UI'
@@ -2332,7 +2309,10 @@ class VIEW3D_PT_paper_model_settings(bpy.types.Panel):
 
         layout.operator("export_mesh.paper_model")
         props = sce.paper_model
-        layout.prop(props, "scale", text="Model Scale:  1/")
+        layout.prop(props, "use_auto_scale")
+        sub = layout.row()
+        sub.active = not props.use_auto_scale
+        sub.prop(props, "scale", text="Model Scale:  1/")
 
         layout.prop(props, "limit_by_page")
         col = layout.column()
@@ -2459,9 +2439,39 @@ class PaperModelSettings(bpy.types.PropertyGroup):
     output_size_y: bpy.props.FloatProperty(
         name="Height", description="Maximal height of an island",
         default=0.29, soft_min=0.148, soft_max=1.189, subtype="UNSIGNED", unit="LENGTH")
+    use_auto_scale: bpy.props.BoolProperty(
+        name="Automatic Scale", description="Scale the net automatically to fit on paper",
+        default=True)
     scale: bpy.props.FloatProperty(
         name="Scale", description="Divisor of all dimensions when exporting",
-        default=1, soft_min=1.0, soft_max=100.0, subtype='FACTOR', precision=1)
+        default=1, soft_min=1.0, soft_max=100.0, subtype='FACTOR', precision=1,
+        update=lambda settings, _: settings.__setattr__('use_auto_scale', False))
+
+
+def factory_update_addon_category(cls, prop):
+    def func(self, context):
+        if hasattr(bpy.types, cls.__name__):
+            bpy.utils.unregister_class(cls)
+        cls.bl_category = self[prop]
+        bpy.utils.register_class(cls)
+    return func
+
+
+class PaperAddonPreferences(bpy.types.AddonPreferences):
+    bl_idname = __name__
+    unfold_category: bpy.props.StringProperty(
+        name="Unfold Panel Category", description="Category in 3D View Toolbox where the Unfold panel is displayed",
+        default="Paper", update=factory_update_addon_category(VIEW3D_PT_paper_model_tools, 'unfold_category'))
+    export_category: bpy.props.StringProperty(
+        name="Export Panel Category", description="Category in 3D View Toolbox where the Export panel is displayed",
+        default="Paper", update=factory_update_addon_category(VIEW3D_PT_paper_model_settings, 'export_category'))
+
+    def draw(self, context):
+        sub = self.layout.column(align=True)
+        sub.use_property_split = True
+        sub.label(text="3D View Panel Category:")
+        sub.prop(self, "unfold_category", text="Unfold Panel:")
+        sub.prop(self, "export_category", text="Export Panel:")
 
 
 module_classes = (
@@ -2469,14 +2479,13 @@ module_classes = (
     ExportPaperModel,
     ClearAllSeams,
     SelectIsland,
-    AddPresetPaperModel,
     FaceList,
     IslandList,
     PaperModelSettings,
-    VIEW3D_MT_paper_model_presets,
     DATA_PT_paper_model_islands,
     VIEW3D_PT_paper_model_tools,
     VIEW3D_PT_paper_model_settings,
+    PaperAddonPreferences,
 )
 
 
@@ -2493,6 +2502,10 @@ def register():
         default=-1, min=-1, max=100, options={'SKIP_SAVE'}, update=island_index_changed)
     bpy.types.TOPBAR_MT_file_export.append(menu_func_export)
     bpy.types.VIEW3D_MT_edit_mesh.prepend(menu_func_unfold)
+    # Force an update on the panel category properties
+    prefs = bpy.context.preferences.addons[__name__].preferences
+    prefs.unfold_category = prefs.unfold_category
+    prefs.export_category = prefs.export_category
 
 
 def unregister():