forked from ldo/qahirah
-
Notifications
You must be signed in to change notification settings - Fork 0
/
README
286 lines (215 loc) · 10.1 KB
/
README
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
**Qahirah** is yet another Python language binding for the [Cairo graphics
library](http://cairographics.org/), for use with Python 3.3 or later.
It is modelled to some extent on [Pycairo](http://cairographics.org/pycairo/),
but differs from it in important ways:
* It is implemented entirely in Python, using the ctypes module.
This offers advantages: automatic support for passing arguments
by keyword, and argument names appearing in help() output.
* It tries to operate at a higher level than the actual C-centric Cairo API,
taking advantage of Python’s powerful data-manipulation facilities.
This is explained in more detail below.
* Because it is pure Python, the abstractions it implements are “leaky”.
As Guido van Rossum has made clear, “We’re all consenting adults here”.
If you want to bypass Qahirah’s facilities and make calls directly
to the underlying Cairo library, there is nothing to prevent you from
doing so. Whether your code will still work with a future version of
Qahirah is another matter...
Installation
============
Installation is explained in the setup.py script.
Overview
========
This introduction will assume you have some knowledge of the Cairo
API itself; possibly you have even used the Pycairo Python binding.
Qahirah also has “Context”, “Surface” and “Pattern” classes, similar
to those in Pycairo. To summarize:
* A _Surface_ (underlying Cairo type cairo_surface_t) is a holder
for the results of drawing. An _ImageSurface_ is a subclass of
Surface that specifically holds a two-dimensional array of pixels
(of one of the Cairo-supported formats), but there are other kinds
of surfaces for rendering direct to PDF files, SVG files and
other purposes.
* You do not do drawing directly into a Surface. Instead, you do
your drawing into a _Context_ (underlying Cairo type cairo_t).
A Context is like a wrapper around a Surface; it holds additional
state that can affect subsequent drawing calls (e.g. current
position, source pattern, operator), but are not part of the
actual contents of the Surface.
* A _Pattern_ (underlying Cairo type cairo_pattern_t) defines how
pixels are individually affected while drawing. A Pattern can
consist of a single plain colour (possibly with transparency), or it
can be a linear or radial gradient of colours, or it can even take
the image from a Surface.
(There are other object types, but understanding these three is,
I think, core to understanding how Cairo works.)
However, Qahirah introduces some important differences from Pycairo
(and from the underlying Cairo API):
* Vectors
* Properties
* Method-chaining
* Colours
* Rects
* Paths
* FreeType fonts
These are further explained in the sections below.
Vectors
=======
Qahirah makes heavy use of the *Vector* type. This
corresponds to the usual mathematical idea of a 2D vector, with *x*
and *y* components. Where Cairo wants you to pass separate *x*- and
*y*-coordinate values, Qahirah wants you to pass the two as a single
Vector. The reason for this is to reduce repetitiveness of coding:
very often, you want to do a calculation for the *x*-coordinate, and
then repeat the exact same form of calculation for the *y*-coordinate.
The Qahirah Vector type defines addition, subtraction, multiplication
and division directly on Vectors in terms of the corresponding
operations on their components, allowing you to write your coordinate
calculations just once.
However, you will still sometimes need to work with separate coordinate
values. To ease the job of conversion, all Vector arguments to Qahirah
calls can also be written directly as 2-tuples of coordinates, and
Vector call results can be directly interpreted as 2-tuples of
coordinates. Thus, where the cairo_move_to call takes separate x and y
arguments, the Qahirah call works more like this:
p = Vector(x, y)
ctx.move_to(p)
which can of course be written as
ctx.move_to(Vector(x, y))
but even more compactly, and closer to the underlying Cairo call, as
ctx.move_to((x, y))
Similarly, where cairo_get_current_point returns separate x and
y values, Qahirah returns both at once:
p = ctx.current_point
x = p.x
y = p.y
which, if you need to separate them out, can also be written more
directly as
x, y = ctx.current_point
The coordinate components of a Vector can be integers or reals.
Some uses (e.g. specifying the dimensions of an ImageSurface)
require integer coordinates; the builtin Python “round()” function
can be used on a Vector to round its coordinates to the nearest
corresponding integers, e.g.:
>>> v = Vector(3.5, 4.5)
>>> round(v)
Vector(4, 4)
Properties
==========
Cairo defines lots of pairs of cairo_get_*property* and cairo_set_*property*
calls. Qahirah collapses these down to a single *property* which can be used
directly in an expression, or assigned to. For example, instead of
cairo_set_source(ctx, pat)
you can write
ctx.source = pat
and instead of
pat = cairo_get_source(ctx)
you write
pat = ctx.source
However, the set_*property* versions of the calls are still provided,
for use with method-chaining.
Method-chaining
===============
*Method-chaining* is a technique for reducing repetitiveness when making
a series of calls on the same object. This is achieved by having the
method calls return the object itself, allowing the immediate application
of another method call, and so on. For example, instead of this series
of a drawing calls on a Context:
ctx.new_path()
ctx.move_to(p1)
ctx.curve_to(p2, p3, p4)
ctx.dash = ((1, 1), 0)
ctx.stroke()
you can write
(ctx
.new_path()
.move_to(p1)
.curve_to(p2, p3, p4)
.set_dash((1, 1), 0)
.stroke()
)
Method-chaining is also available on appropriate methods of Pattern and
Surface objects.
Colours
=======
Qahirah defines a *Colour* type, which takes advantage of the standard
Python colorsys module to provide conversions between RGB colour space
(the only one supported by Cairo) and HSV, HLS and YIQ colour spaces.
You can construct a Colour by specifying components in any of these
spaces, and you can retrieve the components in any of these spaces
from a Colour. Internally, a Colour always stores R, G, B and alpha
components.
Where Cairo provides calls to set separate RGB or RGBA colour
components, Qahirah does a set of a single Colour value. For
convenience, you can directly pass an (R, G, B) or (R, G, B, A) tuple,
and it will be automatically converted to a Colour. Correspondingly,
where a call returns a Colour, you can convert it directly to an
(R, G, B, A) tuple. If you don’t want the alpha value, just append
“[:-1]” or “[:3]” to the tuple.
Rects
=====
Qahirah defines a *Rect* type, which wraps either an underlying
cairo_rectangle_int_t or cairo_rectangle_t, depending on whether
the coordinates are all integers or not. As with Vectors, the
builtin Python “round()” function can be used to convert a Rect
to one with all-integer coordinates.
Qahirah’s Rect type also defines many useful additional operations.
For example, the “transform_to()” method generates a Matrix that
maps the area covered by a Rect onto another Rect, which is a
very common operation for positioning drawing of figures on a
Surface.
Paths
=====
Cairo defines cairo_path_t and cairo_path_data_t types for holding
path data. The only way to create these is to perform path-creation
operations into a context, and then use cairo_copy_path or cairo_copy_path_flat
to retrieve a copy of the path data.
Qahirah instead offers the Path class. While one of these can
be created with Context.copy_path or Context.copy_path_flat, you can
also construct them yourself from a sequence of Path.Segment objects
(directly representing control point geometry) or Path.Element objects
(mapping more directly to Cairo path-construction calls). Furthermore,
Qahirah allows you to transform a Path through a Matrix, either at
drawing time or to produce a new Path. Context.append_path no longer
calls cairo_append_path; instead, the Path object directly generates
move_to, line_to, curve_to and close_path calls from its elements.
FreeType Fonts
==============
Qahirah gives access to Cairo’s support for FreeType fonts. It has
a built-in minimal FreeType wrapper, but it can also take advantage
of the more extensive freetype2 Python module, available from
[GitLab](https://gitlab.com/ldo/python_freetype>) or
[GitHub](https://github.com/ldo/python_freetype>).
For example, with this module installed, the Path.from_ft_outline
method becomes available, for converting a freetype2.Outline to a
Path.
Other Functional Differences From Pycairo
=========================================
Apart from the above differences, Qahirah is not exactly at functional
parity with Pycairo. Qahirah implements the following major Cairo
features that Pycairo does not:
* User fonts
* Script surfaces
* ScaledFont.text_to_glyphs
while it does not handle most of the GUI-specific surface types that
Pycairo does: Win32 and XLib surfaces.
Examples
========
Examples of Qahirah in action are available in the following
repositories:
* qahirah_examples: [GitLab](https://gitlab.com/ldo/qahirah_examples),
[GitHub](https://github.com/ldo/qahirah_examples)
* qahirah_notebooks: [GitLab](https://gitlab.com/ldo/qahirah_notebooks),
[GitHub](https://github.com/ldo/qahirah_notebooks)
* python_pixman_examples: [GitLab](https://gitlab.com/ldo/python_pixman_examples),
[GitHub](https://github.com/ldo/python_pixman_examples)
* python_freetype_examples: [GitLab](https://gitlab.com/ldo/python_freetype_examples),
[GitHub](https://github.com/ldo/python_freetype_examples)
* harfpy_examples: [GitLab](https://gitlab.com/ldo/harfpy_examples),
[GitHub](https://github.com/ldo/harfpy_examples)
* HersheyPy: [GitLab](https://gitlab.com/ldo/hersheypy), [GitHub](https://github.com/ldo/hersheypy)
* anim_framework_examples: [GitLab](https://gitlab.com/ldo/anim_framework_examples),
[GitHub](https://github.com/ldo/anim_framework_examples)
* curve: [GitLab](https://gitlab.com/ldo/curve), [GitHub](https://github.com/ldo/curve)
* GrainyPy: [GitLab](https://gitlab.com/ldo/grainypy), [GitHub](https://github.com/ldo/grainypy)
Lawrence D'Oliveiro <[email protected]>
2017 October 26