Преглед изворни кода

- PDF import tool: added support for save/restore Graphics stack. Only for scale and offset transformations and for the linewidth. This is the final fix for Microsoft PDF printer who saves in PDF format 1.7

Marius Stanciu пре 6 година
родитељ
комит
8a2a48f668
2 измењених фајлова са 123 додато и 109 уклоњено
  1. 4 0
      README.md
  2. 119 109
      flatcamTools/ToolPDF.py

+ 4 - 0
README.md

@@ -9,6 +9,10 @@ CAD program, and create G-Code for Isolation routing.
 
 =================================================
 
+22.04.2019
+
+- PDF import tool: added support for save/restore Graphics stack. Only for scale and offset transformations and for the linewidth. This is the final fix for Microsoft PDF printer who saves in PDF format 1.7
+
 21.04.2019
 
 - fixed the PDF import tool to work with files generated by the Microsoft PDF printer (chained subpaths)

+ 119 - 109
flatcamTools/ToolPDF.py

@@ -75,16 +75,30 @@ class ToolPDF(FlatCAMTool):
         self.no_op_re = re.compile(r'^n$')
 
         # detect offset transformation. Pattern: (1) (0) (0) (1) (x) (y)
-        self.offset_re = re.compile(r'^1\.?0*\s0?\.?0*\s0?\.?0*\s1\.?0*\s(-?\d+\.?\d*)\s(-?\d+\.?\d*)\s*cm$')
+        # self.offset_re = re.compile(r'^1\.?0*\s0?\.?0*\s0?\.?0*\s1\.?0*\s(-?\d+\.?\d*)\s(-?\d+\.?\d*)\s*cm$')
         # detect scale transformation. Pattern: (factor_x) (0) (0) (factor_y) (0) (0)
-        self.scale_re = re.compile(r'^q? (-?\d+\.?\d*) 0\.?0* 0\.?0* (-?\d+\.?\d*) 0\.?0* 0\.?0*\s+cm$')
+        # self.scale_re = re.compile(r'^q? (-?\d+\.?\d*) 0\.?0* 0\.?0* (-?\d+\.?\d*) 0\.?0* 0\.?0*\s+cm$')
         # detect combined transformation. Should always be the last
-        self.combined_transform_re = re.compile(r'^q?\s*(-?\d+\.?\d*) (-?\d+\.?\d*) (-?\d+\.?\d*) (-?\d+\.?\d*) '
+        self.combined_transform_re = re.compile(r'^(q)?\s*(-?\d+\.?\d*) (-?\d+\.?\d*) (-?\d+\.?\d*) (-?\d+\.?\d*) '
                                                 r'(-?\d+\.?\d*) (-?\d+\.?\d*)\s+cm$')
 
         # detect clipping path
         self.clip_path_re = re.compile(r'^W[*]? n?$')
 
+        # detect save graphic state in graphic stack
+        self.save_gs_re = re.compile(r'^q.*?$')
+
+        # detect restore graphic state from graphic stack
+        self.restore_gs_re = re.compile(r'^Q.*$')
+
+        # graphic stack where we save parameters like transformation, line_width
+        self.gs = dict()
+        # each element is a list composed of sublist elements
+        # (each sublist has 2 lists each having 2 elements: first is offset like:
+        # offset_geo = [off_x, off_y], second element is scale list with 2 elements, like: scale_geo = [sc_x, sc_yy])
+        self.gs['transform'] = []
+        self.gs['line_width'] = []   # each element is a float
+
         self.geo_buffer = []
         self.pdf_parsed = ''
 
@@ -201,8 +215,8 @@ class ToolPDF(FlatCAMTool):
         # store the start point (when 'm' command is encountered)
         current_subpath = None
 
-        # set True when 'h' command is encountered (close path)
-        close_path = False
+        # set True when 'h' command is encountered (close subpath)
+        close_subpath = False
 
         start_point = None
         current_point = None
@@ -212,80 +226,94 @@ class ToolPDF(FlatCAMTool):
         offset_geo = [0, 0]
         scale_geo = [1, 1]
 
-        c_offset_f= [0, 0]
-        c_scale_f = [1, 1]
-
         # initial aperture
         aperture = 10
 
         # store the apertures here
         apertures_dict = {}
 
-        # it seems that first transform apply to the whole PDF; signal here if it's first
-        first_transform = True
-
         line_nr = 0
         lines = pdf_content.splitlines()
 
         for pline in lines:
             line_nr += 1
-            log.debug("line %d: %s" % (line_nr, pline))
+            # log.debug("line %d: %s" % (line_nr, pline))
 
             # TRANSFORMATIONS DETECTION #
 
-            # # Detect Scale transform
-            # match = self.scale_re.search(pline)
-            # if match:
-            #     log.debug(
-            #         "ToolPDF.parse_pdf() --> SCALE transformation found on line: %s --> %s" % (line_nr, pline))
-            #     if first_transform:
-            #         first_transform = False
-            #         c_scale_f = [float(match.group(1)), float(match.group(2))]
-            #     else:
-            #         scale_geo = [float(match.group(1)), float(match.group(2))]
-            #     continue
-
-            # # Detect Offset transform
-            # match = self.offset_re.search(pline)
-            # if match:
-            #     log.debug(
-            #         "ToolPDF.parse_pdf() --> OFFSET transformation found on line: %s --> %s" % (line_nr, pline))
-            #     offset_geo = [float(match.group(1)), float(match.group(2))]
-            #     continue
-
-            # Detect combined transformation. Must be always the last from transformations to be checked.
+            # Detect combined transformation.
             match = self.combined_transform_re.search(pline)
             if match:
+                # detect save graphic stack event
+                # sometimes they combine save_to_graphics_stack with the transformation on the same line
+                if match.group(1) == 'q':
+                    log.debug(
+                        "ToolPDF.parse_pdf() --> Save to GS found on line: %s --> offset=[%f, %f] ||| scale=[%f, %f]" %
+                        (line_nr, offset_geo[0], offset_geo[1], scale_geo[0], scale_geo[1]))
+
+                    self.gs['transform'].append(deepcopy([offset_geo, scale_geo]))
+                    self.gs['line_width'].append(deepcopy(size))
+
                 # transformation = TRANSLATION (OFFSET)
-                if (float(match.group(2)) == 0 and float(match.group(3)) == 0) and \
-                        (float(match.group(5)) != 0 or float(match.group(6)) != 0):
+                if (float(match.group(3)) == 0 and float(match.group(4)) == 0) and \
+                        (float(match.group(6)) != 0 or float(match.group(7)) != 0):
                     log.debug(
                         "ToolPDF.parse_pdf() --> OFFSET transformation found on line: %s --> %s" % (line_nr, pline))
-                    if first_transform:
-                        c_offset_f = [float(match.group(5)), float(match.group(6))]
-                    else:
-                        offset_geo = [float(match.group(5)), float(match.group(6))]
+
+                    offset_geo[0] += float(match.group(6))
+                    offset_geo[1] += float(match.group(7))
+                    # log.debug("Offset= [%f, %f]" % (offset_geo[0], offset_geo[1]))
 
                 # transformation = SCALING
-                if float(match.group(1)) != 1 and float(match.group(4)) != 1:
+                if float(match.group(2)) != 1 and float(match.group(5)) != 1:
                     log.debug(
                         "ToolPDF.parse_pdf() --> SCALE transformation found on line: %s --> %s" % (line_nr, pline))
-                    if first_transform:
-                        c_scale_f = [float(match.group(1)), float(match.group(4))]
-                    else:
-                        scale_geo = [float(match.group(1)), float(match.group(4))]
 
-                if first_transform:
-                    first_transform = False
+                    scale_geo[0] *= float(match.group(2))
+                    scale_geo[1] *= float(match.group(5))
+                # log.debug("Scale= [%f, %f]" % (scale_geo[0], scale_geo[1]))
+
                 continue
 
+            # detect save graphic stack event
+            match = self.save_gs_re.search(pline)
+            if match:
+                log.debug(
+                    "ToolPDF.parse_pdf() --> Save to GS found on line: %s --> offset=[%f, %f] ||| scale=[%f, %f]" %
+                    (line_nr, offset_geo[0], offset_geo[1], scale_geo[0], scale_geo[1]))
+                self.gs['transform'].append(deepcopy([offset_geo, scale_geo]))
+                self.gs['line_width'].append(deepcopy(size))
+
+            # detect restore from graphic stack event
+            match = self.restore_gs_re.search(pline)
+            if match:
+                log.debug(
+                    "ToolPDF.parse_pdf() --> Restore from GS found on line: %s --> %s" % (line_nr, pline))
+                try:
+                    restored_transform = self.gs['transform'].pop(-1)
+                    offset_geo = restored_transform[0]
+                    scale_geo = restored_transform[1]
+                except IndexError:
+                    # nothing to remove
+                    log.debug("ToolPDF.parse_pdf() --> Nothing to restore")
+                    pass
+
+                try:
+                    size = self.gs['line_width'].pop(-1)
+                except IndexError:
+                    log.debug("ToolPDF.parse_pdf() --> Nothing to restore")
+                    # nothing to remove
+                    pass
+                # log.debug("Restored Offset= [%f, %f]" % (offset_geo[0], offset_geo[1]))
+                # log.debug("Restored Scale= [%f, %f]" % (scale_geo[0], scale_geo[1]))
+
             # PATH CONSTRUCTION #
 
             # Start SUBPATH
             match = self.start_subpath_re.search(pline)
             if match:
                 # we just started a subpath so we mark it as not closed yet
-                close_path = False
+                close_subpath = False
 
                 # init subpaths
                 subpath['lines'] = []
@@ -295,8 +323,8 @@ class ToolPDF(FlatCAMTool):
                 # detect start point to move to
                 x = float(match.group(1)) + offset_geo[0]
                 y = float(match.group(2)) + offset_geo[1]
-                pt = (x * self.point_to_unit_factor * scale_geo[0] * c_scale_f[0],
-                      y * self.point_to_unit_factor * scale_geo[1] * c_scale_f[1])
+                pt = (x * self.point_to_unit_factor * scale_geo[0],
+                      y * self.point_to_unit_factor * scale_geo[1])
                 start_point = pt
 
                 # add the start point to subpaths
@@ -312,8 +340,8 @@ class ToolPDF(FlatCAMTool):
                 current_subpath = 'lines'
                 x = float(match.group(1)) + offset_geo[0]
                 y = float(match.group(2)) + offset_geo[1]
-                pt = (x * self.point_to_unit_factor * scale_geo[0] * c_scale_f[0],
-                      y * self.point_to_unit_factor * scale_geo[1] * c_scale_f[1])
+                pt = (x * self.point_to_unit_factor * scale_geo[0],
+                      y * self.point_to_unit_factor * scale_geo[1])
                 subpath['lines'].append(pt)
                 current_point = pt
                 continue
@@ -325,16 +353,16 @@ class ToolPDF(FlatCAMTool):
                 start = current_point
                 x = float(match.group(1)) + offset_geo[0]
                 y = float(match.group(2)) + offset_geo[1]
-                c1 = (x * self.point_to_unit_factor * scale_geo[0] * c_scale_f[0],
-                      y * self.point_to_unit_factor * scale_geo[1] * c_scale_f[1])
+                c1 = (x * self.point_to_unit_factor * scale_geo[0],
+                      y * self.point_to_unit_factor * scale_geo[1])
                 x = float(match.group(3)) + offset_geo[0]
                 y = float(match.group(4)) + offset_geo[1]
-                c2 = (x * self.point_to_unit_factor * scale_geo[0] * c_scale_f[0],
-                      y * self.point_to_unit_factor * scale_geo[1] * c_scale_f[1])
+                c2 = (x * self.point_to_unit_factor * scale_geo[0],
+                      y * self.point_to_unit_factor * scale_geo[1])
                 x = float(match.group(5)) + offset_geo[0]
                 y = float(match.group(6)) + offset_geo[1]
-                stop = (x * self.point_to_unit_factor * scale_geo[0] * c_scale_f[0],
-                        y * self.point_to_unit_factor * scale_geo[1] * c_scale_f[1])
+                stop = (x * self.point_to_unit_factor * scale_geo[0],
+                        y * self.point_to_unit_factor * scale_geo[1])
 
                 subpath['bezier'].append([start, c1, c2, stop])
                 current_point = stop
@@ -347,12 +375,12 @@ class ToolPDF(FlatCAMTool):
                 start = current_point
                 x = float(match.group(1)) + offset_geo[0]
                 y = float(match.group(2)) + offset_geo[1]
-                c2 = (x * self.point_to_unit_factor * scale_geo[0] * c_scale_f[0],
-                      y * self.point_to_unit_factor * scale_geo[1] * c_scale_f[1])
+                c2 = (x * self.point_to_unit_factor * scale_geo[0],
+                      y * self.point_to_unit_factor * scale_geo[1])
                 x = float(match.group(3)) + offset_geo[0]
                 y = float(match.group(4)) + offset_geo[1]
-                stop = (x * self.point_to_unit_factor * scale_geo[0] * c_scale_f[0],
-                        y * self.point_to_unit_factor * scale_geo[1] * c_scale_f[1])
+                stop = (x * self.point_to_unit_factor * scale_geo[0],
+                        y * self.point_to_unit_factor * scale_geo[1])
 
                 subpath['bezier'].append([start, start, c2, stop])
                 current_point = stop
@@ -364,33 +392,33 @@ class ToolPDF(FlatCAMTool):
                 start = current_point
                 x = float(match.group(1)) + offset_geo[0]
                 y = float(match.group(2)) + offset_geo[1]
-                c1 = (x * self.point_to_unit_factor * scale_geo[0] * c_scale_f[0],
-                      y * self.point_to_unit_factor * scale_geo[1] * c_scale_f[1])
+                c1 = (x * self.point_to_unit_factor * scale_geo[0],
+                      y * self.point_to_unit_factor * scale_geo[1])
                 x = float(match.group(3)) + offset_geo[0]
                 y = float(match.group(4)) + offset_geo[1]
-                stop = (x * self.point_to_unit_factor * scale_geo[0] * c_scale_f[0],
-                        y * self.point_to_unit_factor * scale_geo[1] * c_scale_f[1])
+                stop = (x * self.point_to_unit_factor * scale_geo[0],
+                        y * self.point_to_unit_factor * scale_geo[1])
 
                 subpath['bezier'].append([start, c1, stop, stop])
                 print(subpath['bezier'])
                 current_point = stop
                 continue
 
-            # Draw RECTANGLE
+            # Draw Rectangle 're
             match = self.rect_re.search(pline)
             if match:
                 current_subpath = 'rectangle'
-                x = (float(match.group(1)) + offset_geo[0]) * self.point_to_unit_factor * scale_geo[0] * c_scale_f[0]
-                y = (float(match.group(2)) + offset_geo[1]) * self.point_to_unit_factor * scale_geo[1] * c_scale_f[1]
+                x = (float(match.group(1)) + offset_geo[0]) * self.point_to_unit_factor * scale_geo[0]
+                y = (float(match.group(2)) + offset_geo[1]) * self.point_to_unit_factor * scale_geo[1]
                 width = (float(match.group(3)) + offset_geo[0]) * \
-                        self.point_to_unit_factor * scale_geo[0] * c_scale_f[0]
+                        self.point_to_unit_factor * scale_geo[0]
                 height = (float(match.group(4)) + offset_geo[1]) * \
-                         self.point_to_unit_factor * scale_geo[1] * c_scale_f[1]
+                         self.point_to_unit_factor * scale_geo[1]
                 pt1 = (x, y)
                 pt2 = (x+width, y)
                 pt3 = (x+width, y+height)
                 pt4 = (x, y+height)
-                # TODO: I'm not sure if rectangles are a subpath in themselves that autoclose
+                # TODO: I'm not sure if rectangles are a type of subpath that close by itself
                 subpath['rectangle'] += [pt1, pt2, pt3, pt4, pt1]
                 current_point = pt1
                 continue
@@ -404,8 +432,8 @@ class ToolPDF(FlatCAMTool):
                 subpath['rectangle'] = []
                 # it measns that we've already added the subpath to path and we need to delete it
                 # clipping path is usually either rectangle or lines
-                if close_path is True:
-                    close_path = False
+                if close_subpath is True:
+                    close_subpath = False
                     if current_subpath == 'lines':
                         path['lines'].pop(-1)
                     if current_subpath == 'rectangle':
@@ -415,7 +443,7 @@ class ToolPDF(FlatCAMTool):
             # Close SUBPATH
             match = self.end_subpath_re.search(pline)
             if match:
-                close_path = True
+                close_subpath = True
                 if current_subpath == 'lines':
                     subpath['lines'].append(start_point)
                     # since we are closing the subpath add it to the path, a path may have chained subpaths
@@ -439,24 +467,6 @@ class ToolPDF(FlatCAMTool):
             match = self.strokewidth_re.search(pline)
             if match:
                 size = float(match.group(1))
-                # flag = 0
-                #
-                # if not apertures_dict:
-                #     apertures_dict[str(aperture)] = dict()
-                #     apertures_dict[str(aperture)]['size'] = size
-                #     apertures_dict[str(aperture)]['type'] = 'C'
-                #     apertures_dict[str(aperture)]['solid_geometry'] = []
-                # else:
-                #     for k in apertures_dict:
-                #         if size == apertures_dict[k]['size']:
-                #             flag = 1
-                #             break
-                #     if flag == 0:
-                #         aperture += 1
-                #         apertures_dict[str(aperture)] = dict()
-                #         apertures_dict[str(aperture)]['size'] = size
-                #         apertures_dict[str(aperture)]['type'] = 'C'
-                #         apertures_dict[str(aperture)]['solid_geometry'] = []
                 continue
 
             # Detect No_Op command, ignore the current subpath
@@ -471,7 +481,7 @@ class ToolPDF(FlatCAMTool):
             match = self.stroke_path__re.search(pline)
             if match:
                 # scale the size here; some PDF printers apply transformation after the size is declared
-                applied_size = size * scale_geo[0] * c_scale_f[0] * self.point_to_unit_factor
+                applied_size = size * scale_geo[0] * self.point_to_unit_factor
 
                 path_geo = list()
                 if current_subpath == 'lines':
@@ -536,7 +546,7 @@ class ToolPDF(FlatCAMTool):
             match = self.fill_path_re.search(pline)
             if match:
                 # scale the size here; some PDF printers apply transformation after the size is declared
-                applied_size = size * scale_geo[0] * c_scale_f[0] * self.point_to_unit_factor
+                applied_size = size * scale_geo[0] * self.point_to_unit_factor
 
                 path_geo = list()
                 if current_subpath == 'lines':
@@ -544,7 +554,7 @@ class ToolPDF(FlatCAMTool):
                         for subp in path['lines']:
                             geo = copy(subp)
                             # close the subpath if it was not closed already
-                            if close_path is False:
+                            if close_subpath is False:
                                 geo.append(geo[0])
                             geo_el = Polygon(geo).buffer(0.0000001, resolution=self.step_per_circles)
                             path_geo.append(geo_el)
@@ -553,7 +563,7 @@ class ToolPDF(FlatCAMTool):
                     else:
                         geo = copy(subpath['lines'])
                         # close the subpath if it was not closed already
-                        if close_path is False:
+                        if close_subpath is False:
                             geo.append(start_point)
                         geo_el = Polygon(geo).buffer(0.0000001, resolution=self.step_per_circles)
                         path_geo.append(geo_el)
@@ -566,7 +576,7 @@ class ToolPDF(FlatCAMTool):
                             for b in subp:
                                 geo += self.bezier_to_points(start=b[0], c1=b[1], c2=b[2], stop=b[3])
                                 # close the subpath if it was not closed already
-                                if close_path is False:
+                                if close_subpath is False:
                                     geo.append(geo[0])
                                 geo_el = Polygon(geo).buffer(0.0000001, resolution=self.step_per_circles)
                                 path_geo.append(geo_el)
@@ -575,7 +585,7 @@ class ToolPDF(FlatCAMTool):
                     else:
                         for b in subpath['bezier']:
                             geo += self.bezier_to_points(start=b[0], c1=b[1], c2=b[2], stop=b[3])
-                        if close_path is False:
+                        if close_subpath is False:
                             geo.append(start_point)
                         geo_el = Polygon(geo).buffer(0.0000001, resolution=self.step_per_circles)
                         path_geo.append(geo_el)
@@ -586,7 +596,7 @@ class ToolPDF(FlatCAMTool):
                         for subp in path['rectangle']:
                             geo = copy(subp)
                             # close the subpath if it was not closed already
-                            if close_path is False:
+                            if close_subpath is False:
                                 geo.append(geo[0])
                             geo_el = Polygon(geo).buffer(0.0000001, resolution=self.step_per_circles)
                             path_geo.append(geo_el)
@@ -595,14 +605,14 @@ class ToolPDF(FlatCAMTool):
                     else:
                         geo = copy(subpath['rectangle'])
                         # close the subpath if it was not closed already
-                        if close_path is False:
+                        if close_subpath is False:
                             geo.append(start_point)
                         geo_el = Polygon(geo).buffer(0.0000001, resolution=self.step_per_circles)
                         path_geo.append(geo_el)
                         subpath['rectangle'] = []
 
                 # we finished painting and also closed the path if it was the case
-                close_path = True
+                close_subpath = True
 
                 try:
                     apertures_dict['0']['solid_geometry'] += path_geo
@@ -619,7 +629,7 @@ class ToolPDF(FlatCAMTool):
             match = self.fill_stroke_path_re.search(pline)
             if match:
                 # scale the size here; some PDF printers apply transformation after the size is declared
-                applied_size = size * scale_geo[0] * c_scale_f[0] * self.point_to_unit_factor
+                applied_size = size * scale_geo[0] * self.point_to_unit_factor
 
                 path_geo = list()
                 if current_subpath == 'lines':
@@ -628,7 +638,7 @@ class ToolPDF(FlatCAMTool):
                         for subp in path['lines']:
                             geo = copy(subp)
                             # close the subpath if it was not closed already
-                            if close_path is False:
+                            if close_subpath is False:
                                 geo.append(geo[0])
                             geo_el = Polygon(geo).buffer(0.0000001, resolution=self.step_per_circles)
                             path_geo.append(geo_el)
@@ -643,7 +653,7 @@ class ToolPDF(FlatCAMTool):
                         # fill
                         geo = copy(subpath['lines'])
                         # close the subpath if it was not closed already
-                        if close_path is False:
+                        if close_subpath is False:
                             geo.append(start_point)
                         geo_el = Polygon(geo).buffer(0.0000001, resolution=self.step_per_circles)
                         path_geo.append(geo_el)
@@ -662,7 +672,7 @@ class ToolPDF(FlatCAMTool):
                             for b in subp:
                                 geo += self.bezier_to_points(start=b[0], c1=b[1], c2=b[2], stop=b[3])
                                 # close the subpath if it was not closed already
-                                if close_path is False:
+                                if close_subpath is False:
                                     geo.append(geo[0])
                                 geo_el = Polygon(geo).buffer(0.0000001, resolution=self.step_per_circles)
                                 path_geo.append(geo_el)
@@ -679,7 +689,7 @@ class ToolPDF(FlatCAMTool):
                         # fill
                         for b in subpath['bezier']:
                             geo += self.bezier_to_points(start=b[0], c1=b[1], c2=b[2], stop=b[3])
-                        if close_path is False:
+                        if close_subpath is False:
                             geo.append(start_point)
                         geo_el = Polygon(geo).buffer(0.0000001, resolution=self.step_per_circles)
                         path_geo.append(geo_el)
@@ -697,7 +707,7 @@ class ToolPDF(FlatCAMTool):
                         for subp in path['rectangle']:
                             geo = copy(subp)
                             # close the subpath if it was not closed already
-                            if close_path is False:
+                            if close_subpath is False:
                                 geo.append(geo[0])
                             geo_el = Polygon(geo).buffer(0.0000001, resolution=self.step_per_circles)
                             path_geo.append(geo_el)
@@ -712,7 +722,7 @@ class ToolPDF(FlatCAMTool):
                         # fill
                         geo = copy(subpath['rectangle'])
                         # close the subpath if it was not closed already
-                        if close_path is False:
+                        if close_subpath is False:
                             geo.append(start_point)
                         geo_el = Polygon(geo).buffer(0.0000001, resolution=self.step_per_circles)
                         path_geo.append(geo_el)
@@ -723,7 +733,7 @@ class ToolPDF(FlatCAMTool):
                         subpath['rectangle'] = []
 
                 # we finished painting and also closed the path if it was the case
-                close_path = True
+                close_subpath = True
 
                 try:
                     apertures_dict['0']['solid_geometry'] += path_geo