3 # A Diagram abstraction based on Cairo
 
   5 # Copyright (C) 2015  Antonio Ospite <ao2@ao2.it>
 
   7 # This program is free software: you can redistribute it and/or modify
 
   8 # it under the terms of the GNU General Public License as published by
 
   9 # the Free Software Foundation, either version 3 of the License, or
 
  10 # (at your option) any later version.
 
  12 # This program is distributed in the hope that it will be useful,
 
  13 # but WITHOUT ANY WARRANTY; without even the implied warranty of
 
  14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
  15 # GNU General Public License for more details.
 
  17 # You should have received a copy of the GNU General Public License
 
  18 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
  24 class Diagram(object):
 
  26     def __init__(self, width, height, background=[1, 1, 1]):
 
  29         self.background = background
 
  31         # TODO: use a RecordingSurface
 
  32         self.surface = cairo.SVGSurface(None, width, height)
 
  33         self.cr = cr = cairo.Context(self.surface)
 
  35         # convert to left-bottom-origin cartesian coordinates
 
  36         cr.translate(0, self.height)
 
  39         cr.select_font_face("Georgia", cairo.FONT_SLANT_NORMAL,
 
  40                             cairo.FONT_WEIGHT_NORMAL)
 
  43         # Adjust the font matrix to left-bottom origin
 
  44         M = cr.get_font_matrix()
 
  51         r, g, b, a = self.color_to_rgba(self.background)
 
  52         cr.set_source_rgba(r, g, b, a)
 
  55     def save_svg(self, filename):
 
  56         surface = cairo.SVGSurface(filename + '.svg', self.width, self.height)
 
  57         cr = cairo.Context(surface)
 
  58         cr.set_source_surface(self.surface, 0, 0)
 
  61     def save_png(self, filename):
 
  62         surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, self.width, self.height)
 
  63         cr = cairo.Context(surface)
 
  64         cr.set_source_surface(self.surface, 0, 0)
 
  66         surface.write_to_png(filename + '.png')
 
  71         f = StringIO.StringIO()
 
  72         surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, self.width, self.height)
 
  73         cr = cairo.Context(surface)
 
  74         cr.set_source_surface(self.surface, 0, 0)
 
  76         surface.write_to_png(f)
 
  81     def get_regular_polygon(self, x, y, sides, r, theta0=0.0):
 
  82         theta = 2 * pi / sides
 
  85         for i in range(0, sides):
 
  86             px = x + r * sin(theta0 + i * theta)
 
  87             py = y + r * cos(theta0 + i * theta)
 
  88             verts.append((px, py))
 
  92     def color_to_rgba(self, color):
 
  94             return color[0], color[1], color[2], 1.0
 
  96             return color[0], color[1], color[2], color[3]
 
 100     def draw_polygon(self, verts, stroke_color=[0, 0, 0], fill_color=None):
 
 105             cr.move_to(v[0], v[1])
 
 107                 cr.line_to(v[0], v[1])
 
 110             r, g, b, a = self.color_to_rgba(fill_color)
 
 111             cr.set_source_rgba(r, g, b, a)
 
 115         for i in range(0, n):
 
 117             v2 = verts[(i + 1) % n]
 
 118             cr.move_to(v1[0], v1[1])
 
 119             cr.line_to(v2[0], v2[1])
 
 121         r, g, b, a = self.color_to_rgba(stroke_color)
 
 122         cr.set_source_rgba(r, g, b, a)
 
 125     def draw_star(self, cx, cy, verts, stroke_color=[0, 0, 0]):
 
 131             cr.line_to(v[0], v[1])
 
 134         r, g, b, a = self.color_to_rgba(stroke_color)
 
 135         cr.set_source_rgba(r, g, b, a)
 
 138     def draw_dot(self, cx, cy, size=10.0, fill_color=[0, 0, 0, 0.5]):
 
 142         cr.set_source_rgba(fill_color[0], fill_color[1], fill_color[2],
 
 144         cr.arc(cx, cy, size, 0, 2 * pi)
 
 148     def normalized_angle_01(self, theta):
 
 149         return fmod(theta, 2 * pi) / (2 * pi)
 
 151     def draw_line(self, x1, y1, x2, y2, stroke_color=[0, 0, 0, 1]):
 
 153         r, g, b, a = self.color_to_rgba(stroke_color)
 
 154         cr.set_source_rgba(r, g, b, a)
 
 159     def draw_rect_from_center(self, cx, cy, width, height, theta=0,
 
 160                               fill=True, fill_color=[1, 1, 1],
 
 161                               stroke=False, stroke_color=[0, 0, 0]):
 
 167         rx = cx - (mx * cos(theta) - my * sin(theta))
 
 168         ry = cy - (mx * sin(theta) + my * cos(theta))
 
 175             cr.rectangle(0, 0, width, height)
 
 176             cr.set_source_rgba(fill_color[0], fill_color[1], fill_color[2], 0.8)
 
 180             cr.rectangle(0, 0, width, height)
 
 181             cr.set_source_rgba(stroke_color[0], stroke_color[1], stroke_color[2], 0.5)
 
 183             self.draw_dot(0, 0, 3.0, list(stroke_color) + [0.5])
 
 187     def draw_rect(self, x, y, width, height, fill=True, fill_color=[1, 1, 1],
 
 188                   stroke=False, stroke_color=[0, 0, 0]):
 
 195             cr.rectangle(0, 0, width, height)
 
 196             cr.set_source_rgba(fill_color[0], fill_color[1], fill_color[2], 0.8)
 
 200             cr.rectangle(0, 0, width, height)
 
 201             cr.set_source_rgba(stroke_color[0], stroke_color[1], stroke_color[2], 0.5)
 
 203             self.draw_dot(0, 0, 3.0, list(stroke_color) + [0.5])
 
 207     def draw_centered_text(self, cx, cy, text, theta=0, color=[0, 0, 0], bounding_box=False):
 
 210         x_bearing, y_bearing, width, height, x_advance = cr.text_extents(text)[:5]
 
 211         ascent, descent = cr.font_extents()[:2]
 
 213         # The offset of the lower-left corner of the text.
 
 214         # NOTE: y is kept on the baseline
 
 215         tx = width / 2.0 + x_bearing
 
 218         # Angles are intended clockwise by the caller, but the trigonometric
 
 219         # functions below consider angles counter-clockwise
 
 222         # The coordinate of the lower-left corner of the rotated rectangle
 
 223         rx = cx - tx * cos(theta) + ty * sin(theta)
 
 224         ry = cy - tx * sin(theta) - ty * cos(theta)
 
 229         self.draw_rect(0, -descent, width, ascent, fill_color=[1, 1, 1, 0.1], stroke=bounding_box)
 
 230         cr.set_source_rgba(color[0], color[1], color[2], 0.8)
 
 239 if __name__ == "__main__":
 
 240     diagram = Diagram(400, 400)
 
 251     advance = diagram.draw_centered_text(x_offset, y, "Ciao", theta, bounding_box=True)
 
 254     advance = diagram.draw_centered_text(x_offset, y, "____", theta + pi / 4, bounding_box=True)
 
 257     advance = diagram.draw_centered_text(x_offset, y, "jxpqdlf", theta + pi / 2, bounding_box=True)
 
 260     advance = diagram.draw_centered_text(x_offset, y, "pppp", theta + 3 * pi / 4, bounding_box=True)
 
 263     advance = diagram.draw_centered_text(x_offset, y, "dddd", theta + pi, bounding_box=True)
 
 266     advance = diagram.draw_centered_text(x_offset, y, "Jjjj", theta + 5 * pi / 4, bounding_box=True)
 
 269     advance = diagram.draw_centered_text(x_offset, y, "1369", theta + 3 * pi / 2, bounding_box=True)
 
 272     advance = diagram.draw_centered_text(x_offset, y, "qqqq", theta + 7 * pi / 4, bounding_box=True)
 
 275     diagram.draw_line(0, y, 400, y, [0, 0, 1, 0.2])