~ubuntu-branches/ubuntu/trusty/python-enable/trusty

« back to all changes in this revision

Viewing changes to enthought/kiva/mac/ABCGI.pyx

  • Committer: Bazaar Package Importer
  • Author(s): Varun Hiremath
  • Date: 2011-04-05 21:54:28 UTC
  • mfrom: (1.1.5 upstream)
  • mto: (8.2.1 sid)
  • mto: This revision was merged to the branch mainline in revision 10.
  • Revision ID: james.westby@ubuntu.com-20110405215428-1x2wtubz3ok2kxaq
Tags: upstream-3.4.1
ImportĀ upstreamĀ versionĀ 3.4.1

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# :Author:    Robert Kern
2
 
# :Copyright: 2004, 2007, Enthought, Inc.
3
 
# :License:   BSD Style
4
 
 
5
 
 
6
 
include "Python.pxi"
7
 
include "CoreFoundation.pxi"
8
 
include "CoreGraphics.pxi"
9
 
include "QuickDraw.pxi"
10
 
include "ATS.pxi"
11
 
include "ATSUI.pxi"
12
 
 
13
 
cimport c_numpy
14
 
 
15
 
import os
16
 
import warnings
17
 
 
18
 
from ATSFont import default_font_info
19
 
 
20
 
cdef extern from "math.h":
21
 
    double sqrt(double arg)
22
 
 
23
 
 
24
 
cdef CFURLRef url_from_filename(char* filename) except NULL:
25
 
    cdef CFStringRef filePath
26
 
    filePath = CFStringCreateWithCString(NULL, filename,
27
 
        kCFStringEncodingUTF8)
28
 
    if filePath == NULL:
29
 
        raise RuntimeError("could not create CFStringRef")
30
 
 
31
 
    cdef CFURLRef cfurl
32
 
    cfurl = CFURLCreateWithFileSystemPath(NULL, filePath,
33
 
        kCFURLPOSIXPathStyle, 0)
34
 
    CFRelease(filePath)
35
 
    if cfurl == NULL:
36
 
        raise RuntimeError("could not create a CFURLRef")
37
 
    return cfurl
38
 
 
39
 
# Enumerations
40
 
 
41
 
class LineCap:
42
 
    butt = kCGLineCapButt
43
 
    round = kCGLineCapRound
44
 
    square = kCGLineCapSquare
45
 
 
46
 
class LineJoin:
47
 
    miter = kCGLineJoinMiter
48
 
    round = kCGLineJoinRound
49
 
    bevel = kCGLineJoinBevel
50
 
 
51
 
class PathDrawingMode:
52
 
    fill = kCGPathFill
53
 
    eof_fill = kCGPathEOFill
54
 
    stroke = kCGPathStroke
55
 
    fill_stroke = kCGPathFillStroke
56
 
    eof_fill_stroke = kCGPathEOFillStroke
57
 
 
58
 
class RectEdge:
59
 
    min_x_edge = CGRectMinXEdge
60
 
    min_y_edge = CGRectMinYEdge
61
 
    max_x_edge = CGRectMaxXEdge
62
 
    max_y_edge = CGRectMaxYEdge
63
 
 
64
 
class ColorRenderingIntent:
65
 
    default = kCGRenderingIntentDefault
66
 
    absolute_colorimetric = kCGRenderingIntentAbsoluteColorimetric
67
 
    realative_colorimetric = kCGRenderingIntentRelativeColorimetric
68
 
    perceptual = kCGRenderingIntentPerceptual
69
 
    saturation = kCGRenderingIntentSaturation
70
 
 
71
 
#class ColorSpaces:
72
 
#    gray = kCGColorSpaceUserGray
73
 
#    rgb = kCGColorSpaceUserRGB
74
 
#    cmyk = kCGColorSpaceUserCMYK
75
 
 
76
 
class FontEnum:
77
 
    index_max  = kCGFontIndexMax
78
 
    index_invalid  = kCGFontIndexInvalid
79
 
    glyph_max  = kCGGlyphMax
80
 
 
81
 
class TextDrawingMode:
82
 
    fill = kCGTextFill
83
 
    stroke = kCGTextStroke
84
 
    fill_stroke = kCGTextFillStroke
85
 
    invisible = kCGTextInvisible
86
 
    fill_clip = kCGTextFillClip
87
 
    stroke_clip = kCGTextStrokeClip
88
 
    fill_stroke_clip = kCGTextFillStrokeClip
89
 
    clip = kCGTextClip
90
 
 
91
 
class TextEncodings:
92
 
    font_specific = kCGEncodingFontSpecific
93
 
    mac_roman = kCGEncodingMacRoman
94
 
 
95
 
class ImageAlphaInfo:
96
 
    none = kCGImageAlphaNone
97
 
    premultiplied_last = kCGImageAlphaPremultipliedLast
98
 
    premultiplied_first = kCGImageAlphaPremultipliedFirst
99
 
    last = kCGImageAlphaLast
100
 
    first = kCGImageAlphaFirst
101
 
    none_skip_last = kCGImageAlphaNoneSkipLast
102
 
    none_skip_first = kCGImageAlphaNoneSkipFirst
103
 
    only = kCGImageAlphaOnly
104
 
 
105
 
class InterpolationQuality:
106
 
    default = kCGInterpolationDefault
107
 
    none = kCGInterpolationNone
108
 
    low = kCGInterpolationLow
109
 
    high = kCGInterpolationHigh
110
 
 
111
 
class PathElementType:
112
 
    move_to = kCGPathElementMoveToPoint,
113
 
    line_to = kCGPathElementAddLineToPoint,
114
 
    quad_curve_to = kCGPathElementAddQuadCurveToPoint,
115
 
    curve_to = kCGPathElementAddCurveToPoint,
116
 
    close_path = kCGPathElementCloseSubpath
117
 
 
118
 
class StringEncoding:
119
 
    mac_roman  = kCFStringEncodingMacRoman
120
 
    windows_latin1  = kCFStringEncodingWindowsLatin1
121
 
    iso_latin1  = kCFStringEncodingISOLatin1
122
 
    nextstep_latin  = kCFStringEncodingNextStepLatin
123
 
    ascii  = kCFStringEncodingASCII
124
 
    unicode  = kCFStringEncodingUnicode
125
 
    utf8  = kCFStringEncodingUTF8
126
 
    nonlossy_ascii  = kCFStringEncodingNonLossyASCII
127
 
 
128
 
class URLPathStyle:
129
 
    posix = kCFURLPOSIXPathStyle
130
 
    hfs = kCFURLHFSPathStyle
131
 
    windows = kCFURLWindowsPathStyle
132
 
 
133
 
c_numpy.import_array()
134
 
import numpy
135
 
 
136
 
from enthought.kiva import constants
137
 
 
138
 
cap_style = {}
139
 
cap_style[constants.CAP_ROUND]  = kCGLineCapRound
140
 
cap_style[constants.CAP_SQUARE] = kCGLineCapSquare
141
 
cap_style[constants.CAP_BUTT]   = kCGLineCapButt
142
 
 
143
 
join_style = {}
144
 
join_style[constants.JOIN_ROUND] = kCGLineJoinRound
145
 
join_style[constants.JOIN_BEVEL] = kCGLineJoinBevel
146
 
join_style[constants.JOIN_MITER] = kCGLineJoinMiter
147
 
 
148
 
draw_modes = {}
149
 
draw_modes[constants.FILL]            = kCGPathFill
150
 
draw_modes[constants.EOF_FILL]        = kCGPathEOFill
151
 
draw_modes[constants.STROKE]          = kCGPathStroke
152
 
draw_modes[constants.FILL_STROKE]     = kCGPathFillStroke
153
 
draw_modes[constants.EOF_FILL_STROKE] = kCGPathEOFillStroke
154
 
 
155
 
text_modes = {}
156
 
text_modes[constants.TEXT_FILL]             = kCGTextFill
157
 
text_modes[constants.TEXT_STROKE]           = kCGTextStroke
158
 
text_modes[constants.TEXT_FILL_STROKE]      = kCGTextFillStroke
159
 
text_modes[constants.TEXT_INVISIBLE]        = kCGTextInvisible
160
 
text_modes[constants.TEXT_FILL_CLIP]        = kCGTextFillClip
161
 
text_modes[constants.TEXT_STROKE_CLIP]      = kCGTextStrokeClip
162
 
text_modes[constants.TEXT_FILL_STROKE_CLIP] = kCGTextFillStrokeClip
163
 
text_modes[constants.TEXT_CLIP]             = kCGTextClip
164
 
# this last one doesn't exist in Quartz
165
 
text_modes[constants.TEXT_OUTLINE]          = kCGTextStroke
166
 
 
167
 
cdef class CGContext
168
 
cdef class CGContextInABox(CGContext)
169
 
cdef class CGImage
170
 
cdef class CGPDFDocument
171
 
cdef class Rect
172
 
cdef class CGLayerContext(CGContextInABox)
173
 
cdef class CGGLContext(CGContextInABox)
174
 
cdef class CGBitmapContext(CGContext)
175
 
cdef class CGPDFContext(CGContext)
176
 
cdef class CGImageMask(CGImage)
177
 
cdef class CGAffine
178
 
cdef class CGMutablePath
179
 
cdef class Shading
180
 
 
181
 
cdef class CGContext:
182
 
    cdef CGContextRef context
183
 
    cdef long can_release
184
 
    cdef object current_font
185
 
    cdef ATSUStyle current_style
186
 
    cdef CGAffineTransform text_matrix
187
 
    cdef object style_cache
188
 
 
189
 
    def __new__(self, *args, **kwds):
190
 
        self.context = NULL
191
 
        self.current_style = NULL
192
 
        self.can_release = 0
193
 
        self.text_matrix = CGAffineTransformMake(1.0, 0.0, 0.0, 1.0, 0.0, 0.0)
194
 
 
195
 
    def __init__(self, long context, long can_release=0):
196
 
        self.context = <CGContextRef>context
197
 
 
198
 
        self.can_release = can_release
199
 
 
200
 
        self._setup_color_space()
201
 
        self._setup_fonts()
202
 
 
203
 
    def _setup_color_space(self):
204
 
        # setup an RGB color space
205
 
        cdef CGColorSpaceRef space
206
 
 
207
 
        space = CGColorSpaceCreateDeviceRGB()
208
 
        CGContextSetFillColorSpace(self.context, space)
209
 
        CGContextSetStrokeColorSpace(self.context, space)
210
 
        CGColorSpaceRelease(space)
211
 
 
212
 
    def _setup_fonts(self):
213
 
        self.style_cache = {}
214
 
        self.select_font("Helvetica", 12)
215
 
        CGContextSetShouldSmoothFonts(self.context, 1)
216
 
        CGContextSetShouldAntialias(self.context, 1)
217
 
 
218
 
    #----------------------------------------------------------------
219
 
    # Coordinate Transform Matrix Manipulation
220
 
    #----------------------------------------------------------------
221
 
 
222
 
    def scale_ctm(self, float sx, float sy):
223
 
        """ Set the coordinate system scale to the given values, (sx,sy).
224
 
 
225
 
            sx:float -- The new scale factor for the x axis
226
 
            sy:float -- The new scale factor for the y axis
227
 
        """
228
 
        CGContextScaleCTM(self.context, sx, sy)
229
 
 
230
 
    def translate_ctm(self, float tx, float ty):
231
 
        """ Translate the coordinate system by the given value by (tx,ty)
232
 
 
233
 
            tx:float --  The distance to move in the x direction
234
 
            ty:float --   The distance to move in the y direction
235
 
        """
236
 
        CGContextTranslateCTM(self.context, tx, ty)
237
 
 
238
 
    def rotate_ctm(self, float angle):
239
 
        """ Rotates the coordinate space for drawing by the given angle.
240
 
 
241
 
            angle:float -- the angle, in radians, to rotate the coordinate
242
 
                           system
243
 
        """
244
 
        CGContextRotateCTM(self.context, angle)
245
 
 
246
 
    def concat_ctm(self, object transform):
247
 
        """ Concatenate the transform to current coordinate transform matrix.
248
 
 
249
 
            transform:affine_matrix -- the transform matrix to concatenate with
250
 
                                       the current coordinate matrix.
251
 
        """
252
 
        cdef float a,b,c,d,tx,ty
253
 
        a,b,c,d,tx,ty = transform
254
 
 
255
 
        cdef CGAffineTransform atransform
256
 
        atransform = CGAffineTransformMake(a,b,c,d,tx,ty)
257
 
 
258
 
        CGContextConcatCTM(self.context, atransform)
259
 
 
260
 
    def get_ctm(self):
261
 
        """ Return the current coordinate transform matrix.
262
 
        """
263
 
        cdef CGAffineTransform t
264
 
        t = CGContextGetCTM(self.context)
265
 
        return (t.a, t.b, t.c, t.d, t.tx, t.ty)
266
 
 
267
 
    def get_ctm_scale(self):
268
 
        """ Returns the average scaling factor of the transform matrix.
269
 
 
270
 
        This isn't really part of the GC interface, but it is a convenience
271
 
        method to make up for us not having full AffineMatrix support in the
272
 
        Mac backend.
273
 
        """
274
 
        cdef CGAffineTransform t
275
 
        t = CGContextGetCTM(self.context)
276
 
        x = sqrt(2.0) / 2.0 * (t.a + t.b)
277
 
        y = sqrt(2.0) / 2.0 * (t.c + t.d)
278
 
        return sqrt(x*x + y*y)
279
 
    
280
 
        
281
 
 
282
 
    #----------------------------------------------------------------
283
 
    # Save/Restore graphics state.
284
 
    #----------------------------------------------------------------
285
 
 
286
 
    def save_state(self):
287
 
        """ Save the current graphic's context state.
288
 
 
289
 
            This should always be paired with a restore_state
290
 
        """
291
 
        CGContextSaveGState(self.context)
292
 
 
293
 
    def restore_state(self):
294
 
        """ Restore the previous graphics state.
295
 
        """
296
 
        CGContextRestoreGState(self.context)
297
 
 
298
 
    #----------------------------------------------------------------
299
 
    # Manipulate graphics state attributes.
300
 
    #----------------------------------------------------------------
301
 
 
302
 
    def set_antialias(self, bool value):
303
 
        """ Set/Unset antialiasing for bitmap graphics context.
304
 
        """
305
 
        CGContextSetShouldAntialias(self.context, value)
306
 
 
307
 
    def set_line_width(self, float width):
308
 
        """ Set the line width for drawing
309
 
 
310
 
            width:float -- The new width for lines in user space units.
311
 
        """
312
 
        CGContextSetLineWidth(self.context, width)
313
 
 
314
 
    def set_line_join(self, object style):
315
 
        """ Set style for joining lines in a drawing.
316
 
 
317
 
            style:join_style -- The line joining style.  The available
318
 
                                styles are JOIN_ROUND, JOIN_BEVEL, JOIN_MITER.
319
 
        """
320
 
        try:
321
 
            sjoin = join_style[style]
322
 
        except KeyError:
323
 
            msg = "Invalid line join style.  See documentation for valid styles"
324
 
            raise ValueError(msg)
325
 
        CGContextSetLineJoin(self.context, sjoin)
326
 
 
327
 
    def set_miter_limit(self, float limit):
328
 
        """ Specifies limits on line lengths for mitering line joins.
329
 
 
330
 
            If line_join is set to miter joins, the limit specifies which
331
 
            line joins should actually be mitered.  If lines aren't mitered,
332
 
            they are joined with a bevel.  The line width is divided by
333
 
            the length of the miter.  If the result is greater than the
334
 
            limit, the bevel style is used.
335
 
 
336
 
            limit:float -- limit for mitering joins.
337
 
        """
338
 
        CGContextSetMiterLimit(self.context, limit)
339
 
 
340
 
    def set_line_cap(self, object style):
341
 
        """ Specify the style of endings to put on line ends.
342
 
 
343
 
            style:cap_style -- the line cap style to use. Available styles
344
 
                               are CAP_ROUND,CAP_BUTT,CAP_SQUARE
345
 
        """
346
 
        try:
347
 
            scap = cap_style[style]
348
 
        except KeyError:
349
 
            msg = "Invalid line cap style.  See documentation for valid styles"
350
 
            raise ValueError(msg)
351
 
        CGContextSetLineCap(self.context, scap)
352
 
 
353
 
    def set_line_dash(self, object lengths, float phase=0.0):
354
 
        """
355
 
            lengths:float array -- An array of floating point values
356
 
                                   specifing the lengths of on/off painting
357
 
                                   pattern for lines.
358
 
            phase:float -- Specifies how many units into dash pattern
359
 
                           to start.  phase defaults to 0.
360
 
        """
361
 
        cdef int n
362
 
        cdef int i
363
 
        cdef float *flengths
364
 
 
365
 
        if lengths is None:
366
 
            # No dash; solid line.
367
 
            CGContextSetLineDash(self.context, 0.0, NULL, 0)
368
 
            return
369
 
        else:
370
 
            n = len(lengths)
371
 
            flengths = <float*>PyMem_Malloc(n*sizeof(float))
372
 
            if flengths == NULL:
373
 
                raise MemoryError("could not allocate %s floats" % n)
374
 
            for i from 0 <= i < n:
375
 
                flengths[i] = lengths[i]
376
 
            CGContextSetLineDash(self.context, phase, flengths, n)
377
 
            PyMem_Free(flengths)
378
 
 
379
 
    def set_flatness(self, float flatness):
380
 
        """
381
 
            It is device dependent and therefore not recommended by
382
 
            the PDF documentation.
383
 
        """
384
 
        CGContextSetFlatness(self.context, flatness)
385
 
 
386
 
    #----------------------------------------------------------------
387
 
    # Sending drawing data to a device
388
 
    #----------------------------------------------------------------
389
 
 
390
 
    def flush(self):
391
 
        """ Send all drawing data to the destination device.
392
 
        """
393
 
        CGContextFlush(self.context)
394
 
 
395
 
    def synchronize(self):
396
 
        """ Prepares drawing data to be updated on a destination device.
397
 
        """
398
 
        CGContextSynchronize(self.context)
399
 
 
400
 
    #----------------------------------------------------------------
401
 
    # Page Definitions
402
 
    #----------------------------------------------------------------
403
 
 
404
 
    def begin_page(self, media_box=None):
405
 
        """ Create a new page within the graphics context.
406
 
        """
407
 
        cdef CGRect mbox
408
 
        cdef CGRect* mbox_ptr
409
 
        if media_box is not None:
410
 
            mbox = CGRectMakeFromPython(media_box)
411
 
            mbox_ptr = &mbox
412
 
        else:
413
 
            mbox_ptr = NULL
414
 
 
415
 
        CGContextBeginPage(self.context, mbox_ptr)
416
 
 
417
 
    def end_page(self):
418
 
        """ End drawing in the current page of the graphics context.
419
 
        """
420
 
        CGContextEndPage(self.context)
421
 
 
422
 
    #----------------------------------------------------------------
423
 
    # Building paths (contours that are drawn)
424
 
    #
425
 
    # + Currently, nothing is drawn as the path is built.  Instead, the
426
 
    #   instructions are stored and later drawn.  Should this be changed?
427
 
    #   We will likely draw to a buffer instead of directly to the canvas
428
 
    #   anyway.
429
 
    #
430
 
    #   Hmmm. No.  We have to keep the path around for storing as a
431
 
    #   clipping region and things like that.
432
 
    #
433
 
    # + I think we should keep the current_path_point hanging around.
434
 
    #
435
 
    #----------------------------------------------------------------
436
 
 
437
 
    def begin_path(self):
438
 
        """ Clear the current drawing path and begin a new one.
439
 
        """
440
 
        CGContextBeginPath(self.context)
441
 
 
442
 
    def move_to(self, float x, float y):
443
 
        """ Start a new drawing subpath at place the current point at (x,y).
444
 
        """
445
 
        CGContextMoveToPoint(self.context, x,y)
446
 
 
447
 
    def line_to(self, float x, float y):
448
 
        """ Add a line from the current point to the given point (x,y).
449
 
 
450
 
            The current point is moved to (x,y).
451
 
        """
452
 
        CGContextAddLineToPoint(self.context, x,y)
453
 
 
454
 
    def lines(self, object points):
455
 
        """ Add a series of lines as a new subpath.
456
 
 
457
 
            Points is an Nx2 array of x,y pairs.
458
 
 
459
 
            current_point is moved to the last point in points
460
 
        """
461
 
 
462
 
        cdef int n
463
 
        cdef int i
464
 
        cdef c_numpy.ndarray apoints
465
 
        cdef float x, y
466
 
 
467
 
        n = len(points)
468
 
 
469
 
        # Shortcut for the 0 and 1 point case
470
 
        if n < 2:
471
 
            return
472
 
 
473
 
        apoints = <c_numpy.ndarray>(numpy.asarray(points, dtype=numpy.float32))
474
 
 
475
 
        if apoints.nd != 2 or apoints.dimensions[1] != 2:
476
 
            msg = "must pass array of 2-D points"
477
 
            raise ValueError(msg)
478
 
 
479
 
        x = (<float*>c_numpy.PyArray_GETPTR2(apoints, 0, 0))[0]
480
 
        y = (<float*>c_numpy.PyArray_GETPTR2(apoints, 0, 1))[0]
481
 
        CGContextMoveToPoint(self.context, x, y)
482
 
        for i from 1 <= i < n:  
483
 
            x = (<float*>c_numpy.PyArray_GETPTR2(apoints, i, 0))[0]
484
 
            y = (<float*>c_numpy.PyArray_GETPTR2(apoints, i, 1))[0]
485
 
            CGContextAddLineToPoint(self.context, x, y)
486
 
 
487
 
    def line_set(self, object starts, object ends):
488
 
        """ Adds a series of disconnected line segments as a new subpath.
489
 
 
490
 
            starts and ends are Nx2 arrays of (x,y) pairs indicating the
491
 
            starting and ending points of each line segment.
492
 
 
493
 
            current_point is moved to the last point in ends
494
 
        """
495
 
        cdef int n
496
 
        n = len(starts)
497
 
        if len(ends) < n:
498
 
            n = len(ends)
499
 
 
500
 
        cdef int i
501
 
        for i from 0 <= i < n:
502
 
            CGContextMoveToPoint(self.context, starts[i][0], starts[i][1])
503
 
            CGContextAddLineToPoint(self.context, ends[i][0], ends[i][1])
504
 
 
505
 
    def rect(self, float x, float y, float sx, float sy):
506
 
        """ Add a rectangle as a new subpath.
507
 
        """
508
 
        CGContextAddRect(self.context, CGRectMake(x,y,sx,sy))
509
 
 
510
 
    def rects(self, object rects):
511
 
        """ Add multiple rectangles as separate subpaths to the path.
512
 
        """
513
 
        cdef int n
514
 
        n = len(rects)
515
 
        cdef int i
516
 
        for i from 0 <= i < n:
517
 
            CGContextAddRect(self.context, CGRectMakeFromPython(rects[i]))
518
 
 
519
 
    def close_path(self):
520
 
        """ Close the path of the current subpath.
521
 
        """
522
 
        CGContextClosePath(self.context)
523
 
 
524
 
    def curve_to(self, float cp1x, float cp1y, float cp2x, float cp2y,
525
 
        float x, float y):
526
 
        """
527
 
        """
528
 
        CGContextAddCurveToPoint(self.context, cp1x, cp1y, cp2x, cp2y, x, y )
529
 
 
530
 
    def quad_curve_to(self, float cpx, float cpy, float x, float y):
531
 
        """
532
 
        """
533
 
        CGContextAddQuadCurveToPoint(self.context, cpx, cpy, x, y)
534
 
 
535
 
    def arc(self, float x, float y, float radius, float start_angle,
536
 
        float end_angle, bool clockwise=False):
537
 
        """
538
 
        """
539
 
        CGContextAddArc(self.context, x, y, radius, start_angle, end_angle,
540
 
                           clockwise)
541
 
 
542
 
    def arc_to(self, float x1, float y1, float x2, float y2, float radius):
543
 
        """
544
 
        """
545
 
        CGContextAddArcToPoint(self.context, x1, y1, x2, y2, radius)
546
 
 
547
 
    def add_path(self, CGMutablePath path not None):
548
 
        """
549
 
        """
550
 
        CGContextAddPath(self.context, path.path)
551
 
 
552
 
    #----------------------------------------------------------------
553
 
    # Getting information on paths
554
 
    #----------------------------------------------------------------
555
 
 
556
 
    def is_path_empty(self):
557
 
        """ Test to see if the current drawing path is empty
558
 
        """
559
 
        return CGContextIsPathEmpty(self.context)
560
 
 
561
 
    def get_path_current_point(self):
562
 
        """ Return the current point from the graphics context.
563
 
 
564
 
            Note: This should be a tuple or array.
565
 
 
566
 
        """
567
 
        cdef CGPoint result
568
 
        result = CGContextGetPathCurrentPoint(self.context)
569
 
        return result.x, result.y
570
 
 
571
 
    def get_path_bounding_box(self):
572
 
        """
573
 
            should return a tuple or array instead of a strange object.
574
 
        """
575
 
        cdef CGRect result
576
 
        result = CGContextGetPathBoundingBox(self.context)
577
 
        return (result.origin.x, result.origin.y,
578
 
                result.size.width, result.size.height)
579
 
 
580
 
    #----------------------------------------------------------------
581
 
    # Clipping path manipulation
582
 
    #----------------------------------------------------------------
583
 
 
584
 
    def clip(self):
585
 
        """
586
 
        """
587
 
        CGContextClip(self.context)
588
 
 
589
 
    def even_odd_clip(self):
590
 
        """
591
 
        """
592
 
        CGContextEOClip(self.context)
593
 
 
594
 
    def clip_to_rect(self, float x, float y, float width, float height):
595
 
        """ Clip context to the given rectangular region.
596
 
        """
597
 
        CGContextClipToRect(self.context, CGRectMake(x,y,width,height))
598
 
 
599
 
    def clip_to_rects(self, object rects):
600
 
        """
601
 
        """
602
 
        cdef int n
603
 
        n = len(rects)
604
 
        cdef int i
605
 
        cdef CGRect* cgrects
606
 
 
607
 
        cgrects = <CGRect*>PyMem_Malloc(n*sizeof(CGRect))
608
 
        if cgrects == NULL:
609
 
            raise MemoryError("could not allocate memory for CGRects")
610
 
 
611
 
        for i from 0 <= i < n:
612
 
            cgrects[i] = CGRectMakeFromPython(rects[i])
613
 
        CGContextClipToRects(self.context, cgrects, n)
614
 
        PyMem_Free(cgrects)
615
 
 
616
 
 
617
 
    #----------------------------------------------------------------
618
 
    # Color space manipulation
619
 
    #
620
 
    # I'm not sure we'll mess with these at all.  They seem to
621
 
    # be for setting the color system.  Hard coding to RGB or
622
 
    # RGBA for now sounds like a reasonable solution.
623
 
    #----------------------------------------------------------------
624
 
 
625
 
    def set_fill_color_space(self):
626
 
        """
627
 
        """
628
 
        msg = "set_fill_color_space not implemented on Macintosh yet."
629
 
        raise NotImplementedError(msg)
630
 
 
631
 
    def set_stroke_color_space(self):
632
 
        """
633
 
        """
634
 
        msg = "set_stroke_color_space not implemented on Macintosh yet."
635
 
        raise NotImplementedError(msg)
636
 
 
637
 
    def set_rendering_intent(self, intent):
638
 
        """
639
 
        """
640
 
        CGContextSetRenderingIntent(self.context, intent)
641
 
 
642
 
    #----------------------------------------------------------------
643
 
    # Color manipulation
644
 
    #----------------------------------------------------------------
645
 
 
646
 
    def set_fill_color(self, object color):
647
 
        """
648
 
        """
649
 
        r,g,b = color[:3]
650
 
        try:
651
 
            a = color[3]
652
 
        except IndexError:
653
 
            a = 1.0
654
 
        CGContextSetRGBFillColor(self.context, r, g, b, a)
655
 
 
656
 
    def set_stroke_color(self, object color):
657
 
        """
658
 
        """
659
 
        r,g,b = color[:3]
660
 
        try:
661
 
            a = color[3]
662
 
        except IndexError:
663
 
            a = 1.0
664
 
        CGContextSetRGBStrokeColor(self.context, r, g, b, a)
665
 
 
666
 
    def set_alpha(self, float alpha):
667
 
        """
668
 
        """
669
 
        CGContextSetAlpha(self.context, alpha)
670
 
 
671
 
    #def set_gray_fill_color(self):
672
 
    #    """
673
 
    #    """
674
 
    #    pass
675
 
 
676
 
    #def set_gray_stroke_color(self):
677
 
    #    """
678
 
    #    """
679
 
    #    pass
680
 
 
681
 
    #def set_rgb_fill_color(self):
682
 
    #    """
683
 
    #    """
684
 
    #    pass
685
 
 
686
 
    #def set_rgb_stroke_color(self):
687
 
    #    """
688
 
    #    """
689
 
    #    pass
690
 
 
691
 
    #def cmyk_fill_color(self):
692
 
    #    """
693
 
    #    """
694
 
    #    pass
695
 
 
696
 
    #def cmyk_stroke_color(self):
697
 
    #    """
698
 
    #    """
699
 
    #    pass
700
 
 
701
 
    #----------------------------------------------------------------
702
 
    # Drawing Images
703
 
    #----------------------------------------------------------------
704
 
 
705
 
    def draw_image(self, object image, object rect=None):
706
 
        """ Draw an image or another CGContext onto a region.
707
 
        """
708
 
        if rect is None:
709
 
            rect = (0, 0, self.width(), self.height())
710
 
        if isinstance(image, numpy.ndarray):
711
 
            self._draw_cgimage(CGImage(image), rect)
712
 
        elif isinstance(image, CGImage):
713
 
            self._draw_cgimage(image, rect)
714
 
        elif hasattr(image, 'bmp_array'):
715
 
            self._draw_cgimage(CGImage(image.bmp_array), rect)
716
 
        elif isinstance(image, CGLayerContext):
717
 
            self._draw_cglayer(image, rect)
718
 
        else:
719
 
            raise TypeError("could not recognize image %r" % type(image))
720
 
 
721
 
    def _draw_cgimage(self, CGImage image, object rect):
722
 
        """ Draw a CGImage into a region.
723
 
        """
724
 
        CGContextDrawImage(self.context, CGRectMakeFromPython(rect),
725
 
            image.image)
726
 
 
727
 
    def _draw_cglayer(self, CGLayerContext layer, object rect):
728
 
        """ Draw a CGLayer into a region.
729
 
        """
730
 
        CGContextDrawLayerInRect(self.context, CGRectMakeFromPython(rect),
731
 
            layer.layer)
732
 
 
733
 
    def set_interpolation_quality(self, quality):
734
 
        CGContextSetInterpolationQuality(self.context, quality)
735
 
 
736
 
    #----------------------------------------------------------------
737
 
    # Drawing PDF documents
738
 
    #----------------------------------------------------------------
739
 
 
740
 
    def draw_pdf_document(self, object rect, CGPDFDocument document not None,
741
 
        int page=1):
742
 
        """
743
 
            rect:(x,y,width,height) -- rectangle to draw into
744
 
            document:CGPDFDocument -- PDF file to read from
745
 
            page=1:int -- page number of PDF file
746
 
        """
747
 
        cdef CGRect cgrect
748
 
        cgrect = CGRectMakeFromPython(rect)
749
 
 
750
 
        CGContextDrawPDFDocument(self.context, cgrect, document.document, page)
751
 
 
752
 
 
753
 
    #----------------------------------------------------------------
754
 
    # Drawing Text
755
 
    #----------------------------------------------------------------
756
 
 
757
 
    def select_font(self, object name, float size, style='regular'):
758
 
        """
759
 
        """
760
 
        cdef ATSUStyle atsu_style
761
 
 
762
 
        key = (name, size, style)
763
 
        if key not in self.style_cache:
764
 
            font = default_font_info.lookup(name, style=style)
765
 
            self.current_font = font
766
 
            ps_name = font.postscript_name
767
 
 
768
 
            atsu_style = _create_atsu_style(ps_name, size)
769
 
            if atsu_style == NULL:
770
 
                raise RuntimeError("could not create style for font %r" % ps_name)
771
 
            self.style_cache[key] = PyCObject_FromVoidPtr(<void*>atsu_style,
772
 
                <cobject_destr>ATSUDisposeStyle)
773
 
 
774
 
        atsu_style = <ATSUStyle>PyCObject_AsVoidPtr(self.style_cache[key])
775
 
        self.current_style = atsu_style
776
 
 
777
 
    def set_font(self, font):
778
 
        """ Set the font for the current graphics context.
779
 
 
780
 
            I need to figure out this one.
781
 
        """
782
 
 
783
 
        style = {
784
 
            constants.NORMAL: 'regular',
785
 
            constants.BOLD: 'bold',
786
 
            constants.ITALIC: 'italic',
787
 
            constants.BOLD_ITALIC: 'bold italic',
788
 
        }[font.weight | font.style]
789
 
        self.select_font(font.face_name, font.size, style=style)
790
 
 
791
 
    def set_font_size(self, float size):
792
 
        """
793
 
        """
794
 
        cdef ATSUAttributeTag attr_tag
795
 
        cdef ByteCount attr_size
796
 
        cdef ATSUAttributeValuePtr attr_value
797
 
        cdef Fixed fixed_size
798
 
        cdef OSStatus err
799
 
 
800
 
        if self.current_style == NULL:
801
 
            return
802
 
 
803
 
        attr_tag = kATSUSizeTag
804
 
        attr_size = sizeof(Fixed)
805
 
        fixed_size = FloatToFixed(size)
806
 
        attr_value = <ATSUAttributeValuePtr>&fixed_size
807
 
        err = ATSUSetAttributes(self.current_style, 1, &attr_tag, &attr_size, &attr_value)
808
 
        if err:
809
 
            raise RuntimeError("could not set font size on current style")
810
 
 
811
 
    def set_character_spacing(self, float spacing):
812
 
        """
813
 
        """
814
 
 
815
 
        # XXX: This does not fit in with ATSUI, really.
816
 
        CGContextSetCharacterSpacing(self.context, spacing)
817
 
 
818
 
    def set_text_drawing_mode(self, object mode):
819
 
        """
820
 
        """
821
 
        try:
822
 
            cgmode = text_mode[mode]
823
 
        except KeyError:
824
 
            msg = "Invalid text drawing mode.  See documentation for valid modes"
825
 
            raise ValueError(msg)
826
 
        CGContextSetTextDrawingMode(self.context, cgmode)
827
 
 
828
 
    def set_text_position(self, float x,float y):
829
 
        """
830
 
        """
831
 
        self.text_matrix.tx = x
832
 
        self.text_matrix.ty = y
833
 
 
834
 
    def get_text_position(self):
835
 
        """
836
 
        """
837
 
        return self.text_matrix.tx, self.text_matrix.ty
838
 
 
839
 
    def set_text_matrix(self, object ttm):
840
 
        """
841
 
        """
842
 
        cdef float a,b,c,d,tx,ty
843
 
        ((a,  b,  _),
844
 
         (c,  d,  _),
845
 
         (tx, ty, _)) = ttm
846
 
 
847
 
        cdef CGAffineTransform transform
848
 
        transform = CGAffineTransformMake(a,b,c,d,tx,ty)
849
 
        self.text_matrix = transform
850
 
 
851
 
    def get_text_matrix(self):
852
 
        """
853
 
        """
854
 
        return ((self.text_matrix.a, self.text_matrix.b, 0.0),
855
 
                (self.text_matrix.c, self.text_matrix.d, 0.0),
856
 
                (self.text_matrix.tx,self.text_matrix.ty,1.0))
857
 
 
858
 
    def get_text_extent(self, object text):
859
 
        """ Measure the space taken up by given text using the current font.
860
 
        """
861
 
        cdef CGPoint start
862
 
        cdef CGPoint stop
863
 
        cdef ATSFontMetrics metrics
864
 
        cdef OSStatus status
865
 
        cdef ATSUTextLayout layout
866
 
        cdef double x1, x2, y1, y2
867
 
        cdef ATSUTextMeasurement before, after, ascent, descent
868
 
        cdef ByteCount actual_size
869
 
 
870
 
        layout = NULL
871
 
        try:
872
 
            if not text:
873
 
                # ATSUGetUnjustifiedBounds does not handle empty strings.
874
 
                text = " "
875
 
                empty = True
876
 
            else:
877
 
                empty = False
878
 
            _create_atsu_layout(text, self.current_style, &layout)
879
 
            status = ATSUGetUnjustifiedBounds(layout, 0, len(text), &before,
880
 
                &after, &ascent, &descent)
881
 
            if status:
882
 
                raise RuntimeError("could not calculate font metrics")
883
 
 
884
 
            if empty:
885
 
                x1 = 0.0
886
 
                x2 = 0.0
887
 
            else:
888
 
                x1 = FixedToFloat(before)
889
 
                x2 = FixedToFloat(after)
890
 
 
891
 
            y1 = -FixedToFloat(descent)
892
 
            y2 = -y1 + FixedToFloat(ascent)
893
 
 
894
 
        finally:
895
 
            if layout != NULL:
896
 
                ATSUDisposeTextLayout(layout)
897
 
 
898
 
        return x1, y1, x2, y2
899
 
 
900
 
    def get_full_text_extent(self, object text):
901
 
        """ Backwards compatibility API over .get_text_extent() for Enable.
902
 
        """
903
 
 
904
 
        x1, y1, x2, y2 = self.get_text_extent(text)
905
 
 
906
 
        return x2, y2, y1, x1
907
 
 
908
 
 
909
 
    def show_text(self, object text, object xy=None):
910
 
        """ Draw text on the device at current text position.
911
 
 
912
 
            This is also used for showing text at a particular point
913
 
            specified by xy == (x, y).
914
 
        """
915
 
        cdef float x
916
 
        cdef float y
917
 
        cdef CGAffineTransform text_matrix
918
 
        cdef ATSUTextLayout layout
919
 
 
920
 
        if not text:
921
 
            # I don't think we can draw empty strings using the ATSU API.
922
 
            return
923
 
 
924
 
        if xy is None:
925
 
            x = 0.0
926
 
            y = 0.0
927
 
        else:
928
 
            x = xy[0]
929
 
            y = xy[1]
930
 
 
931
 
        self.save_state()
932
 
        try:
933
 
            CGContextConcatCTM(self.context, self.text_matrix)
934
 
            _create_atsu_layout(text, self.current_style, &layout)
935
 
            _set_cgcontext_for_layout(self.context, layout)
936
 
            ATSUDrawText(layout, 0, len(text), FloatToFixed(x), FloatToFixed(y))
937
 
        finally:
938
 
            self.restore_state()
939
 
            if layout != NULL:
940
 
                ATSUDisposeTextLayout(layout)
941
 
 
942
 
    def show_text_at_point(self, object text, float x, float y):
943
 
        """ Draw text on the device at a given text position.
944
 
        """
945
 
        self.show_text(text, (x, y))
946
 
 
947
 
    def show_glyphs(self):
948
 
        """
949
 
        """
950
 
        msg = "show_glyphs not implemented on Macintosh yet."
951
 
        raise NotImplementedError(msg)
952
 
 
953
 
    #----------------------------------------------------------------
954
 
    # Painting paths (drawing and filling contours)
955
 
    #----------------------------------------------------------------
956
 
 
957
 
    def stroke_path(self):
958
 
        """
959
 
        """
960
 
        CGContextStrokePath(self.context)
961
 
 
962
 
    def fill_path(self):
963
 
        """
964
 
        """
965
 
        CGContextFillPath(self.context)
966
 
 
967
 
    def eof_fill_path(self):
968
 
        """
969
 
        """
970
 
        CGContextEOFillPath(self.context)
971
 
 
972
 
    def stroke_rect(self, object rect):
973
 
        """
974
 
        """
975
 
        CGContextStrokeRect(self.context, CGRectMakeFromPython(rect))
976
 
 
977
 
    def stroke_rect_with_width(self, object rect, float width):
978
 
        """
979
 
        """
980
 
        CGContextStrokeRectWithWidth(self.context, CGRectMakeFromPython(rect), width)
981
 
 
982
 
    def fill_rect(self, object rect):
983
 
        """
984
 
        """
985
 
        CGContextFillRect(self.context, CGRectMakeFromPython(rect))
986
 
 
987
 
    def fill_rects(self, object rects):
988
 
        """
989
 
        """
990
 
        cdef int n
991
 
        n = len(rects)
992
 
        cdef int i
993
 
        cdef CGRect* cgrects
994
 
 
995
 
        cgrects = <CGRect*>PyMem_Malloc(n*sizeof(CGRect))
996
 
        if cgrects == NULL:
997
 
            raise MemoryError("could not allocate memory for CGRects")
998
 
 
999
 
        for i from 0 <= i < n:
1000
 
            cgrects[i] = CGRectMakeFromPython(rects[i])
1001
 
 
1002
 
        CGContextFillRects(self.context, cgrects, n)
1003
 
 
1004
 
    def clear_rect(self, object rect):
1005
 
        """
1006
 
        """
1007
 
        CGContextClearRect(self.context, CGRectMakeFromPython(rect))
1008
 
 
1009
 
    def draw_path(self, object mode=constants.FILL_STROKE):
1010
 
        """ Walk through all the drawing subpaths and draw each element.
1011
 
 
1012
 
            Each subpath is drawn separately.
1013
 
        """
1014
 
 
1015
 
        cg_mode = draw_modes[mode]
1016
 
        CGContextDrawPath(self.context, cg_mode)
1017
 
 
1018
 
    def draw_rect(self, rect, object mode=constants.FILL_STROKE):
1019
 
        """ Draw a rectangle with the given mode.
1020
 
        """
1021
 
 
1022
 
        self.save_state()
1023
 
        CGContextBeginPath(self.context)
1024
 
        CGContextAddRect(self.context, CGRectMakeFromPython(rect))
1025
 
        cg_mode = draw_modes[mode]
1026
 
        CGContextDrawPath(self.context, cg_mode)
1027
 
        self.restore_state()
1028
 
 
1029
 
    def get_empty_path(self):
1030
 
        """ Return a path object that can be built up and then reused.
1031
 
        """
1032
 
 
1033
 
        return CGMutablePath()
1034
 
 
1035
 
    def draw_path_at_points(self, points, CGMutablePath marker not None,
1036
 
        object mode=constants.FILL_STROKE):
1037
 
 
1038
 
        cdef int i
1039
 
        cdef int n
1040
 
        cdef c_numpy.ndarray apoints
1041
 
        cdef float x, y
1042
 
 
1043
 
        apoints = <c_numpy.ndarray>(numpy.asarray(points, dtype=numpy.float32))
1044
 
 
1045
 
        if apoints.nd != 2 or apoints.dimensions[1] != 2:
1046
 
            msg = "must pass array of 2-D points"
1047
 
            raise ValueError(msg)
1048
 
 
1049
 
        cg_mode = draw_modes[mode]
1050
 
 
1051
 
        n = len(points)
1052
 
        for i from 0 <= i < n:
1053
 
            x = (<float*>c_numpy.PyArray_GETPTR2(apoints, i, 0))[0]
1054
 
            y = (<float*>c_numpy.PyArray_GETPTR2(apoints, i, 1))[0]
1055
 
            CGContextSaveGState(self.context)
1056
 
            CGContextTranslateCTM(self.context, x, y)
1057
 
            CGContextAddPath(self.context, marker.path)
1058
 
            CGContextDrawPath(self.context, cg_mode)
1059
 
            CGContextRestoreGState(self.context)
1060
 
            
1061
 
    def linear_gradient(self, x1, y1, x2, y2, stops, spread_method, units='userSpaceOnUse'):
1062
 
        self.clip()           
1063
 
        stops_list = stops.transpose().tolist()
1064
 
        func = PiecewiseLinearColorFunction(stops_list)
1065
 
        shading = AxialShading(func, (x1,y1), (x2,y2),
1066
 
                               extend_start=1, extend_end=1)
1067
 
        self.draw_shading(shading)
1068
 
            
1069
 
    def radial_gradient(self, cx, cy, r, fx, fy,  stops, spread_method, units='userSpaceOnUse'):
1070
 
        self.clip()           
1071
 
        stops_list = stops.transpose().tolist()
1072
 
        func = PiecewiseLinearColorFunction(stops_list)
1073
 
        shading = RadialShading(func, (fx, fy), 0.0, (cx, cy), r,
1074
 
                               extend_start=1, extend_end=1)
1075
 
        self.draw_shading(shading)
1076
 
 
1077
 
    def draw_shading(self, Shading shading not None):
1078
 
        CGContextDrawShading(self.context, shading.shading)
1079
 
 
1080
 
 
1081
 
    #----------------------------------------------------------------
1082
 
    # Extra routines that aren't part of DisplayPDF
1083
 
    #
1084
 
    # Some access to font metrics are needed for laying out text.
1085
 
    # Not sure how to handle this yet.  The candidates below are
1086
 
    # from Piddle.  Perhaps there is another alternative?
1087
 
    #
1088
 
    #----------------------------------------------------------------
1089
 
 
1090
 
    #def font_height(self):
1091
 
    #    '''Find the total height (ascent + descent) of the given font.'''
1092
 
    #    #return self.font_ascent() + self.font_descent()
1093
 
 
1094
 
    #def font_ascent(self):
1095
 
    #    '''Find the ascent (height above base) of the given font.'''
1096
 
    #    pass
1097
 
 
1098
 
    #def font_descent(self):
1099
 
    #    '''Find the descent (extent below base) of the given font.'''
1100
 
    #    extents = self.dc.GetFullTextExtent(' ', wx_font)
1101
 
    #    return extents[2]
1102
 
 
1103
 
    def __dealloc__(self):
1104
 
        if self.context != NULL and self.can_release:
1105
 
            CGContextRelease(self.context)
1106
 
            self.context = NULL
1107
 
 
1108
 
    # The following are Quartz APIs not in Kiva
1109
 
 
1110
 
    def set_pattern_phase(self, float tx, float ty):
1111
 
        """
1112
 
            tx,ty:floats -- A translation in user-space to apply to a
1113
 
                           pattern before it is drawn
1114
 
        """
1115
 
        CGContextSetPatternPhase(self.context, CGSizeMake(tx, ty))
1116
 
 
1117
 
    def set_should_smooth_fonts(self, bool value):
1118
 
        """
1119
 
            value:bool -- specify whether to enable font smoothing or not
1120
 
        """
1121
 
        CGContextSetShouldSmoothFonts(self.context, value)
1122
 
 
1123
 
cdef class CGContextInABox(CGContext):
1124
 
    """ A CGContext that knows its size.
1125
 
    """
1126
 
    cdef readonly object size
1127
 
    cdef readonly int _width
1128
 
    cdef readonly int _height
1129
 
 
1130
 
    def __init__(self, long context, object size, long can_release=0):
1131
 
        self.context = <CGContextRef>context
1132
 
 
1133
 
        self.can_release = can_release
1134
 
 
1135
 
        self._width, self._height = size
1136
 
 
1137
 
        self._setup_color_space()
1138
 
        self._setup_fonts()
1139
 
 
1140
 
    def clear(self, object clear_color=(1.0,1.0,1.0,1.0)):
1141
 
        self.save_state()
1142
 
        # Reset the transformation matrix back to the identity.
1143
 
        CGContextConcatCTM(self.context, 
1144
 
            CGAffineTransformInvert(CGContextGetCTM(self.context)))
1145
 
        self.set_fill_color(clear_color)
1146
 
        CGContextFillRect(self.context, CGRectMake(0,0,self._width,self._height))
1147
 
        self.restore_state()
1148
 
 
1149
 
    def width(self):
1150
 
        return self._width
1151
 
 
1152
 
    def height(self):
1153
 
        return self._height
1154
 
 
1155
 
 
1156
 
cdef class CGLayerContext(CGContextInABox):
1157
 
    cdef CGLayerRef layer
1158
 
    cdef object gc
1159
 
 
1160
 
    def __init__(self, CGContext gc not None, object size):
1161
 
        self.gc = <object>gc
1162
 
        self.layer = CGLayerCreateWithContext(gc.context,
1163
 
            CGSizeMake(size[0], size[1]), NULL)
1164
 
        self.context = CGLayerGetContext(self.layer)
1165
 
        self.size = size
1166
 
        self._width, self._height = size
1167
 
        self.can_release = 1
1168
 
 
1169
 
        self._setup_color_space()
1170
 
        self._setup_fonts()
1171
 
 
1172
 
    def __dealloc__(self):
1173
 
        if self.layer != NULL:
1174
 
            CGLayerRelease(self.layer)
1175
 
            self.layer = NULL
1176
 
            # The documentation doesn't say whether I need to release the
1177
 
            # context derived from the layer or not. I believe that means
1178
 
            # I don't.
1179
 
            self.context = NULL
1180
 
        self.gc = None
1181
 
 
1182
 
    def save(self, object filename, file_format=None, pil_options=None):
1183
 
        """ Save the GraphicsContext to a file.  Output files are always saved
1184
 
        in RGB or RGBA format; if this GC is not in one of these formats, it is
1185
 
        automatically converted.
1186
 
 
1187
 
        If filename includes an extension, the image format is inferred from it.
1188
 
        file_format is only required if the format can't be inferred from the
1189
 
        filename (e.g. if you wanted to save a PNG file as a .dat or .bin).
1190
 
 
1191
 
        filename may also be "file-like" object such as a StringIO, in which
1192
 
        case a file_format must be supplied.
1193
 
 
1194
 
        pil_options is a dict of format-specific options that are passed down to
1195
 
        the PIL image file writer.  If a writer doesn't recognize an option, it
1196
 
        is silently ignored.
1197
 
 
1198
 
        If the image has an alpha channel and the specified output file format
1199
 
        does not support alpha, the image is saved in rgb24 format.
1200
 
        """
1201
 
 
1202
 
        cdef CGBitmapContext bmp
1203
 
 
1204
 
        # Create a CGBitmapContext from this layer, draw to it, then let it save
1205
 
        # itself out.
1206
 
        rect = (0, 0) + self.size
1207
 
        bmp = CGBitmapContext(self.size)
1208
 
        CGContextDrawLayerInRect(bmp.context,  CGRectMakeFromPython(rect), self.layer)
1209
 
        bmp.save(filename, file_format=file_format, pil_options=pil_options)
1210
 
 
1211
 
 
1212
 
cdef class CGContextFromSWIG(CGContext):
1213
 
    def __init__(self, swig_obj):
1214
 
        self.can_release = False
1215
 
        ptr = int(swig_obj.this.split('_')[1], 16)
1216
 
        CGContext.__init__(self, ptr)
1217
 
 
1218
 
cdef class CGContextForPort(CGContextInABox):
1219
 
    cdef readonly long port
1220
 
    cdef readonly int _begun
1221
 
 
1222
 
    def __init__(self, long port):
1223
 
        cdef OSStatus status
1224
 
 
1225
 
#        status = QDBeginCGContext(<CGrafPtr>port, &(self.context))
1226
 
#        if status:
1227
 
#            self.port = 0
1228
 
#            raise RuntimeError("QuickDraw could not make CGContext")
1229
 
 
1230
 
        self.context = NULL
1231
 
        self.can_release = 0
1232
 
        self._begun = 0
1233
 
 
1234
 
        self.port = port
1235
 
 
1236
 
        cdef QDRect r
1237
 
        GetPortBounds(<CGrafPtr>port, &r)
1238
 
 
1239
 
        self._width = r.right - r.left
1240
 
        self._height = r.bottom - r.top
1241
 
        self.size = (self._width, self._height)
1242
 
 
1243
 
        #self.begin()
1244
 
 
1245
 
    def begin(self):
1246
 
        cdef OSStatus status
1247
 
        cdef QDRect port_rect
1248
 
        if not self._begun:
1249
 
            status = QDBeginCGContext(<CGrafPtr>self.port, &(self.context))
1250
 
            if status != noErr:
1251
 
                raise RuntimeError("QuickDraw could not make CGContext")
1252
 
            SyncCGContextOriginWithPort(self.context, <CGrafPtr>self.port)
1253
 
            self._setup_color_space()
1254
 
            self._setup_fonts()
1255
 
 
1256
 
            #GetPortBounds(<CGrafPtr>self.port, &port_rect)
1257
 
            #CGContextTranslateCTM(self.context, 0, (port_rect.bottom - port_rect.top))
1258
 
            #CGContextScaleCTM(self.context, 1.0, -1.0)
1259
 
            self._begun = 1
1260
 
 
1261
 
    def end(self):
1262
 
        if self.port and self.context and self._begun:
1263
 
            CGContextFlush(self.context)
1264
 
            QDEndCGContext(<CGrafPtr>(self.port), &(self.context))
1265
 
            self._begun = 0
1266
 
 
1267
 
    def clear(self, object clear_color=(1.0,1.0,1.0,1.0)):
1268
 
        already_begun = self._begun
1269
 
        self.begin()
1270
 
        CGContextInABox.clear(self, clear_color)
1271
 
        if not already_begun:
1272
 
            self.end()
1273
 
 
1274
 
    def __dealloc__(self):
1275
 
        self.end()
1276
 
        #if self.port and self.context and self._begun:
1277
 
        #    QDEndCGContext(<CGrafPtr>(self.port), &(self.context))
1278
 
        #if self.context and self.can_release:
1279
 
        #    CGContextRelease(self.context)
1280
 
        #    self.context = NULL
1281
 
 
1282
 
 
1283
 
 
1284
 
cdef class CGGLContext(CGContextInABox):
1285
 
    cdef readonly long glcontext
1286
 
 
1287
 
    def __init__(self, long glcontext, int width, int height):
1288
 
        if glcontext == 0:
1289
 
            raise ValueError("Need a valid pointer")
1290
 
 
1291
 
        self.glcontext = glcontext
1292
 
 
1293
 
        self.context = CGGLContextCreate(<void*>glcontext,
1294
 
            CGSizeMake(width, height), NULL)
1295
 
        if self.context == NULL:
1296
 
            raise RuntimeError("could not create CGGLContext")
1297
 
        self.can_release = 1
1298
 
 
1299
 
        self._width = width
1300
 
        self._height = height
1301
 
        self.size = (self._width, self._height)
1302
 
 
1303
 
        self._setup_color_space()
1304
 
        self._setup_fonts()
1305
 
 
1306
 
 
1307
 
    def resize(self, int width, int height):
1308
 
        CGGLContextUpdateViewportSize(self.context, CGSizeMake(width, height))
1309
 
        self._width = width
1310
 
        self._height = height
1311
 
        self.size = (width, height)
1312
 
 
1313
 
 
1314
 
 
1315
 
cdef class CGPDFContext(CGContext):
1316
 
    cdef readonly char* filename
1317
 
    cdef CGRect media_box
1318
 
    def __init__(self, char* filename, rect=None):
1319
 
        cdef CFURLRef cfurl
1320
 
        cfurl = url_from_filename(filename)
1321
 
        cdef CGRect cgrect
1322
 
        cdef CGRect* cgrect_ptr
1323
 
 
1324
 
        if rect is None:
1325
 
            cgrect = CGRectMake(0,0,612,792)
1326
 
            cgrect_ptr = &cgrect
1327
 
        else:
1328
 
            cgrect = CGRectMakeFromPython(rect)
1329
 
            cgrect_ptr = &cgrect
1330
 
        self.context = CGPDFContextCreateWithURL(cfurl, cgrect_ptr, NULL)
1331
 
        CFRelease(cfurl)
1332
 
 
1333
 
        self.filename = filename
1334
 
        self.media_box = cgrect
1335
 
 
1336
 
        if self.context == NULL:
1337
 
            raise RuntimeError("could not create CGPDFContext")
1338
 
        self.can_release = 1
1339
 
 
1340
 
        self._setup_color_space()
1341
 
        self._setup_fonts()
1342
 
 
1343
 
        CGContextBeginPage(self.context, cgrect_ptr)
1344
 
 
1345
 
    def begin_page(self, media_box=None):
1346
 
        cdef CGRect* box_ptr
1347
 
        cdef CGRect box
1348
 
        if media_box is None:
1349
 
            box_ptr = &(self.media_box)
1350
 
        else:
1351
 
            box = CGRectMakeFromPython(media_box)
1352
 
            box_ptr = &box
1353
 
        CGContextBeginPage(self.context, box_ptr)
1354
 
 
1355
 
    def flush(self, end_page=True):
1356
 
        if end_page:
1357
 
            self.end_page()
1358
 
        CGContextFlush(self.context)
1359
 
 
1360
 
    def begin_transparency_layer(self):
1361
 
        CGContextBeginTransparencyLayer(self.context, NULL)
1362
 
 
1363
 
    def end_transparency_layer(self):
1364
 
        CGContextEndTransparencyLayer(self.context)
1365
 
 
1366
 
cdef class CGBitmapContext(CGContext):
1367
 
    cdef void* data
1368
 
 
1369
 
    def __new__(self, *args, **kwds):
1370
 
        self.data = NULL
1371
 
 
1372
 
    def __init__(self, object size_or_array, bool grey_scale=0,
1373
 
        int bits_per_component=8, int bytes_per_row=-1,
1374
 
        alpha_info=kCGImageAlphaPremultipliedLast):
1375
 
 
1376
 
        cdef int bits_per_pixel
1377
 
        cdef CGColorSpaceRef colorspace
1378
 
        cdef void* dataptr
1379
 
 
1380
 
        if hasattr(size_or_array, '__array_interface__'):
1381
 
            # It's an array.
1382
 
            arr = numpy.asarray(size_or_array, order='C')
1383
 
            typestr = arr.dtype.str
1384
 
            if typestr != '|u1':
1385
 
                raise ValueError("expecting an array of unsigned bytes; got %r"
1386
 
                    % typestr)
1387
 
            shape = arr.shape
1388
 
            if len(shape) != 3 or shape[-1] not in (3, 4):
1389
 
                raise ValueError("expecting a shape (width, height, depth) "
1390
 
                    "with depth either 3 or 4; got %r" % shape)
1391
 
            height, width, depth = shape
1392
 
            if depth == 3:
1393
 
                # Need to add an alpha channel.
1394
 
                alpha = numpy.empty((height, width), dtype=numpy.uint8)
1395
 
                alpha.fill(255)
1396
 
                arr = numpy.dstack([arr, alpha])
1397
 
                depth = 4
1398
 
            ptr, readonly = arr.__array_interface__['data']
1399
 
            dataptr = <void*><long>ptr
1400
 
        else:
1401
 
            # It's a size tuple.
1402
 
            width, height = size_or_array
1403
 
            arr = None
1404
 
 
1405
 
        if grey_scale:
1406
 
            alpha_info = kCGImageAlphaNone
1407
 
            bits_per_component = 8
1408
 
            bits_per_pixel = 8
1409
 
            colorspace = CGColorSpaceCreateWithName(kCGColorSpaceGenericGray)
1410
 
        elif bits_per_component == 5:
1411
 
            alpha_info = kCGImageAlphaNoneSkipFirst
1412
 
            bits_per_pixel = 16
1413
 
            colorspace = CGColorSpaceCreateWithName(kCGColorSpaceGenericRGB)
1414
 
        elif bits_per_component == 8:
1415
 
            if alpha_info not in (kCGImageAlphaNoneSkipFirst,
1416
 
                                  kCGImageAlphaNoneSkipLast,
1417
 
                                  kCGImageAlphaPremultipliedFirst,
1418
 
                                  kCGImageAlphaPremultipliedLast,
1419
 
                                 ):
1420
 
                raise ValueError("not a valid alpha_info")
1421
 
            bits_per_pixel = 32
1422
 
            colorspace = CGColorSpaceCreateWithName(kCGColorSpaceGenericRGB)
1423
 
        else:
1424
 
            raise ValueError("bits_per_component must be 5 or 8")
1425
 
 
1426
 
        cdef int min_bytes
1427
 
        min_bytes = (width*bits_per_pixel + 7) / 8
1428
 
        if bytes_per_row < min_bytes:
1429
 
            bytes_per_row = min_bytes
1430
 
 
1431
 
        self.data = PyMem_Malloc(height*bytes_per_row)
1432
 
        if self.data == NULL:
1433
 
            CGColorSpaceRelease(colorspace)
1434
 
            raise MemoryError("could not allocate memory")
1435
 
        if arr is not None:
1436
 
            # Copy the data from the array.
1437
 
            memcpy(self.data, dataptr, width*height*depth)
1438
 
 
1439
 
        self.context = CGBitmapContextCreate(self.data, width, height,
1440
 
            bits_per_component, bytes_per_row, colorspace, alpha_info)
1441
 
        CGColorSpaceRelease(colorspace)
1442
 
 
1443
 
        if self.context == NULL:
1444
 
            raise RuntimeError("could not create CGBitmapContext")
1445
 
        self.can_release = 1
1446
 
 
1447
 
        self._setup_fonts()
1448
 
 
1449
 
 
1450
 
    def __dealloc__(self):
1451
 
        if self.context != NULL and self.can_release:
1452
 
            CGContextRelease(self.context)
1453
 
            self.context = NULL
1454
 
        if self.data != NULL:
1455
 
            # Hmm, this could be tricky if anything in Quartz retained a
1456
 
            # reference to self.context
1457
 
            PyMem_Free(self.data)
1458
 
            self.data = NULL
1459
 
 
1460
 
    property alpha_info:
1461
 
        def __get__(self):
1462
 
            return CGBitmapContextGetAlphaInfo(self.context)
1463
 
 
1464
 
    property bits_per_component:
1465
 
        def __get__(self):
1466
 
            return CGBitmapContextGetBitsPerComponent(self.context)
1467
 
 
1468
 
    property bits_per_pixel:
1469
 
        def __get__(self):
1470
 
            return CGBitmapContextGetBitsPerPixel(self.context)
1471
 
 
1472
 
    property bytes_per_row:
1473
 
        def __get__(self):
1474
 
            return CGBitmapContextGetBytesPerRow(self.context)
1475
 
 
1476
 
#    property colorspace:
1477
 
#        def __get__(self):
1478
 
#            return CGBitmapContextGetColorSpace(self.context)
1479
 
 
1480
 
    def height(self):
1481
 
        return CGBitmapContextGetHeight(self.context)
1482
 
 
1483
 
    def width(self):
1484
 
        return CGBitmapContextGetWidth(self.context)
1485
 
 
1486
 
    def __getsegcount__(self, int* lenp):
1487
 
        if lenp != NULL:
1488
 
            lenp[0] = self.height()*self.bytes_per_row
1489
 
        return 1
1490
 
 
1491
 
    def __getreadbuffer__(self, int segment, void** ptr):
1492
 
        # ignore invalid segment; the caller can't mean anything but the only
1493
 
        # segment available; we're all adults
1494
 
        ptr[0] = self.data
1495
 
        return self.height()*self.bytes_per_row
1496
 
 
1497
 
    def __getwritebuffer__(self, int segment, void** ptr):
1498
 
        # ignore invalid segment; the caller can't mean anything but the only
1499
 
        # segment available; we're all adults
1500
 
        ptr[0] = self.data
1501
 
        return self.height()*self.bytes_per_row
1502
 
 
1503
 
    def __getcharbuffer__(self, int segment, char** ptr):
1504
 
        # ignore invalid segment; the caller can't mean anything but the only
1505
 
        # segment available; we're all adults
1506
 
        ptr[0] = <char*>(self.data)
1507
 
        return self.height()*self.bytes_per_row
1508
 
 
1509
 
    def clear(self, object clear_color=(1.0, 1.0, 1.0, 1.0)):
1510
 
        """Paint over the whole image with a solid color.
1511
 
        """
1512
 
 
1513
 
        self.save_state()
1514
 
        # Reset the transformation matrix back to the identity.
1515
 
        CGContextConcatCTM(self.context, 
1516
 
            CGAffineTransformInvert(CGContextGetCTM(self.context)))
1517
 
        
1518
 
        self.set_fill_color(clear_color)
1519
 
        CGContextFillRect(self.context, CGRectMake(0, 0, self.width(), self.height()))
1520
 
        self.restore_state()
1521
 
 
1522
 
    def save(self, object filename, file_format=None, pil_options=None):
1523
 
        """ Save the GraphicsContext to a file.  Output files are always saved
1524
 
        in RGB or RGBA format; if this GC is not in one of these formats, it is
1525
 
        automatically converted.
1526
 
 
1527
 
        If filename includes an extension, the image format is inferred from it.
1528
 
        file_format is only required if the format can't be inferred from the
1529
 
        filename (e.g. if you wanted to save a PNG file as a .dat or .bin).
1530
 
 
1531
 
        filename may also be "file-like" object such as a StringIO, in which
1532
 
        case a file_format must be supplied.
1533
 
 
1534
 
        pil_options is a dict of format-specific options that are passed down to
1535
 
        the PIL image file writer.  If a writer doesn't recognize an option, it
1536
 
        is silently ignored.
1537
 
 
1538
 
        If the image has an alpha channel and the specified output file format
1539
 
        does not support alpha, the image is saved in rgb24 format.
1540
 
        """
1541
 
 
1542
 
        try:
1543
 
            import Image
1544
 
        except ImportError:
1545
 
            raise ImportError("need PIL to save images")
1546
 
 
1547
 
        if self.bits_per_pixel == 32:
1548
 
            if self.alpha_info == kCGImageAlphaPremultipliedLast:
1549
 
                mode = 'RGBA'
1550
 
            elif self.alpha_info == kCGImageAlphaPremultipliedFirst:
1551
 
                mode = 'ARGB'
1552
 
            else:
1553
 
                raise ValueError("cannot save this pixel format")
1554
 
        elif self.bits_per_pixel == 8:
1555
 
            mode = 'L'
1556
 
        else:
1557
 
            raise ValueError("cannot save this pixel format")
1558
 
 
1559
 
        img = Image.fromstring(mode, (self.width(), self.height()), self)
1560
 
        if 'A' in mode:
1561
 
            # Check the output format to see if it can handle an alpha channel.
1562
 
            no_alpha_formats = ('jpg', 'bmp', 'eps', 'jpeg')
1563
 
            if ((isinstance(filename, basestring) and
1564
 
                 os.path.splitext(filename)[1][1:] in no_alpha_formats) or
1565
 
                (file_format.lower() in no_alpha_formats)):
1566
 
                img = img.convert('RGB')
1567
 
 
1568
 
        img.save(filename, format=file_format, options=pil_options)
1569
 
 
1570
 
cdef class CGImage:
1571
 
    cdef CGImageRef image
1572
 
    cdef void* data
1573
 
    cdef readonly c_numpy.ndarray bmp_array
1574
 
 
1575
 
    def __new__(self, *args, **kwds):
1576
 
        self.image = NULL
1577
 
 
1578
 
    property width:
1579
 
        def __get__(self):
1580
 
            return CGImageGetWidth(self.image)
1581
 
 
1582
 
    property height:
1583
 
        def __get__(self):
1584
 
            return CGImageGetHeight(self.image)
1585
 
 
1586
 
    property bits_per_component:
1587
 
        def __get__(self):
1588
 
            return CGImageGetBitsPerComponent(self.image)
1589
 
 
1590
 
    property bits_per_pixel:
1591
 
        def __get__(self):
1592
 
            return CGImageGetBitsPerPixel(self.image)
1593
 
 
1594
 
    property bytes_per_row:
1595
 
        def __get__(self):
1596
 
            return CGImageGetBytesPerRow(self.image)
1597
 
 
1598
 
    property alpha_info:
1599
 
        def __get__(self):
1600
 
            return CGImageGetAlphaInfo(self.image)
1601
 
 
1602
 
    property should_interpolate:
1603
 
        def __get__(self):
1604
 
            return CGImageGetShouldInterpolate(self.image)
1605
 
 
1606
 
    property is_mask:
1607
 
        def __get__(self):
1608
 
            return CGImageIsMask(self.image)
1609
 
 
1610
 
    def __init__(self, object size_or_array, bool grey_scale=0,
1611
 
        int bits_per_component=8, int bytes_per_row=-1,
1612
 
        alpha_info=kCGImageAlphaLast, int should_interpolate=1):
1613
 
 
1614
 
        cdef int bits_per_pixel
1615
 
        cdef CGColorSpaceRef colorspace
1616
 
 
1617
 
        if hasattr(size_or_array, '__array_interface__'):
1618
 
            # It's an array.
1619
 
            arr = size_or_array
1620
 
            typestr = arr.__array_interface__['typestr']
1621
 
            if typestr != '|u1':
1622
 
                raise ValueError("expecting an array of unsigned bytes; got %r"
1623
 
                    % typestr)
1624
 
            shape = arr.__array_interface__['shape']
1625
 
            if grey_scale:
1626
 
                if (len(shape) == 3 and shape[-1] != 1) or (len(shape) != 2):
1627
 
                    raise ValueError("with grey_scale, expecting a shape "
1628
 
                        "(height, width) or (height, width, 1); got %r" % shape)
1629
 
                height, width = shape[:2]
1630
 
                depth = 1
1631
 
            else:
1632
 
                if len(shape) != 3 or shape[-1] not in (3, 4):
1633
 
                    raise ValueError("expecting a shape (height, width, depth) "
1634
 
                        "with depth either 3 or 4; got %r" % shape)
1635
 
                height, width, depth = shape
1636
 
            if depth in (1, 3):
1637
 
                alpha_info = kCGImageAlphaNone
1638
 
            else:
1639
 
                # Make a copy.
1640
 
                arr = numpy.array(arr)
1641
 
                alpha_info = kCGImageAlphaPremultipliedLast
1642
 
        else:
1643
 
            # It's a size tuple.
1644
 
            width, height = size_or_array
1645
 
            if grey_scale:
1646
 
                lastdim = 1
1647
 
                alpha_info = kCGImageAlphaNone
1648
 
            else:
1649
 
                lastdim = 4
1650
 
                alpha_info = kCGImageAlphaPremultipliedLast
1651
 
            arr = numpy.zeros((height, width, lastdim), dtype=numpy.uint8)
1652
 
 
1653
 
        self.bmp_array = <c_numpy.ndarray>arr
1654
 
        Py_INCREF(self.bmp_array)
1655
 
        self.data = <void*>c_numpy.PyArray_DATA(self.bmp_array)
1656
 
 
1657
 
        if grey_scale:
1658
 
            alpha_info = kCGImageAlphaNone
1659
 
            bits_per_component = 8
1660
 
            bits_per_pixel = 8
1661
 
            colorspace = CGColorSpaceCreateDeviceGray()
1662
 
        elif bits_per_component == 5:
1663
 
            alpha_info = kCGImageAlphaNoneSkipFirst
1664
 
            bits_per_pixel = 16
1665
 
            colorspace = CGColorSpaceCreateDeviceRGB()
1666
 
        elif bits_per_component == 8:
1667
 
            if alpha_info in (kCGImageAlphaNoneSkipFirst,
1668
 
                              kCGImageAlphaNoneSkipLast,
1669
 
                              kCGImageAlphaPremultipliedFirst,
1670
 
                              kCGImageAlphaPremultipliedLast,
1671
 
                              kCGImageAlphaFirst,
1672
 
                              kCGImageAlphaLast,
1673
 
                             ):
1674
 
                bits_per_pixel = 32
1675
 
            elif alpha_info == kCGImageAlphaNone:
1676
 
                bits_per_pixel = 24
1677
 
            colorspace = CGColorSpaceCreateDeviceRGB()
1678
 
        else:
1679
 
            raise ValueError("bits_per_component must be 5 or 8")
1680
 
 
1681
 
        cdef int min_bytes
1682
 
        min_bytes = (width*bits_per_pixel + 7) / 8
1683
 
        if bytes_per_row < min_bytes:
1684
 
            bytes_per_row = min_bytes
1685
 
 
1686
 
        cdef CGDataProviderRef provider
1687
 
        provider = CGDataProviderCreateWithData(
1688
 
            NULL, self.data, c_numpy.PyArray_SIZE(self.bmp_array), NULL)
1689
 
        if provider == NULL:
1690
 
            raise RuntimeError("could not make provider")
1691
 
 
1692
 
        cdef CGColorSpaceRef space
1693
 
        space = CGColorSpaceCreateDeviceRGB()
1694
 
 
1695
 
        self.image = CGImageCreate(width, height, bits_per_component,
1696
 
            bits_per_pixel, bytes_per_row, space, alpha_info, provider, NULL,
1697
 
            should_interpolate, kCGRenderingIntentDefault)
1698
 
        CGColorSpaceRelease(space)
1699
 
        CGDataProviderRelease(provider)
1700
 
 
1701
 
        if self.image == NULL:
1702
 
            raise RuntimeError("could not make image")
1703
 
 
1704
 
    def __dealloc__(self):
1705
 
        if self.image != NULL:
1706
 
            CGImageRelease(self.image)
1707
 
            self.image = NULL
1708
 
        Py_XDECREF(self.bmp_array)
1709
 
 
1710
 
cdef class CGImageFile(CGImage):
1711
 
    def __init__(self, object image_or_filename, int should_interpolate=1):
1712
 
        cdef int width, height, bits_per_component, bits_per_pixel, bytes_per_row
1713
 
        cdef CGImageAlphaInfo alpha_info
1714
 
 
1715
 
        import Image
1716
 
        import types
1717
 
 
1718
 
        if type(image_or_filename) is str:
1719
 
            img = Image.open(image_or_filename)
1720
 
            img.load()
1721
 
        elif isinstance(image_or_filename, Image.Image):
1722
 
            img = image_or_filename
1723
 
        else:
1724
 
            raise ValueError("need a PIL Image or a filename")
1725
 
 
1726
 
        width, height = img.size
1727
 
        mode = img.mode
1728
 
 
1729
 
        if mode not in ["L", "RGB","RGBA"]:
1730
 
            img = img.convert(mode="RGBA")
1731
 
            mode = 'RGBA'
1732
 
 
1733
 
        bits_per_component = 8
1734
 
 
1735
 
        if mode == 'RGB':
1736
 
            bits_per_pixel = 24
1737
 
            alpha_info = kCGImageAlphaNone
1738
 
        elif mode == 'RGBA':
1739
 
            bits_per_pixel = 32
1740
 
            alpha_info = kCGImageAlphaPremultipliedLast
1741
 
        elif mode == 'L':
1742
 
            bits_per_pixel = 8
1743
 
            alpha_info = kCGImageAlphaNone
1744
 
 
1745
 
        bytes_per_row = (bits_per_pixel*width + 7)/ 8
1746
 
 
1747
 
        cdef char* data
1748
 
        cdef char* py_data
1749
 
        cdef int dims[3]
1750
 
        dims[0] = height
1751
 
        dims[1] = width
1752
 
        dims[2] = bits_per_pixel/bits_per_component
1753
 
 
1754
 
        self.bmp_array = c_numpy.PyArray_SimpleNew(3, &(dims[0]), NPY_UBYTE)
1755
 
 
1756
 
        data = self.bmp_array.data
1757
 
        s = img.tostring()
1758
 
        py_data = PyString_AsString(s)
1759
 
 
1760
 
        memcpy(<void*>data, <void*>py_data, len(s))
1761
 
 
1762
 
        self.data = data
1763
 
 
1764
 
        cdef CGDataProviderRef provider
1765
 
        provider = CGDataProviderCreateWithData(
1766
 
            NULL, <void*>data, len(data), NULL)
1767
 
 
1768
 
        if provider == NULL:
1769
 
            raise RuntimeError("could not make provider")
1770
 
 
1771
 
        cdef CGColorSpaceRef space
1772
 
        space = CGColorSpaceCreateDeviceRGB()
1773
 
 
1774
 
        self.image = CGImageCreate(width, height, bits_per_component,
1775
 
            bits_per_pixel, bytes_per_row, space, alpha_info, provider, NULL,
1776
 
            should_interpolate, kCGRenderingIntentDefault)
1777
 
        CGColorSpaceRelease(space)
1778
 
        CGDataProviderRelease(provider)
1779
 
 
1780
 
        if self.image == NULL:
1781
 
            raise RuntimeError("could not make image")
1782
 
 
1783
 
    def __dealloc__(self):
1784
 
        if self.image != NULL:
1785
 
            CGImageRelease(self.image)
1786
 
            self.image = NULL
1787
 
        Py_XDECREF(self.bmp_array)
1788
 
 
1789
 
cdef class CGImageMask(CGImage):
1790
 
    def __init__(self, char* data, int width, int height,
1791
 
        int bits_per_component, int bits_per_pixel, int bytes_per_row,
1792
 
        int should_interpolate=1):
1793
 
 
1794
 
        cdef CGDataProviderRef provider
1795
 
        provider = CGDataProviderCreateWithData(
1796
 
            NULL, <void*>data, len(data), NULL)
1797
 
 
1798
 
        if provider == NULL:
1799
 
            raise RuntimeError("could not make provider")
1800
 
 
1801
 
        self.image = CGImageMaskCreate(width, height, bits_per_component,
1802
 
            bits_per_pixel, bytes_per_row, provider, NULL,
1803
 
            should_interpolate)
1804
 
        CGDataProviderRelease(provider)
1805
 
 
1806
 
        if self.image == NULL:
1807
 
            raise RuntimeError("could not make image")
1808
 
 
1809
 
cdef class CGPDFDocument:
1810
 
    cdef CGPDFDocumentRef document
1811
 
 
1812
 
    property number_of_pages:
1813
 
        def __get__(self):
1814
 
            return CGPDFDocumentGetNumberOfPages(self.document)
1815
 
 
1816
 
    property allows_copying:
1817
 
        def __get__(self):
1818
 
            return CGPDFDocumentAllowsCopying(self.document)
1819
 
 
1820
 
    property allows_printing:
1821
 
        def __get__(self):
1822
 
            return CGPDFDocumentAllowsPrinting(self.document)
1823
 
 
1824
 
    property is_encrypted:
1825
 
        def __get__(self):
1826
 
            return CGPDFDocumentIsEncrypted(self.document)
1827
 
 
1828
 
    property is_unlocked:
1829
 
        def __get__(self):
1830
 
            return CGPDFDocumentIsUnlocked(self.document)
1831
 
 
1832
 
    def __init__(self, char* filename):
1833
 
        import os
1834
 
        if not os.path.exists(filename) or not os.path.isfile(filename):
1835
 
            raise ValueError("%s is not a file" % filename)
1836
 
 
1837
 
        cdef CFURLRef cfurl
1838
 
        cfurl = url_from_filename(filename)
1839
 
 
1840
 
        self.document = CGPDFDocumentCreateWithURL(cfurl)
1841
 
        CFRelease(cfurl)
1842
 
        if self.document == NULL:
1843
 
            raise RuntimeError("could not create CGPDFDocument")
1844
 
 
1845
 
    def unlock_with_password(self, char* password):
1846
 
        return CGPDFDocumentUnlockWithPassword(self.document, password)
1847
 
 
1848
 
    def get_media_box(self, int page):
1849
 
        cdef CGRect cgrect
1850
 
        cgrect = CGPDFDocumentGetMediaBox(self.document, page)
1851
 
        return (cgrect.origin.x, cgrect.origin.y,
1852
 
                cgrect.size.width, cgrect.size.height)
1853
 
 
1854
 
    def get_crop_box(self, int page):
1855
 
        cdef CGRect cgrect
1856
 
        cgrect = CGPDFDocumentGetCropBox(self.document, page)
1857
 
        return (cgrect.origin.x, cgrect.origin.y,
1858
 
                cgrect.size.width, cgrect.size.height)
1859
 
 
1860
 
    def get_bleed_box(self, int page):
1861
 
        cdef CGRect cgrect
1862
 
        cgrect = CGPDFDocumentGetBleedBox(self.document, page)
1863
 
        return (cgrect.origin.x, cgrect.origin.y,
1864
 
                cgrect.size.width, cgrect.size.height)
1865
 
 
1866
 
    def get_trim_box(self, int page):
1867
 
        cdef CGRect cgrect
1868
 
        cgrect = CGPDFDocumentGetTrimBox(self.document, page)
1869
 
        return (cgrect.origin.x, cgrect.origin.y,
1870
 
                cgrect.size.width, cgrect.size.height)
1871
 
 
1872
 
    def get_art_box(self, int page):
1873
 
        cdef CGRect cgrect
1874
 
        cgrect = CGPDFDocumentGetArtBox(self.document, page)
1875
 
        return (cgrect.origin.x, cgrect.origin.y,
1876
 
                cgrect.size.width, cgrect.size.height)
1877
 
 
1878
 
    def get_rotation_angle(self, int page):
1879
 
        cdef int angle
1880
 
        angle = CGPDFDocumentGetRotationAngle(self.document, page)
1881
 
        if angle == 0:
1882
 
            raise ValueError("page %d does not exist" % page)
1883
 
 
1884
 
    def __dealloc__(self):
1885
 
        if self.document != NULL:
1886
 
            CGPDFDocumentRelease(self.document)
1887
 
            self.document = NULL
1888
 
 
1889
 
cdef CGDataProviderRef CGDataProviderFromFilename(char* string) except NULL:
1890
 
    cdef CFURLRef cfurl
1891
 
    cdef CGDataProviderRef result
1892
 
 
1893
 
    cfurl = url_from_filename(string)
1894
 
    if cfurl == NULL:
1895
 
        raise RuntimeError("could not create CFURLRef")
1896
 
 
1897
 
    result = CGDataProviderCreateWithURL(cfurl)
1898
 
    CFRelease(cfurl)
1899
 
    if result == NULL:
1900
 
        raise RuntimeError("could not create CGDataProviderRef")
1901
 
    return result
1902
 
 
1903
 
cdef class CGAffine:
1904
 
    cdef CGAffineTransform real_transform
1905
 
 
1906
 
    property a:
1907
 
        def __get__(self):
1908
 
            return self.real_transform.a
1909
 
        def __set__(self, float value):
1910
 
            self.real_transform.a = value
1911
 
 
1912
 
    property b:
1913
 
        def __get__(self):
1914
 
            return self.real_transform.b
1915
 
        def __set__(self, float value):
1916
 
            self.real_transform.b = value
1917
 
 
1918
 
    property c:
1919
 
        def __get__(self):
1920
 
            return self.real_transform.c
1921
 
        def __set__(self, float value):
1922
 
            self.real_transform.c = value
1923
 
 
1924
 
    property d:
1925
 
        def __get__(self):
1926
 
            return self.real_transform.d
1927
 
        def __set__(self, float value):
1928
 
            self.real_transform.d = value
1929
 
 
1930
 
    property tx:
1931
 
        def __get__(self):
1932
 
            return self.real_transform.tx
1933
 
        def __set__(self, float value):
1934
 
            self.real_transform.tx = value
1935
 
 
1936
 
    property ty:
1937
 
        def __get__(self):
1938
 
            return self.real_transform.ty
1939
 
        def __set__(self, float value):
1940
 
            self.real_transform.ty = value
1941
 
 
1942
 
    def __init__(self, float a=1.0, float b=0.0, float c=0.0, float d=1.0,
1943
 
        float tx=0.0, float ty=0.0):
1944
 
        self.real_transform = CGAffineTransformMake(a,b,c,d,tx,ty)
1945
 
 
1946
 
    def translate(self, float tx, float ty):
1947
 
        self.real_transform = CGAffineTransformTranslate(self.real_transform,
1948
 
            tx, ty)
1949
 
        return self
1950
 
 
1951
 
    def rotate(self, float angle):
1952
 
        self.real_transform = CGAffineTransformRotate(self.real_transform,
1953
 
            angle)
1954
 
        return self
1955
 
 
1956
 
    def scale(self, float sx, float sy):
1957
 
        self.real_transform = CGAffineTransformScale(self.real_transform, sx,
1958
 
            sy)
1959
 
        return self
1960
 
 
1961
 
    def invert(self):
1962
 
        self.real_transform = CGAffineTransformInvert(self.real_transform)
1963
 
        return self
1964
 
 
1965
 
    def concat(self, CGAffine other not None):
1966
 
        self.real_transform = CGAffineTransformConcat(self.real_transform,
1967
 
            other.real_transform)
1968
 
        return self
1969
 
 
1970
 
    def __mul__(CGAffine x not None, CGAffine y not None):
1971
 
        cdef CGAffineTransform new_transform
1972
 
        new_transform = CGAffineTransformConcat(x.real_transform,
1973
 
            y.real_transform)
1974
 
        new_affine = CGAffine()
1975
 
        set_affine_transform(new_affine, new_transform)
1976
 
        return new_affine
1977
 
 
1978
 
    cdef void init_from_cgaffinetransform(self, CGAffineTransform t):
1979
 
        self.real_transform = t
1980
 
 
1981
 
    def __div__(CGAffine x not None, CGAffine y not None):
1982
 
        cdef CGAffineTransform new_transform
1983
 
        new_transform = CGAffineTransformInvert(y.real_transform)
1984
 
        new_affine = CGAffine()
1985
 
        set_affine_transform(new_affine, CGAffineTransformConcat(x.real_transform, new_transform))
1986
 
        return new_affine
1987
 
 
1988
 
    def apply_to_point(self, float x, float y):
1989
 
        cdef CGPoint oldpoint
1990
 
        oldpoint = CGPointMake(x, y)
1991
 
        cdef CGPoint newpoint
1992
 
        newpoint = CGPointApplyAffineTransform(oldpoint,
1993
 
            self.real_transform)
1994
 
        return newpoint.x, newpoint.y
1995
 
 
1996
 
    def apply_to_size(self, float width, float height):
1997
 
        cdef CGSize oldsize
1998
 
        oldsize = CGSizeMake(width, height)
1999
 
        cdef CGSize newsize
2000
 
        newsize = CGSizeApplyAffineTransform(oldsize, self.real_transform)
2001
 
        return newsize.width, newsize.height
2002
 
 
2003
 
    def __repr__(self):
2004
 
        return "CGAffine(%r, %r, %r, %r, %r, %r)" % (self.a, self.b, self.c,
2005
 
            self.d, self.tx, self.ty)
2006
 
 
2007
 
    def as_matrix(self):
2008
 
        return ((self.a, self.b, 0.0),
2009
 
                (self.c, self.d, 0.0),
2010
 
                (self.tx,self.ty,1.0))
2011
 
 
2012
 
cdef set_affine_transform(CGAffine t, CGAffineTransform newt):
2013
 
    t.init_from_cgaffinetransform(newt)
2014
 
 
2015
 
##cdef class Point:
2016
 
##    cdef CGPoint real_point
2017
 
##
2018
 
##    property x:
2019
 
##        def __get__(self):
2020
 
##            return self.real_point.x
2021
 
##        def __set__(self, float value):
2022
 
##            self.real_point.x = value
2023
 
##
2024
 
##    property y:
2025
 
##        def __get__(self):
2026
 
##            return self.real_point.y
2027
 
##        def __set__(self, float value):
2028
 
##            self.real_point.y = value
2029
 
##
2030
 
##    def __init__(self, float x, float y):
2031
 
##        self.real_point = CGPointMake(x, y)
2032
 
##
2033
 
##    def apply_transform(self, CGAffine transform not None):
2034
 
##        self.real_point = CGPointApplyTransform(self.real_point,
2035
 
##            transform.real_transform)
2036
 
 
2037
 
cdef class Rect:
2038
 
    cdef CGRect real_rect
2039
 
 
2040
 
    property x:
2041
 
        def __get__(self):
2042
 
            return self.real_rect.origin.x
2043
 
        def __set__(self, float value):
2044
 
            self.real_rect.origin.x = value
2045
 
 
2046
 
    property y:
2047
 
        def __get__(self):
2048
 
            return self.real_rect.origin.y
2049
 
        def __set__(self, float value):
2050
 
            self.real_rect.origin.y = value
2051
 
 
2052
 
    property width:
2053
 
        def __get__(self):
2054
 
            return self.real_rect.size.width
2055
 
        def __set__(self, float value):
2056
 
            self.real_rect.size.width = value
2057
 
 
2058
 
    property height:
2059
 
        def __get__(self):
2060
 
            return self.real_rect.size.height
2061
 
        def __set__(self, float value):
2062
 
            self.real_rect.size.height = value
2063
 
 
2064
 
    property min_x:
2065
 
        def __get__(self):
2066
 
            return CGRectGetMinX(self.real_rect)
2067
 
 
2068
 
    property max_x:
2069
 
        def __get__(self):
2070
 
            return CGRectGetMaxX(self.real_rect)
2071
 
 
2072
 
    property min_y:
2073
 
        def __get__(self):
2074
 
            return CGRectGetMinY(self.real_rect)
2075
 
 
2076
 
    property max_y:
2077
 
        def __get__(self):
2078
 
            return CGRectGetMaxY(self.real_rect)
2079
 
 
2080
 
    property mid_x:
2081
 
        def __get__(self):
2082
 
            return CGRectGetMidX(self.real_rect)
2083
 
 
2084
 
    property mid_y:
2085
 
        def __get__(self):
2086
 
            return CGRectGetMidY(self.real_rect)
2087
 
 
2088
 
    property is_null:
2089
 
        def __get__(self):
2090
 
            return CGRectIsNull(self.real_rect)
2091
 
 
2092
 
    property is_empty:
2093
 
        def __get__(self):
2094
 
            return CGRectIsEmpty(self. real_rect)
2095
 
 
2096
 
    def __init__(self, float x=0.0, float y=0.0, float width=0.0, float
2097
 
        height=0.0):
2098
 
        self.real_rect = CGRectMake(x,y,width,height)
2099
 
 
2100
 
    def intersects(self, Rect other not None):
2101
 
        return CGRectIntersectsRect(self.real_rect, other.real_rect)
2102
 
 
2103
 
    def contains_rect(self, Rect other not None):
2104
 
        return CGRectContainsRect(self.real_rect, other.real_rect)
2105
 
 
2106
 
    def contains_point(self, float x, float y):
2107
 
        return CGRectContainsPoint(self.real_rect, CGPointMake(x,y))
2108
 
 
2109
 
    def __richcmp__(Rect x not None, Rect y not None, int op):
2110
 
        if op == 2:
2111
 
            return CGRectEqualToRect(x.real_rect, y.real_rect)
2112
 
        elif op == 3:
2113
 
            return not CGRectEqualToRect(x.real_rect, y.real_rect)
2114
 
        else:
2115
 
            raise NotImplementedError("only (in)equality can be tested")
2116
 
 
2117
 
    def standardize(self):
2118
 
        self.real_rect = CGRectStandardize(self.real_rect)
2119
 
        return self
2120
 
 
2121
 
    def inset(self, float x, float y):
2122
 
        cdef CGRect new_rect
2123
 
        new_rect = CGRectInset(self.real_rect, x, y)
2124
 
        rect = Rect()
2125
 
        set_rect(rect, new_rect)
2126
 
        return rect
2127
 
 
2128
 
    def offset(self, float x, float y):
2129
 
        cdef CGRect new_rect
2130
 
        new_rect = CGRectOffset(self.real_rect, x, y)
2131
 
        rect = Rect()
2132
 
        set_rect(rect, new_rect)
2133
 
        return rect
2134
 
 
2135
 
    def integral(self):
2136
 
        self.real_rect = CGRectIntegral(self.real_rect)
2137
 
        return self
2138
 
 
2139
 
    def __add__(Rect x not None, Rect y not None):
2140
 
        cdef CGRect new_rect
2141
 
        new_rect = CGRectUnion(x.real_rect, y.real_rect)
2142
 
        rect = Rect()
2143
 
        set_rect(rect, new_rect)
2144
 
        return rect
2145
 
 
2146
 
    def union(self, Rect other not None):
2147
 
        cdef CGRect new_rect
2148
 
        new_rect = CGRectUnion(self.real_rect, other.real_rect)
2149
 
        rect = Rect()
2150
 
        set_rect(rect, new_rect)
2151
 
        return rect
2152
 
 
2153
 
    def intersection(self, Rect other not None):
2154
 
        cdef CGRect new_rect
2155
 
        new_rect = CGRectIntersection(self.real_rect, other.real_rect)
2156
 
        rect = Rect()
2157
 
        set_rect(rect, new_rect)
2158
 
        return rect
2159
 
 
2160
 
    def divide(self, float amount, edge):
2161
 
        cdef CGRect slice
2162
 
        cdef CGRect remainder
2163
 
        CGRectDivide(self.real_rect, &slice, &remainder, amount, edge)
2164
 
        pyslice = Rect()
2165
 
        set_rect(pyslice, slice)
2166
 
        pyrem = Rect()
2167
 
        set_rect(pyrem, remainder)
2168
 
        return pyslice, pyrem
2169
 
 
2170
 
    cdef init_from_cgrect(self, CGRect cgrect):
2171
 
        self.real_rect = cgrect
2172
 
 
2173
 
    def __repr__(self):
2174
 
        return "Rect(%r, %r, %r, %r)" % (self.x, self.y, self.width,
2175
 
            self.height)
2176
 
 
2177
 
cdef set_rect(Rect pyrect, CGRect cgrect):
2178
 
    pyrect.init_from_cgrect(cgrect)
2179
 
 
2180
 
cdef class CGMutablePath:
2181
 
    cdef CGMutablePathRef path
2182
 
 
2183
 
    def __init__(self, CGMutablePath path=None):
2184
 
        if path is not None:
2185
 
            self.path = CGPathCreateMutableCopy(path.path)
2186
 
        else:
2187
 
            self.path = CGPathCreateMutable()
2188
 
 
2189
 
    def begin_path(self):
2190
 
        return
2191
 
 
2192
 
    def move_to(self, float x, float y, CGAffine transform=None):
2193
 
        cdef CGAffineTransform *ptr
2194
 
        ptr = NULL
2195
 
        if transform is not None:
2196
 
            ptr = &(transform.real_transform)
2197
 
        CGPathMoveToPoint(self.path, ptr, x, y)
2198
 
 
2199
 
    def arc(self, float x, float y, float r, float startAngle, float endAngle,
2200
 
        bool clockwise=False, CGAffine transform=None):
2201
 
 
2202
 
        cdef CGAffineTransform *ptr
2203
 
        ptr = NULL
2204
 
        if transform is not None:
2205
 
            ptr = &(transform.real_transform)
2206
 
 
2207
 
        CGPathAddArc(self.path, ptr, x, y, r, startAngle, endAngle, clockwise)
2208
 
 
2209
 
    def arc_to(self, float x1, float y1, float x2, float y2, float r,
2210
 
        CGAffine transform=None):
2211
 
 
2212
 
        cdef CGAffineTransform *ptr
2213
 
        ptr = NULL
2214
 
        if transform is not None:
2215
 
            ptr = &(transform.real_transform)
2216
 
 
2217
 
        CGPathAddArcToPoint(self.path, ptr, x1,y1, x2,y2, r)
2218
 
 
2219
 
    def curve_to(self, float cx1, float cy1, float cx2, float cy2, float x,
2220
 
        float y, CGAffine transform=None):
2221
 
 
2222
 
        cdef CGAffineTransform *ptr
2223
 
        ptr = NULL
2224
 
        if transform is not None:
2225
 
            ptr = &(transform.real_transform)
2226
 
 
2227
 
        CGPathAddCurveToPoint(self.path, ptr, cx1, cy1, cx2, cy2, x, y)
2228
 
 
2229
 
    def line_to(self, float x, float y, CGAffine transform=None):
2230
 
        cdef CGAffineTransform *ptr
2231
 
        ptr = NULL
2232
 
        if transform is not None:
2233
 
            ptr = &(transform.real_transform)
2234
 
 
2235
 
        CGPathAddLineToPoint(self.path, ptr, x, y)
2236
 
 
2237
 
    def lines(self, points, CGAffine transform=None):
2238
 
        cdef CGAffineTransform *ptr
2239
 
        ptr = NULL
2240
 
        if transform is not None:
2241
 
            ptr = &(transform.real_transform)
2242
 
 
2243
 
        cdef int n
2244
 
        n = len(points)
2245
 
        cdef int i
2246
 
 
2247
 
        CGPathMoveToPoint(self.path, ptr, points[0][0], points[0][1])
2248
 
 
2249
 
        for i from 1 <= i < n:
2250
 
            CGPathAddLineToPoint(self.path, ptr, points[i][0], points[i][1])
2251
 
 
2252
 
    def add_path(self, CGMutablePath other_path not None, CGAffine transform=None):
2253
 
        cdef CGAffineTransform *ptr
2254
 
        ptr = NULL
2255
 
        if transform is not None:
2256
 
            ptr = &(transform.real_transform)
2257
 
 
2258
 
        CGPathAddPath(self.path, ptr, other_path.path)
2259
 
 
2260
 
    def quad_curve_to(self, float cx, float cy, float x, float y, CGAffine transform=None):
2261
 
        cdef CGAffineTransform *ptr
2262
 
        ptr = NULL
2263
 
        if transform is not None:
2264
 
            ptr = &(transform.real_transform)
2265
 
 
2266
 
        CGPathAddQuadCurveToPoint(self.path, ptr, cx, cy, x, y)
2267
 
 
2268
 
    def rect(self, float x, float y, float sx, float sy, CGAffine transform=None):
2269
 
        cdef CGAffineTransform *ptr
2270
 
        ptr = NULL
2271
 
        if transform is not None:
2272
 
            ptr = &(transform.real_transform)
2273
 
 
2274
 
        CGPathAddRect(self.path, ptr, CGRectMake(x,y,sx,sy))
2275
 
 
2276
 
    def rects(self, rects, CGAffine transform=None):
2277
 
        cdef CGAffineTransform *ptr
2278
 
        ptr = NULL
2279
 
        if transform is not None:
2280
 
            ptr = &(transform.real_transform)
2281
 
 
2282
 
        cdef int n
2283
 
        n = len(rects)
2284
 
        cdef int i
2285
 
        for i from 0 <= i < n:
2286
 
            CGPathAddRect(self.path, ptr, CGRectMakeFromPython(rects[i]))
2287
 
 
2288
 
    def close_path(self):
2289
 
        CGPathCloseSubpath(self.path)
2290
 
 
2291
 
    def is_empty(self):
2292
 
        return CGPathIsEmpty(self.path)
2293
 
 
2294
 
    def get_current_point(self):
2295
 
        cdef CGPoint point
2296
 
        point = CGPathGetCurrentPoint(self.path)
2297
 
        return point.x, point.y
2298
 
 
2299
 
    def get_bounding_box(self):
2300
 
        cdef CGRect rect
2301
 
        rect = CGPathGetBoundingBox(self.path)
2302
 
        return (rect.origin.x, rect.origin.y,
2303
 
                rect.size.width, rect.size.height)
2304
 
 
2305
 
    def __richcmp__(CGMutablePath x not None, CGMutablePath y not None, int op):
2306
 
        if op == 2:
2307
 
            # testing for equality
2308
 
            return CGPathEqualToPath(x.path, y.path)
2309
 
        elif op == 3:
2310
 
            # testing for inequality
2311
 
            return not CGPathEqualToPath(x.path, y.path)
2312
 
        else:
2313
 
            raise NotImplementedError("only (in)equality tests are allowed")
2314
 
 
2315
 
    def __dealloc__(self):
2316
 
        if self.path != NULL:
2317
 
            CGPathRelease(self.path)
2318
 
            self.path = NULL
2319
 
 
2320
 
cdef class _Markers:
2321
 
 
2322
 
    def get_marker(self, int marker_type, float size=1.0):
2323
 
        """ Return the CGMutablePath corresponding to the given marker
2324
 
        enumeration.
2325
 
 
2326
 
          Marker.get_marker(marker_type, size=1.0)
2327
 
 
2328
 
        Parameters
2329
 
        ----------
2330
 
        marker_type : int
2331
 
            One of the enumerated marker types in enthought.kiva.constants.
2332
 
        size : float, optional
2333
 
            The linear size in points of the marker. Some markers (e.g. dot)
2334
 
            ignore this.
2335
 
 
2336
 
        Returns
2337
 
        -------
2338
 
        path : CGMutablePath
2339
 
        """
2340
 
 
2341
 
        if marker_type == constants.NO_MARKER:
2342
 
            return CGMutablePath()
2343
 
        elif marker_type == constants.SQUARE_MARKER:
2344
 
            return self.square(size)
2345
 
        elif marker_type == constants.DIAMOND_MARKER:
2346
 
            return self.diamond(size)
2347
 
        elif marker_type == constants.CIRCLE_MARKER:
2348
 
            return self.circle(size)
2349
 
        elif marker_type == constants.CROSSED_CIRCLE_MARKER:
2350
 
            raise NotImplementedError
2351
 
        elif marker_type == constants.CROSS_MARKER:
2352
 
            return self.cross(size)
2353
 
        elif marker_type == constants.TRIANGLE_MARKER:
2354
 
            raise NotImplementedError
2355
 
        elif marker_type == constants.INVERTED_TRIANGLE_MARKER:
2356
 
            raise NotImplementedError
2357
 
        elif marker_type == constants.PLUS_MARKER:
2358
 
            raise NotImplementedError
2359
 
        elif marker_type == constants.DOT_MARKER:
2360
 
            raise NotImplementedError
2361
 
        elif marker_type == constants.PIXEL_MARKER:
2362
 
            raise NotImplementedError
2363
 
 
2364
 
 
2365
 
    def square(self, float size):
2366
 
        cdef float half
2367
 
        half = size / 2
2368
 
 
2369
 
        m = CGMutablePath()
2370
 
        m.rect(-half,-half,size,size)
2371
 
        return m
2372
 
 
2373
 
    def diamond(self, float size):
2374
 
        cdef float half
2375
 
        half = size / 2
2376
 
 
2377
 
        m = CGMutablePath()
2378
 
        m.move_to(0.0, -half)
2379
 
        m.line_to(-half, 0.0)
2380
 
        m.line_to(0.0, half)
2381
 
        m.line_to(half, 0.0)
2382
 
        m.close_path()
2383
 
        return m
2384
 
 
2385
 
    def x(self, float size):
2386
 
        cdef float half
2387
 
        half = size / 2
2388
 
 
2389
 
        m = CGMutablePath()
2390
 
        m.move_to(-half,-half)
2391
 
        m.line_to(half,half)
2392
 
        m.move_to(-half,half)
2393
 
        m.line_to(half,-half)
2394
 
        return m
2395
 
 
2396
 
    def cross(self, float size):
2397
 
        cdef float half
2398
 
        half = size / 2
2399
 
 
2400
 
        m = CGMutablePath()
2401
 
        m.move_to(0.0, -half)
2402
 
        m.line_to(0.0, half)
2403
 
        m.move_to(-half, 0.0)
2404
 
        m.line_to(half, 0.0)
2405
 
        return m
2406
 
 
2407
 
    def dot(self):
2408
 
        m = CGMutablePath()
2409
 
        m.rect(-0.5,-0.5,1.0,1.0)
2410
 
        return m
2411
 
 
2412
 
    def circle(self, float size):
2413
 
        cdef float half
2414
 
        half = size / 2
2415
 
        m = CGMutablePath()
2416
 
        m.arc(0.0, 0.0, half, 0.0, 6.2831853071795862, 1)
2417
 
        return m
2418
 
 
2419
 
Markers = _Markers()
2420
 
 
2421
 
cdef class ShadingFunction:
2422
 
    cdef CGFunctionRef function
2423
 
 
2424
 
    cdef void _setup_function(self, CGFunctionEvaluateCallback callback):
2425
 
        cdef int i
2426
 
        cdef CGFunctionCallbacks callbacks
2427
 
        callbacks.version = 0
2428
 
        callbacks.releaseInfo = NULL
2429
 
        callbacks.evaluate = <CGFunctionEvaluateCallback>callback
2430
 
 
2431
 
        cdef float domain_bounds[2]
2432
 
        cdef float range_bounds[8]
2433
 
 
2434
 
        domain_bounds[0] = 0.0
2435
 
        domain_bounds[1] = 1.0
2436
 
        for i from 0 <= i < 4:
2437
 
            range_bounds[2*i] = 0.0
2438
 
            range_bounds[2*i+1] = 1.0
2439
 
 
2440
 
        self.function = CGFunctionCreate(<void*>self, 1, domain_bounds,
2441
 
            4, range_bounds, &callbacks)
2442
 
        if self.function == NULL:
2443
 
            raise RuntimeError("could not make CGFunctionRef")
2444
 
 
2445
 
cdef void shading_callback(object self, float* in_data, float* out_data):
2446
 
    cdef int i
2447
 
    out = self(in_data[0])
2448
 
    for i from 0 <= i < self.n_dims:
2449
 
        outData[i] = out[i]
2450
 
 
2451
 
cdef class Shading:
2452
 
    cdef CGShadingRef shading
2453
 
    cdef public object function
2454
 
    cdef int n_dims
2455
 
 
2456
 
    def __init__(self, ShadingFunction func not None):
2457
 
        raise NotImplementedError("use AxialShading or RadialShading")
2458
 
 
2459
 
    def __dealloc__(self):
2460
 
        if self.shading != NULL:
2461
 
            CGShadingRelease(self.shading)
2462
 
 
2463
 
cdef class AxialShading(Shading):
2464
 
    def __init__(self, ShadingFunction func not None, object start, object end,
2465
 
        int extend_start=0, int extend_end=0):
2466
 
 
2467
 
        self.n_dims = 4
2468
 
 
2469
 
        cdef CGPoint start_point, end_point
2470
 
        start_point = CGPointMake(start[0], start[1])
2471
 
        end_point = CGPointMake(end[0], end[1])
2472
 
 
2473
 
        self.function = func
2474
 
 
2475
 
        cdef CGColorSpaceRef space
2476
 
        space = CGColorSpaceCreateDeviceRGB()
2477
 
        self.shading = CGShadingCreateAxial(space, start_point, end_point,
2478
 
            func.function, extend_start, extend_end)
2479
 
        CGColorSpaceRelease(space)
2480
 
        if self.shading == NULL:
2481
 
            raise RuntimeError("could not make CGShadingRef")
2482
 
 
2483
 
cdef class RadialShading(Shading):
2484
 
    def __init__(self, ShadingFunction func not None, object start,
2485
 
        float start_radius, object end, float end_radius, int extend_start=0,
2486
 
        int extend_end=0):
2487
 
 
2488
 
        self.n_dims = 4
2489
 
 
2490
 
        cdef CGPoint start_point, end_point
2491
 
        start_point = CGPointMake(start[0], start[1])
2492
 
        end_point = CGPointMake(end[0], end[1])
2493
 
 
2494
 
        self.function = func
2495
 
 
2496
 
        cdef CGColorSpaceRef space
2497
 
        space = CGColorSpaceCreateDeviceRGB()
2498
 
        self.shading = CGShadingCreateRadial(space, start_point, start_radius,
2499
 
            end_point, end_radius, func.function, extend_start, extend_end)
2500
 
        CGColorSpaceRelease(space)
2501
 
        if self.shading == NULL:
2502
 
            raise RuntimeError("could not make CGShadingRef")
2503
 
 
2504
 
cdef void safe_free(void* mem):
2505
 
    if mem != NULL:
2506
 
        PyMem_Free(mem)
2507
 
 
2508
 
cdef class PiecewiseLinearColorFunction(ShadingFunction):
2509
 
    cdef int num_stops
2510
 
    cdef float* stops
2511
 
    cdef float* red
2512
 
    cdef float* green
2513
 
    cdef float* blue
2514
 
    cdef float* alpha
2515
 
 
2516
 
    def __init__(self, object stop_colors):
2517
 
        cdef c_numpy.ndarray stop_array
2518
 
        cdef int i
2519
 
 
2520
 
        stop_colors = numpy.array(stop_colors).astype(numpy.float32)
2521
 
 
2522
 
        if not (4 <= stop_colors.shape[0] <= 5) or len(stop_colors.shape) != 2:
2523
 
            raise ValueError("need array [stops, red, green, blue[, alpha]]")
2524
 
 
2525
 
        if stop_colors[0,0] != 0.0 or stop_colors[0,-1] != 1.0:
2526
 
            raise ValueError("stops need to start with 0.0 and end with 1.0")
2527
 
 
2528
 
        if not numpy.greater_equal(numpy.diff(stop_colors[0]), 0.0).all():
2529
 
            raise ValueError("stops must be sorted and unique")
2530
 
 
2531
 
        self.num_stops = stop_colors.shape[1]
2532
 
        self.stops = <float*>PyMem_Malloc(sizeof(float)*self.num_stops)
2533
 
        self.red = <float*>PyMem_Malloc(sizeof(float)*self.num_stops)
2534
 
        self.green = <float*>PyMem_Malloc(sizeof(float)*self.num_stops)
2535
 
        self.blue = <float*>PyMem_Malloc(sizeof(float)*self.num_stops)
2536
 
        self.alpha = <float*>PyMem_Malloc(sizeof(float)*self.num_stops)
2537
 
 
2538
 
        has_alpha = stop_colors.shape[0] == 5
2539
 
        for i from 0 <= i < self.num_stops:
2540
 
            self.stops[i] = stop_colors[0,i]
2541
 
            self.red[i] = stop_colors[1,i]
2542
 
            self.green[i] = stop_colors[2,i]
2543
 
            self.blue[i] = stop_colors[3,i]
2544
 
            if has_alpha:
2545
 
                self.alpha[i] = stop_colors[4,i]
2546
 
            else:
2547
 
                self.alpha[i] = 1.0
2548
 
 
2549
 
        self._setup_function(piecewise_callback)
2550
 
 
2551
 
    def dump(self):
2552
 
        cdef int i
2553
 
        print 'PiecewiseLinearColorFunction'
2554
 
        print '  num_stops = %i' % self.num_stops
2555
 
        print '  stops = ',
2556
 
        for i from 0 <= i < self.num_stops:
2557
 
            print self.stops[i],
2558
 
        print
2559
 
        print '  red = ',
2560
 
        for i from 0 <= i < self.num_stops:
2561
 
            print self.red[i],
2562
 
        print
2563
 
        print '  green = ',
2564
 
        for i from 0 <= i < self.num_stops:
2565
 
            print self.green[i],
2566
 
        print
2567
 
        print '  blue = ',
2568
 
        for i from 0 <= i < self.num_stops:
2569
 
            print self.blue[i],
2570
 
        print
2571
 
        print '  alpha = ',
2572
 
        for i from 0 <= i < self.num_stops:
2573
 
            print self.alpha[i],
2574
 
        print
2575
 
 
2576
 
    def __dealloc__(self):
2577
 
        safe_free(self.stops)
2578
 
        safe_free(self.red)
2579
 
        safe_free(self.green)
2580
 
        safe_free(self.blue)
2581
 
        safe_free(self.alpha)
2582
 
 
2583
 
 
2584
 
cdef int bisect_left(PiecewiseLinearColorFunction self, float t):
2585
 
    cdef int lo, hi, mid
2586
 
    cdef float stop
2587
 
 
2588
 
    hi = self.num_stops
2589
 
    lo = 0
2590
 
    while lo < hi:
2591
 
        mid = (lo + hi)/2
2592
 
        stop = self.stops[mid]
2593
 
        if t < stop:
2594
 
            hi = mid
2595
 
        else:
2596
 
            lo = mid + 1
2597
 
    return lo
2598
 
 
2599
 
cdef void piecewise_callback(void* obj, float* t, float* out):
2600
 
   cdef int i
2601
 
   cdef float eps
2602
 
   cdef PiecewiseLinearColorFunction self
2603
 
 
2604
 
   self = <PiecewiseLinearColorFunction>obj
2605
 
 
2606
 
   eps = 1e-6
2607
 
 
2608
 
   if fabs(t[0]) < eps:
2609
 
       out[0] = self.red[0]
2610
 
       out[1] = self.green[0]
2611
 
       out[2] = self.blue[0]
2612
 
       out[3] = self.alpha[0]
2613
 
       return
2614
 
   if fabs(t[0] - 1.0) < eps:
2615
 
       i = self.num_stops - 1
2616
 
       out[0] = self.red[i]
2617
 
       out[1] = self.green[i]
2618
 
       out[2] = self.blue[i]
2619
 
       out[3] = self.alpha[i]
2620
 
       return
2621
 
 
2622
 
   i = bisect_left(self, t[0])
2623
 
 
2624
 
   cdef float f, g, dx
2625
 
   dx = self.stops[i] - self.stops[i-1]
2626
 
 
2627
 
   if dx > eps:
2628
 
       f = (t[0]-self.stops[i-1])/dx
2629
 
   else:
2630
 
       f = 1.0
2631
 
 
2632
 
   g = 1.0 - f
2633
 
 
2634
 
   out[0] = f*self.red[i] + g*self.red[i-1]
2635
 
   out[1] = f*self.green[i] + g*self.green[i-1]
2636
 
   out[2] = f*self.blue[i] + g*self.blue[i-1]
2637
 
   out[3] = f*self.alpha[i] + g*self.alpha[i-1]
2638
 
 
2639
 
 
2640
 
#### Font utilities ####
2641
 
 
2642
 
cdef ATSUStyle _create_atsu_style(object postscript_name, float font_size):
2643
 
    cdef OSStatus err
2644
 
    cdef ATSUStyle atsu_style
2645
 
    cdef ATSUFontID atsu_font
2646
 
    cdef Fixed atsu_size
2647
 
    cdef char* c_ps_name
2648
 
 
2649
 
    # Create the attribute arrays.
2650
 
    cdef ATSUAttributeTag attr_tags[2]
2651
 
    cdef ByteCount attr_sizes[2]
2652
 
    cdef ATSUAttributeValuePtr attr_values[2]
2653
 
 
2654
 
    err = noErr
2655
 
    atsu_style = NULL
2656
 
    atsu_font = 0
2657
 
    atsu_size = FloatToFixed(font_size)
2658
 
 
2659
 
    # Look up the ATSUFontID for the given PostScript name of the font.
2660
 
    postscript_name = postscript_name.encode('utf-8')
2661
 
    c_ps_name = PyString_AsString(postscript_name)
2662
 
    err = ATSUFindFontFromName(<void*>c_ps_name, len(postscript_name),
2663
 
        kFontPostscriptName, kFontNoPlatformCode, kFontNoScriptCode,
2664
 
        kFontNoLanguageCode, &atsu_font)
2665
 
    if err:
2666
 
        return NULL
2667
 
 
2668
 
    # Set the ATSU font in the attribute arrays.
2669
 
    attr_tags[0] = kATSUFontTag
2670
 
    attr_sizes[0] = sizeof(ATSUFontID)
2671
 
    attr_values[0] = &atsu_font
2672
 
 
2673
 
    # Set the font size in the attribute arrays.
2674
 
    attr_tags[1] = kATSUSizeTag
2675
 
    attr_sizes[1] = sizeof(Fixed)
2676
 
    attr_values[1] = &atsu_size
2677
 
 
2678
 
    # Create the ATSU style.
2679
 
    err = ATSUCreateStyle(&atsu_style)
2680
 
    if err:
2681
 
        if atsu_style != NULL:
2682
 
            ATSUDisposeStyle(atsu_style)
2683
 
        return NULL
2684
 
 
2685
 
    # Set the style attributes.
2686
 
    err = ATSUSetAttributes(atsu_style, 2, attr_tags, attr_sizes, attr_values)
2687
 
    if err:
2688
 
        if atsu_style != NULL:
2689
 
            ATSUDisposeStyle(atsu_style)
2690
 
        return NULL
2691
 
 
2692
 
    return atsu_style
2693
 
 
2694
 
 
2695
 
cdef object _create_atsu_layout(object the_string, ATSUStyle style, ATSUTextLayout* layout):
2696
 
    cdef ATSUTextLayout atsu_layout
2697
 
    cdef CFIndex text_length
2698
 
    cdef OSStatus err
2699
 
    cdef UniChar *uni_buffer
2700
 
    cdef CFRange uni_range
2701
 
    cdef CFStringRef cf_string
2702
 
    cdef char* c_string
2703
 
 
2704
 
    layout[0] = atsu_layout = NULL
2705
 
    if len(the_string) == 0:
2706
 
        return
2707
 
 
2708
 
    err = noErr
2709
 
    the_string = the_string.encode('utf-8')
2710
 
    c_string = PyString_AsString(the_string)
2711
 
 
2712
 
    cf_string = CFStringCreateWithCString(NULL, c_string, kCFStringEncodingUTF8)
2713
 
    text_length = CFStringGetLength(cf_string)
2714
 
 
2715
 
    # Extract the Unicode data from the CFStringRef.
2716
 
    uni_range = CFRangeMake(0, text_length)
2717
 
    uni_buffer = <UniChar*>PyMem_Malloc(text_length * sizeof(UniChar))
2718
 
    if uni_buffer == NULL:
2719
 
        raise MemoryError("could not allocate %d bytes of memory" % (text_length * sizeof(UniChar)))
2720
 
    CFStringGetCharacters(cf_string, uni_range, uni_buffer)
2721
 
 
2722
 
    # Create the ATSUI layout.
2723
 
    err = ATSUCreateTextLayoutWithTextPtr(<ConstUniCharArrayPtr>uni_buffer, 0, text_length, text_length, 1, <UniCharCount*>&text_length, &style, &atsu_layout)
2724
 
    if err:
2725
 
        PyMem_Free(uni_buffer)
2726
 
        raise RuntimeError("could not create an ATSUI layout")
2727
 
 
2728
 
    layout[0] = atsu_layout
2729
 
    return
2730
 
 
2731
 
 
2732
 
cdef object _set_cgcontext_for_layout(CGContextRef context, ATSUTextLayout layout):
2733
 
    cdef ATSUAttributeTag tag
2734
 
    cdef ByteCount size
2735
 
    cdef ATSUAttributeValuePtr value
2736
 
    cdef OSStatus err
2737
 
 
2738
 
    tag = kATSUCGContextTag
2739
 
    size = sizeof(CGContextRef)
2740
 
    value = &context
2741
 
 
2742
 
    err = ATSUSetLayoutControls(layout, 1, &tag, &size, &value)
2743
 
    if err:
2744
 
        raise RuntimeError("could not assign the CGContextRef to the ATSUTextLayout")
2745
 
    return
2746
 
 
2747
 
#### EOF #######################################################################