Skip to content

SVG Utilities


Commands:

Use the following functions by first importing the module in your script like this:

from harnice.utils import svg_utils
then use as written.

svg_utils.table(layout_dict, format_dict, columns_list, content_list, path_to_svg, contents_group_name)

This function is called when the user needs to build a general SVG table.

svg_utils.table(
    layout_dict,
    format_dict,
    columns_list,
    content_list,
    path_to_caller,
    svg_name
)

Arguments

  • layout_dict expects a dictionary describing in which direction the table is built
  • format_dict expects a dictionary containing a description of how you want your table to appear.
  • columns_list expects a list containing your column header content, width, and formatting rules.
  • content_list expects a list containing what is actually presented on your table.

Returns

  • A string of SVG primatives in xml format intended to look like a table.

1. Layout

The SVG origin (0,0) must exist somewhere in your table. Defining this correctly will help later when tables dynamically update with changing inputs.

example:

layout = {
    "origin_corner": "top-left",
    "build_direction": "down",
}
Both fields are required.

Origin Corner

The origin is defined to be at one of the four corners of the first row content[0]. Valid options:

  • top-left
  • top-right
  • bottom-left
  • bottom-right

Build Direction

When building a table, you can choose to build rows downwards (below the previous, positive y in svg coords) or upwards (above the previous, negative y in svg coords). The direction property defines this:

  • down → rows appear below the previous
  • up → new rows appear above the previous

2. Format

The format dictionary defines appearance and style of your table.

Any number of appearance keys can be defined and named with an identifier that is called when printing that row. This allows you to have rows that whose appearance can be varied dynamically with the table contents.

example:

format_dict={
    "globals": {
        "font_size": 11,
        "row_height": 20,
    },
    "header": {
        "font_weight":"B",
        "fill_color": "lightgray",
    },
    "row_with_bubble": {
        "row_height": 40,
    },
}

The only reserved key is globals which can optionally be used to define fallbacks for any row that does not have a style explicitly called out.

Format Arguments

Any of the following keys can be defined in any of the format dictionaries.

  • font_size (number, default=12) Default font size (px) for all text
  • font_family (string, default=helvetica) Default font family (e.g., "Arial", "Helvetica")
  • font_weight(BIU, default=None) Add each character for bold, italic, or underline
  • row_height (number, default=18) Self-explanatory (px)
  • padding (number, default=3) Default text inset from border if not center or middle justified
  • line_spacing (number, default=14) Vertical spacing between multi-line text entries
  • justify (left | center | right, default=center) Default horizontal alignment
  • valign (top | middle | bottom, default=center) Default vertical alignment
  • fill_color (default=white) Cell background color
  • stroke_color (default=black) Border line color
  • stroke_width (number, default=1) Border width
  • text_color (default=black) Default text color

Style Resolution Order

If something is defined at the row level, it takes precedent over some parameter defined at the column level, which takes precedent over a definition in key globals, if defined. If something is not defined at all, the above defaults will apply.

Color Standard

  • Default color: black
  • Accepted formats:
  • Named SVG colors https://www.w3.org/TR/SVG11/types.html#ColorKeywords
  • Hex values (#RGB or #RRGGBB)

3. Columns

The column argument is a list of dictionaries containing definition of how many columns there are, the order in which they exist, how to reference them, and any applicable formatting.

ex:

columns_list=[
    {
        "name": "rev"
        "width": 60,
        "justify": "center"
    },
    {
        "name": "updated"
        "width": 260,
    },
        "name": "status"
        "width": 120,
        "fill_color": "yellow",
    }
]

Column Arguments

Each field must have the following required keys:

  • name (string) Used to identify a column when defining contents later. Must be unique.
  • width (number) Self-explanatory (px)

You may add any formatting key as defined in the formatting section as needed.

Note that the order of the items in the list represents the order in which they will be printed from left to right, regardless of the layout you've chosen for this table.


4. Content Structure

The table content will be referenced from information stored in this argument. It is a list (rows) of dictionaries (columns).

content_list = [
    {
        "format_key": "header"
        "columns": {
            "rev": "REV",
            "updated": "UPDATED",
            "status": "STATUS",
        }
    },
    {
        "columns": {
            "rev": "1",
            "updated": "12/6/25",
            "status": "requires review",
        }
    },
    {
        "columns": {
            "rev": "2",
            "updated": ["12/6/25", "update incomplete"],
        }
    },
    {
        "format_key": "row_with_bubble",
        "columns": {
            "rev": {
                "instance_name": "rev3-bubble",
                "item_type": "flagnote"
            },
            "updated": "12/6/25",
            "status": "clear"
        }
    }
]

Content (the root argument) is a list. Each entry of the root list is representative of a row's worth of data.

Each entry of that list must contain the dictionary columns and may contain dictionary format_key.

format_key may only contain one value which corresponds to the name of a key in the format dictionary. It represents the appearance of that row. If it is not defined, the format of that row will fall back to globals and defaults. Custom formatting of individual cells is not supported.

columns is a dictionary that contains the actual content you want to appear in each column. The name of each key at this level must match one of the keys in the columns argument. It is agnostic to order, and by leaving a key out, simply nothing will appear in that cell. Existing formatting (cell fill and border) will still apply.

The value of each column key may take one of the following forms:

  • string or number → single-line text, prints directly
  • list[str] → multi-line text where the 0th element prints highest within the cell. Use format key line_spacing as needed.
  • dict → custom

Importing a Symbol into a Cell

If you add a dictionary to one of the content cells, content start/end groups will be written into your svg. This will allow the user to generate and/or import symbols into the table using their own logic, without regard for placement into the table.

```python

from your macro or wherever you're building the table from...

example_symbol = { "lib_repo": instance.get("lib_repo"), "item_type": "flagnote", "mpn": instance.get("mpn"), "instance_name": f"bubble{build_note_number}", "note_text": build_note_number, } symbols_to_build=[example_symbol]

svg_utils.table( layout_dict, format_dict, columns_list, content_list, os.dirname(path_to_table_svg), artifact_id )

user import logic

for symbol in symbols_to_build: path_to_symbol = #... library_utils.pull( symbol, update_instances_list=False, destination_directory=path_to_symbol, )

svg_utils.find_and_replace_svg_group(
    os.path.join(path_to_symbol, f"{symbol.get('instance_name')}-drawing.svg"),
    symbol.get("instance_name"),
    path_to_table_svg,
    symbol.get("instance_name")
)
svg_utils.add_entire_svg_file_contents_to_group(filepath, new_group_name)

Wraps the entire contents of an SVG file in a new group element.

Reads an SVG file, extracts its inner content (everything between <svg> tags), and wraps it in a new group element with start and end markers. The original file is modified in place.

Args: - filepath (str): Path to the SVG file to modify. - new_group_name (str): Name to use for the new group element (will create {new_group_name}-contents-start and {new_group_name}-contents-end markers).

Raises: - ValueError: If the file does not appear to be a valid SVG or has no inner contents.

svg_utils.find_and_replace_svg_group(source_svg_filepath, source_group_name, destination_svg_filepath, destination_group_name)

Copies SVG group content from one file to another, replacing existing group content.

Extracts the content between group markers in a source SVG file and replaces the content between corresponding markers in a destination SVG file. The group markers are identified by {group_name}-contents-start and {group_name}-contents-end IDs.

Args: - source_svg_filepath (str): Path to the source SVG file containing the group to copy. - source_group_name (str): Name of the source group to extract content from. - destination_svg_filepath (str): Path to the destination SVG file to modify. - destination_group_name (str): Name of the destination group to replace content in.

Returns: - int: Always returns 1 (success indicator).

Raises: - ValueError: If any of the required group markers are not found in the source or destination files.

svg_utils.draw_styled_path(spline_points, stroke_width_inches, appearance_dict, local_group)

Adds a styled spline path to the local group. Call as if you were appending any other element to an svg group.

Spline points are a list of dictionaries with x and y coordinates. [{"x": 0, "y": 0, "tangent": 0}, {"x": 1, "y": 1, "tangent": 0}] Appearance dictionary is a dictionary with the following keys: base_color, outline_color, parallelstripe, perpstripe, slash_lines Slash lines dictionary is a dictionary with the following keys: direction, angle, step, color, slash_width_inches

If appearance_dict is missing/empty, or if it does not define base_color, a rainbow placeholder spline is drawn.

svg_utils.draw_filleted_styled_path(points, stroke_width_inches, appearance_dict, local_group)

Draws a path through a list of corner points, inserting a circular fillet at each interior point, and appends a single SVG element to local_group.

Each point carries its own fillet radius. At every interior corner the straight segments are trimmed back by R * tan(alpha / 2) and a cubic Bezier arc approximation of that radius is inserted, where alpha is the turn angle at that corner. First and last points are endpoints — no fillet is applied there.

The setback is clamped to half the shorter adjacent segment so fillets never overlap each other regardless of how tight the geometry is.

Args: points: list of {"x": float, "y": float, "radius": float} Corner positions in SVG pixel coordinates. "radius" is ignored for the first and last points. stroke_width_inches: Stroke width in inches (multiplied by 96 for px). appearance_dict: Dict with at least {"base_color": }. local_group: List to which the SVG element is appended.

svg_utils.table(layout_dict, format_dict, columns_list, content_list, path_to_svg, contents_group_name)

This function is called when the user needs to build a general SVG table.

svg_utils.table(
    layout_dict,
    format_dict,
    columns_list,
    content_list,
    path_to_caller,
    svg_name
)

Arguments

  • layout_dict expects a dictionary describing in which direction the table is built
  • format_dict expects a dictionary containing a description of how you want your table to appear.
  • columns_list expects a list containing your column header content, width, and formatting rules.
  • content_list expects a list containing what is actually presented on your table.

Returns

  • A string of SVG primatives in xml format intended to look like a table.

1. Layout

The SVG origin (0,0) must exist somewhere in your table. Defining this correctly will help later when tables dynamically update with changing inputs.

example:

layout = {
    "origin_corner": "top-left",
    "build_direction": "down",
}
Both fields are required.

Origin Corner

The origin is defined to be at one of the four corners of the first row content[0]. Valid options:

  • top-left
  • top-right
  • bottom-left
  • bottom-right

Build Direction

When building a table, you can choose to build rows downwards (below the previous, positive y in svg coords) or upwards (above the previous, negative y in svg coords). The direction property defines this:

  • down → rows appear below the previous
  • up → new rows appear above the previous

2. Format

The format dictionary defines appearance and style of your table.

Any number of appearance keys can be defined and named with an identifier that is called when printing that row. This allows you to have rows that whose appearance can be varied dynamically with the table contents.

example:

format_dict={
    "globals": {
        "font_size": 11,
        "row_height": 20,
    },
    "header": {
        "font_weight":"B",
        "fill_color": "lightgray",
    },
    "row_with_bubble": {
        "row_height": 40,
    },
}

The only reserved key is globals which can optionally be used to define fallbacks for any row that does not have a style explicitly called out.

Format Arguments

Any of the following keys can be defined in any of the format dictionaries.

  • font_size (number, default=12) Default font size (px) for all text
  • font_family (string, default=helvetica) Default font family (e.g., "Arial", "Helvetica")
  • font_weight(BIU, default=None) Add each character for bold, italic, or underline
  • row_height (number, default=18) Self-explanatory (px)
  • padding (number, default=3) Default text inset from border if not center or middle justified
  • line_spacing (number, default=14) Vertical spacing between multi-line text entries
  • justify (left | center | right, default=center) Default horizontal alignment
  • valign (top | middle | bottom, default=center) Default vertical alignment
  • fill_color (default=white) Cell background color
  • stroke_color (default=black) Border line color
  • stroke_width (number, default=1) Border width
  • text_color (default=black) Default text color

Style Resolution Order

If something is defined at the row level, it takes precedent over some parameter defined at the column level, which takes precedent over a definition in key globals, if defined. If something is not defined at all, the above defaults will apply.

Color Standard

  • Default color: black
  • Accepted formats:
  • Named SVG colors https://www.w3.org/TR/SVG11/types.html#ColorKeywords
  • Hex values (#RGB or #RRGGBB)

3. Columns

The column argument is a list of dictionaries containing definition of how many columns there are, the order in which they exist, how to reference them, and any applicable formatting.

ex:

columns_list=[
    {
        "name": "rev"
        "width": 60,
        "justify": "center"
    },
    {
        "name": "updated"
        "width": 260,
    },
        "name": "status"
        "width": 120,
        "fill_color": "yellow",
    }
]

Column Arguments

Each field must have the following required keys:

  • name (string) Used to identify a column when defining contents later. Must be unique.
  • width (number) Self-explanatory (px)

You may add any formatting key as defined in the formatting section as needed.

Note that the order of the items in the list represents the order in which they will be printed from left to right, regardless of the layout you've chosen for this table.


4. Content Structure

The table content will be referenced from information stored in this argument. It is a list (rows) of dictionaries (columns).

content_list = [
    {
        "format_key": "header"
        "columns": {
            "rev": "REV",
            "updated": "UPDATED",
            "status": "STATUS",
        }
    },
    {
        "columns": {
            "rev": "1",
            "updated": "12/6/25",
            "status": "requires review",
        }
    },
    {
        "columns": {
            "rev": "2",
            "updated": ["12/6/25", "update incomplete"],
        }
    },
    {
        "format_key": "row_with_bubble",
        "columns": {
            "rev": {
                "instance_name": "rev3-bubble",
                "item_type": "flagnote"
            },
            "updated": "12/6/25",
            "status": "clear"
        }
    }
]

Content (the root argument) is a list. Each entry of the root list is representative of a row's worth of data.

Each entry of that list must contain the dictionary columns and may contain dictionary format_key.

format_key may only contain one value which corresponds to the name of a key in the format dictionary. It represents the appearance of that row. If it is not defined, the format of that row will fall back to globals and defaults. Custom formatting of individual cells is not supported.

columns is a dictionary that contains the actual content you want to appear in each column. The name of each key at this level must match one of the keys in the columns argument. It is agnostic to order, and by leaving a key out, simply nothing will appear in that cell. Existing formatting (cell fill and border) will still apply.

The value of each column key may take one of the following forms:

  • string or number → single-line text, prints directly
  • list[str] → multi-line text where the 0th element prints highest within the cell. Use format key line_spacing as needed.
  • dict → custom

Importing a Symbol into a Cell

If you add a dictionary to one of the content cells, content start/end groups will be written into your svg. This will allow the user to generate and/or import symbols into the table using their own logic, without regard for placement into the table.

```python

from your macro or wherever you're building the table from...

example_symbol = { "lib_repo": instance.get("lib_repo"), "item_type": "flagnote", "mpn": instance.get("mpn"), "instance_name": f"bubble{build_note_number}", "note_text": build_note_number, } symbols_to_build=[example_symbol]

svg_utils.table( layout_dict, format_dict, columns_list, content_list, os.dirname(path_to_table_svg), artifact_id )

user import logic

for symbol in symbols_to_build: path_to_symbol = #... library_utils.pull( symbol, update_instances_list=False, destination_directory=path_to_symbol, )

svg_utils.find_and_replace_svg_group(
    os.path.join(path_to_symbol, f"{symbol.get('instance_name')}-drawing.svg"),
    symbol.get("instance_name"),
    path_to_table_svg,
    symbol.get("instance_name")
)
svg_utils.label_svg(x, y, angle, text, text_color='black', background_color='white', outline='black', font_size=8, font_family='Arial, Helvetica, sans-serif', font_weight='normal')

Generate an SVG label: text in a rectangular box, aligned to a wire tangent.

Args: x: X position in SVG user units (same space as path geometry) y: Y position in SVG user units angle: Wire tangent angle in degrees; text is rendered parallel to wire text: Label content text_color: CSS color for the text background_color: Box fill color; use "black" for dark endpoint labels outline: Box stroke color font_size: Single control in SVG user units: nominal text height, rectangle height ≈ font_size + 2×margin, width from content + padding, with margin = 0.32×font_size. Passed as font-size on <text> (presentation attribute), not CSS style. font_family: CSS font-family string font_weight: CSS font-weight string

Returns: SVG string for the label group element

svg_utils.draw_segments_across_paths(input_instances, input_network_2D, stroke_width, segment_spacing, min_label_segment_length=30.0, label_font_size=8, from_node_id_field=None, to_node_id_field=None, trace_group_opacity=None)

Maps segment-type instances onto a 2D wire network, offsets parallel paths at shared nodes, draws each path and its labels, and returns raw SVG markup.

Args: input_instances: list of instance dicts, each with: - instance_name: str - from/to node fields (see from_node_id_field / to_node_id_field below) - (optional) parent_instance: str used to group sub-instances at nodes - (optional) appearance: dict or str passed to draw_styled_path. If it is missing/empty or omits base_color, the rainbow placeholder is drawn. - (optional) print_name: str label at the midpoint of each long sub-segment; omit to draw no midpoint labels - (optional) print_name_at_end_a: str label at the start of the path; omit to draw no start label - (optional) print_name_at_end_b: str label at the end of the path; omit to draw no end label input_network_2D: dict with: - 'nodes': {node_id: {'x': float, 'y': float}} (coordinates in inches) - 'segments': {seg_uuid: {'node_at_end_a': node_id, 'node_at_end_b': node_id}} stroke_width: inches, passed to draw_styled_path as stroke_width_inches (×96 for SVG stroke-width) segment_spacing: lateral spread between parallel wires at shared nodes (in input units) min_label_segment_length: minimum sub-segment length to render a midpoint label (in input units) label_font_size: SVG user units for label_svg (same space as node x/y). Block diagram callers should pass inches×96 so text scales like stroke (e.g. 0.5 * 96 with stroke_width=0.5). from_node_id_field: instance field whose value is the full from-node ID. If None, falls back to the KiCad compound format: f"{this_net_from_device_refdes}.{this_net_from_device_connector_name}" to_node_id_field: instance field whose value is the full to-node ID. If None, falls back to the KiCad compound format: f"{this_net_to_device_refdes}.{this_net_to_device_connector_name}" trace_group_opacity: optional opacity for each trace <g> (e.g. 0 for fully transparent on save). Omitted when None (default: no opacity attribute).

Returns: str: raw SVG markup for all paths and labels (no wrapper). Each trace is wrapped in <g id="…" data-harnice-trace-label="…" [opacity="…"]> using instance_name for the trace group name attribute.

Raises: ValueError if a required node or path is not found in the network.

svg_utils.draw_clean_line(from_coords, to_coords, scale=1, from_arrowhead=False, to_arrowhead=True, stroke='black', thickness=1)

Generates SVG markup for a line connecting two instance locations, with optional arrowheads.

Coordinates are given in inches and converted to pixels (96 dpi) with Y-axis flipped for the SVG coordinate system.

Args: from_coords (tuple): Starting (x, y) in inches. to_coords (tuple): Ending (x, y) in inches. scale (float): Scale factor for arrowhead size and line thickness. Defaults to 1. from_leader (bool): If True, draw an arrowhead at the start. Defaults to False. to_leader (bool): If True, draw an arrowhead at the end. Defaults to True. stroke (str): Stroke color. Defaults to "black". thickness (float): Line thickness in pixels before scaling. Defaults to 1.

Returns: str: SVG markup string, or empty string if coordinates are identical.