Function to remove whitespace from svg
Opened this issue · 2 comments
I already wrote about whitespace in svg in #6, but I need zero whitespace.
Here is the function I use to remove all the whitespace from svg files generated by SchemDraw:
import inkex
import os
# This function processes an SVG file
def process_svg(svg_file):
try:
# Load the SVG file and get the root element
svg_element = inkex.load_svg(svg_file).getroot()
# Get the largest bounding box element
_, largest_bbox_element = get_largest_geometric_bbox(svg_element)
except:
# If the SVG file cannot be loaded, print an error message
print(f'Cannot load svg {svg_file}')
return
# Crop the SVG and save the processed file
crop_and_save_processed_svg(svg_element, largest_bbox_element, svg_file)
# This function crops the SVG and saves it
def crop_and_save_processed_svg(svg_element, largest_bbox_element, svg_file):
# Change the SVG canvas size according to the bounding box
if largest_bbox_element and hasattr(largest_bbox_element, 'bounding_box'):
bbox = largest_bbox_element.bounding_box()
svg_element.set('width', str(bbox.width))
svg_element.set('height', str(bbox.height))
svg_element.set('viewBox', f'{bbox.left} {bbox.top} {bbox.width} {bbox.height}')
# Write the changes to the same SVG file
with open(svg_file, 'w') as output_file:
output_file.write(svg_element.tostring().decode('utf-8'))
# Define the actions for Inkscape command line
my_actions = 'select-all;fit-canvas-to-selection;'
export_actions = my_actions + f'export-type:svg;export-filename:{svg_file};export-do;'
# Execute the Inkscape actions
inkex.command.inkscape(svg_file, actions=export_actions)
# This function returns the largest geometric bounding box
def get_largest_geometric_bbox(svg_element):
element_area = 0
largest_bbox_element_id = None
largest_bbox_element = None
# Create a list of potential elements to check
element_list = svg_element.xpath('//svg:path | //svg:polygon | //svg:polyline | //svg:rect | //svg:use | //svg:image')
# Determine the largest bounding box
for element in element_list:
if hasattr(element, 'bounding_box'):
bbox_area = float(element.bounding_box().width) * float(element.bounding_box().height)
if bbox_area > element_area:
element_area = bbox_area
largest_bbox_element_id = element.get_id()
largest_bbox_element = element
else:
continue
return largest_bbox_element_id, largest_bbox_element
# Example usage
svg_file_name = 'filename.svg'
if not os.path.exists(svg_file_name):
print(f'File Not Found: {svg_file_name}')
else:
process_svg(svg_file_name)
It is not optimal as there is need for Inkscape installation.
Nevertheless I'd like SchemDraw to allow for this usage, something like d.save('filename.svg', crop=True)
.
It works for all my plots, but the elements I use are limited to a little part of basic elements,
hence more testing might be necessary.
4c1a8f8 made some improvements to bounding box calculations that should help when there is text falling on the border, but only when text is drawn as paths (also requires new ziafont and ziamath commits). Using d.config(margin=0)
works to remove white space in many cases, but won't be perfect for example with the following two cases.
If text is drawn as svg elements, there's no way to know what font will be used by the renderer. May not work reliably with matplotlib either.
There are other situations where the bounding box won't be perfect, such as when a Bezier curve control point extends the box - it doesn't distinguish between actual and control points. Inkscape is probably smarter about tracing the actual curve path when cropping.