# Copyright (c) 2012, 2018, Oracle and/or its affiliates. All rights reserved. # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License, version 2.0, # as published by the Free Software Foundation. # # This program is also distributed with certain software (including # but not limited to OpenSSL) that is licensed under separate terms, as # designated in a particular file or component or in included license # documentation. The authors of MySQL hereby grant you an additional # permission to link the program and your derivative works with the # separately licensed software that they have included with MySQL. # This program 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, version 2.0, for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software Foundation, Inc., # 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA import cairo from math import ceil, floor, fabs, atan, pi class Surface(object): def __del__(self): cairo.cairo_surface_destroy(self.s) def status(self): return cairo.cairo_surface_status(self.s) def write_to_png(self, file): cairo.cairo_surface_write_to_png(self.s, file) class ImageSurface(Surface): def __init__(self, format=cairo.CAIRO_FORMAT_ARGB32, width=None, height=None, surfobj=None): Surface.__init__(self) if surfobj: self.s = surfobj else: self.s = cairo.cairo_image_surface_create(format, width, height) def get_width(self): return cairo.cairo_image_surface_get_width(self.s) def get_height(self): return cairo.cairo_image_surface_get_height(self.s) @classmethod def from_png(self, png): return ImageSurface(surfobj = cairo.cairo_image_surface_create_from_png(png)) class Context(object): def __init__(self, arg): if isinstance(arg, Surface): self.cr = cairo.cairo_create(arg.s) self._owned = True else: self.cr = arg self._owned = False def __del__(self): if self._owned: cairo.cairo_destroy(self.cr) def new_path(self): cairo.cairo_new_path(self.cr) def new_sub_path(self): cairo.cairo_new_sub_path(self.cr) def close_path(self): cairo.cairo_close_path(self.cr) def set_fill_rule(self, rule): cairo.cairo_set_fill_rule(self.cr, rule) def move_to(self, x, y): cairo.cairo_move_to(self.cr, x, y) def rel_move_to(self, x, y): cairo.cairo_rel_move_to(self.cr, x, y) def line_to(self, x, y): cairo.cairo_line_to(self.cr, x, y) def curve_to(self, x1, y1, x2, y2, x3, y3): cairo.cairo_curve_to(self.cr, x1, y1, x2, y2, x3, y3) def arc(self, xc, yc, r, a1, a2): cairo.cairo_arc(self.cr, xc, yc, r, a1, a2) def arc_negative(self, xc, yc, r, a1, a2): cairo.cairo_arc_negative(self.cr, xc, yc, r, a1, a2) def scale(self, x, y): cairo.cairo_scale(self.cr, x, y) def translate(self, x, y): cairo.cairo_translate(self.cr, x, y) def rotate(self, d): cairo.cairo_rotate(self.cr, d) def fill(self): cairo.cairo_fill(self.cr) def fill_preserve(self): cairo.cairo_fill_preserve(self.cr) def paint(self): cairo.cairo_paint(self.cr) def rectangle(self, x, y, w, h): cairo.cairo_rectangle(self.cr, x, y, w, h) def save(self): cairo.cairo_save(self.cr) def restore(self): cairo.cairo_restore(self.cr) def set_dash(self, dashes, offset): cairo.cairo_set_dash(self.cr, dashes, offset) def set_font(self, family, italic=False, bold=False): cairo.cairo_select_font_face(self.cr, family, cairo.CAIRO_FONT_SLANT_ITALIC if italic else cairo.CAIRO_FONT_SLANT_NORMAL, cairo.CAIRO_FONT_WEIGHT_BOLD if bold else cairo.CAIRO_FONT_WEIGHT_NORMAL) def set_font_size(self, size): cairo.cairo_set_font_size(self.cr, size) def set_line_width(self, w): cairo.cairo_set_line_width(self.cr, w) def set_source(self, pat): cairo.cairo_set_source(self.cr, pat.p) def set_source_surface(self, sur, x, y): cairo.cairo_set_source_surface(self.cr, sur.s, x, y) def set_source_rgb(self, r, g, b): cairo.cairo_set_source_rgb(self.cr, r, g, b) def set_source_rgba(self, r, g, b, a): cairo.cairo_set_source_rgba(self.cr, r, g, b, a) def mask(self, pattern): cairo.cairo_mask(self.cr, pattern.p) def mask_surface(self, surface, x, y): cairo.cairo_mask_surface(self.cr, surface.s, x, y) def show_text(self, text): cairo.cairo_show_text(self.cr, text) def rounded_rect(self, x, y, w, h, r): self.move_to(x+r, y) self.line_to(x+w-r, y) self.curve_to(x+w, y, x+w, y, x+w, y+r) self.line_to(x+w, y+h-r) self.curve_to(x+w, y+h, x+w, y+h, x+w-r, y+h) self.line_to(x+r, y+h) self.curve_to(x, y+h, x, y+h, x, y+h-r) self.line_to(x, y+r) self.curve_to(x, y, x, y, x+r, y) def show_text_lines_at(self, x, y, text, spacing, line_height= None): if line_height is None: line_height = 0 for line in text.split("\n"): extents = self.text_extents(line) line_height = max(line_height, int(extents.height + (extents.height + extents.y_advance + extents.y_bearing))) for line in text.split("\n"): extents = self.text_extents(line) self.move_to(x, y + int(line_height - (extents.height + extents.y_bearing))) self.show_text(line) y += line_height + spacing def show_rtext_lines_at(self, x, y, text, spacing, line_height= None): if line_height is None: line_height = 0 for line in text.split("\n"): extents = self.text_extents(line) line_height = max(line_height, int(extents.height + (extents.height + extents.y_advance + extents.y_bearing))) for line in text.split("\n"): extents = self.text_extents(line) self.move_to(x - extents.width, y + int(line_height - (extents.height + extents.y_bearing))) self.show_text(line) y += line_height + spacing def stroke(self): cairo.cairo_stroke(self.cr) def stroke_preserve(self): cairo.cairo_stroke_preserve(self.cr) def text_extents(self, text): return cairo.cairo_text_extents(self.cr, text) class Pattern(object): def __init__(self, surface): self.p = cairo.cairo_pattern_create_for_surface(surface.s) def __del__(self): cairo.cairo_pattern_destroy(self.p) def set_extend(self, mode): cairo.cairo_pattern_set_extend(self.p, mode) def show_centered_text_with_background(c, x, y, text, bg): ext = c.text_extents(text) c.save() c.set_source_rgb(*bg) c.rectangle(x-2-ext.width/2, y-2, ext.width + ext.x_bearing + 4, ext.height + (ext.height + ext.y_bearing)+4) c.fill() c.restore() c.move_to(x-ext.width/2, y+ext.height + (ext.height + ext.y_bearing)) c.show_text(text) def show_text_with_border(c, x, y, text): c.save() c.set_source_rgba(1, 1, 1, 1) c.move_to(x-1, y) c.show_text(text) c.move_to(x+1, y) c.show_text(text) c.move_to(x, y-1) c.show_text(text) c.move_to(x, y+1) c.show_text(text) c.restore() c.move_to(x, y) c.show_text(text) def show_text_lines_with_border(c, x, y, text, spacing, line_height= None): if line_height is None: line_height = 0 for line in text.split("\n"): extents = c.text_extents(line) line_height = max(line_height, int(extents.height + (extents.height + extents.y_advance + extents.y_bearing))) for line in text.split("\n"): extents = c.text_extents(line) show_text_with_border(c, x + 0.5, y + line_height - (extents.height + extents.y_bearing) + 0.5, line) y += line_height + spacing def angle_of_line(p1, p2): if p1 == p2: angle= 0 else: if p2[1] == p1[1]: if p2[0] < p1[0]: return 180 else: return 0 elif p2[1] < p1[1]: angle = 90.0 + atan((p2[0]-p1[0])/(p2[1]-p1[1])) * 180.0/pi else: angle = 270.0 + atan((p2[0]-p1[0])/(p2[1]-p1[1])) * 180.0/pi angle = angle - floor(angle/360)*360; return angle def draw_arrow_head(c, p, ps, l=10, w=3): c.save() a = angle_of_line(p, ps) c.translate(int(p[0])+0.5, int(p[1])+0.5) c.rotate(-a*pi/180) c.move_to(0, 0) c.line_to(l, w) c.stroke() c.move_to(0, 0) c.line_to(l, -w) c.stroke() c.restore() def line_center(p1, p2): return int((p1[0]+p2[0])/2), int((p1[1]+p2[1])/2) def intersect_lines(p1s, p1e, p2s, p2e): e1x, e1y = p1e s1x, s1y = p1s e2x, e2y = p2e s2x, s2y = p2s a1= e1y - s1y b1= s1x - e1x a2= e2y - s2y b2= s2x - e2x d = a1*b2 - a2*b1 if fabs(d) <= 0.000000000001: return None else: c1 = e1x*s1y - s1x*e1y c2 = e2x*s2y - s2x*e2y x = floor((b1*c2 - b2*c1) / d + 0.5) y = floor((a2*c1 - a1*c2) / d + 0.5) if floor(min(s1x, e1x)) <= x and x <= ceil(max(s1x, e1x)) and\ floor(min(s1y, e1y)) <= y and y <= ceil(max(s1y, e1y)) and\ floor(min(s2x, e2x)) <= x and x <= ceil(max(s2x, e2x)) and\ floor(min(s2y, e2y)) <= y and y <= ceil(max(s2y, e2y)): return x, y return None class Node: def __init__(self): self.pos = (0, 0) self.size = (0, 0) self.color = (0, 0, 0, 1) self.fill_color = (1, 1, 1, 1) self.line_width = 1 self.padding = (5, 5, 5, 5) # t l b r def center(self): x, y = self.pos w, h = self.size return int(x + w/2)+0.5, int(y + h/2)+0.5 def top_vertex(self): x, y = self.pos w, h = self.size return (x, y), (x+w, y) def bottom_vertex(self): x, y = self.pos w, h = self.size return (x, y+h), (x+w, y+h) def right_vertex(self): x, y = self.pos w, h = self.size return (x+w, y), (x+w, y+h) def left_vertex(self): x, y = self.pos w, h = self.size return (x, y), (x, y+h) def set_color(self, r, g, b, a = 1.0): self.color = (r, g, b, a) def set_fill_color(self, r, g, b, a = 1.0): self.fill_color = (r, g, b, a) def apply_attributes(self, c): c.set_source_rgba(*self.color) c.set_line_width(self.line_width) def render(self, ctx): self.calc(ctx) self.apply_attributes(ctx) self.do_render(ctx) def render_shadow(self, c): p1, p2 = self.bottom_vertex() c.set_source_rgba(0, 0, 0, 0.2) c.move_to(p1[0]+0.5, p1[1]+1.5) c.line_to(p2[0]+0.5, p2[1]+1.5) c.stroke() c.set_source_rgba(0, 0, 0, 0.05) c.move_to(p1[0]+0.5, p1[1]+2.5) c.line_to(p2[0]+0.5, p2[1]+2.5) c.stroke() p1, p2 = self.left_vertex() c.set_source_rgba(0, 0, 0, 0.2) c.move_to(p1[0]-0.5, p1[1]+1.5) c.line_to(p2[0]-0.5, p2[1]+1.5) c.stroke() p1, p2 = self.right_vertex() c.set_source_rgba(0, 0, 0, 0.2) c.move_to(p1[0]+1.5, p1[1]+1.5) c.line_to(p2[0]+1.5, p2[1]+1.5) c.stroke() def stroke_line_to_parent(self, c, parent, vertical): if vertical: return self.stroke_line_to_parent_v(c, parent) else: return self.stroke_line_to_parent_h(c, parent) def stroke_line_to_parent_v(self, c, parent): if parent: c.set_line_width(1) c.set_source_rgba(0.6, 0.6, 0.6, 1) v1 = parent.bottom_vertex() v2 = self.top_vertex() p1 = line_center(*v1) p2 = line_center(*v2) if p1 and p2: c.move_to(p1[0]+.5, p1[1]+.5) c.line_to(p2[0]+.5, p2[1]+.5) c.stroke() draw_arrow_head(c, p1, p2) return (p1, p2) def stroke_line_to_parent_h(self, c, parent): if parent: c.set_line_width(1) c.set_source_rgba(0.6, 0.6, 0.6, 1) v1 = parent.right_vertex() v2 = self.left_vertex() p1 = line_center(*v1) p2 = line_center(*v2) if p1 and p2: c.move_to(p1[0]+.5, p1[1]+.5) c.line_to(p2[0]+.5, p2[1]+.5) c.stroke() draw_arrow_head(c, p1, p2) return (p1, p2) def stroke_line_from_node(self, c, node): if node: c.set_line_width(2) c.set_source_rgba(0.6, 0.6, 0.6, 1) sx, sy = self.center() nx, ny = node.center() if abs(sx-nx) < abs(sy-ny): if sy > ny: v1 = node.bottom_vertex() v2 = self.top_vertex() else: v1 = node.top_vertex() v2 = self.bottom_vertex() else: if sx < nx: v1 = node.left_vertex() v2 = self.right_vertex() else: v1 = node.right_vertex() v2 = self.left_vertex() p1 = line_center(*v1) p2 = line_center(*v2) if p1 and p2: c.move_to(p1[0]+.5, p1[1]+.5) c.line_to(p2[0]+.5, p2[1]+.5) c.stroke() draw_arrow_head(c, p1, p2) return (p1, p2) class TextNode(Node): def __init__(self, text): Node.__init__(self) self.text = text self.line_spacing = 4 self.line_height = 0 self.font_size = 12 self.bold = False def calc(self, ctx): ctx.save() ctx.set_font_size(self.font_size) if self.bold: ctx.set_font("Helvetica", False, self.bold) if "\n" in self.text: lines = self.text.split("\n") w, h = 0, 0 lh = 0 for line in lines: ext = ctx.text_extents(line) w = max(w, int(ext.x_bearing + ext.x_advance)) lh = max(lh, int(ext.height + (ext.height + ext.y_advance + ext.y_bearing))) self.line_height = lh h = lh * len(lines) + self.line_spacing * (len(lines)-1) t, r, b, l = self.padding self.size = (w + r+l, h + t+b) else: ext = ctx.text_extents(self.text) self._extents = ext t, r, b, l = self.padding self.line_height = int(ext.height + (ext.height + ext.y_advance + ext.y_bearing)) self.size = (int(ext.x_bearing + ext.x_advance) + r+l, self.line_height + t+b) ctx.restore() def do_render(self, ctx): ctx.save() ctx.set_font_size(self.font_size) if self.bold: ctx.set_font("Helvetica", False, self.bold) t, r, b, l = self.padding x, y = self.pos w, h = self.size ctx.show_text_lines_at(int(x+l)+0.5, int(y+t)+0.5, self.text, self.line_spacing, self.line_height) ctx.stroke() ctx.restore() class TextRectangle(TextNode): def __init__(self, text): TextNode.__init__(self, text) self.icon = None self.icon_alpha = 1 self.border_color = (125.0/255, 125.0/255, 125.0/255, 1) self.draw_vertices = (True, True, True, True) def set_border_color(self, r, g, b, a = 1.0): self.border_color = (r, g, b, a) def set_icon(self, path): image = ImageSurface.from_png(path) if image.status() == cairo.CAIRO_STATUS_SUCCESS: self.icon = image else: self.icon = None def calc(self, ctx): TextNode.calc(self, ctx) w, h = self.size if self.icon: w += self.icon.get_width() + 3 self.size = max(50, w), h def do_render(self, ctx): x, y = self.pos w, h = self.size if self.fill_color: ctx.save() ctx.rectangle(x+0.5, y+0.5, w, h) ctx.set_source_rgba(*self.fill_color) ctx.fill() ctx.restore() if self.icon: ctx.save() t, r, b, l = self.padding ctx.translate(x + l, y + int((h - self.icon.get_height())/2)) ctx.set_source_surface(self.icon, 0, 0) if int(self.icon_alpha) == 1: ctx.paint() else: ctx.paint_with_alpha(self.icon_alpha) ctx.restore() ctx.save() if self.icon: ctx.translate(self.icon.get_width() + 4, 0) TextNode.do_render(self, ctx) ctx.restore() if self.border_color and self.draw_vertices: ctx.set_source_rgba(*self.border_color) if self.draw_vertices == (True, True, True, True): ctx.rectangle(x+0.5, y+0.5, w, h) ctx.stroke() else: t, l, b, r = self.draw_vertices if t: ctx.move_to(x+0.5, y+0.5) ctx.line_to(x+0.5+w, y+0.5) ctx.stroke() if b: ctx.move_to(x+0.5, y+0.5+h) ctx.line_to(x+0.5+w, y+0.5+h) ctx.stroke() if l: ctx.move_to(x+0.5, y+0.5) ctx.line_to(x+0.5, y+0.5+h) ctx.stroke() if r: ctx.move_to(x+0.5+w, y+0.5) ctx.line_to(x+0.5+w, y+0.5+h) ctx.stroke() class VBoxNode(Node): def __init__(self): Node.__init__(self) self.items = [] def set_color(self, r, g, b, a = 1.0): for item in self.items: item.set_color(r, g, b, a) def set_fill_color(self, r, g, b, a = 1.0): for item in self.items: item.set_fill_color(r, g, b, a) def layout_internal(self): w, h = self.size y = 0 for item in self.items: item.pos = 0, y item.size = w, item.size[1] y += item.size[1] def calc(self, c): h = 0 w = 0 for item in self.items: item.calc(c) ww, hh = item.size w = max(ww, w) h += hh self.size = w, h y = 0 for item in self.items: item.pos = (0, y) item.size = (w, item.size[1]) y += item.size[1] def do_render(self, c): pass def stroke_line_to_parent(self, c, node, vertical): c.set_line_width(1) c.set_source_rgb(0, 0, 0) ch = self.items[0] pos = ch.pos ch.pos = pos[0] + self.pos[0], pos[1] + self.pos[1] p1, p2 = ch.stroke_line_to_parent(c, node, vertical) ch.pos = pos return p1, p2 def render(self, c): self.do_render(c) c.save() c.translate(self.pos[0], self.pos[1]) for item in self.items: item.apply_attributes(c) item.do_render(c) c.restore()