callirhoe/lib/xcairo.py
geortz@gmail.com 11e2ac0313 restructured code in main script
epydoc strings



git-svn-id: https://callirhoe.googlecode.com/svn/branches/next@45 81c8bb96-aa45-f2e2-0eef-c4fa4a15c6df
2014-08-28 16:31:41 +00:00

255 lines
8.9 KiB
Python

# -*- coding: utf-8 -*-
# callirhoe - high quality calendar rendering
# Copyright (C) 2012 George M. Tzoumas
# This program 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.
#
# 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 for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see http://www.gnu.org/licenses/
# ********************************************************************
# #
""" general-purpose drawing routines & higher-level CAIRO routines """
# #
# ********************************************************************
import cairo
import math
import random
from os.path import splitext
from geom import *
XDPI = 72.0
ISOPAGE = map(lambda x: int(210*math.sqrt(2)**x+0.5), range(5,-6,-1))
def page_spec(spec = None):
if not spec:
return (ISOPAGE[5], ISOPAGE[4])
if len(spec) == 2 and spec[0].lower() == 'a':
k = int(spec[1])
return (ISOPAGE[k+1], ISOPAGE[k])
if len(spec) == 3 and spec[0].lower() == 'a' and spec[2].lower() == 'w':
k = int(spec[1])
return (ISOPAGE[k], ISOPAGE[k+1])
if ':' in spec:
s = spec.split(':')
w, h = float(s[0]), float(s[1])
if w < 0: w = dots_to_mm(-w)
if h < 0: h = dots_to_mm(-h)
return (w,h)
def mm_to_dots(mm):
return mm/25.4 * XDPI
def dots_to_mm(dots):
return dots*25.4/XDPI
class Page(object):
def __init__(self, landscape, w, h, b, raster):
if not landscape:
self.Size_mm = (w, h) # (width, height) in mm
else:
self.Size_mm = (h, w)
self.landscape = landscape
self.Size = (mm_to_dots(self.Size_mm[0]), mm_to_dots(self.Size_mm[1])) # size in dots/pixels
self.raster = raster
self.Margins = (mm_to_dots(b),)*4
txs = (self.Size[0] - self.Margins[1] - self.Margins[3],
self.Size[1] - self.Margins[0] - self.Margins[2])
self.Text_rect = (self.Margins[1], self.Margins[0], txs[0], txs[1])
class InvalidFormat(Exception): pass
class PageWriter(Page):
PDF = 0
PNG = 1
def __init__(self, filename, landscape = False, pagespec = None, b = 0.0, keep_transparency = True):
self.base,self.ext = splitext(filename)
self.filename = filename
self.curpage = 1
if self.ext.lower() == ".pdf": self.format = PageWriter.PDF
elif self.ext.lower() == ".png": self.format = PageWriter.PNG
else:
raise InvalidFormat(self.ext)
self.keep_transparency = keep_transparency
if keep_transparency:
self.img_format = cairo.FORMAT_ARGB32
else:
self.img_format = cairo.FORMAT_RGB24
w, h = page_spec(pagespec)
if landscape and self.format == PageWriter.PNG:
w, h = h, w
landscape = False
super(PageWriter,self).__init__(landscape, w, h, b, self.format == PageWriter.PNG)
self.setup_surface_and_context()
def setup_surface_and_context(self):
if not self.landscape:
if self.format == PageWriter.PDF:
self.Surface = cairo.PDFSurface(self.filename, self.Size[0], self.Size[1])
else:
self.Surface = cairo.ImageSurface(self.img_format, int(self.Size[0]), int(self.Size[1]))
else:
if self.format == PageWriter.PDF:
self.Surface = cairo.PDFSurface(self.filename, self.Size[1], self.Size[0])
else:
self.Surface = cairo.ImageSurface(self.img_format, int(self.Size[1]), int(self.Size[0]))
self.cr = cairo.Context(self.Surface)
if self.landscape:
self.cr.translate(0,self.Size[0])
self.cr.rotate(-math.pi/2)
if not self.keep_transparency:
self.cr.set_source_rgb(1,1,1)
self.cr.move_to(0,0)
self.cr.line_to(0,int(self.Size[1]))
self.cr.line_to(int(self.Size[0]),int(self.Size[1]))
self.cr.line_to(int(self.Size[0]),0)
self.cr.close_path()
self.cr.fill()
def end_page(self):
if self.format == PageWriter.PNG:
outfile = self.filename if self.curpage < 2 else self.base + "%02d" % (self.curpage) + self.ext
self.Surface.write_to_png(outfile)
def new_page(self):
if self.format == PageWriter.PDF:
self.cr.show_page()
else:
self.curpage += 1
self.setup_surface_and_context()
def set_color(cr, rgba):
if len(rgba) == 3:
cr.set_source_rgb(*rgba)
else:
cr.set_source_rgba(*rgba)
def extract_font_name(f):
return f if type(f) is str else f[0]
def apply_rect(cr, rect, sdx = 0.0, sdy = 0.0, srot = 0.0):
x, y, w, h = rect
cr.save()
cr.translate(x,y)
if sdx != 0.0 or sdy != 0.0 or srot != 0.0:
cr.translate(w/2, h/2)
cr.translate(w*(random.random() - 0.5)*sdx, h*(random.random() - 0.5)*sdy)
cr.rotate((random.random() - 0.5)*srot)
cr.translate(-w/2.0, -h/2.0)
def draw_shadow(cr, rect, thickness = None, shadow_color = (0,0,0,0.3)):
if thickness is None: return
fx = mm_to_dots(thickness[0])
fy = mm_to_dots(thickness[1])
x1, y1, x3, y3 = rect_to_abs(rect)
x2, y2 = x1, y3
x4, y4 = x3, y1
u1, v1 = cr.user_to_device(x1,y1)
u2, v2 = cr.user_to_device(x2,y2)
u3, v3 = cr.user_to_device(x3,y3)
u4, v4 = cr.user_to_device(x4,y4)
u1 += fx; v1 += fy; u2 += fx; v2 += fy;
u3 += fx; v3 += fy; u4 += fx; v4 += fy;
x1, y1 = cr.device_to_user(u1, v1)
x2, y2 = cr.device_to_user(u2, v2)
x3, y3 = cr.device_to_user(u3, v3)
x4, y4 = cr.device_to_user(u4, v4)
cr.move_to(x1, y1)
cr.line_to(x2, y2); cr.line_to(x3, y3); cr.line_to(x4, y4)
set_color(cr, shadow_color)
cr.close_path(); cr.fill();
def draw_line(cr, rect, stroke_rgba = None, stroke_width = 1.0):
if (stroke_width <= 0): return
x, y, w, h = rect
cr.move_to(x, y)
cr.rel_line_to(w, h)
cr.close_path()
if stroke_rgba:
set_color(cr, stroke_rgba)
cr.set_line_width(stroke_width)
cr.stroke()
def draw_box(cr, rect, stroke_rgba = None, fill_rgba = None, stroke_width = 1.0, shadow = None):
if (stroke_width <= 0): return
draw_shadow(cr, rect, shadow)
x, y, w, h = rect
cr.move_to(x, y)
cr.rel_line_to(w, 0)
cr.rel_line_to(0, h)
cr.rel_line_to(-w, 0)
cr.close_path()
if fill_rgba:
set_color(cr, fill_rgba)
cr.fill_preserve()
if stroke_rgba:
set_color(cr, stroke_rgba)
cr.set_line_width(stroke_width)
cr.stroke()
def draw_str(cr, text, rect, stretch = -1, stroke_rgba = None, align = (2,0), bbox = False,
font = "Times", measure = None, shadow = None):
x, y, w, h = rect
cr.save()
slant = weight = 0
if type(font) is str: fontname = font
elif len(font) == 3: fontname, slant, weight = font
elif len(font) == 2: fontname, slant = font
elif len(font) == 1: fontname = font[0]
cr.select_font_face(fontname, slant, weight)
if measure is None: measure = text
te = cr.text_extents(measure)
mw, mh = te[2], te[3]
if mw < 5:
mw = 5.
if mh < 5:
mh = 5.
ratio, tratio = w*1.0/h, mw*1.0/mh;
xratio, yratio = mw*1.0/w, mh*1.0/h;
if stretch < 0: stretch = 1 if xratio >= yratio else 2
if stretch == 0: crs = (1,1)
elif stretch == 1: crs = (1.0/xratio, 1.0/xratio)
elif stretch == 2: crs = (1.0/yratio, 1.0/yratio)
elif stretch == 3: crs = (1.0/xratio, 1.0/yratio)
te = cr.text_extents(text)
tw,th = te[2], te[3]
tw *= crs[0]
th *= crs[1]
px, py = x, y + h
if align[0] == 1: px += w - tw
elif align[0] == 2: px += (w-tw)/2.0
if align[1] == 1: py -= h - th
elif align[1] == 2: py -= (h-th)/2.0
cr.translate(px,py)
cr.scale(*crs)
if shadow is not None:
sx = mm_to_dots(shadow[0])
sy = mm_to_dots(shadow[1])
cr.set_source_rgba(0, 0, 0, 0.5)
u1, v1 = cr.user_to_device(0, 0)
u1 += sx; v1 += sy
x1, y1 = cr.device_to_user(u1, v1)
cr.move_to(x1, y1)
cr.show_text(text)
cr.move_to(0, 0)
if stroke_rgba: set_color(cr, stroke_rgba)
cr.show_text(text)
cr.restore()
if bbox:
draw_box(cr, (x, y, w, h), stroke_rgba)
#draw_box(cr, (x, y+h, mw*crs[0], -mh*crs[1]), stroke_rgba)
draw_box(cr, (px, py, tw, -th), stroke_rgba)