def read_svg( filename: str, quantization: float, simplify: bool = False, return_size: bool = False ) -> Union["LineCollection", Tuple["LineCollection", float, float]]: """Read a SVG file an return its content as a :class:`LineCollection` instance. All curved geometries are chopped in segments no longer than the value of *quantization*. Optionally, the geometries are simplified using Shapely, using the value of *quantization* as tolerance. Args: filename: path of the SVG file quantization: maximum size of segment used to approximate curved geometries simplify: run Shapely's simplify on loaded geometry return_size: if True, return a size 3 Tuple containing the geometries and the SVG width and height Returns: imported geometries, and optionally width and height of the SVG """ doc = svg.Document(filename) width, height, scale_x, scale_y, offset_x, offset_y = _calculate_page_size(doc.root) lc = _convert_flattened_paths( doc.flatten_all_paths(), quantization, scale_x, scale_y, offset_x, offset_y, simplify, ) if return_size: if width is None or height is None: _, _, width, height = lc.bounds() return lc, width, height else: return lc
def main () : for i in range(50) : doc = svg.Document(None) doc.set_viewbox('0 0 100 100') person = Person() doc = person.addToDocument(doc) with open(f'./Data/body{i+1}.svg', 'wb') as fd: fd.write(etree.tostring(doc.tree.getroot(), encoding='utf8', method='xml'))
def shrink_svg(svgfilepath, shrinkBorder): """ Shrink the SVG canvas to the size of the drawing """ document = svgpathtools.Document(svgfilepath) paths = document.paths() if len(paths) == 0: return bbox = paths[0].bbox() for x in paths: bbox = merge_bbox(bbox, x.bbox()) bbox = list(bbox) bbox[0] -= ki2svg(mm2ki(shrinkBorder)) bbox[1] += ki2svg(mm2ki(shrinkBorder)) bbox[2] -= ki2svg(mm2ki(shrinkBorder)) bbox[3] += ki2svg(mm2ki(shrinkBorder)) svg = document.tree root = svg.getroot() root.attrib["viewBox"] = "{} {} {} {}".format(bbox[0], bbox[2], bbox[1] - bbox[0], bbox[3] - bbox[2]) root.attrib["width"] = str(ki2mm(svg2ki(bbox[1] - bbox[0]))) + "mm" root.attrib["height"] = str(ki2mm(svg2ki(bbox[3] - bbox[2]))) + "mm" document.save(svgfilepath)
def read_multilayer_svg( filename: str, quantization: float, simplify: bool = False, return_size: bool = False ) -> Union["VectorData", Tuple["VectorData", float, float]]: """Read a multilayer SVG file and return its content as a :class:`VectorData` instance retaining the SVG's layer structure. Each top-level group is considered a layer. All non-group, top-level elements are imported in layer 1. Groups are matched to layer ID according their `inkscape:label` attribute, their `id` attribute or their appearing order, in that order of priority. Labels are stripped of non-numeric characters and the remaining is used as layer ID. Lacking numeric characters, the appearing order is used. If the label is 0, its changed to 1. All curved geometries are chopped in segments no longer than the value of *quantization*. Optionally, the geometries are simplified using Shapely, using the value of *quantization* as tolerance. Args: filename: path of the SVG file quantization: maximum size of segment used to approximate curved geometries simplify: run Shapely's simplify on loaded geometry return_size: if True, return a size 3 Tuple containing the geometries and the SVG width and height Returns: imported geometries, and optionally width and height of the SVG """ doc = svg.Document(filename) width, height, scale_x, scale_y, offset_x, offset_y = _calculate_page_size( doc.root) vector_data = VectorData() # non-group top level elements are loaded in layer 1 top_level_elements = doc.flatten_all_paths( group_filter=lambda x: x is doc.root) if top_level_elements: vector_data.add( _convert_flattened_paths( top_level_elements, quantization, scale_x, scale_y, offset_x, offset_y, simplify, ), 1, ) for i, g in enumerate(doc.root.iterfind("svg:g", SVG_NAMESPACE)): # compute a decent layer ID lid_str = re.sub( "[^0-9]", "", g.get("{http://www.inkscape.org/namespaces/inkscape}label") or "") if not lid_str: lid_str = re.sub("[^0-9]", "", g.get("id") or "") if lid_str: lid = int(lid_str) if lid == 0: lid = 1 else: lid = i + 1 vector_data.add( _convert_flattened_paths( flatten_group(g, g), quantization, scale_x, scale_y, offset_x, offset_y, simplify, ), lid, ) if return_size: if width is None or height is None: _, _, width, height = vector_data.bounds() or 0, 0, 0, 0 return vector_data, width, height else: return vector_data
def extract_lines(filepath, page_dir): doc = svgpathtools.Document(filepath) content_group = doc.get_group([None, "content"]) # bounds are in the format (xmin, xmax, ymin, ymax) page_bounds = doc.paths_from_group(content_group)[0].bbox() line_height = (page_bounds[3] - page_bounds[2]) / 15 lines = {} debug_lines = {} debug_nodes = {} for line_number in range(1, 16): lines[line_number] = svgpathtools.Path() debug_lines[line_number] = svgpathtools.Path() debug_nodes[line_number] = [] full_path = svgpathtools.Path() for doc_path in doc.paths(): for sub_path in doc_path: full_path.append(sub_path) indeterminate_paths = [] for _path in full_path.d().split("M"): if len(_path.strip()) > 0: glyph_path = svgpathtools.parse_path(f"M{_path}") line_number = detect_line_number(glyph_path.bbox(), page_bounds[2], line_height) if line_number: for path_command in glyph_path: lines[line_number].append(path_command) else: indeterminate_paths.append(glyph_path) indeterminate_paths = [ indeterminate_path_info(p, page_bounds[2], line_height) for p in indeterminate_paths ] indeterminate_num = len(indeterminate_paths) print(f"Found {indeterminate_num} indeterminate paths") indeterminate_paths.sort(key=lambda x: x[2]) for i, indeterminate_path in enumerate(indeterminate_paths): start_time = time.time() determination = detect_indeterminate_line(indeterminate_path, lines) if determination[0]: if determination[1]: debug_nodes[determination[0]].append(determination[1]) for path_command in indeterminate_path[0]: lines[determination[0]].append(path_command) debug_lines[determination[0]].append(path_command) print( f"Completed {i+1}/{indeterminate_num} path determations in {time.time() - start_time:.2} seconds" ) attribs = {"fill": "#000000", "fill-rule": "evenodd"} debug_attribs = { "stroke": "#FF0000", "stroke-width": "0.5", "fill-opacity": "0" } svg_attribs = { "xml:space": "preserve", "viewBox": "0 0 345 50", "width": "", "height": "" } for svg_line in lines.items(): if len(svg_line[1]) == 0: continue line_number = svg_line[0] y_pos = floor(svg_line[1].bbox()[2]) svg_attribs["viewBox"] = f"0 {y_pos} 345 50" filename = path.join(page_dir, f"{line_number}.svg") _paths = [svg_line[1]] _attribs = [attribs] _nodes = [] if debug_mode: _nodes = debug_nodes[line_number] if len(debug_lines[line_number]) > 0: _paths.append(debug_lines[line_number]) _attribs.append(debug_attribs) svgpathtools.wsvg(_paths, filename=filename, attributes=_attribs, svg_attributes=svg_attribs, nodes=_nodes) if debug_mode: filename = path.join(page_dir, "debug.svg") debug_paths = [full_path] svg_attribs["viewBox"] = "0 0 345 550" _attribs = [attribs] for line in range(1, 16): debug_paths.append( debug_overlay_path(page_bounds[2], line_height, line)) _attribs.append(debug_attribs) svgpathtools.wsvg( debug_paths, filename=filename, attributes=_attribs, svg_attributes=svg_attribs, )