#!/usr/bin/env python3 # Copyright (c) 2011 Qtrac Ltd. All rights reserved. # This program or module is free software: you can redistribute it and/or # modify it under the terms of the GNU General Public License as published # by the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. It is provided for educational # purposes and is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # General Public License for more details. import itertools import Block __all__ = ["save_blocks_as_svg"] SVG_START = """ """ SVG_END = "\n" SVG_RECT = """""" SVG_TEXT = """{text}""" class Cell: def __init__(self, row=0, column=0, rows=0, columns=0, text=None, color=None): self.row = row self.column = column self.rows = rows self.columns = columns self.text = text self.color = color def __repr__(self): return ("Cell({0.row!r}, {0.column!r}, {0.rows!r}, " "{0.columns!r}, {0.text!r}, {0.color!r})" .format(self)) def __lt__(self, other): if self.row != other.row: return self.row < other.row if self.column != other.column: return self.column < other.column if self.rows != other.rows: return self.rows < other.rows if self.columns != other.columns: return self.columns < other.columns return self.text < other.text def populate_cells(block, row, column, cells): cell = Cell(row, column, 1, 1, block.name, block.color) if block.children: row += 1 for child in block.children: if Block.is_new_row(child): row += 1 column = 0 else: child_cell = populate_cells(child, row, column, cells) column += 1 cell.rows += child_cell.rows cell.columns += child_cell.columns cells.append(cell) return cell def cells_for_blocks(blocks): cells = [] populate_cells(blocks, 0, 0, cells) return sorted(cells) def compute_widths_and_rows(cells, SCALE_BY): columns = 0 widths = [0] * cells[0].columns for row, group in itertools.groupby(cells[1:], lambda x: x.row): offset = 0 for x, cell in enumerate(group): columns = max(columns, x) width = len(cell.text) // cell.columns for column in range(cell.columns): widths[column + offset] = max(width, widths[column + offset]) offset += cell.columns widths = [width * SCALE_BY for width in widths[:columns + 1]] return widths, row + 1 def compute_x_offsets_for_columns(widths): x_for_column = [0] x = 0 for width in widths: x += width x_for_column.append(x) return x_for_column def populate_svg(svg, cells, widths, x_for_column, ROW_HEIGHT): for cell in cells: if cell.text is None or cell.text == "": continue x = x_for_column[cell.column] y = cell.row * ROW_HEIGHT width = sum(widths[cell.column:cell.column + cell.columns]) height = cell.rows * ROW_HEIGHT fill = cell.color stroke = "black" svg.append(SVG_RECT.format(**locals())) x += (width // 2) y += 2 + ROW_HEIGHT // 2 fontsize = ROW_HEIGHT // 2 text = cell.text svg.append(SVG_TEXT.format(**locals())) def save_blocks_as_svg(blocks, filename): if not blocks.has_children(): return False SCALE_BY = 4 ROW_HEIGHT = 10 cells = cells_for_blocks(blocks) widths, rows = compute_widths_and_rows(cells, SCALE_BY) x_for_column = compute_x_offsets_for_columns(widths) width = x_for_column.pop() height = rows * ROW_HEIGHT pxwidth = width * SCALE_BY pxheight = height * SCALE_BY svg = [SVG_START.format(**locals())] populate_svg(svg, cells, widths, x_for_column, ROW_HEIGHT) svg.append(SVG_END) try: with open(filename, "wt", encoding="utf8") as fh: fh.write("\n".join(svg)) except EnvironmentError as err: print("error: {0}".format(err), file=sys.stderr) return False return True