2
# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai
5
__copyright__ = '2010, Kovid Goyal <kovid@kovidgoyal.net>'
6
__docformat__ = 'restructuredtext en'
8
from ctypes import byref, c_double
10
import calibre.utils.PythonMagickWand as p
11
from calibre.ptempfile import TemporaryFile
12
from calibre.constants import filesystem_encoding, __appname__, __version__
17
def __init__(self, left, top, right, bottom):
18
self.left, self.top, self.right, self.bottom = left, top, right, bottom
21
return '(%s, %s) -- (%s, %s)'%(self.left, self.top, self.right,
24
class FontMetrics(object):
26
def __init__(self, ret):
28
for i, x in enumerate(('char_width', 'char_height', 'ascender',
29
'descender', 'text_width', 'text_height',
30
'max_horizontal_advance')):
31
setattr(self, x, ret[i])
33
self.bounding_box = Rect(ret[7], ret[8], ret[9], ret[10])
34
self.x, self.y = ret[11], ret[12]
35
self._attrs.extend(['bounding_box', 'x', 'y'])
36
self._attrs = tuple(self._attrs)
39
return '''FontMetrics:
46
max_horizontal_advance: %s
50
'''%tuple([getattr(self, x) for x in self._attrs])
53
def get_font_metrics(image, d_wand, text):
54
if isinstance(text, unicode):
55
text = text.encode('utf-8')
56
ret = p.MagickQueryFontMetrics(image, d_wand, text)
57
return FontMetrics(ret)
61
class TextLine(object):
63
def __init__(self, text, font_size, bottom_margin=30, font_path=None):
64
self.text, self.font_size, = text, font_size
65
self.bottom_margin = bottom_margin
66
self.font_path = font_path
69
return u'TextLine:%r:%f'%(self.text, self.font_size)
72
ans = getattr(p, name)()
74
raise RuntimeError('Cannot create wand')
77
def create_text_wand(font_size, font_path=None):
79
font_path = P('fonts/liberation/LiberationSerif-Bold.ttf')
80
if isinstance(font_path, unicode):
81
font_path = font_path.encode(filesystem_encoding)
82
ans = alloc_wand('NewDrawingWand')
83
if not p.DrawSetFont(ans, font_path):
84
raise ValueError('Failed to set font to: '+font_path)
85
p.DrawSetFontSize(ans, font_size)
86
p.DrawSetGravity(ans, p.CenterGravity)
87
p.DrawSetTextAntialias(ans, p.MagickTrue)
91
def _get_line(img, dw, tokens, line_width):
92
line, rest = tokens, []
94
m = get_font_metrics(img, dw, ' '.join(line))
95
width, height = m.text_width, m.text_height
96
if width < line_width:
98
rest = line[-1:] + rest
101
def annotate_img(img, dw, left, top, rotate, text,
102
translate_from_top_left=True):
103
if isinstance(text, unicode):
104
text = text.encode('utf-8')
105
if translate_from_top_left:
106
m = get_font_metrics(img, dw, text)
107
img_width = p.MagickGetImageWidth(img)
108
img_height = p.MagickGetImageHeight(img)
109
left = left - img_width/2. + m.text_width/2.
110
top = top - img_height/2. + m.text_height/2.
111
p.MagickAnnotateImage(img, dw, left, top, rotate, text)
113
def draw_centered_line(img, dw, line, top):
114
m = get_font_metrics(img, dw, line)
115
width, height = m.text_width, m.text_height
116
img_width = p.MagickGetImageWidth(img)
117
left = max(int((img_width - width)/2.), 0)
118
annotate_img(img, dw, left, top, 0, line)
121
def draw_centered_text(img, dw, text, top, margin=10):
122
img_width = p.MagickGetImageWidth(img)
123
tokens = text.split(' ')
125
line, tokens = _get_line(img, dw, tokens, img_width-2*margin)
127
# Could not fit the first token on the line
130
bottom = draw_centered_line(img, dw, ' '.join(line), top)
134
def create_canvas(width, height, bgcolor):
135
canvas = alloc_wand('NewMagickWand')
136
p_wand = alloc_wand('NewPixelWand')
137
p.PixelSetColor(p_wand, bgcolor)
138
p.MagickNewImage(canvas, width, height, p_wand)
139
p.DestroyPixelWand(p_wand)
142
def compose_image(canvas, image, left, top):
143
p.MagickCompositeImage(canvas, image, p.OverCompositeOp, int(left),
146
def load_image(path):
147
img = alloc_wand('NewMagickWand')
148
if not p.MagickReadImage(img, path):
149
severity = p.ExceptionType(0)
150
msg = p.MagickGetException(img, byref(severity))
151
raise IOError('Failed to read image from: %s: %s'
155
def create_text_arc(text, font_size, font=None, bgcolor='white'):
156
if isinstance(text, unicode):
157
text = text.encode('utf-8')
159
canvas = create_canvas(300, 300, bgcolor)
160
tw = create_text_wand(font_size, font_path=font)
161
m = get_font_metrics(canvas, tw, text)
162
p.DestroyMagickWand(canvas)
163
canvas = create_canvas(int(m.text_width)+20, int(m.text_height*3.5), bgcolor)
164
p.MagickAnnotateImage(canvas, tw, 0, 0, 0, text)
165
angle = c_double(120.)
166
p.MagickDistortImage(canvas, 9, 1, byref(angle),
168
p.MagickTrimImage(canvas, 0)
171
def add_borders_to_image(path_to_image, left=0, top=0, right=0, bottom=0,
172
border_color='white'):
173
with p.ImageMagick():
174
img = load_image(path_to_image)
175
lwidth = p.MagickGetImageWidth(img)
176
lheight = p.MagickGetImageHeight(img)
177
canvas = create_canvas(lwidth+left+right, lheight+top+bottom,
179
compose_image(canvas, img, left, top)
180
p.DestroyMagickWand(img)
181
p.MagickWriteImage(canvas,path_to_image)
182
p.DestroyMagickWand(canvas)
184
def create_cover_page(top_lines, logo_path, width=590, height=750,
185
bgcolor='white', output_format='jpg'):
187
with p.ImageMagick():
188
canvas = create_canvas(width, height, bgcolor)
191
for line in top_lines:
192
twand = create_text_wand(line.font_size, font_path=line.font_path)
193
bottom = draw_centered_text(canvas, twand, line.text, bottom)
194
bottom += line.bottom_margin
195
p.DestroyDrawingWand(twand)
196
bottom -= top_lines[-1].bottom_margin
198
vanity = create_text_arc(__appname__ + ' ' + __version__, 24,
199
font=P('fonts/liberation/LiberationMono-Regular.ttf'))
200
lwidth = p.MagickGetImageWidth(vanity)
201
lheight = p.MagickGetImageHeight(vanity)
202
left = int(max(0, (width - lwidth)/2.))
203
top = height - lheight - 10
204
compose_image(canvas, vanity, left, top)
206
logo = load_image(logo_path)
207
lwidth = p.MagickGetImageWidth(logo)
208
lheight = p.MagickGetImageHeight(logo)
209
left = int(max(0, (width - lwidth)/2.))
210
top = max(int((height - lheight)/2.), bottom+20)
211
compose_image(canvas, logo, left, top)
212
p.DestroyMagickWand(logo)
214
with TemporaryFile('.'+output_format) as f:
215
p.MagickWriteImage(canvas, f)
216
with open(f, 'rb') as f:
218
p.DestroyMagickWand(canvas)
221
def save_cover_data_to(data, path, bgcolor='white'):
223
Saves image in data to path, in the format specified by the path
224
extension. Composes the image onto a blank cancas so as to
225
properly convert transparent images.
227
with open(path, 'wb') as f:
229
with p.ImageMagick():
230
img = load_image(path)
231
canvas = create_canvas(p.MagickGetImageWidth(img),
232
p.MagickGetImageHeight(img), bgcolor)
233
compose_image(canvas, img, 0, 0)
234
p.MagickWriteImage(canvas, path)
235
p.DestroyMagickWand(img)
236
p.DestroyMagickWand(canvas)
240
with TemporaryFile('.png') as f:
241
data = create_cover_page(
242
[TextLine('A very long title indeed, don\'t you agree?', 42),
243
TextLine('Mad Max & Mixy poo', 32)], I('library.png'))
244
with open(f, 'wb') as g:
246
subprocess.check_call(['gwenview', f])
248
if __name__ == '__main__':