Armed with a text editor

mu's views on program and recipe! design

#! /usr/bin/env python
import cairo
from math import pi, sqrt

class Diagram(object):
    def __init__(self, filename, width, height):
        self.surface = cairo.SVGSurface(filename + '.svg', width, height)
        cr = self.cr = cairo.Context(self.surface)

        cr.scale(width, height)
        cr.set_line_width(0.01)

        cr.rectangle(0, 0, 1, 1)
        cr.set_source_rgb(1, 1, 1)
        cr.fill()

        self.draw_dest(cr)

        cr.set_line_width( max(cr.device_to_user_distance(2, 2)) )
        cr.set_source_rgb(0, 0, 0)
        cr.rectangle(0, 0, 1, 1)
        cr.stroke()

        self.surface.write_to_png(filename + '.png')
        cr.show_page()
        self.surface.finish()

class SetSourceRGBA(Diagram):
    def draw_dest(self, cr):
        cr.set_source_rgb(0, 0, 0)         #rgba
        cr.move_to(0, 0)                   #rgba
        cr.line_to(1, 1)                   #rgba
        cr.move_to(1, 0)                   #rgba
        cr.line_to(0, 1)                   #rgba
        cr.set_line_width(0.2)             #rgba
        cr.stroke()                        #rgba
                                           #rgba
        cr.rectangle(0, 0, 0.5, 0.5)       #rgba
        cr.set_source_rgba(1, 0, 0, 0.80)  #rgba
        cr.fill()                          #rgba
                                           #rgba
        cr.rectangle(0, 0.5, 0.5, 0.5)     #rgba
        cr.set_source_rgba(0, 1, 0, 0.60)  #rgba
        cr.fill()                          #rgba
                                           #rgba
        cr.rectangle(0.5, 0, 0.5, 0.5)     #rgba
        cr.set_source_rgba(0, 0, 1, 0.40)  #rgba
        cr.fill()                          #rgba

class SetSourceGradient(Diagram):
    def draw_dest(self, cr):
        radial = cairo.RadialGradient(0.25, 0.25, 0.1,  0.5, 0.5, 0.5) #gradient
        radial.add_color_stop_rgb(0,  1.0, 0.8, 0.8)                   #gradient
        radial.add_color_stop_rgb(1,  0.9, 0.0, 0.0)                   #gradient
                                                                       #gradient
        for i in range(1, 10):                                         #gradient
            for j in range(1, 10):                                     #gradient
                cr.rectangle(i/10.0 - 0.04, j/10.0 - 0.04, 0.08, 0.08) #gradient
        cr.set_source(radial)                                          #gradient
        cr.fill()                                                      #gradient
                                                                       #gradient
        linear = cairo.LinearGradient(0.25, 0.35, 0.75, 0.65)          #gradient
        linear.add_color_stop_rgba(0.00,  1, 1, 1, 0)                  #gradient
        linear.add_color_stop_rgba(0.25,  0, 1, 0, 0.5)                #gradient
        linear.add_color_stop_rgba(0.50,  1, 1, 1, 0)                  #gradient
        linear.add_color_stop_rgba(0.75,  0, 0, 1, 0.5)                #gradient
        linear.add_color_stop_rgba(1.00,  1, 1, 1, 0)                  #gradient
                                                                       #gradient
        cr.rectangle(0.0, 0.0, 1, 1)                                   #gradient
        cr.set_source(linear)                                          #gradient
        cr.fill()                                                      #gradient

class PathDiagram(Diagram):
    def draw_dest(self, cr):
        self.draw_dest_path(cr)

        path = list(cr.copy_path_flat())

        cr.set_line_width(max(cr.device_to_user_distance(3, 3)))
        cr.set_source_rgb(0, 0.6, 0)
        cr.stroke()

        if len(path) and path[-1][0] != cairo.PATH_CLOSE_PATH:
            x, y = path[0][1]
            cr.arc(x, y, max(cr.device_to_user_distance(5, 5)), 0, 2*pi)
            cr.set_source_rgba(0.0, 0.6, 0.0, 0.5)
            cr.fill()

            x, y = path[-1][1]
            cr.arc(x, y, max(cr.device_to_user_distance(5, 5)), 0, 2*pi)
            cr.set_source_rgba(0.0, 0.0, 0.75, 0.5)
            cr.fill()

class PathDiagramMoveTo(PathDiagram):
    def draw_dest_path(self, cr):
        cr.move_to(0.25, 0.25)                     #moveto

class PathDiagramLineTo(PathDiagramMoveTo):
    def draw_dest_path(self, cr):
        PathDiagramMoveTo.draw_dest_path(self, cr)
        cr.line_to(0.5, 0.375)                     #lineto
        cr.rel_line_to(0.25, -0.125)               #lineto

class PathDiagramArcTo(PathDiagramLineTo):
    def draw_dest_path(self, cr):
        PathDiagramLineTo.draw_dest_path(self, cr)
        cr.arc(0.5, 0.5, 0.25 * sqrt(2), -0.25 * pi, 0.25 * pi) #arc

class PathDiagramCurveTo(PathDiagramArcTo):
    def draw_dest_path(self, cr):
        if type(self) == PathDiagramCurveTo:
            cr.save()
            for x, y in ((0.5, 0.625), (0.5, 0.875)):
                cr.new_sub_path()
                cr.arc(x, y, max(cr.device_to_user_distance(3, 3)), 0, 2*pi)
                cr.set_source_rgba(0.5, 0, 0, 0.5)
                cr.fill()
            for (x, y, w, h) in ((0.25, 0.75, 0.25, 0.125),
                    (0.75, 0.75, -0.25, -0.125)):
                cr.move_to(x, y)
                cr.rel_line_to(w, h)
                cr.set_line_width(max(cr.device_to_user_distance(2, 2)))
                cr.set_source_rgba(0.5, 0, 0, 0.25)
                cr.stroke()
            cr.restore()
        PathDiagramArcTo.draw_dest_path(self, cr)
        cr.rel_curve_to(-0.25, -0.125, -0.25, 0.125, -0.5, 0)  #curveto

class PathDiagramClose(PathDiagramCurveTo):
    def draw_dest_path(self, cr):
        PathDiagramCurveTo.draw_dest_path(self, cr)
        cr.close_path()                                        #closepath

class TextExtents(Diagram):
    def draw_dest(self, cr):
        text = 'joy'
        cr.select_font_face('Georgia', cairo.FONT_SLANT_NORMAL, cairo.FONT_WEIGHT_BOLD)
        cr.set_font_size(0.5)
        px = max(cr.device_to_user_distance(1, 1))
        fascent, fdescent, fheight, fxadvance, fyadvance = cr.font_extents()
        xbearing, ybearing, width, height, xadvance, yadvance = \
                cr.text_extents(text)
        x = 0.5 - xbearing - width / 2
        y = 0.5 - fdescent + fheight / 2

        # baseline, descent, ascent, height
        cr.set_line_width(4 * px)
        cr.set_dash([9 * px], 0)
        cr.set_source_rgba(0, 0.6, 0, 0.5)
        cr.move_to(x + xbearing, y)
        cr.rel_line_to(width, 0)
        cr.move_to(x + xbearing, y + fdescent)
        cr.rel_line_to(width, 0)
        cr.move_to(x + xbearing, y - fascent)
        cr.rel_line_to(width, 0)
        cr.move_to(x + xbearing, y - fheight)
        cr.rel_line_to(width, 0)
        cr.stroke()

        # extents: width & height
        cr.set_source_rgba(0, 0, 0.75, 0.5)
        cr.set_line_width(1 * px)
        cr.set_dash([3 * px], 0)
        cr.rectangle(x + xbearing, y + ybearing, width, height)
        cr.stroke()

        # text
        cr.move_to(x, y)
        cr.set_source_rgb(0, 0, 0)
        cr.show_text(text)

        # bearing
        cr.set_dash([], 0)
        cr.set_line_width(2 * px)
        cr.set_source_rgba(0, 0, 0.75, 0.5)
        cr.move_to(x, y)
        cr.rel_line_to(xbearing, ybearing)
        cr.stroke()

        # text's advance
        cr.set_source_rgba(0, 0, 0.75, 0.5)
        cr.arc(x + xadvance, y + yadvance, 5 * px, 0, 2 * pi)
        cr.fill()

        # reference point
        cr.arc(x, y, 5 * px, 0, 2 * pi)
        cr.set_source_rgba(0.75, 0, 0, 0.5)
        cr.fill()


if __name__ == '__main__':
    size = 120
    SetSourceRGBA('setsourcergba', size, size)
    SetSourceGradient('setsourcegradient', size, size)
    PathDiagramMoveTo('path-moveto', size, size)
    PathDiagramLineTo('path-lineto', size, size)
    PathDiagramArcTo('path-arcto', size, size)
    PathDiagramCurveTo('path-curveto', size, size)
    PathDiagramClose('path-close', size, size)
    TextExtents('textextents', size, size)