From: <jd...@us...> - 2008-12-09 03:51:40
|
Revision: 6531 http://matplotlib.svn.sourceforge.net/matplotlib/?rev=6531&view=rev Author: jdh2358 Date: 2008-12-09 03:51:35 +0000 (Tue, 09 Dec 2008) Log Message: ----------- added mdehoon's native macosx patch Modified Paths: -------------- trunk/matplotlib/CHANGELOG Added Paths: ----------- trunk/matplotlib/lib/matplotlib/backends/backend_macosx.py trunk/matplotlib/src/_macosx.m Modified: trunk/matplotlib/CHANGELOG =================================================================== --- trunk/matplotlib/CHANGELOG 2008-12-09 03:49:11 UTC (rev 6530) +++ trunk/matplotlib/CHANGELOG 2008-12-09 03:51:35 UTC (rev 6531) @@ -1,4 +1,4 @@ -2008-12-08 Added mdehoon's native maxosx backend from sf patch 2179017 +2008-12-08 Added mdehoon's native macosx backend from sf patch 2179017 2008-12-08 Removed the prints in the set_*style commands. Return the list of pprinted strings instead Added: trunk/matplotlib/lib/matplotlib/backends/backend_macosx.py =================================================================== --- trunk/matplotlib/lib/matplotlib/backends/backend_macosx.py (rev 0) +++ trunk/matplotlib/lib/matplotlib/backends/backend_macosx.py 2008-12-09 03:51:35 UTC (rev 6531) @@ -0,0 +1,415 @@ +from __future__ import division + +import os +import numpy + +from matplotlib._pylab_helpers import Gcf +from matplotlib.backend_bases import RendererBase, GraphicsContextBase,\ + FigureManagerBase, FigureCanvasBase, NavigationToolbar2 +from matplotlib.cbook import maxdict +from matplotlib.figure import Figure +from matplotlib.path import Path +from matplotlib.mathtext import MathTextParser + + + +from matplotlib.widgets import SubplotTool + +import matplotlib +from matplotlib.backends import _macosx + +def show(): + """Show all the figures and enter the Cocoa mainloop. + This function will not return until all windows are closed or + the interpreter exits.""" + # Having a Python-level function "show" wrapping the built-in + # function "show" in the _macosx extension module allows us to + # to add attributes to "show". This is something ipython does. + _macosx.show() + +class RendererMac(RendererBase): + """ + The renderer handles drawing/rendering operations. Most of the renderer's + methods forwards the command to the renderer's graphics context. The + renderer does not wrap a C object and is written in pure Python. + """ + + texd = maxdict(50) # a cache of tex image rasters + + def __init__(self, dpi, width, height): + RendererBase.__init__(self) + self.dpi = dpi + self.width = width + self.height = height + self.gc = GraphicsContextMac() + self.mathtext_parser = MathTextParser('MacOSX') + + def set_width_height (self, width, height): + self.width, self.height = width, height + + def draw_path(self, gc, path, transform, rgbFace=None): + path = transform.transform_path(path) + for points, code in path.iter_segments(): + if code == Path.MOVETO: + gc.moveto(points) + elif code == Path.LINETO: + gc.lineto(points) + elif code == Path.CURVE3: + gc.curve3(points) + elif code == Path.CURVE4: + gc.curve4(points) + elif code == Path.CLOSEPOLY: + gc.closepoly() + if rgbFace is not None: + rgbFace = tuple(rgbFace) + gc.stroke(rgbFace) + + def new_gc(self): + self.gc.reset() + return self.gc + + def draw_image(self, x, y, im, bbox, clippath=None, clippath_trans=None): + self.gc.set_clip_rectangle(bbox) + im.flipud_out() + nrows, ncols, data = im.as_rgba_str() + self.gc.draw_image(x, y, nrows, ncols, data) + im.flipud_out() + + def draw_tex(self, gc, x, y, s, prop, angle): + # todo, handle props, angle, origins + size = prop.get_size_in_points() + texmanager = self.get_texmanager() + key = s, size, self.dpi, angle, texmanager.get_font_config() + im = self.texd.get(key) # Not sure what this does; just copied from backend_agg.py + if im is None: + Z = texmanager.get_grey(s, size, self.dpi) + Z = numpy.array(255.0 - Z * 255.0, numpy.uint8) + + gc.draw_mathtext(x, y, angle, Z) + + def _draw_mathtext(self, gc, x, y, s, prop, angle): + size = prop.get_size_in_points() + ox, oy, width, height, descent, image, used_characters = \ + self.mathtext_parser.parse(s, self.dpi, prop) + gc.draw_mathtext(x, y, angle, 255 - image.as_array()) + + def draw_text(self, gc, x, y, s, prop, angle, ismath=False): + if ismath: + self._draw_mathtext(gc, x, y, s, prop, angle) + else: + family = prop.get_family() + size = prop.get_size_in_points() + weight = prop.get_weight() + style = prop.get_style() + gc.draw_text(x, y, unicode(s), family, size, weight, style, angle) + + def get_text_width_height_descent(self, s, prop, ismath): + if ismath=='TeX': + # TODO: handle props + size = prop.get_size_in_points() + texmanager = self.get_texmanager() + Z = texmanager.get_grey(s, size, self.dpi) + m,n = Z.shape + # TODO: handle descent; This is based on backend_agg.py + return n, m, 0 + if ismath: + ox, oy, width, height, descent, fonts, used_characters = \ + self.mathtext_parser.parse(s, self.dpi, prop) + return width, height, descent + family = prop.get_family() + size = prop.get_size_in_points() + weight = prop.get_weight() + style = prop.get_style() + return self.gc.get_text_width_height_descent(unicode(s), family, size, weight, style) + + def flipy(self): + return False + + def points_to_pixels(self, points): + return points/72.0 * self.dpi + + def option_image_nocomposite(self): + return True + +class GraphicsContextMac(_macosx.GraphicsContext, GraphicsContextBase): + """ + The GraphicsContext wraps a Quartz graphics context. All methods + are implemented at the C-level in macosx.GraphicsContext. These + methods set drawing properties such as the line style, fill color, + etc. The actual drawing is done by the Renderer, which draws into + the GraphicsContext. + """ + def __init__(self): + GraphicsContextBase.__init__(self) + _macosx.GraphicsContext.__init__(self) + + def set_clip_rectangle(self, box): + GraphicsContextBase.set_clip_rectangle(self, box) + if not box: return + _macosx.GraphicsContext.set_clip_rectangle(self, box.bounds) + + def set_clip_path(self, path): + GraphicsContextBase.set_clip_path(self, path) + if not path: return + path = path.get_fully_transformed_path() + for points, code in path.iter_segments(): + if code == Path.MOVETO: + self.moveto(points) + elif code == Path.LINETO: + self.lineto(points) + elif code == Path.CURVE3: + self.curve3(points) + elif code == Path.CURVE4: + self.curve4(points) + elif code == Path.CLOSEPOLY: + self.closepoly() + self.clip_path() + + +######################################################################## +# +# The following functions and classes are for pylab and implement +# window/figure managers, etc... +# +######################################################################## + +def draw_if_interactive(): + """ + For performance reasons, we don't want to redraw the figure after + each draw command. Instead, we mark the figure as invalid, so that + it will be redrawn as soon as the event loop resumes via PyOS_InputHook. + This function should be called after each draw event, even if + matplotlib is not running interactively. + """ + figManager = Gcf.get_active() + if figManager is not None: + figManager.canvas.invalidate() + +def new_figure_manager(num, *args, **kwargs): + """ + Create a new figure manager instance + """ + FigureClass = kwargs.pop('FigureClass', Figure) + figure = FigureClass(*args, **kwargs) + canvas = FigureCanvasMac(figure) + manager = FigureManagerMac(canvas, num) + return manager + +class FigureCanvasMac(_macosx.FigureCanvas, FigureCanvasBase): + """ + The canvas the figure renders into. Calls the draw and print fig + methods, creates the renderers, etc... + + Public attribute + + figure - A Figure instance + + Events such as button presses, mouse movements, and key presses + are handled in the C code and the base class methods + button_press_event, button_release_event, motion_notify_event, + key_press_event, and key_release_event are called from there. + """ + + def __init__(self, figure): + FigureCanvasBase.__init__(self, figure) + width, height = self.get_width_height() + self.renderer = RendererMac(figure.dpi, width, height) + _macosx.FigureCanvas.__init__(self, width, height) + + def resize(self, width, height): + self.renderer.set_width_height(width, height) + dpi = self.figure.dpi + width /= dpi + height /= dpi + self.figure.set_size_inches(width, height) + + def print_figure(self, filename, dpi=None, facecolor='w', edgecolor='w', + orientation='portrait', **kwargs): + if dpi is None: dpi = matplotlib.rcParams['savefig.dpi'] + filename = unicode(filename) + root, ext = os.path.splitext(filename) + ext = ext[1:].lower() + if not ext: + ext = "png" + filename = root + "." + ext + if ext=="jpg": ext = "jpeg" + + # save the figure settings + origfacecolor = self.figure.get_facecolor() + origedgecolor = self.figure.get_edgecolor() + + # set the new parameters + self.figure.set_facecolor(facecolor) + self.figure.set_edgecolor(edgecolor) + + if ext in ('jpeg', 'png', 'tiff', 'gif', 'bmp'): + width, height = self.figure.get_size_inches() + width, height = width*dpi, height*dpi + self.write_bitmap(filename, width, height) + elif ext == 'pdf': + self.write_pdf(filename) + elif ext in ('ps', 'eps'): + from backend_ps import FigureCanvasPS + # Postscript backend changes figure.dpi, but doesn't change it back + origDPI = self.figure.dpi + fc = self.switch_backends(FigureCanvasPS) + fc.print_figure(filename, dpi, facecolor, edgecolor, + orientation, **kwargs) + self.figure.dpi = origDPI + self.figure.set_canvas(self) + elif ext=='svg': + from backend_svg import FigureCanvasSVG + fc = self.switch_backends(FigureCanvasSVG) + fc.print_figure(filename, dpi, facecolor, edgecolor, + orientation, **kwargs) + self.figure.set_canvas(self) + else: + raise ValueError("Figure format not available (extension %s)" % ext) + + # restore original figure settings + self.figure.set_facecolor(origfacecolor) + self.figure.set_edgecolor(origedgecolor) + + + +class FigureManagerMac(_macosx.FigureManager, FigureManagerBase): + """ + Wrap everything up into a window for the pylab interface + """ + def __init__(self, canvas, num): + FigureManagerBase.__init__(self, canvas, num) + title = "Figure %d" % num + _macosx.FigureManager.__init__(self, canvas, title) + if matplotlib.rcParams['toolbar']=='classic': + self.toolbar = NavigationToolbarMac(canvas) + elif matplotlib.rcParams['toolbar']=='toolbar2': + self.toolbar = NavigationToolbar2Mac(canvas) + else: + self.toolbar = None + if self.toolbar is not None: + self.toolbar.update() + + def notify_axes_change(fig): + 'this will be called whenever the current axes is changed' + if self.toolbar != None: self.toolbar.update() + self.canvas.figure.add_axobserver(notify_axes_change) + + # This is ugly, but this is what tkagg and gtk are doing. + # It is needed to get ginput() working. + self.canvas.figure.show = lambda *args: self.show() + + def show(self): + self.canvas.draw() + + def close(self): + Gcf.destroy(self.num) + +class NavigationToolbarMac(_macosx.NavigationToolbar): + + def __init__(self, canvas): + self.canvas = canvas + basedir = os.path.join(matplotlib.rcParams['datapath'], "images") + images = {} + for imagename in ("stock_left", + "stock_right", + "stock_up", + "stock_down", + "stock_zoom-in", + "stock_zoom-out", + "stock_save_as"): + filename = os.path.join(basedir, imagename+".ppm") + images[imagename] = self._read_ppm_image(filename) + _macosx.NavigationToolbar.__init__(self, images) + self.message = None + + def _read_ppm_image(self, filename): + data = "" + imagefile = open(filename) + for line in imagefile: + if "#" in line: + i = line.index("#") + line = line[:i] + "\n" + data += line + imagefile.close() + magic, width, height, maxcolor, imagedata = data.split(None, 4) + width, height = int(width), int(height) + assert magic=="P6" + assert len(imagedata)==width*height*3 # 3 colors in RGB + return (width, height, imagedata) + + def panx(self, direction): + axes = self.canvas.figure.axes + selected = self.get_active() + for i in selected: + axes[i].xaxis.pan(direction) + self.canvas.invalidate() + + def pany(self, direction): + axes = self.canvas.figure.axes + selected = self.get_active() + for i in selected: + axes[i].yaxis.pan(direction) + self.canvas.invalidate() + + def zoomx(self, direction): + axes = self.canvas.figure.axes + selected = self.get_active() + for i in selected: + axes[i].xaxis.zoom(direction) + self.canvas.invalidate() + + def zoomy(self, direction): + axes = self.canvas.figure.axes + selected = self.get_active() + for i in selected: + axes[i].yaxis.zoom(direction) + self.canvas.invalidate() + + def save_figure(self): + filename = _macosx.choose_save_file('Save the figure') + if filename is None: # Cancel + return + self.canvas.print_figure(filename) + +class NavigationToolbar2Mac(_macosx.NavigationToolbar2, NavigationToolbar2): + + def __init__(self, canvas): + NavigationToolbar2.__init__(self, canvas) + + def _init_toolbar(self): + basedir = os.path.join(matplotlib.rcParams['datapath'], "images") + _macosx.NavigationToolbar2.__init__(self, basedir) + + def draw_rubberband(self, event, x0, y0, x1, y1): + self.canvas.set_rubberband(x0, y0, x1, y1) + + def release(self, event): + self.canvas.remove_rubberband() + + def set_cursor(self, cursor): + _macosx.set_cursor(cursor) + + def save_figure(self): + filename = _macosx.choose_save_file('Save the figure') + if filename is None: # Cancel + return + self.canvas.print_figure(filename) + + def prepare_configure_subplots(self): + toolfig = Figure(figsize=(6,3)) + canvas = FigureCanvasMac(toolfig) + toolfig.subplots_adjust(top=0.9) + tool = SubplotTool(self.canvas.figure, toolfig) + return canvas + + def set_message(self, message): + _macosx.NavigationToolbar2.set_message(self, message.encode('utf-8')) + +######################################################################## +# +# Now just provide the standard names that backend.__init__ is expecting +# +######################################################################## + + +FigureManager = FigureManagerMac Added: trunk/matplotlib/src/_macosx.m =================================================================== --- trunk/matplotlib/src/_macosx.m (rev 0) +++ trunk/matplotlib/src/_macosx.m 2008-12-09 03:51:35 UTC (rev 6531) @@ -0,0 +1,3773 @@ +#include <Cocoa/Cocoa.h> +#include <ApplicationServices/ApplicationServices.h> +#include <sys/socket.h> +#include <Python.h> +#include "numpy/arrayobject.h" + +static int nwin = 0; + + +/* Varius NSApplicationDefined event subtypes */ +#define STDIN_READY 0 +#define SIGINT_CALLED 1 +#define STOP_EVENT_LOOP 2 +#define WINDOW_CLOSING 3 + +/* -------------------------- Helper function ---------------------------- */ + +static void stdin_ready(CFReadStreamRef readStream, CFStreamEventType eventType, void* context) +{ + NSEvent* event = [NSEvent otherEventWithType: NSApplicationDefined + location: NSZeroPoint + modifierFlags: 0 + timestamp: 0.0 + windowNumber: 0 + context: nil + subtype: STDIN_READY + data1: 0 + data2: 0]; + [NSApp postEvent: event atStart: true]; +} + +static int sigint_fd = -1; + +static void _sigint_handler(int sig) +{ + const char c = 'i'; + write(sigint_fd, &c, 1); +} + +static void _callback(CFSocketRef s, + CFSocketCallBackType type, + CFDataRef address, + const void * data, + void *info) +{ + char c; + CFSocketNativeHandle handle = CFSocketGetNative(s); + read(handle, &c, 1); + NSEvent* event = [NSEvent otherEventWithType: NSApplicationDefined + location: NSZeroPoint + modifierFlags: 0 + timestamp: 0.0 + windowNumber: 0 + context: nil + subtype: SIGINT_CALLED + data1: 0 + data2: 0]; + [NSApp postEvent: event atStart: true]; +} + +static int wait_for_stdin(void) +{ + const UInt8 buffer[] = "/dev/fd/0"; + const CFIndex n = (CFIndex)strlen((char*)buffer); + CFRunLoopRef runloop = CFRunLoopGetCurrent(); + CFURLRef url = CFURLCreateFromFileSystemRepresentation(kCFAllocatorDefault, + buffer, + n, + false); + CFReadStreamRef stream = CFReadStreamCreateWithFile(kCFAllocatorDefault, + url); + CFRelease(url); + + CFReadStreamOpen(stream); + if (!CFReadStreamHasBytesAvailable(stream)) + /* This is possible because of how PyOS_InputHook is called from Python */ + { + int error; + int interrupted = 0; + int channel[2]; + CFSocketRef sigint_socket = NULL; + PyOS_sighandler_t py_sigint_handler = NULL; + CFStreamClientContext clientContext = {0, NULL, NULL, NULL, NULL}; + CFReadStreamSetClient(stream, + kCFStreamEventHasBytesAvailable, + stdin_ready, + &clientContext); + CFReadStreamScheduleWithRunLoop(stream, runloop, kCFRunLoopCommonModes); + error = pipe(channel); + if (error==0) + { + fcntl(channel[1], F_SETFL, O_WRONLY | O_NONBLOCK); + + sigint_socket = CFSocketCreateWithNative(kCFAllocatorDefault, + channel[0], + kCFSocketReadCallBack, + _callback, + NULL); + if (sigint_socket) + { + CFRunLoopSourceRef source; + source = CFSocketCreateRunLoopSource(kCFAllocatorDefault, + sigint_socket, + 0); + CFRelease(sigint_socket); + if (source) + { + CFRunLoopAddSource(runloop, source, kCFRunLoopDefaultMode); + CFRelease(source); + sigint_fd = channel[1]; + py_sigint_handler = PyOS_setsig(SIGINT, _sigint_handler); + } + } + else + close(channel[0]); + } + + NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init]; + NSDate* date = [NSDate distantFuture]; + while (true) + { NSEvent* event = [NSApp nextEventMatchingMask: NSAnyEventMask + untilDate: date + inMode: NSDefaultRunLoopMode + dequeue: YES]; + if (!event) break; /* No windows open */ + if ([event type]==NSApplicationDefined) + { short subtype = [event subtype]; + if (subtype==STDIN_READY) break; + if (subtype==SIGINT_CALLED) + { interrupted = true; + break; + } + } + [NSApp sendEvent: event]; + } + [pool release]; + + if (py_sigint_handler) PyOS_setsig(SIGINT, py_sigint_handler); + CFReadStreamUnscheduleFromRunLoop(stream, + runloop, + kCFRunLoopCommonModes); + if (sigint_socket) CFSocketInvalidate(sigint_socket); + if (error==0) close(channel[1]); + if (interrupted) raise(SIGINT); + } + CFReadStreamClose(stream); + return 1; +} + +static char show__doc__[] = "Show all the figures and enter the main loop.\nThis function does not return until all Matplotlib windows are closed,\nand is normally not needed in interactive sessions."; + +/* ---------------------------- Cocoa classes ---------------------------- */ + + +@interface Window : NSWindow +{ PyObject* manager; +} +- (Window*)initWithContentRect:(NSRect)rect styleMask:(unsigned int)mask backing:(NSBackingStoreType)bufferingType defer:(BOOL)deferCreation withManager: (PyObject*)theManager; +- (BOOL)closeButtonPressed; +- (void)close; +- (void)dealloc; +@end + +@interface ToolWindow : NSWindow +{ +} +- (ToolWindow*)initWithContentRect:(NSRect)rect master:(NSWindow*)window; +- (void)masterCloses:(NSNotification*)notification; +- (void)close; +@end + +@interface View : NSView +{ PyObject* canvas; + NSRect rubberband; +} +- (void)dealloc; +- (void)drawRect:(NSRect)rect; +- (void)windowDidResize:(NSNotification*)notification; +- (View*)initWithFrame:(NSRect)rect canvas:(PyObject*)fc; +- (BOOL)windowShouldClose:(NSNotification*)notification; +- (BOOL)isFlipped; +- (void)mouseDown:(NSEvent*)event; +- (void)mouseUp:(NSEvent*)event; +- (void)mouseDragged:(NSEvent*)event; +- (void)mouseMoved:(NSEvent*)event; +- (void)setRubberband:(NSRect)rect; +- (void)removeRubberband; +- (const char*)convertKeyEvent:(NSEvent*)event; +- (void)keyDown:(NSEvent*)event; +- (void)keyUp:(NSEvent*)event; +- (void)scrollWheel:(NSEvent *)event; +- (void)flagsChanged:(NSEvent*)event; +@end + +@interface ScrollableButton : NSButton +{ + SEL scrollWheelUpAction; + SEL scrollWheelDownAction; +} +- (void)setScrollWheelUpAction:(SEL)action; +- (void)setScrollWheelDownAction:(SEL)action; +- (void)scrollWheel:(NSEvent *)event; +@end + +@interface MenuItem: NSMenuItem +{ int index; +} ++ (MenuItem*)menuItemWithTitle:(NSString*)title; ++ (MenuItem*)menuItemSelectAll; ++ (MenuItem*)menuItemInvertAll; ++ (MenuItem*)menuItemForAxis:(int)i; +- (void)toggle:(id)sender; +- (void)selectAll:(id)sender; +- (void)invertAll:(id)sender; +- (int)index; +@end + +/* ---------------------------- Python classes ---------------------------- */ + +typedef struct { + PyObject_HEAD + CGContextRef cr; + PyObject* converter; /* Convert color specifications to r,g,b triples */ + CGPatternRef pattern; /* For drawing hatches */ + ATSUStyle style; /* For drawing Unicode strings with ATSUI */ + ATSUTextLayout layout; /* For drawing Unicode strings with ATSUI */ +} GraphicsContext; + +static PyObject* +GraphicsContext_new(PyTypeObject* type, PyObject *args, PyObject *kwds) +{ + OSStatus status; + + GraphicsContext* self = (GraphicsContext*)type->tp_alloc(type, 0); + if (!self) return NULL; + self->cr = NULL; + PyObject* module = PyImport_AddModule("matplotlib.colors"); + if (!module) return NULL; + PyObject* dict = PyObject_GetAttrString(module, "__dict__"); + if (!dict) return NULL; + PyObject* colorConverter = PyDict_GetItemString(dict, "colorConverter"); + Py_DECREF(dict); + if (!colorConverter) + { + PyErr_SetString(PyExc_KeyError, + "failed to find colorConverter in matplotlib.colors"); + return NULL; + } + self->converter = PyObject_GetAttrString(colorConverter, "to_rgb"); + if (!self->converter) return NULL; + + self->pattern = NULL; + + status = ATSUCreateStyle(&self->style); + if (status!=noErr) + { + Py_DECREF(self->converter); + PyErr_SetString(PyExc_RuntimeError, "ATSUCreateStyle failed"); + return NULL; + } + + status = ATSUCreateTextLayout(&self->layout); + if (status!=noErr) + { + Py_DECREF(self->converter); + status = ATSUDisposeStyle(self->style); + if (status!=noErr) + PyErr_WarnEx(PyExc_RuntimeWarning, "ATSUDisposeStyle failed", 1); + PyErr_SetString(PyExc_RuntimeError, "ATSUCreateTextLayout failed"); + return NULL; + } + + return (PyObject*) self; +} + +static void +GraphicsContext_dealloc(GraphicsContext *self) +{ + Py_DECREF(self->converter); + + if (self->pattern) CGPatternRelease(self->pattern); + + OSStatus status; + + status = ATSUDisposeStyle(self->style); + if (status!=noErr) + PyErr_WarnEx(PyExc_RuntimeWarning, "ATSUDisposeStyle failed", 1); + + status = ATSUDisposeTextLayout(self->layout); + if (status!=noErr) + PyErr_WarnEx(PyExc_RuntimeWarning, "ATSUDisposeTextLayout failed", 1); + + self->ob_type->tp_free((PyObject*)self); +} + +static PyObject* +GraphicsContext_repr(GraphicsContext* self) +{ + return PyString_FromFormat("GraphicsContext object %p wrapping the Quartz 2D graphics context %p", self, self->cr); +} + +static PyObject* +GraphicsContext_reset (GraphicsContext* self) +{ + CGContextRef cr = self->cr; + if (!cr) + { + PyErr_SetString(PyExc_RuntimeError, "CGContextRef is NULL"); + return NULL; + } + CGContextRestoreGState(cr); + CGContextSaveGState(cr); + Py_INCREF(Py_None); + return Py_None; +} + +static PyObject* +GraphicsContext_set_alpha (GraphicsContext* self, PyObject* args) +{ + float alpha; + if (!PyArg_ParseTuple(args, "f", &alpha)) return NULL; + CGContextRef cr = self->cr; + if (!cr) + { + PyErr_SetString(PyExc_RuntimeError, "CGContextRef is NULL"); + return NULL; + } + CGContextSetAlpha(cr, alpha); + Py_INCREF(Py_None); + return Py_None; +} + +static PyObject* +GraphicsContext_set_antialiased (GraphicsContext* self, PyObject* args) +{ + int shouldAntialias; + if (!PyArg_ParseTuple(args, "i", &shouldAntialias)) return NULL; + CGContextRef cr = self->cr; + if (!cr) + { + PyErr_SetString(PyExc_RuntimeError, "CGContextRef is NULL"); + return NULL; + } + CGContextSetShouldAntialias(cr, shouldAntialias); + Py_INCREF(Py_None); + return Py_None; +} + +static PyObject* +GraphicsContext_set_capstyle (GraphicsContext* self, PyObject* args) +{ + char* string; + CGLineCap cap; + + if (!PyArg_ParseTuple(args, "s", &string)) return NULL; + + if (!strcmp(string, "butt")) cap = kCGLineCapButt; + else if (!strcmp(string, "round")) cap = kCGLineCapRound; + else if (!strcmp(string, "projecting")) cap = kCGLineCapSquare; + else + { + PyErr_SetString(PyExc_ValueError, + "capstyle should be 'butt', 'round', or 'projecting'"); + return NULL; + } + CGContextRef cr = self->cr; + if (!cr) + { + PyErr_SetString(PyExc_RuntimeError, "CGContextRef is NULL"); + return NULL; + } + CGContextSetLineCap(cr, cap); + + Py_INCREF(Py_None); + return Py_None; +} + +static PyObject* +GraphicsContext_set_clip_rectangle (GraphicsContext* self, PyObject* args) +{ + CGRect rect; + float x, y, width, height; + if (!PyArg_ParseTuple(args, "(ffff)", &x, &y, &width, &height)) return NULL; + + rect.origin.x = x; + rect.origin.y = y; + rect.size.width = width; + rect.size.height = height; + + CGContextRef cr = self->cr; + if (!cr) + { + PyErr_SetString(PyExc_RuntimeError, "CGContextRef is NULL"); + return NULL; + } + + CGContextClipToRect(cr, rect); + Py_INCREF(Py_None); + return Py_None; +} + +static PyObject* +GraphicsContext_clip_path (GraphicsContext* self) +{ + CGContextRef cr = self->cr; + if (!cr) + { + PyErr_SetString(PyExc_RuntimeError, "CGContextRef is NULL"); + return NULL; + } + CGContextRestoreGState(cr); + CGContextSaveGState(cr); + CGContextClip(cr); + Py_INCREF(Py_None); + return Py_None; +} + +static PyObject* +GraphicsContext_set_dashes (GraphicsContext* self, PyObject* args) +{ + float phase = 0.0; + PyObject* offset; + PyObject* dashes; + + if (!PyArg_ParseTuple(args, "OO", &offset, &dashes)) return NULL; + + CGContextRef cr = self->cr; + if (!cr) + { + PyErr_SetString(PyExc_RuntimeError, "CGContextRef is NULL"); + return NULL; + } + + if (offset!=Py_None) + { + if (PyFloat_Check(offset)) phase = PyFloat_AsDouble(offset); + else if (PyInt_Check(offset)) phase = PyInt_AsLong(offset); + else + { + PyErr_SetString(PyExc_TypeError, + "offset should be a floating point value"); + return NULL; + } + } + + if (dashes!=Py_None) + { + if (PyList_Check(dashes)) dashes = PyList_AsTuple(dashes); + else if (PyTuple_Check(dashes)) Py_INCREF(dashes); + else + { + PyErr_SetString(PyExc_TypeError, + "dashes should be a tuple or a list"); + return NULL; + } + int n = PyTuple_GET_SIZE(dashes); + int i; + float* lengths = malloc(n*sizeof(float)); + if(!lengths) + { + PyErr_SetString(PyExc_MemoryError, "Failed to store dashes"); + Py_DECREF(dashes); + return NULL; + } + for (i = 0; i < n; i++) + { + PyObject* value = PyTuple_GET_ITEM(dashes, i); + if (PyFloat_Check(value)) + lengths[i] = (float) PyFloat_AS_DOUBLE(value); + else if (PyInt_Check(value)) + lengths[i] = (float) PyInt_AS_LONG(value); + else break; + } + Py_DECREF(dashes); + if (i < n) /* break encountered */ + { + free(lengths); + PyErr_SetString(PyExc_TypeError, "Failed to read dashes"); + return NULL; + } + CGContextSetLineDash(cr, phase, lengths, n); + free(lengths); + } + else + CGContextSetLineDash(cr, phase, NULL, 0); + + Py_INCREF(Py_None); + return Py_None; +} + +static PyObject* +GraphicsContext_set_foreground(GraphicsContext* self, PyObject* args, PyObject* keywords) +{ float r, g, b; + PyObject* fg; + int isRGB = 0; + static char* kwlist[] = {"fg", "isRGB", NULL}; + if(!PyArg_ParseTupleAndKeywords(args, keywords, "O|i", kwlist, + &fg, &isRGB)) return NULL; + if (isRGB) + { + if(!PyArg_ParseTuple(fg, "fff", &r, &g, &b)) return NULL; + } + else + { fg = PyObject_CallFunctionObjArgs(self->converter, fg, NULL); + if(!fg) return NULL; + if(!PyArg_ParseTuple(fg, "fff", &r, &g, &b)) return NULL; + Py_DECREF(fg); + } + + CGContextRef cr = self->cr; + if (!cr) + { + PyErr_SetString(PyExc_RuntimeError, "CGContextRef is NULL"); + return NULL; + } + + CGContextSetRGBStrokeColor(cr, r, g, b, 1.0); + CGContextSetRGBFillColor(cr, r, g, b, 1.0); + Py_INCREF(Py_None); + return Py_None; +} + +static PyObject* +GraphicsContext_set_graylevel(GraphicsContext* self, PyObject* args) +{ float gray; + if(!PyArg_ParseTuple(args, "f", &gray)) return NULL; + + CGContextRef cr = self->cr; + if (!cr) + { + PyErr_SetString(PyExc_RuntimeError, "CGContextRef is NULL"); + return NULL; + } + + CGContextSetGrayStrokeColor(cr, gray, 1.0); + CGContextSetGrayFillColor(cr, gray, 1.0); + Py_INCREF(Py_None); + return Py_None; +} + +static void drawHatch (void *info, CGContextRef cr) +{ + int i; + + PyObject* string = (PyObject*)info; + char* hatches = PyString_AS_STRING(string); + + int frequency[4] = {0, 0, 0, 0}; + float position, distance; + + const float size = 12.0; + const int n = strlen(hatches); + + for (i = 0; i < n; i++) + { + switch(hatches[i]) + { + case '/': frequency[3]++; break; + case '\\': frequency[2]++; break; + case '|': frequency[1]++; break; + case '-': frequency[0]++; break; + case '+': frequency[0]++; frequency[1]++; break; + case 'x': frequency[2]++; frequency[3]++; break; + } + } + + distance = size / frequency[0]; + position = distance / 2.0; + for (i = 0; i < frequency[0]; i++, position += distance) + { + CGContextMoveToPoint(cr, 0.0, position); + CGContextAddLineToPoint(cr, size, position); + } + distance = size / frequency[1]; + position = distance / 2.0; + for (i = 0; i < frequency[1]; i++, position += distance) + { + CGContextMoveToPoint(cr, position, 0.0); + CGContextAddLineToPoint(cr, position, size); + } + distance = size / frequency[2]; + position = distance / 2.0; + for (i = 0; i < frequency[2]; i++, position += distance) + { + CGContextMoveToPoint(cr, position, 0.0); + CGContextAddLineToPoint(cr, 0.0, position); + CGContextMoveToPoint(cr, position, size); + CGContextAddLineToPoint(cr, size, position); + } + distance = size / frequency[3]; + position = distance / 2.0; + for (i = 0; i < frequency[3]; i++, position += distance) + { + CGContextMoveToPoint(cr, position, 0.0); + CGContextAddLineToPoint(cr, size, size-position); + CGContextMoveToPoint(cr, position, size); + CGContextAddLineToPoint(cr, 0.0, size-position); + } + CGContextSetLineWidth(cr, 2.0); + CGContextSetLineCap(cr, kCGLineCapSquare); + CGContextStrokePath(cr); + + Py_DECREF(string); +} + +static PyObject* +GraphicsContext_set_hatch(GraphicsContext* self, PyObject* args) +{ PyObject* hatches; + + const float size = 12.0; + static const CGPatternCallbacks callbacks = {0, &drawHatch, NULL}; + + CGContextRef cr = self->cr; + if (!cr) + { + PyErr_SetString(PyExc_RuntimeError, "CGContextRef is NULL"); + return NULL; + } + + if(!PyArg_ParseTuple(args, "O", &hatches)) return NULL; + if(!PyString_Check(hatches)) return NULL; + + Py_INCREF(hatches); + + CGColorSpaceRef baseSpace = CGColorSpaceCreateDeviceRGB(); + CGColorSpaceRef patternSpace = CGColorSpaceCreatePattern(baseSpace); + CGColorSpaceRelease(baseSpace); + CGContextSetFillColorSpace(cr, patternSpace); + CGColorSpaceRelease(patternSpace); + + self->pattern = CGPatternCreate((void*)hatches, + CGRectMake(0, 0, size, size), + CGAffineTransformIdentity, size, size, + kCGPatternTilingNoDistortion, + false, + &callbacks); + + Py_INCREF(Py_None); + return Py_None; +} + +static PyObject* +GraphicsContext_set_linewidth (GraphicsContext* self, PyObject* args) +{ + float width; + if (!PyArg_ParseTuple(args, "f", &width)) return NULL; + + CGContextRef cr = self->cr; + if (!cr) + { + PyErr_SetString(PyExc_RuntimeError, "CGContextRef is NULL"); + return NULL; + } + + CGContextSetLineWidth(cr, width); + + Py_INCREF(Py_None); + return Py_None; +} + +static PyObject* +GraphicsContext_set_joinstyle(GraphicsContext* self, PyObject* args) +{ char* string; + CGLineJoin join; + + if (!PyArg_ParseTuple(args, "s", &string)) return NULL; + + if (!strcmp(string, "miter")) join = kCGLineJoinMiter; + else if (!strcmp(string, "round")) join = kCGLineJoinRound; + else if (!strcmp(string, "bevel")) join = kCGLineJoinBevel; + else + { + PyErr_SetString(PyExc_ValueError, + "joinstyle should be 'miter', 'round', or 'bevel'"); + return NULL; + } + + CGContextRef cr = self->cr; + if (!cr) + { + PyErr_SetString(PyExc_RuntimeError, "CGContextRef is NULL"); + return NULL; + } + CGContextSetLineJoin(cr, join); + + Py_INCREF(Py_None); + return Py_None; +} + +static PyObject* +GraphicsContext_moveto(GraphicsContext* self, PyObject* args) +{ + float x; + float y; + + if(!PyArg_ParseTuple(args, "(ff)", &x, &y)) return NULL; + + CGContextRef cr = self->cr; + if (!cr) + { + PyErr_SetString(PyExc_RuntimeError, "CGContextRef is NULL"); + return NULL; + } + CGContextMoveToPoint(cr, x, y); + + Py_INCREF(Py_None); + return Py_None; +} + +static PyObject* +GraphicsContext_lineto(GraphicsContext* self, PyObject* args) +{ + float x; + float y; + + if(!PyArg_ParseTuple(args, "(ff)", &x, &y)) return NULL; + + CGContextRef cr = self->cr; + if (!cr) + { + PyErr_SetString(PyExc_RuntimeError, "CGContextRef is NULL"); + return NULL; + } + CGContextAddLineToPoint(cr, x, y); + + Py_INCREF(Py_None); + return Py_None; +} + +static PyObject* +GraphicsContext_curve3(GraphicsContext* self, PyObject* args) +{ + float cpx; + float cpy; + float x; + float y; + + CGContextRef cr = self->cr; + if (!cr) + { + PyErr_SetString(PyExc_RuntimeError, "CGContextRef is NULL"); + return NULL; + } + + if(!PyArg_ParseTuple(args, "(ffff)", &cpx, + &cpy, + &x, + &y)) return NULL; + + CGContextAddQuadCurveToPoint(cr, cpx, cpy, x, y); + + Py_INCREF(Py_None); + return Py_None; +} + +static PyObject* +GraphicsContext_curve4 (GraphicsContext* self, PyObject* args) +{ + float cp1x; + float cp1y; + float cp2x; + float cp2y; + float x; + float y; + + CGContextRef cr = self->cr; + if (!cr) + { + PyErr_SetString(PyExc_RuntimeError, "CGContextRef is NULL"); + return NULL; + } + + if(!PyArg_ParseTuple(args, "(ffffff)", &cp1x, + &cp1y, + &cp2x, + &cp2y, + &x, + &y)) return NULL; + + CGContextAddCurveToPoint(cr, cp1x, cp1y, cp2x, cp2y, x, y); + + Py_INCREF(Py_None); + return Py_None; +} + +static PyObject* +GraphicsContext_closepoly (GraphicsContext* self) +{ + CGContextRef cr = self->cr; + if (!cr) + { + PyErr_SetString(PyExc_RuntimeError, "CGContextRef is NULL"); + return NULL; + } + + CGContextClosePath(cr); + + Py_INCREF(Py_None); + return Py_None; +} + +static PyObject* +GraphicsContext_stroke (GraphicsContext* self, PyObject* args) +{ + PyObject* color; + CGContextRef cr = self->cr; + + if (!cr) + { + PyErr_SetString(PyExc_RuntimeError, "CGContextRef is NULL"); + return NULL; + } + + if(!PyArg_ParseTuple(args, "O", &color)) return NULL; + + if(color!=Py_None) + { + float r, g, b; + if(!PyArg_ParseTuple(color, "fff", &r, &g, &b)) return NULL; + if(self->pattern) + { + float components[4]; + components[0] = r; + components[1] = g; + components[2] = b; + components[3] = 1.0; + CGContextSetFillPattern(cr, self->pattern, components); + CGPatternRelease (self->pattern); + self->pattern = nil; + } + else CGContextSetRGBFillColor(cr, r, g, b, 1.0); + CGContextDrawPath(cr, kCGPathFillStroke); + } + else CGContextStrokePath(cr); + + Py_INCREF(Py_None); + return Py_None; +} + +static ATSFontRef +setfont(CGContextRef cr, PyObject* family, float size, const char weight[], + const char style[]) +{ +#define NMAP 40 +#define NFONT 31 + int i, j, n; + const char* temp; + const char* name = "Times-Roman"; + CFStringRef string; + ATSFontRef atsfont = 0; + + const int k = (strcmp(style, "italic") ? 0 : 2) + + (strcmp(weight, "bold") ? 0 : 1); + + struct {char* name; int index;} map[NMAP] = { + {"New Century Schoolbook", 0}, + {"Century Schoolbook L", 0}, + {"Utopia", 1}, + {"ITC Bookman", 2}, + {"Bookman", 2}, + {"Bitstream Vera Serif", 3}, + {"Nimbus Roman No9 L", 4}, + {"Times New Roman", 5}, + {"Times", 6}, + {"Palatino", 7}, + {"Charter", 8}, + {"serif", 0}, + {"Lucida Grande", 9}, + {"Verdana", 10}, + {"Geneva", 11}, + {"Lucida", 12}, + {"Bitstream Vera Sans", 13}, + {"Arial", 14}, + {"Helvetica", 15}, + {"Avant Garde", 16}, + {"sans-serif", 10}, + {"Apple Chancery", 17}, + {"Textile", 18}, + {"Zapf Chancery", 19}, + {"Sand", 20}, + {"cursive", 17}, + {"Comic Sans MS", 21}, + {"Chicago", 22}, + {"Charcoal", 23}, + {"Impact", 24}, + {"Western", 25}, + {"fantasy", 21}, + {"Andale Mono", 26}, + {"Bitstream Vera Sans Mono", 27}, + {"Nimbus Mono L", 28}, + {"Courier", 29}, + {"Courier New", 30}, + {"Fixed", 30}, + {"Terminal", 30}, + {"monospace", 30}, + }; + + const char* psnames[NFONT][4] = { + {"CenturySchoolbook", /* 0 */ + "CenturySchoolbook-Bold", + "CenturySchoolbook-Italic", + "CenturySchoolbook-BoldItalic"}, + {"Utopia", /* 1 */ + "Utopia-Bold", + "Utopia-Italic", + "Utopia-BoldItalic"}, + {"Bookman-Light", /* 2 */ + "Bookman-Bold", + "Bookman-LightItalic", + "Bookman-BoldItalic"}, + {"BitstreamVeraSerif-Roman", /* 3 */ + "BitstreamVeraSerif-Bold", + "", + ""}, + {"NimbusRomNo9L-Reg", /* 4 */ + "NimbusRomNo9T-Bol", + "NimbusRomNo9L-RegIta", + "NimbusRomNo9T-BolIta"}, + {"TimesNewRomanPSMT", /* 5 */ + "TimesNewRomanPS-BoldMT", + "TimesNewRomanPS-ItalicMT", + "TimesNewRomanPS-BoldItalicMT"}, + {"Times-Roman", /* 6 */ + "Times-Bold", + "Times-Italic", + "Times-BoldItalic"}, + {"Palatino-Roman", /* 7 */ + "Palatino-Bold", + "Palatino-Italic", + "Palatino-BoldItalic"}, + {"CharterBT-Roman", /* 8 */ + "CharterBT-Bold", + "CharterBT-Italic", + "CharterBT-BoldItalic"}, + {"LucidaGrande", /* 9 */ + "LucidaGrande-Bold", + "", + ""}, + {"Verdana", /* 10 */ + "Verdana-Bold", + "Verdana-Italic", + "Verdana-BoldItalic"}, + {"Geneva", /* 11 */ + "", + "", + ""}, + {"LucidaSans", /* 12 */ + "LucidaSans-Demi", + "LucidaSans-Italic", + "LucidaSans-DemiItalic"}, + {"BitstreamVeraSans-Roman", /* 13 */ + "BitstreamVeraSans-Bold", + "BitstreamVeraSans-Oblique", + "BitstreamVeraSans-BoldOblique"}, + {"ArialMT", /* 14 */ + "Arial-BoldMT", + "Arial-ItalicMT", + "Arial-BoldItalicMT"}, + {"Helvetica", /* 15 */ + "Helvetica-Bold", + "", + ""}, + {"AvantGardeITC-Book", /* 16 */ + "AvantGardeITC-Demi", + "AvantGardeITC-BookOblique", + "AvantGardeITC-DemiOblique"}, + {"Apple-Chancery", /* 17 */ + "", + "", + ""}, + {"TextileRegular", /* 18 */ + "", + "", + ""}, + {"ZapfChancery-Roman", /* 19 */ + "ZapfChancery-Bold", + "ZapfChancery-Italic", + "ZapfChancery-MediumItalic"}, + {"SandRegular", /* 20 */ + "", + "", + ""}, + {"ComicSansMS", /* 21 */ + "ComicSansMS-Bold", + "", + ""}, + {"Chicago", /* 22 */ + "", + "", + ""}, + {"Charcoal", /* 23 */ + "", + "", + ""}, + {"Impact", /* 24 */ + "", + "", + ""}, + {"Playbill", /* 25 */ + "", + "", + ""}, + {"AndaleMono", /* 26 */ + "", + "", + ""}, + {"BitstreamVeraSansMono-Roman", /* 27 */ + "BitstreamVeraSansMono-Bold", + "BitstreamVeraSansMono-Oblique", + "BitstreamVeraSansMono-BoldOb"}, + {"NimbusMonL-Reg", /* 28 */ + "NimbusMonL-Bol", + "NimbusMonL-RegObl", + "NimbusMonL-BolObl"}, + {"Courier", /* 29 */ + "Courier-Bold", + "", + ""}, + {"CourierNewPS", /* 30 */ + "CourierNewPS-BoldMT", + "CourierNewPS-ItalicMT", + "CourierNewPS-Bold-ItalicMT"}, + }; + + if(!PyList_Check(family)) return 0; + n = PyList_GET_SIZE(family); + + for (i = 0; i < n; i++) + { + PyObject* item = PyList_GET_ITEM(family, i); + if(!PyString_Check(item)) return 0; + temp = PyString_AS_STRING(item); + for (j = 0; j < NMAP; j++) + { if (!strcmp(map[j].name, temp)) + { temp = psnames[map[j].index][k]; + break; + } + } + /* If the font name is not found in mapping, we assume */ + /* that the user specified the Postscript name directly */ + + /* Check if this font can be found on the system */ + string = CFStringCreateWithCString(kCFAllocatorDefault, + temp, + kCFStringEncodingMacRoman); + atsfont = ATSFontFindFromPostScriptName(string, kATSOptionFlagsDefault); + CFRelease(string); + + if(atsfont) + { + name = temp; + break; + } + } + if(!atsfont) + { string = CFStringCreateWithCString(kCFAllocatorDefault, + name, + kCFStringEncodingMacRoman); + atsfont = ATSFontFindFromPostScriptName(string, kATSOptionFlagsDefault); + CFRelease(string); + } + CGContextSelectFont(cr, name, size, kCGEncodingMacRoman); + return atsfont; +} + +static PyObject* +GraphicsContext_draw_text (GraphicsContext* self, PyObject* args) +{ + float x; + float y; + const UniChar* text; + int n; + PyObject* family; + float size; + const char* weight; + const char* style; + float angle; + ATSFontRef atsfont; + CGContextRef cr = self->cr; + if (!cr) + { + PyErr_SetString(PyExc_RuntimeError, "CGContextRef is NULL"); + return NULL; + } + + if(!PyArg_ParseTuple(args, "ffu#Ofssf", + &x, + &y, + &text, + &n, + &family, + &size, + &weight, + &style, + &angle)) return NULL; + + atsfont = setfont(cr, family, size, weight, style); + + OSStatus status; + + ATSUAttributeTag tags[] = {kATSUFontTag, kATSUSizeTag, kATSUQDBoldfaceTag}; + ByteCount sizes[] = {sizeof(ATSUFontID), sizeof(Fixed), sizeof(Boolean)}; + Fixed atsuSize = Long2Fix(size); + Boolean isBold = FALSE; /* setfont takes care of this */ + + ATSUAttributeValuePtr values[] = {&atsfont, &atsuSize, &isBold}; + status = ATSUSetAttributes(self->style, 3, tags, sizes, values); + if (status!=noErr) + { + PyErr_SetString(PyExc_RuntimeError, "ATSUSetAttributes failed"); + return NULL; + } + + status = ATSUSetTextPointerLocation(self->layout, + text, + kATSUFromTextBeginning, // offset from beginning + kATSUToTextEnd, // length of text range + n); // length of text buffer + if (status!=noErr) + { + PyErr_SetString(PyExc_RuntimeError, + "ATSUCreateTextLayoutWithTextPtr failed"); + return NULL; + } + + status = ATSUSetRunStyle(self->layout, + self->style, + kATSUFromTextBeginning, + kATSUToTextEnd); + if (status!=noErr) + { + PyErr_SetString(PyExc_RuntimeError, "ATSUSetRunStyle failed"); + return NULL; + } + + Fixed atsuAngle = X2Fix(angle); + ATSUAttributeTag tags2[] = {kATSUCGContextTag, kATSULineRotationTag}; + ByteCount sizes2[] = {sizeof (CGContextRef), sizeof(Fixed)}; + ATSUAttributeValuePtr values2[] = {&cr, &atsuAngle}; + status = ATSUSetLayoutControls(self->layout, 2, tags2, sizes2, values2); + if (status!=noErr) + { + PyErr_SetString(PyExc_RuntimeError, "ATSUSetLayoutControls failed"); + return NULL; + } + + status = ATSUDrawText(self->layout, + kATSUFromTextBeginning, + kATSUToTextEnd, + X2Fix(x), + X2Fix(y)); + if (status!=noErr) + { + PyErr_SetString(PyExc_RuntimeError, "ATSUDrawText failed"); + return NULL; + } + + Py_INCREF(Py_None); + return Py_None; +} + +static void _data_provider_release(void* info, const void* data, size_t size) +{ + PyObject* image = (PyObject*)info; + Py_DECREF(image); +} + +static PyObject* +GraphicsContext_draw_mathtext(GraphicsContext* self, PyObject* args) +{ + float x, y, angle; + npy_intp nrows, ncols; + int n; + + PyObject* object; + PyArrayObject* image; + + CGContextRef cr = self->cr; + if (!cr) + { + PyErr_SetString(PyExc_RuntimeError, "CGContextRef is NULL"); + return NULL; + } + + if(!PyArg_ParseTuple(args, "fffO", &x, &y, &angle, &object)) return NULL; + + /* ------------- Check the image ---------------------------- */ + if(!PyArray_Check (object)) + { + PyErr_SetString(PyExc_TypeError, "image should be a NumPy array."); + return NULL; + } + image = (PyArrayObject*) object; + if(PyArray_NDIM(image) != 2) + { + PyErr_Format(PyExc_TypeError, + "image has incorrect rank (%d expected 2)", + PyArray_NDIM(image)); + return NULL; + } + if (PyArray_TYPE(image) != NPY_UBYTE) + { + PyErr_SetString(PyExc_TypeError, + "image has incorrect type (should be uint8)"); + return NULL; + } + if (!PyArray_ISCONTIGUOUS(image)) + { + PyErr_SetString(PyExc_TypeError, "image array is not contiguous"); + return NULL; + } + + nrows = PyArray_DIM(image, 0); + ncols = PyArray_DIM(image, 1); + if (nrows != (int) nrows || ncols != (int) ncols) + { + PyErr_SetString(PyExc_RuntimeError, "bitmap image too large"); + return NULL; + } + n = nrows * ncols; + Py_INCREF(object); + + const size_t bytesPerComponent = 1; + const size_t bitsPerComponent = 8 * bytesPerComponent; + const size_t nComponents = 1; /* gray */ + const size_t bitsPerPixel = bitsPerComponent * nComponents; + const size_t bytesPerRow = nComponents * bytesPerComponent * ncols; + CGDataProviderRef provider = CGDataProviderCreateWithData(object, + PyArray_DATA(image), + n, + _data_provider_release); + CGImageRef bitmap = CGImageMaskCreate ((int) ncols, + (int) nrows, + bitsPerComponent, + bitsPerPixel, + bytesPerRow, + provider, + NULL, + false); + CGDataProviderRelease(provider); + + if(!bitmap) + { + PyErr_SetString(PyExc_RuntimeError, "CGImageMaskCreate failed"); + return NULL; + } + + if (angle==0.0) + { + CGContextDrawImage(cr, CGRectMake(x,y,ncols,nrows), bitmap); + } + else + { + CGContextSaveGState(cr); + CGContextTranslateCTM(cr, x, y); + CGContextRotateCTM(cr, angle*M_PI/180); + CGContextDrawImage(cr, CGRectMake(0,0,ncols,nrows), bitmap); + CGContextRestoreGState(cr); + } + CGImageRelease(bitmap); + + Py_INCREF(Py_None); + return Py_None; +} + +static PyObject* +GraphicsContext_get_text_width_height_descent(GraphicsContext* self, PyObject* args) +{ + const UniChar* text; + int n; + PyObject* family; + float size; + const char* weight; + const char* style; + + ATSFontRef atsfont; + + CGContextRef cr = self->cr; + if (!cr) + { + PyErr_SetString(PyExc_RuntimeError, "CGContextRef is NULL"); + return NULL; + } + + if(!PyArg_ParseTuple(args, "u#Ofss", &text, &n, &family, &size, &weight, &style)) return NULL; + + atsfont = setfont(cr, family, size, weight, style); + + OSStatus status = noErr; + ATSUAttributeTag tags[] = {kATSUFontTag, + kATSUSizeTag, + kATSUQDBoldfaceTag, + kATSUQDItalicTag}; + ByteCount sizes[] = {sizeof(ATSUFontID), + sizeof(Fixed), + sizeof(Boolean), + sizeof(Boolean)}; + Fixed atsuSize = Long2Fix(size); + Boolean isBold = FALSE; /* setfont takes care of this */ + Boolean isItalic = FALSE; /* setfont takes care of this */ + ATSUAttributeValuePtr values[] = {&atsfont, &atsuSize, &isBold, &isItalic}; + + status = ATSUSetAttributes(self->style, 4, tags, sizes, values); + if (status!=noErr) + { + PyErr_SetString(PyExc_RuntimeError, "ATSUSetAttributes failed"); + return NULL; + } + + status = ATSUSetTextPointerLocation(self->layout, + text, + kATSUFromTextBeginning, // offset from beginning + kATSUToTextEnd, // length of text range + n); // length of text buffer + if (status!=noErr) + { + PyErr_SetString(PyExc_RuntimeError, + "ATSUCreateTextLayoutWithTextPtr failed"); + return NULL; + } + + status = ATSUSetRunStyle(self->layout, + self->style, + kATSUFromTextBeginning, + kATSUToTextEnd); + if (status!=noErr) + { + PyErr_SetString(PyExc_RuntimeError, "ATSUSetRunStyle failed"); + return NULL; + } + + ATSUAttributeTag tag = kATSUCGContextTag; + ByteCount bc = sizeof (CGContextRef); + ATSUAttributeValuePtr value = &cr; + status = ATSUSetLayoutControls(self->layout, 1, &tag, &bc, &value); + if (status!=noErr) + { + PyErr_SetString(PyExc_RuntimeError, "ATSUSetLayoutControls failed"); + return NULL; + } + + ATSUTextMeasurement before; + ATSUTextMeasurement after; + ATSUTextMeasurement ascent; + ATSUTextMeasurement descent; + status = ATSUGetUnjustifiedBounds(self->layout, + kATSUFromTextBeginning, kATSUToTextEnd, + &before, &after, &ascent, &descent); + if (status!=noErr) + { + PyErr_SetString(PyExc_RuntimeError, "ATSUGetUnjustifiedBounds failed"); + return NULL; + } + + const float width = FixedToFloat(after-before); + const float height = FixedToFloat(ascent-descent); + + return Py_BuildValue("fff", width, height, FixedToFloat(descent)); +} + +static PyObject* +GraphicsContext_draw_image(GraphicsContext* self, PyObject* args) +{ + float x, y; + int nrows, ncols; + const char* data; + int n; + PyObject* image; + CGContextRef cr = self->cr; + if (!cr) + { + PyErr_SetString(PyExc_RuntimeError, "CGContextRef is NULL"); + return NULL; + } + + if(!PyArg_ParseTuple(args, "ffiiO", &x, + &y, + &nrows, + &ncols, + &image)) return NULL; + + if (!PyString_Check(image)) + { + PyErr_SetString(PyExc_RuntimeError, "image is not a string"); + return NULL; + } + + const size_t bytesPerComponent = 1; + const size_t bitsPerComponent = 8 * bytesPerComponent; + const size_t nComponents = 4; /* red, green, blue, alpha */ + const size_t bitsPerPixel = bitsPerComponent * nComponents; + const size_t bytesPerRow = nComponents * bytesPerComponent * ncols; + CGColorSpaceRef colorspace = CGColorSpaceCreateDeviceRGB(); + + Py_INCREF(image); + n = PyString_GET_SIZE(image); + data = PyString_AsString(image); + + CGDataProviderRef provider = CGDataProviderCreateWithData(image, + data, + n, + _data_provider_release); + CGImageRef bitmap = CGImageCreate (ncols, + nrows, + bitsPerComponent, + bitsPerPixel, + bytesPerRow, + colorspace, + kCGImageAlphaLast, + provider, + ... [truncated message content] |