示例#1
0
 def pointsAttrib(self, node, attr, value):
     """Read the 'points' attribute."""
     floats = parseFloats(value)
     if len(floats) % 2 == 0:
         node[attr] = floats
     else:
         log.error("odd number of vertices")
示例#2
0
 def pointsAttrib(self, node, attr, value):
 	"""Read the 'points' attribute."""
 	floats = parseFloats(value)
     if len(floats) % 2 == 0:
         node[attr] = floats
     else:
     	log.error("odd number of vertices")
示例#3
0
    def _parseColor(self, val):
        """ Parse a color definition.

        Returns a color in hex format, 'inherit', or 'none'.
        'none' means that the geometry is not to be rendered.
        See: http://www.w3.org/TR/SVG11/painting.html#SpecifyingPaint
        """
        # http://www.w3.org/TR/SVG11/color.html
        # http://www.w3.org/TR/2008/REC-CSS2-20080411/syndata.html#color-units
        if val[0] == " ":
            val = val.strip()

        if val[0] == '#':
            return normalize_hex(val)
        elif val.startswith('rgba'):
            floats = parseFloats(val[5:-1])
            if len(floats) == 4:
                log.warn("opacity in rgba is ignored, \
                              use stroke-opacity/fill-opacity instead")
                return rgb_to_hex(tuple(floats[:3]))
        elif val.startswith('rgb'):
            floats = parseFloats(val[4:-1])
            if len(floats) == 3:
                return rgb_to_hex(tuple(floats))
        elif val == 'none':
            # 'none' means the geometry is not to be filled or stroked
            # http://www.w3.org/TR/SVG11/painting.html#SpecifyingPaint
            return 'none'
        elif val.startswith('hsl'):
            log.warn("hsl/hsla color spaces are not supported")
        elif val.startswith('url'):
            log.warn("defs are not supported")
        elif val in css3_names_to_hex:  # named colors
            return css3_names_to_hex[val]
        elif val in ['currentColor', 'inherit']:
            return 'inherit'
        else:
            log.warn("invalid color, skipped: " + str(val))
            return 'inherit'
示例#4
0
    def _parseColor(self, val):
        """ Parse a color definition.

        Returns a color in hex format, 'inherit', or 'none'.
        'none' means that the geometry is not to be rendered.
        See: http://www.w3.org/TR/SVG11/painting.html#SpecifyingPaint
        """
        # http://www.w3.org/TR/SVG11/color.html
        # http://www.w3.org/TR/2008/REC-CSS2-20080411/syndata.html#color-units
        if val[0] == " ":
            val = val.strip()

        if val[0] == '#':
            return normalize_hex(val)
        elif val.startswith('rgba'):
            floats = parseFloats(val[5:-1])
            if len(floats) == 4:
                log.warn("opacity in rgba is ignored, \
                              use stroke-opacity/fill-opacity instead")
                return rgb_to_hex(tuple(floats[:3]))
        elif val.startswith('rgb'):
            floats = parseFloats(val[4:-1])
            if len(floats) == 3:
                return rgb_to_hex(tuple(floats))
        elif val == 'none':
            # 'none' means the geometry is not to be filled or stroked
            # http://www.w3.org/TR/SVG11/painting.html#SpecifyingPaint
            return 'none'
        elif val.startswith('hsl'):
            log.warn("hsl/hsla color spaces are not supported")
        elif val.startswith('url'):
            log.warn("defs are not supported");
        elif val in css3_names_to_hex:  # named colors
            return css3_names_to_hex[val]
        elif val in ['currentColor', 'inherit']:
            return 'inherit'
        else:
            log.warn("invalid color, skipped: " + str(val))
            return 'inherit'
示例#5
0
    def parse(self, svgstring, force_dpi=None):
        """ Parse a SVG document.

        This traverses through the document tree and collects all path
        data and converts it to polylines of the requested tolerance.

        Path data is returned as paths by color:
        {'#ff0000': [[path0, path1, ..], [path0, ..], ..]}
        Each path is a list of vertices which is a list of two floats.

        Determining Physical Dimensions
        -------------------------------
        SVG files may use physical units (mm, in) or screen units (px).
        For obvious reason former are preferred as these take out any
        guess-work of how to interpret any coordinates.

        A good SVG authoring app writes physical dimensions to file like this:
        - the svg tag has a width, height, viewBox attribute
        - width and height contains the page dimensions and unit
        - viewBox defines a rectangle with (x, y, width, height)
        - width/viewBox:width is the factor that needs to be applied to
          any (unit-less) coordinates in the file
        - x,y is a translation that needs to be applied to any coordinates

        One issue with svg documents is that they are not always clear on
        the physical dimensions. Often they lack or use px units in the
        width/height attributes (no units implies px units in the SVG
        standard). For example, it's possible to encounter px
        units in the file even when the authoring app interprets these
        as physical units (e.g mm). This means there is an implied DPI
        conversion in the app that we need to guess/know.

        The following strategy is used to get physical dimensions:

        1. from argument (force_dpi)
        2. from units of svg width/height and viewBox
        3. from hints of (known) originating apps
        4. from ratio of page and target size
        5. defaults to 90 DPI
        """
        self.px2mm = None
        self.boundarys = {}

        vb_x = None
        vb_y = None
        vb_w = None
        vb_h = None

        # parse xml
        svgRootElement = ET.fromstring(svgstring)
        tagName = self._tagReader._get_tag(svgRootElement)

        if tagName != 'svg':
            log.error("Invalid file, no 'svg' tag found.")
            return self.boundarys

        # 1. Get px2mm from argument
        if force_dpi is not None:
            self.px2mm = 25.4/force_dpi
            log.info("SVG import forced to %s dpi." % (force_dpi))

        # Get width, height, viewBox for further processing
        if not self.px2mm:
            width = None
            height = None
            unit = ''

            # get width, height, unit
            width_str = svgRootElement.attrib.get('width')
            height_str = svgRootElement.attrib.get('height')
            if width_str and height_str:
                width, width_unit = parseScalar(width_str)
                height, height_unit = parseScalar(height_str)
                if width_unit != height_unit:
                    log.error("Conflicting units found.")
                unit = width_unit
                log.info("SVG w,h (unit) is %s,%s (%s)." % (width, height, unit))

            # get viewBox
            # http://www.w3.org/TR/SVG11/coords.html#ViewBoxAttribute
            vb = svgRootElement.attrib.get('viewBox')
            if vb:
                vb_x, vb_y, vb_w, vb_h = parseFloats(vb)
                log.info("SVG viewBox (%s,%s,%s,%s)." % (vb_x, vb_y, vb_w, vb_h))

        # 2. Get px2mm from width, height, viewBox
        if not self.px2mm:
            if (width and height) or vb:
                if not (width and height):
                    # default to viewBox
                    width = vb_w
                    height = vb_h
                if not vb:
                    # default to width, height, and no offset
                    vb_x = 0.0
                    vb_y = 0.0
                    vb_w = width
                    vb_h = height

                self.px2mm = width/vb_w

                if unit == 'mm':
                    # great, the svg file already uses mm
                    pass
                    log.info("px2mm by svg mm unit")
                elif unit == 'in':
                    # prime for inch to mm conversion
                    self.px2mm *= 25.4
                    log.info("px2mm by svg inch unit")
                elif unit == 'cm':
                    # prime for cm to mm conversion
                    self.px2mm *= 10.0
                    log.info("px2mm by svg cm unit")
                elif unit == 'px' or unit == '':
                    # no physical units in file
                    # we have to interpret user (px) units
                    # 3. For some apps we can make a good guess.
                    svghead = svgstring[0:400]
                    if 'Inkscape' in svghead:
                        self.px2mm *= 25.4/90.0
                        log.info("SVG exported with Inkscape -> 90dpi.")
                    elif 'Illustrator' in svghead:
                        self.px2mm *= 25.4/72.0
                        log.info("SVG exported with Illustrator -> 72dpi.")
                    elif 'Intaglio' in svghead:
                        self.px2mm *= 25.4/72.0
                        log.info("SVG exported with Intaglio -> 72dpi.")
                    elif 'CorelDraw' in svghead:
                        self.px2mm *= 25.4/96.0
                        log.info("SVG exported with CorelDraw -> 96dpi.")
                    elif 'Qt' in svghead:
                        self.px2mm *= 25.4/90.0
                        log.info("SVG exported with Qt lib -> 90dpi.")
                    else:
                        # give up in this step
                        self.px2mm = None
                else:
                    log.error("SVG with unsupported unit.")
                    self.px2mm = None

        # 4. Get px2mm by the ratio of svg size to target size
        if not self.px2mm and (width and height):
            self.px2mm = self._target_size[0]/width
            log.info("px2mm by target_size/page_size ratio")


        # 5. Fall back on px unit DPIs default value
        if not self.px2mm:
            log.warn("Failed to determin physical dimensions -> defaulting to 90dpi.")
            self.px2mm = 25.4/90.0

        # adjust tolerances to px units
        self.tolerance2_px = (self.tolerance/self.px2mm)*(self.tolerance/self.px2mm)

        # translation from viewbox
        if vb_x:
            tx = vb_x
        else:
            tx = 0.0
        if vb_y:
            ty = vb_y
        else:
            ty = 0.0

        # let the fun begin
        # recursively parse children
        # output will be in self.boundarys
        node = {
            'xformToWorld': [1,0,0,1,tx,ty],
            'display': 'visible',
            'visibility': 'visible',
            'fill': '#000000',
            'stroke': '#000000',
            'color': '#000000',
            'fill-opacity': 1.0,
            'stroke-opacity': 1.0,
            'opacity': 1.0
        }

        self._tagReader.px2mm = self.px2mm
        self.parse_children(svgRootElement, node)

        # build result dictionary
        parse_results = {'boundarys':self.boundarys, 'dpi':round(25.4/self.px2mm)}
        if self.lasertags:
            parse_results['lasertags'] = self.lasertags

        return parse_results
示例#6
0
    def parse(self, svgstring, force_dpi=None):
        """ Parse a SVG document.

        This traverses through the document tree and collects all path
        data and converts it to polylines of the requested tolerance.

        Path data is returned as paths by color:
        {'#ff0000': [[path0, path1, ..], [path0, ..], ..]}
        Each path is a list of vertices which is a list of two floats.

        Determining Physical Dimensions
        -------------------------------
        SVG files may use physical units (mm, in) or screen units (px).
        For obvious reason former are preferred as these take out any
        guess-work of how to interpret any coordinates.

        A good SVG authoring app writes physical dimensions to file like this:
        - the svg tag has a width, height, viewBox attribute
        - width and height contains the page dimensions and unit
        - viewBox defines a rectangle with (x, y, width, height)
        - width/viewBox:width is the factor that needs to be applied to
          any (unit-less) coordinates in the file
        - x,y is a translation that needs to be applied to any coordinates

        One issue with svg documents is that they are not always clear on
        the physical dimensions. Often they lack or use px units in the
        width/height attributes (no units implies px units in the SVG
        standard). For example, it's possible to encounter px
        units in the file even when the authoring app interprets these
        as physical units (e.g mm). This means there is an implied DPI
        conversion in the app that we need to guess/know.

        The following strategy is used to get physical dimensions:

        1. from argument (force_dpi)
        2. from units of svg width/height and viewBox
        3. from hints of (known) originating apps
        4. from ratio of page and target size
        5. defaults to 90 DPI
        """
        self.px2mm = None
        self.boundarys = {}

        vb_x = None
        vb_y = None
        vb_w = None
        vb_h = None

        # parse xml
        svgRootElement = ET.fromstring(svgstring)
        tagName = self._tagReader._get_tag(svgRootElement)

        if tagName != 'svg':
            log.error("Invalid file, no 'svg' tag found.")
            return self.boundarys

        # 1. Get px2mm from argument
        if force_dpi is not None:
            self.px2mm = 25.4 / force_dpi
            log.info("SVG import forced to %s dpi." % (force_dpi))

        # Get width, height, viewBox for further processing
        if not self.px2mm:
            width = None
            height = None
            unit = ''

            # get width, height, unit
            width_str = svgRootElement.attrib.get('width')
            height_str = svgRootElement.attrib.get('height')
            if width_str and height_str:
                width, width_unit = parseScalar(width_str)
                height, height_unit = parseScalar(height_str)
                if width_unit != height_unit:
                    log.error("Conflicting units found.")
                unit = width_unit
                log.info("SVG w,h (unit) is %s,%s (%s)." %
                         (width, height, unit))

            # get viewBox
            # http://www.w3.org/TR/SVG11/coords.html#ViewBoxAttribute
            vb = svgRootElement.attrib.get('viewBox')
            if vb:
                vb_x, vb_y, vb_w, vb_h = parseFloats(vb)
                log.info("SVG viewBox (%s,%s,%s,%s)." %
                         (vb_x, vb_y, vb_w, vb_h))

        # 2. Get px2mm from width, height, viewBox
        if not self.px2mm:
            if (width and height) or vb:
                if not (width and height):
                    # default to viewBox
                    width = vb_w
                    height = vb_h
                if not vb:
                    # default to width, height, and no offset
                    vb_x = 0.0
                    vb_y = 0.0
                    vb_w = width
                    vb_h = height

                self.px2mm = width / vb_w

                if unit == 'mm':
                    # great, the svg file already uses mm
                    pass
                    log.info("px2mm by svg mm unit")
                elif unit == 'in':
                    # prime for inch to mm conversion
                    self.px2mm *= 25.4
                    log.info("px2mm by svg inch unit")
                elif unit == 'cm':
                    # prime for cm to mm conversion
                    self.px2mm *= 10.0
                    log.info("px2mm by svg cm unit")
                elif unit == 'px' or unit == '':
                    # no physical units in file
                    # we have to interpret user (px) units
                    # 3. For some apps we can make a good guess.
                    svghead = svgstring[0:400]
                    if 'Inkscape' in svghead:
                        self.px2mm *= 25.4 / 90.0
                        log.info("SVG exported with Inkscape -> 90dpi.")
                    elif 'Illustrator' in svghead:
                        self.px2mm *= 25.4 / 72.0
                        log.info("SVG exported with Illustrator -> 72dpi.")
                    elif 'Intaglio' in svghead:
                        self.px2mm *= 25.4 / 72.0
                        log.info("SVG exported with Intaglio -> 72dpi.")
                    elif 'CorelDraw' in svghead:
                        self.px2mm *= 25.4 / 96.0
                        log.info("SVG exported with CorelDraw -> 96dpi.")
                    elif 'Qt' in svghead:
                        self.px2mm *= 25.4 / 90.0
                        log.info("SVG exported with Qt lib -> 90dpi.")
                    else:
                        # give up in this step
                        self.px2mm = None
                else:
                    log.error("SVG with unsupported unit.")
                    self.px2mm = None

        # 4. Get px2mm by the ratio of svg size to target size
        if not self.px2mm and (width and height):
            self.px2mm = self._target_size[0] / width
            log.info("px2mm by target_size/page_size ratio")

        # 5. Fall back on px unit DPIs default value
        if not self.px2mm:
            log.warn(
                "Failed to determin physical dimensions -> defaulting to 90dpi."
            )
            self.px2mm = 25.4 / 90.0

        # adjust tolerances to px units
        self.tolerance2_px = (self.tolerance / self.px2mm) * (self.tolerance /
                                                              self.px2mm)

        # translation from viewbox
        if vb_x:
            tx = vb_x
        else:
            tx = 0.0
        if vb_y:
            ty = vb_y
        else:
            ty = 0.0

        # let the fun begin
        # recursively parse children
        # output will be in self.boundarys
        node = {
            'xformToWorld': [1, 0, 0, 1, tx, ty],
            'display': 'visible',
            'visibility': 'visible',
            'fill': '#000000',
            'stroke': '#000000',
            'color': '#000000',
            'fill-opacity': 1.0,
            'stroke-opacity': 1.0,
            'opacity': 1.0
        }

        self._tagReader.px2mm = self.px2mm
        self.parse_children(svgRootElement, node)

        # build result dictionary
        parse_results = {
            'boundarys': self.boundarys,
            'dpi': round(25.4 / self.px2mm)
        }
        if self.lasertags:
            parse_results['lasertags'] = self.lasertags

        return parse_results
示例#7
0
    def transformAttrib(self, node, attr, value):
        # http://www.w3.org/TR/SVG11/coords.html#EstablishingANewUserSpace
        xforms = []
        matches = self.re_findall_transforms(value)
        # this parses  something like "translate(50,50), rotate(56)"" to
        # [('translate(50,50)', 'translate', '50,50'), ('rotate(56)', 'rotate', '56')]
        for match in matches:
            xformKind = match[1]
            params = parseFloats(match[2])

            # translate
            if xformKind == 'translate':
                if len(params) == 1:
                    xforms.append([1, 0, 0, 1, params[0], params[0]])
                elif len(params) == 2:
                    xforms.append([1, 0, 0, 1, params[0], params[1]])
                else:
                    log.warn('translate skipped; invalid num of params')
            # rotate
            elif xformKind == 'rotate':
                if len(params) == 3:
                    angle = params[0] * self.DEG_TO_RAD
                    xforms.append([1, 0, 0, 1, params[1], params[2]])
                    xforms.append([
                        math.cos(angle),
                        math.sin(angle), -math.sin(angle),
                        math.cos(angle), 0, 0
                    ])
                    xforms.append([1, 0, 0, 1, -params[1], -params[2]])
                elif len(params) == 1:
                    angle = params[0] * self.DEG_TO_RAD
                    xforms.append([
                        math.cos(angle),
                        math.sin(angle), -math.sin(angle),
                        math.cos(angle), 0, 0
                    ])
                else:
                    log.warn('rotate skipped; invalid num of params')
            #scale
            elif xformKind == 'scale':
                if len(params) == 1:
                    xforms.append([params[0], 0, 0, params[0], 0, 0])
                elif len(params) == 2:
                    xforms.append([params[0], 0, 0, params[1], 0, 0])
                else:
                    log.warn('scale skipped; invalid num of params')
            # matrix
            elif xformKind == 'matrix':
                if len(params) == 6:
                    xforms.append(params)
                else:
                    log.warn('matrix skipped; invalid num of params')
            # skewX
            elif xformKind == 'skewX':
                if len(params) == 1:
                    angle = params[0] * self.DEG_TO_RAD
                    xforms.append([1, 0, math.tan(angle), 1, 0, 0])
                else:
                    log.warn('skewX skipped; invalid num of params')
            # skewY
            elif xformKind == 'skewY':
                if len(params) == 1:
                    angle = params[0] * self.DEG_TO_RAD
                    xforms.append([1, math.tan(angle), 0, 1, 0, 0])
                else:
                    log.warn('skewY skipped; invalid num of params')

        #calculate combined transformation matrix
        xform_combined = [1, 0, 0, 1, 0, 0]
        for xform in xforms:
            xform_combined = matrixMult(xform_combined, xform)

        # assign
        node['xform'] = xform_combined
示例#8
0
    def transformAttrib(self, node, attr, value):
        # http://www.w3.org/TR/SVG11/coords.html#EstablishingANewUserSpace
        xforms = []
        matches = self.re_findall_transforms(value)
        # this parses  something like "translate(50,50), rotate(56)"" to
        # [('translate(50,50)', 'translate', '50,50'), ('rotate(56)', 'rotate', '56')]
        for match in matches:
            xformKind = match[1]
            params = parseFloats(match[2])

            # translate
            if xformKind == 'translate':
                if len(params) == 1:
                    xforms.append([1, 0, 0, 1, params[0], params[0]])
                elif len(params) == 2:
                    xforms.append([1, 0, 0, 1, params[0], params[1]])
                else:
                    log.warn('translate skipped; invalid num of params')
            # rotate
            elif xformKind == 'rotate':
                if len(params) == 3:
                    angle = params[0] * self.DEG_TO_RAD
                    xforms.append([1, 0, 0, 1, params[1], params[2]])
                    xforms.append([math.cos(angle), math.sin(angle), -math.sin(angle), math.cos(angle), 0, 0])
                    xforms.append([1, 0, 0, 1, -params[1], -params[2]])
                elif len(params) == 1:
                    angle = params[0] * self.DEG_TO_RAD
                    xforms.append([math.cos(angle), math.sin(angle), -math.sin(angle), math.cos(angle), 0, 0])
                else:
                    log.warn('rotate skipped; invalid num of params')
            #scale
            elif xformKind == 'scale':
                if len(params) == 1:
                    xforms.append([params[0], 0, 0, params[0], 0, 0])
                elif len(params) == 2:
                    xforms.append([params[0], 0, 0, params[1], 0, 0])
                else:
                    log.warn('scale skipped; invalid num of params')
            # matrix
            elif xformKind == 'matrix':
                if len(params) == 6:
                    xforms.append(params)
                else:
                    log.warn('matrix skipped; invalid num of params')
            # skewX
            elif xformKind == 'skewX':
                if len(params) == 1:
                    angle = params[0]*self.DEG_TO_RAD
                    xforms.append([1, 0, math.tan(angle), 1, 0, 0])
                else:
                    log.warn('skewX skipped; invalid num of params')
            # skewY
            elif xformKind == 'skewY':
                if len(params) == 1:
                    angle = params[0]*self.DEG_TO_RAD
                    xforms.append([1, math.tan(angle), 0, 1, 0, 0])
                else:
                    log.warn('skewY skipped; invalid num of params')

        #calculate combined transformation matrix
        xform_combined = [1,0,0,1,0,0]
        for xform in xforms:
            xform_combined = matrixMult(xform_combined, xform)

        # assign
        node['xform'] = xform_combined