def __init__(self, shape, bbox=None): xfrm = shape.element.xfrm # From Section L.4.7.6 of ECMA-376 Part 1 (Bx, By) = ((xfrm.chOff.x, xfrm.chOff.y) if xfrm.chOff is not None else (0, 0)) (Dx, Dy) = ((xfrm.chExt.cx, xfrm.chExt.cy) if xfrm.chExt is not None else bbox) (Bx_, By_) = (xfrm.off.x, xfrm.off.y) (Dx_, Dy_) = (xfrm.ext.cx, xfrm.ext.cy) theta = radians(xfrm.rot) Fx = -1 if xfrm.flipH else 1 Fy = -1 if xfrm.flipV else 1 T_st = np.array( [[Dx_ / Dx, 0, Bx_ - (Dx_ / Dx) * Bx] if Dx != 0 else [1, 0, Bx_], [0, Dy_ / Dy, By_ - (Dy_ / Dy) * By] if Dy != 0 else [0, 1, By_], [0, 0, 1]]) U = np.array([[1, 0, -(Bx_ + Dx_ / 2.0)], [0, 1, -(By_ + Dy_ / 2.0)], [0, 0, 1]]) R = np.array([[cos(theta), -sin(theta), 0], [sin(theta), cos(theta), 0], [0, 0, 1]]) Flip = np.array([[Fx, 0, 0], [0, Fy, 0], [0, 0, 1]]) T_rf = np.linalg.inv(U) @ R @ Flip @ U super().__init__(T_rf @ T_st)
def __init__(self, transform): T = np.identity(3) if transform is not None: # A simple parser, assuming well-formed SVG tokens = transform.replace('(', ' ').replace(')', ' ').replace(',', ' ').split() pos = 0 while pos < len(tokens): xfm = tokens[pos] pos += 1 if xfm == 'matrix': params = tuple(float(x) for x in tokens[pos:pos + 6]) pos += 6 T = T @ np.array([[params[0], params[2], params[4]], [params[1], params[3], params[5]], [0, 0, 1]]) elif xfm == 'translate': x = float(tokens[pos]) pos += 1 if pos >= len(tokens) or tokens[pos].isalpha(): y = 0 else: y = float(tokens[pos]) pos += 1 T = T @ np.array([[1, 0, x], [0, 1, y], [0, 0, 1]]) elif xfm == 'scale': sx = float(tokens[pos]) pos += 1 if pos >= len(tokens) or tokens[pos].isalpha(): sy = sx else: sy = float(tokens[pos]) pos += 1 T = T @ np.array([[sx, 0, 0], [0, sy, 0], [0, 0, 1]]) elif xfm == 'rotate': a = radians(float(tokens[pos])) pos += 1 if pos >= len(tokens) or tokens[pos].isalpha(): T = T @ np.array([[cos(a), -sin(a), 0], [sin(a), cos(a), 0], [0, 0, 1]]) else: (cx, cy) = tuple(float(x) for x in tokens[pos:pos + 2]) pos += 2 T = T @ np.array([[ cos(a), -sin(a), -cx * cos(a) + cy * sin(a) + cx ], [sin(a), cos(a), -cx * sin(a) - cy * cos(a) + cy], [0, 0, 1]]) elif xfm == 'skewX': a = float(tokens[pos]) pos += 1 T = T @ np.array([[1, tan(a), 0], [0, 1, 0], [0, 0, 1]]) elif xfm == 'skewY': a = float(tokens[pos]) pos += 1 T = T @ np.array([[1, 0, 0], [tan(a), 1, 0], [0, 0, 1]]) else: raise ValueError( 'Invalid SVG transform: {}'.format(transform)) super().__init__(T)
def __path_from_tokens(self, tokens, transform): #=============================================== moved = False first_point = None current_point = None closed = False path = skia.Path() pos = 0 while pos < len(tokens): if isinstance(tokens[pos], str) and tokens[pos].isalpha(): cmd = tokens[pos] pos += 1 # Else repeat previous command with new coordinates # with `moveTo` becoming `lineTo` elif cmd == 'M': cmd = 'L' elif cmd == 'm': cmd = 'l' if cmd not in ['s', 'S']: second_cubic_control = None if cmd not in ['t', 'T']: second_quad_control = None if cmd in ['a', 'A']: params = [float(x) for x in tokens[pos:pos + 7]] pos += 7 pt = params[5:7] if cmd == 'a': pt[0] += current_point[0] pt[1] += current_point[1] phi = radians(params[2]) if moved: path.moveTo(*transform.transform_point(current_point)) moved = False (rx, ry) = transform.scale_length(params[0:2]) path.arcTo( rx, ry, degrees(transform.rotate_angle(phi)), skia.Path.ArcSize.kSmall_ArcSize if params[3] == 0 else skia.Path.ArcSize.kLarge_ArcSize, skia.PathDirection.kCCW if params[4] == 0 else skia.PathDirection.kCW, *transform.transform_point(pt)) current_point = pt elif cmd in ['c', 'C', 's', 'S']: if moved: path.moveTo(*transform.transform_point(current_point)) moved = False if cmd in ['c', 'C']: n_params = 6 coords = [] else: n_params = 4 if second_cubic_control is None: coords = list(transform.transform_point(current_point)) else: coords = list( transform.transform_point( reflect_point(second_cubic_control, current_point))) params = [float(x) for x in tokens[pos:pos + n_params]] pos += n_params for n in range(0, n_params, 2): pt = params[n:n + 2] if cmd.islower(): pt[0] += current_point[0] pt[1] += current_point[1] if n == (n_params - 4): second_cubic_control = pt coords.extend(transform.transform_point(pt)) path.cubicTo(*coords) current_point = pt elif cmd in ['l', 'L', 'h', 'H', 'v', 'V']: if cmd in ['l', 'L']: params = [float(x) for x in tokens[pos:pos + 2]] pos += 2 pt = params[0:2] if cmd == 'l': pt[0] += current_point[0] pt[1] += current_point[1] else: param = float(tokens[pos]) pos += 1 if cmd == 'h': param += current_point[0] elif cmd == 'v': param += current_point[1] if cmd in ['h', 'H']: pt = [param, current_point[1]] else: pt = [current_point[0], param] if moved: path.moveTo(*transform.transform_point(current_point)) moved = False path.lineTo(*transform.transform_point(pt)) current_point = pt elif cmd in ['m', 'M']: params = [float(x) for x in tokens[pos:pos + 2]] pos += 2 pt = params[0:2] if first_point is None: # First `m` in a path is treated as `M` first_point = pt else: if cmd == 'm': pt[0] += current_point[0] pt[1] += current_point[1] current_point = pt moved = True elif cmd in ['q', 'Q', 't', 'T']: if moved: path.moveTo(*transform.transform_point(current_point)) moved = False if cmd in ['t', 'T']: n_params = 4 coords = [] else: n_params = 2 if second_quad_control is None: coords = list(transform.transform_point(current_point)) else: coords = list( transform.transform_point( reflect_point(second_quad_control, current_point))) params = [float(x) for x in tokens[pos:pos + n_params]] pos += n_params for n in range(0, n_params, 2): pt = params[n:n + 2] if cmd.islower(): pt[0] += current_point[0] pt[1] += current_point[1] if n == (n_params - 4): second_quad_control = pt coords.extend(transform.transform_point(pt)) path.quadTo(*coords) current_point = pt elif cmd in ['z', 'Z']: if first_point is not None and current_point != first_point: pass #path.close() closed = True first_point = None else: print('Unknown path command: {}'.format(cmd)) return path
def __get_geometry(self, element, properties, transform): #======================================================= ## ## Returns path element as a `shapely` object. ## coordinates = [] bezier_segments = [] moved = False first_point = None current_point = None closed = False path_tokens = [] T = transform@SVGTransform(element.attrib.get('transform')) if element.tag == SVG_NS('path'): path_tokens = list(parse_svg_path(element.attrib.get('d', ''))) elif element.tag == SVG_NS('rect'): x = length_as_pixels(element.attrib.get('x', 0)) y = length_as_pixels(element.attrib.get('y', 0)) width = length_as_pixels(element.attrib.get('width', 0)) height = length_as_pixels(element.attrib.get('height', 0)) rx = length_as_pixels(element.attrib.get('rx')) ry = length_as_pixels(element.attrib.get('ry')) if width == 0 or height == 0: return None if rx is None and ry is None: rx = ry = 0 elif ry is None: ry = rx elif rx is None: rx = ry rx = min(rx, width/2) ry = min(ry, height/2) if rx == 0 and ry == 0: path_tokens = ['M', x, y, 'H', x+width, 'V', y+height, 'H', x, 'V', y, 'Z'] else: path_tokens = ['M', x+rx, y, 'H', x+width-rx, 'A', rx, ry, 0, 0, 1, x+width, y+ry, 'V', y+height-ry, 'A', rx, ry, 0, 0, 1, x+width-rx, y+height, 'H', x+rx, 'A', rx, ry, 0, 0, 1, x, y+height-ry, 'V', y+ry, 'A', rx, ry, 0, 0, 1, x+rx, y, 'Z'] elif element.tag == SVG_NS('line'): x1 = length_as_pixels(element.attrib.get('x1', 0)) y1 = length_as_pixels(element.attrib.get('y1', 0)) x2 = length_as_pixels(element.attrib.get('x2', 0)) y2 = length_as_pixels(element.attrib.get('y2', 0)) path_tokens = ['M', x1, y1, x2, y2] elif element.tag == SVG_NS('polyline'): points = element.attrib.get('points', '').replace(',', ' ').split() path_tokens = ['M'] + points elif element.tag == SVG_NS('polygon'): points = element.attrib.get('points', '').replace(',', ' ').split() path_tokens = ['M'] + points + ['Z'] elif element.tag == SVG_NS('circle'): cx = length_as_pixels(element.attrib.get('cx', 0)) cy = length_as_pixels(element.attrib.get('cy', 0)) r = length_as_pixels(element.attrib.get('r', 0)) if r == 0: return None path_tokens = ['M', cx+r, cy, 'A', r, r, 0, 0, 0, cx, cy-r, 'A', r, r, 0, 0, 0, cx-r, cy, 'A', r, r, 0, 0, 0, cx, cy+r, 'A', r, r, 0, 0, 0, cx+r, cy, 'Z'] elif element.tag == SVG_NS('ellipse'): cx = length_as_pixels(element.attrib.get('cx', 0)) cy = length_as_pixels(element.attrib.get('cy', 0)) rx = length_as_pixels(element.attrib.get('rx', 0)) ry = length_as_pixels(element.attrib.get('ry', 0)) if rx == 0 or ry == 0: return None path_tokens = ['M', cx+rx, cy, 'A', rx, ry, 0, 0, 0, cx, cy-ry, 'A', rx, ry, 0, 0, 0, cx-rx, cy, 'A', rx, ry, 0, 0, 0, cx, cy+ry, 'A', rx, ry, 0, 0, 0, cx+rx, cy, 'Z'] elif element.tag == SVG_NS('image'): if 'id' in properties or 'class' in properties: width = length_as_pixels(element.attrib.get('width', 0)) height = length_as_pixels(element.attrib.get('height', 0)) path_tokens = ['M', 0, 0, 'H', width, 'V', height, 'H', 0, 'V', 0, 'Z'] pos = 0 while pos < len(path_tokens): if isinstance(path_tokens[pos], str) and path_tokens[pos].isalpha(): cmd = path_tokens[pos] pos += 1 # Else repeat previous command with new coordinates # with `moveTo` becoming `lineTo` elif cmd == 'M': cmd = 'L' elif cmd == 'm': cmd = 'l' if cmd not in ['s', 'S']: second_cubic_control = None if cmd not in ['t', 'T']: second_quad_control = None if cmd in ['a', 'A']: params = [float(x) for x in path_tokens[pos:pos+7]] pos += 7 pt = params[5:7] if cmd == 'a': pt[0] += current_point[0] pt[1] += current_point[1] phi = radians(params[2]) path = bezier_path_from_arc_endpoints(tuple2(*params[0:2]), phi, *params[3:5], tuple2(*current_point), tuple2(*pt), T) bezier_segments.extend(path.asSegments()) coordinates.extend(bezier_sample(path)) current_point = pt elif cmd in ['c', 'C', 's', 'S']: coords = [BezierPoint(*T.transform_point(current_point))] if cmd in ['c', 'C']: n_params = 6 else: n_params = 4 if second_cubic_control is None: coords.append(BezierPoint(*T.transform_point(current_point))) else: coords.append(BezierPoint(*T.transform_point( reflect_point(second_cubic_control, current_point)))) params = [float(x) for x in path_tokens[pos:pos+n_params]] pos += n_params for n in range(0, n_params, 2): pt = params[n:n+2] if cmd.islower(): pt[0] += current_point[0] pt[1] += current_point[1] if n == (n_params - 4): second_cubic_control = pt coords.append(BezierPoint(*T.transform_point(pt))) bz = CubicBezier(*coords) bezier_segments.append(bz) coordinates.extend(bezier_sample(bz)) current_point = pt elif cmd in ['l', 'L', 'h', 'H', 'v', 'V']: if cmd in ['l', 'L']: params = [float(x) for x in path_tokens[pos:pos+2]] pos += 2 pt = params[0:2] if cmd == 'l': pt[0] += current_point[0] pt[1] += current_point[1] else: param = float(path_tokens[pos]) pos += 1 if cmd == 'h': param += current_point[0] elif cmd == 'v': param += current_point[1] if cmd in ['h', 'H']: pt = [param, current_point[1]] else: pt = [current_point[0], param] if moved: coordinates.append(T.transform_point(current_point)) moved = False coordinates.append(T.transform_point(pt)) current_point = pt elif cmd in ['m', 'M']: params = [float(x) for x in path_tokens[pos:pos+2]] pos += 2 pt = params[0:2] if first_point is None: # First `m` in a path is treated as `M` first_point = pt else: if cmd == 'm': pt[0] += current_point[0] pt[1] += current_point[1] current_point = pt moved = True elif cmd in ['q', 'Q', 't', 'T']: coords = [BezierPoint(*T.transform_point(current_point))] if cmd in ['q', 'Q']: n_params = 4 else: n_params = 2 if second_quad_control is None: coords.append(BezierPoint(*T.transform_point(current_point))) else: coords.append(BezierPoint(*T.transform_point( reflect_point(second_quad_control, current_point)))) params = [float(x) for x in path_tokens[pos:pos+n_params]] pos += n_params for n in range(0, n_params, 2): pt = params[n:n+2] if cmd.islower(): pt[0] += current_point[0] pt[1] += current_point[1] if n == (n_params - 4): second_quad_control = pt coords.append(BezierPoint(*T.transform_point(pt))) bz = QuadraticBezier(*coords) bezier_segments.append(bz) coordinates.extend(bezier_sample(bz)) current_point = pt elif cmd in ['z', 'Z']: if first_point is not None and current_point != first_point: coordinates.append(T.transform_point(first_point)) closed = True first_point = None else: log.warn('Unknown path command: {}'.format(cmd)) if len(bezier_segments) > 0: properties['bezier-path'] = BezierPath.fromSegments(bezier_segments) if closed and len(coordinates) >= 3: geometry = shapely.geometry.Polygon(coordinates) elif properties.get('closed', False) and len(coordinates) >= 3: # Return a polygon if flagged as `closed` coordinates.append(coordinates[0]) geometry = shapely.geometry.Polygon(coordinates) elif len(coordinates) >= 2: geometry = shapely.geometry.LineString(coordinates) else: geometry = None return geometry