| 20 | | DEFAULT_WIDTH, DEFAULT_HEIGHT = 1000, 1000 |
| 21 | | |
| 22 | | inch = 72 |
| 23 | | cm = 28.3465 |
| 24 | | mm = 2.8346 |
| 25 | | |
| 26 | | RGB = "rgb" |
| 27 | | HSB = "hsb" |
| 28 | | CMYK = "cmyk" |
| 29 | | |
| 30 | | CENTER = "center" |
| 31 | | CORNER = "corner" |
| 32 | | |
| 33 | | MOVETO = NSMoveToBezierPathElement |
| 34 | | LINETO = NSLineToBezierPathElement |
| 35 | | CURVETO = NSCurveToBezierPathElement |
| 36 | | CLOSE = NSClosePathBezierPathElement |
| 37 | | |
| 38 | | LEFT = NSLeftTextAlignment |
| 39 | | RIGHT = NSRightTextAlignment |
| 40 | | CENTER = NSCenterTextAlignment |
| 41 | | JUSTIFY = NSJustifiedTextAlignment |
| 42 | | |
| 43 | | NORMAL=1 |
| 44 | | FORTYFIVE=2 |
| 45 | | |
| 46 | | NUMBER = 1 |
| 47 | | TEXT = 2 |
| 48 | | BOOLEAN = 3 |
| 49 | | BUTTON = 4 |
| 50 | | |
| 51 | | _STATE_NAMES = { |
| 52 | | '_outputmode': 'outputmode', |
| 53 | | '_colorrange': 'colorrange', |
| 54 | | '_fillcolor': 'fill', |
| 55 | | '_strokecolor': 'stroke', |
| 56 | | '_strokewidth': 'strokewidth', |
| 57 | | '_transform': 'transform', |
| 58 | | '_transformmode': 'transformmode', |
| 59 | | '_fontname': 'font', |
| 60 | | '_fontsize': 'fontsize', |
| 61 | | '_align': 'align', |
| 62 | | '_lineheight': 'lineheight', |
| 63 | | } |
| 64 | | |
| 65 | | def _save(): |
| 66 | | NSGraphicsContext.currentContext().saveGraphicsState() |
| 67 | | |
| 68 | | def _restore(): |
| 69 | | NSGraphicsContext.currentContext().restoreGraphicsState() |
| 70 | | |
| 71 | | class NodeBoxError(Exception): pass |
| 72 | | |
| 73 | | class Point(object): |
| 74 | | |
| 75 | | def __init__(self, *args): |
| 76 | | if len(args) == 2: |
| 77 | | self.x, self.y = args |
| 78 | | elif len(args) == 1: |
| 79 | | self.x, self.y = args[0] |
| 80 | | elif len(args) == 0: |
| 81 | | self.x = self.y = 0.0 |
| 82 | | else: |
| 83 | | raise NodeBoxError, "Wrong initializer for Point object" |
| 84 | | |
| 85 | | def __repr__(self): |
| 86 | | return "Point(x=%.3f, y=%.3f)" % (self.x, self.y) |
| 87 | | |
| 88 | | def __eq__(self, other): |
| 89 | | if other is None: return False |
| 90 | | return self.x == other.x and self.y == other.y |
| 91 | | |
| 92 | | def __ne__(self, other): |
| 93 | | return not self.__eq__(other) |
| 94 | | |
| 95 | | class Grob(object): |
| 96 | | """A GRaphic OBject is the base class for all DrawingPrimitives.""" |
| 97 | | |
| 98 | | def __init__(self, ctx): |
| 99 | | """Initializes this object with the current context.""" |
| 100 | | self._ctx = ctx |
| 101 | | |
| 102 | | def draw(self): |
| 103 | | """Appends the grob to the canvas. |
| 104 | | This will result in a draw later on, when the scene graph is rendered.""" |
| 105 | | self._ctx._canvas.append( self ) |
| 106 | | |
| 107 | | def copy(self): |
| 108 | | """Returns a deep copy of this grob.""" |
| 109 | | raise NotImplementedError, "Copy is not implemented on this Grob class." |
| 110 | | |
| 111 | | def inheritFromContext(self, ignore=()): |
| 112 | | attrs_to_copy = list(self.__class__.stateAttributes) |
| 113 | | [attrs_to_copy.remove(k) for k, v in _STATE_NAMES.items() if v in ignore] |
| 114 | | _copy_attrs(self._ctx, self, attrs_to_copy) |
| 115 | | |
| 116 | | def checkKwargs(self, kwargs): |
| 117 | | remaining = [arg for arg in kwargs.keys() if arg not in self.kwargs] |
| 118 | | if remaining: |
| 119 | | raise NodeBoxError, "Unknown argument(s) '%s'" % ", ".join(remaining) |
| 120 | | checkKwargs = classmethod(checkKwargs) |
| 121 | | |
| 122 | | class TransformMixin(object): |
| 123 | | |
| 124 | | """Mixin class for transformation support. |
| 125 | | Adds the _transform and _transformmode attributes to the class.""" |
| 126 | | |
| 127 | | def __init__(self): |
| 128 | | self._reset() |
| 129 | | |
| 130 | | def _reset(self): |
| 131 | | self._transform = Transform() |
| 132 | | self._transformmode = CENTER |
| 133 | | |
| 134 | | def transform(self, mode): |
| 135 | | self._transformmode = mode |
| 136 | | |
| 137 | | def translate(self, x, y): |
| 138 | | self._transform.translate(x, y) |
| 139 | | |
| 140 | | def transform(self, mode=None): |
| 141 | | if mode is not None: |
| 142 | | self._transformmode = mode |
| 143 | | return self._transformmode |
| 144 | | |
| 145 | | def reset(self): |
| 146 | | self._transform = Transform() |
| 147 | | |
| 148 | | def rotate(self, degrees=0, radians=0): |
| 149 | | self._transform.rotate(-degrees,-radians) |
| 150 | | |
| 151 | | def translate(self, x=0, y=0): |
| 152 | | self._transform.translate(x,y) |
| 153 | | |
| 154 | | def scale(self, x=1, y=None): |
| 155 | | self._transform.scale(x,y) |
| 156 | | |
| 157 | | def skew(self, x=0, y=0): |
| 158 | | self._transform.skew(x,y) |
| 159 | | |
| 160 | | class ColorMixin(object): |
| 161 | | |
| 162 | | """Mixin class for color support. |
| 163 | | Adds the _fillcolor, _strokecolor and _strokewidth attributes to the class.""" |
| 164 | | |
| 165 | | def __init__(self, **kwargs): |
| 166 | | try: |
| 167 | | self._fillcolor = Color(self._ctx, kwargs['fill']) |
| 168 | | except KeyError: |
| 169 | | self._fillcolor = Color(self._ctx) |
| 170 | | try: |
| 171 | | self._strokecolor = Color(self._ctx, kwargs['stroke']) |
| 172 | | except KeyError: |
| 173 | | self._strokecolor = None |
| 174 | | self._strokewidth = kwargs.get('strokewidth', 1.0) |
| 175 | | |
| 176 | | def nofill(self): |
| 177 | | self._fillcolor = None |
| 178 | | |
| 179 | | def fill(self, *args): |
| 180 | | if len(args) > 0: |
| 181 | | self._fillcolor = Color(self._ctx, *args) |
| 182 | | return self._fillcolor |
| 183 | | |
| 184 | | def nostroke(self): |
| 185 | | self._strokecolor = None |
| 186 | | |
| 187 | | def stroke(self, *args): |
| 188 | | if len(args) > 0: |
| 189 | | self._strokecolor = Color(self._ctx, *args) |
| 190 | | return self._strokecolor |
| 191 | | |
| 192 | | def strokewidth(self, width=None): |
| 193 | | if width is not None: |
| 194 | | self._strokewidth = width |
| 195 | | return self._strokewidth |
| 196 | | |
| 197 | | class BezierPath(Grob, TransformMixin, ColorMixin): |
| 198 | | """A BezierPath provides a wrapper around NSBezierPath.""" |
| 199 | | |
| 200 | | stateAttributes = ('_fillcolor', '_strokecolor', '_strokewidth', '_transform', '_transformmode') |
| 201 | | kwargs = ('fill', 'stroke', 'strokewidth') |
| 202 | | |
| 203 | | def __init__(self, ctx, path=None, **kwargs): |
| 204 | | super(BezierPath, self).__init__(ctx) |
| 205 | | TransformMixin.__init__(self) |
| 206 | | ColorMixin.__init__(self, **kwargs) |
| 207 | | self._segment_cache = None |
| 208 | | if path is None: |
| 209 | | self.path = NSBezierPath.bezierPath() |
| 210 | | elif isinstance(path, list): |
| 211 | | self.path = NSBezierPath.bezierPath() |
| 212 | | self.extend(path) |
| 213 | | elif isinstance(path, BezierPath): |
| 214 | | self.path = path.path.copy() |
| 215 | | _copy_attrs(path, self, self.stateAttributes) |
| 216 | | elif isinstance(path, NSBezierPath): |
| 217 | | self.path = path |
| 218 | | else: |
| 219 | | raise NodeBoxError, "Don't know what to do with %s." % path |
| 220 | | |
| 221 | | def copy(self): |
| 222 | | return self.__class__(self._ctx, self) |
| 223 | | |
| 224 | | ### Path methods ### |
| 225 | | |
| 226 | | def moveto(self, x, y): |
| 227 | | self._segment_cache = None |
| 228 | | self.path.moveToPoint_( (x, y) ) |
| 229 | | |
| 230 | | def lineto(self, x, y): |
| 231 | | self._segment_cache = None |
| 232 | | self.path.lineToPoint_( (x, y) ) |
| 233 | | |
| 234 | | def curveto(self, x1, y1, x2, y2, x3, y3): |
| 235 | | self._segment_cache = None |
| 236 | | self.path.curveToPoint_controlPoint1_controlPoint2_( (x3, y3), (x1, y1), (x2, y2) ) |
| 237 | | |
| 238 | | def closepath(self): |
| 239 | | self._segment_cache = None |
| 240 | | self.path.closePath() |
| 241 | | |
| 242 | | def setlinewidth(self, width): |
| 243 | | self.linewidth = width |
| 244 | | |
| 245 | | def _get_bounds(self): |
| 246 | | try: |
| 247 | | return self.path.bounds() |
| 248 | | except: |
| 249 | | # Path is empty -- no bounds |
| 250 | | return (0,0) , (0,0) |
| 251 | | |
| 252 | | bounds = property(_get_bounds) |
| 253 | | |
| 254 | | def contains(self, x, y): |
| 255 | | return self.path.containsPoint_((x,y)) |
| 256 | | |
| 257 | | ### Basic shapes ### |
| 258 | | |
| 259 | | def rect(self, x, y, width, height): |
| 260 | | self._segment_cache = None |
| 261 | | self.path.appendBezierPathWithRect_( ((x, y), (width, height)) ) |
| 262 | | |
| 263 | | def oval(self, x, y, width, height): |
| 264 | | self._segment_cache = None |
| 265 | | self.path.appendBezierPathWithOvalInRect_( ((x, y), (width, height)) ) |
| 266 | | |
| 267 | | def line(self, x1, y1, x2, y2): |
| 268 | | self._segment_cache = None |
| 269 | | self.path.moveToPoint_( (x1, y1) ) |
| 270 | | self.path.lineToPoint_( (x2, y2) ) |
| 271 | | |
| 272 | | ### List methods ### |
| 273 | | |
| 274 | | def __getitem__(self, index): |
| 275 | | cmd, el = self.path.elementAtIndex_associatedPoints_(index) |
| 276 | | return PathElement(cmd, el) |
| 277 | | |
| 278 | | def __iter__(self): |
| 279 | | for i in range(len(self)): |
| 280 | | yield self[i] |
| 281 | | |
| 282 | | def __len__(self): |
| 283 | | return self.path.elementCount() |
| 284 | | |
| 285 | | def extend(self, pathElements): |
| 286 | | self._segment_cache = None |
| 287 | | for el in pathElements: |
| 288 | | self.append(el) |
| 289 | | |
| 290 | | def append(self, el): |
| 291 | | self._segment_cache = None |
| 292 | | if el.cmd == MOVETO: |
| 293 | | self.moveto(el.x, el.y) |
| 294 | | elif el.cmd == LINETO: |
| 295 | | self.lineto(el.x, el.y) |
| 296 | | elif el.cmd == CURVETO: |
| 297 | | self.curveto(el.ctrl1.x, el.ctrl1.y, el.ctrl2.x, el.ctrl2.y, el.x, el.y) |
| 298 | | elif el.cmd == CLOSE: |
| 299 | | self.closepath() |
| 300 | | |
| 301 | | def _get_contours(self): |
| 302 | | import bezier |
| 303 | | return bezier.contours(self) |
| 304 | | contours = property(_get_contours) |
| 305 | | |
| 306 | | ### Drawing methods ### |
| 307 | | |
| 308 | | def _get_transform(self): |
| 309 | | trans = self._transform.copy() |
| 310 | | |
| 311 | | if (self._transformmode == CENTER): |
| 312 | | (x, y), (w, h) = self.bounds |
| 313 | | deltax = x+w/2 |
| 314 | | deltay = y+h/2 |
| 315 | | t = Transform() |
| 316 | | t.translate(-deltax,-deltay) |
| 317 | | trans.prepend(t) |
| 318 | | t = Transform() |
| 319 | | t.translate(deltax,deltay) |
| 320 | | trans.append(t) |
| 321 | | |
| 322 | | return trans |
| 323 | | |
| 324 | | transform = property(_get_transform) |
| 325 | | |
| 326 | | def _draw(self): |
| 327 | | _save() |
| 328 | | self.transform.concat() |
| 329 | | if (self._fillcolor): |
| 330 | | self._fillcolor.set() |
| 331 | | self.path.fill() |
| 332 | | if (self._strokecolor): |
| 333 | | self._strokecolor.set() |
| 334 | | self.path.setLineWidth_(self._strokewidth) |
| 335 | | self.path.stroke() |
| 336 | | _restore() |
| 337 | | |
| 338 | | ### Mathematics ### |
| 339 | | |
| 340 | | def segmentlengths(self, relative=False, n=10): |
| 341 | | import bezier |
| 342 | | if relative: # Use the opportunity to store the segment cache. |
| 343 | | if self._segment_cache is None: |
| 344 | | self._segment_cache = bezier.segment_lengths(self, relative=True, n=n) |
| 345 | | return self._segment_cache |
| 346 | | else: |
| 347 | | return bezier.segment_lengths(self, relative=False, n=n) |
| 348 | | |
| 349 | | def _get_length(self, segmented=False, n=10): |
| 350 | | import bezier |
| 351 | | return bezier.length(self, segmented=segmented, n=n) |
| 352 | | length = property(_get_length) |
| 353 | | |
| 354 | | def point(self, t): |
| 355 | | import bezier |
| 356 | | return bezier.point(self, t) |
| 357 | | |
| 358 | | def points(self, amount=100): |
| 359 | | import bezier |
| 360 | | if len(self) == 0: |
| 361 | | raise NodeBoxError, "The given path is empty" |
| 362 | | |
| 363 | | # The delta value is divided by amount - 1, because we also want the last point (t=1.0) |
| 364 | | # If I wouldn't use amount - 1, I fall one point short of the end. |
| 365 | | # E.g. if amount = 4, I want point at t 0.0, 0.33, 0.66 and 1.0, |
| 366 | | # if amount = 2, I want point at t 0.0 and t 1.0 |
| 367 | | try: |
| 368 | | delta = 1.0/(amount-1) |
| 369 | | except ZeroDivisionError: |
| 370 | | delta = 1.0 |
| 371 | | |
| 372 | | for i in xrange(amount): |
| 373 | | yield self.point(delta*i) |
| 374 | | |
| 375 | | def addpoint(self, t): |
| 376 | | import bezier |
| 377 | | self.path = bezier.insert_point(self, t).path |
| 378 | | self._segment_cache = None |
| 379 | | |
| 380 | | class PathElement(object): |
| 381 | | |
| 382 | | def __init__(self, cmd=None, pts=None): |
| 383 | | self.cmd = cmd |
| 384 | | if cmd == MOVETO: |
| 385 | | assert len(pts) == 1 |
| 386 | | self.x, self.y = pts[0] |
| 387 | | self.ctrl1 = Point(pts[0]) |
| 388 | | self.ctrl2 = Point(pts[0]) |
| 389 | | elif cmd == LINETO: |
| 390 | | assert len(pts) == 1 |
| 391 | | self.x, self.y = pts[0] |
| 392 | | self.ctrl1 = Point(pts[0]) |
| 393 | | self.ctrl2 = Point(pts[0]) |
| 394 | | elif cmd == CURVETO: |
| 395 | | assert len(pts) == 3 |
| 396 | | self.ctrl1 = Point(pts[0]) |
| 397 | | self.ctrl2 = Point(pts[1]) |
| 398 | | self.x, self.y = pts[2] |
| 399 | | elif cmd == CLOSE: |
| 400 | | assert pts is None or len(pts) == 0 |
| 401 | | self.x = self.y = 0.0 |
| 402 | | self.ctrl1 = Point(0.0, 0.0) |
| 403 | | self.ctrl2 = Point(0.0, 0.0) |
| 404 | | else: |
| 405 | | self.x = self.y = 0.0 |
| 406 | | self.ctrl1 = Point() |
| 407 | | self.ctrl2 = Point() |
| 408 | | |
| 409 | | def __repr__(self): |
| 410 | | if self.cmd == MOVETO: |
| 411 | | return "PathElement(MOVETO, ((%.3f, %.3f),))" % (self.x, self.y) |
| 412 | | elif self.cmd == LINETO: |
| 413 | | return "PathElement(LINETO, ((%.3f, %.3f),))" % (self.x, self.y) |
| 414 | | elif self.cmd == CURVETO: |
| 415 | | return "PathElement(CURVETO, ((%.3f, %.3f), (%.3f, %s), (%.3f, %.3f))" % \ |
| 416 | | (self.ctrl1.x, self.ctrl1.y, self.ctrl2.x, self.ctrl2.y, self.x, self.y) |
| 417 | | elif self.cmd == CLOSE: |
| 418 | | return "PathElement(CLOSE)" |
| 419 | | |
| 420 | | def __eq__(self, other): |
| 421 | | if other is None: return False |
| 422 | | if self.cmd != other.cmd: return False |
| 423 | | return self.x == other.x and self.y == other.y \ |
| 424 | | and self.ctrl1 == other.ctrl1 and self.ctrl2 == other.ctrl2 |
| 425 | | |
| 426 | | def __ne__(self, other): |
| 427 | | return not self.__eq__(other) |
| 428 | | |
| 429 | | class ClippingPath(Grob): |
| 430 | | |
| 431 | | def __init__(self, ctx, path): |
| 432 | | self._ctx = ctx |
| 433 | | self.path = path |
| 434 | | self._grobs = [] |
| 435 | | |
| 436 | | def append(self, grob): |
| 437 | | self._grobs.append(grob) |
| 438 | | |
| 439 | | def _draw(self): |
| 440 | | _save() |
| 441 | | cp = self.path.transform.transformBezierPath(self.path) |
| 442 | | cp.path.addClip() |
| 443 | | for grob in self._grobs: |
| 444 | | grob._draw() |
| 445 | | _restore() |
| 446 | | |
| 447 | | class Rect(BezierPath): |
| 448 | | |
| 449 | | def __init__(self, ctx, x, y, width, height, **kwargs): |
| 450 | | r = (x,y), (width,height) |
| 451 | | super(Rect, self).__init__(ctx, NSBezierPath.bezierPathWithRect_(r), **kwargs) |
| 452 | | |
| 453 | | def copy(self): |
| 454 | | raise NotImplementedError, "Please don't use Rect anymore" |
| 455 | | |
| 456 | | class Oval(BezierPath): |
| 457 | | |
| 458 | | def __init__(self, ctx, x, y, width, height, **kwargs): |
| 459 | | r = (x,y), (width,height) |
| 460 | | super(Oval, self).__init__(ctx, NSBezierPath.bezierPathWithOvalInRect_(r), **kwargs) |
| 461 | | |
| 462 | | def copy(self): |
| 463 | | raise NotImplementedError, "Please don't use Oval anymore" |
| 464 | | |
| 465 | | class Color(object): |
| 466 | | |
| 467 | | def __init__(self, ctx, *args): |
| 468 | | self._ctx = ctx |
| 469 | | params = len(args) |
| 470 | | |
| 471 | | # Decompose the arguments into tuples. |
| 472 | | if params == 1 and isinstance(args[0], tuple): |
| 473 | | args = args[0] |
| 474 | | params = len(args) |
| 475 | | |
| 476 | | if params == 1 and isinstance(args[0], Color): |
| 477 | | clr = args[0]._cmyk |
| 478 | | elif params == 1 and isinstance(args[0], NSColor): |
| 479 | | clr = args[0] |
| 480 | | elif params == 1: # Gray, no alpha |
| 481 | | args = self._normalizeList(args) |
| 482 | | g, = args |
| 483 | | clr = NSColor.colorWithDeviceWhite_alpha_(g, 1) |
| 484 | | elif params == 2: # Gray and alpha |
| 485 | | args = self._normalizeList(args) |
| 486 | | g, a = args |
| 487 | | clr = NSColor.colorWithDeviceWhite_alpha_(g, a) |
| 488 | | elif params == 3 and self._ctx._colormode == RGB: # RGB, no alpha |
| 489 | | args = self._normalizeList(args) |
| 490 | | r,g,b = args |
| 491 | | clr = NSColor.colorWithDeviceRed_green_blue_alpha_(r, g, b, 1) |
| 492 | | elif params == 3 and self._ctx._colormode == HSB: # HSB, no alpha |
| 493 | | args = self._normalizeList(args) |
| 494 | | h, s, b = args |
| 495 | | clr = NSColor.colorWithDeviceHue_saturation_brightness_alpha_(h, s, b, 1) |
| 496 | | elif params == 4 and self._ctx._colormode == RGB: # RGB and alpha |
| 497 | | args = self._normalizeList(args) |
| 498 | | r,g,b, a = args |
| 499 | | clr = NSColor.colorWithDeviceRed_green_blue_alpha_(r, g, b, a) |
| 500 | | elif params == 4 and self._ctx._colormode == HSB: # HSB and alpha |
| 501 | | args = self._normalizeList(args) |
| 502 | | h, s, b, a = args |
| 503 | | clr = NSColor.colorWithDeviceHue_saturation_brightness_alpha_(h, s, b, a) |
| 504 | | elif params == 4 and self._ctx._colormode == CMYK: # CMYK, no alpha |
| 505 | | args = self._normalizeList(args) |
| 506 | | c, m, y, k = args |
| 507 | | clr = NSColor.colorWithDeviceCyan_magenta_yellow_black_alpha_(c, m, y, k, 1) |
| 508 | | elif params == 5 and self._ctx._colormode == CMYK: # CMYK and alpha |
| 509 | | args = self._normalizeList(args) |
| 510 | | c, m, y, k, a = args |
| 511 | | clr = NSColor.colorWithDeviceCyan_magenta_yellow_black_alpha_(c, m, y, k, a) |
| 512 | | else: |
| 513 | | clr = NSColor.colorWithDeviceWhite_alpha_(0, 1) |
| 514 | | |
| 515 | | self._cmyk = clr.colorUsingColorSpaceName_(NSDeviceCMYKColorSpace) |
| 516 | | self._rgb = clr.colorUsingColorSpaceName_(NSDeviceRGBColorSpace) |
| 517 | | |
| 518 | | def __repr__(self): |
| 519 | | return "%s(%.3f, %.3f, %.3f, %.3f)" % (self.__class__.__name__, self.red, |
| 520 | | self.green, self.blue, self.alpha) |
| 521 | | |
| 522 | | def set(self): |
| 523 | | self.nsColor.set() |
| 524 | | |
| 525 | | def _get_nsColor(self): |
| 526 | | if self._ctx._outputmode == RGB: |
| 527 | | return self._rgb |
| 528 | | else: |
| 529 | | return self._cmyk |
| 530 | | nsColor = property(_get_nsColor) |
| 531 | | |
| 532 | | |
| 533 | | def copy(self): |
| 534 | | new = self.__class__(self._ctx) |
| 535 | | new._rgb = self._rgb.copy() |
| 536 | | new._updateCmyk() |
| 537 | | return new |
| 538 | | |
| 539 | | def _updateCmyk(self): |
| 540 | | self._cmyk = self._rgb.colorUsingColorSpaceName_(NSDeviceCMYKColorSpace) |
| 541 | | |
| 542 | | def _updateRgb(self): |
| 543 | | self._rgb = self._cmyk.colorUsingColorSpaceName_(NSDeviceRGBColorSpace) |
| 544 | | |
| 545 | | def _get_hue(self): |
| 546 | | return self._rgb.hueComponent() |
| 547 | | def _set_hue(self, val): |
| 548 | | val = self._normalize(val) |
| 549 | | h, s, b, a = self._rgb.getHue_saturation_brightness_alpha_() |
| 550 | | self._rgb = NSColor.colorWithDeviceHue_saturation_brightness_alpha_(val, s, b, a) |
| 551 | | self._updateCmyk() |
| 552 | | h = hue = property(_get_hue, _set_hue, doc="the hue of the color") |
| 553 | | |
| 554 | | def _get_saturation(self): |
| 555 | | return self._rgb.saturationComponent() |
| 556 | | def _set_saturation(self, val): |
| 557 | | val = self._normalize(val) |
| 558 | | h, s, b, a = self._rgb.getHue_saturation_brightness_alpha_() |
| 559 | | self._rgb = NSColor.colorWithDeviceHue_saturation_brightness_alpha_(h, val, b, a) |
| 560 | | self._updateCmyk() |
| 561 | | s = saturation = property(_get_saturation, _set_saturation, doc="the saturation of the color") |
| 562 | | |
| 563 | | def _get_brightness(self): |
| 564 | | return self._rgb.brightnessComponent() |
| 565 | | def _set_brightness(self, val): |
| 566 | | val = self._normalize(val) |
| 567 | | h, s, b, a = self._rgb.getHue_saturation_brightness_alpha_() |
| 568 | | self._rgb = NSColor.colorWithDeviceHue_saturation_brightness_alpha_(h, s, val, a) |
| 569 | | self._updateCmyk() |
| 570 | | v = brightness = property(_get_brightness, _set_brightness, doc="the brightness of the color") |
| 571 | | |
| 572 | | def _get_hsba(self): |
| 573 | | return self._rgb.getHue_saturation_brightness_alpha_() |
| 574 | | def _set_hsba(self, values): |
| 575 | | val = self._normalize(val) |
| 576 | | h, s, b, a = values |
| 577 | | self._rgb = NSColor.colorWithDeviceHue_saturation_brightness_alpha_(h, s, b, a) |
| 578 | | self._updateCmyk() |
| 579 | | hsba = property(_get_hsba, _set_hsba, doc="the hue, saturation, brightness and alpha of the color") |
| 580 | | |
| 581 | | def _get_red(self): |
| 582 | | return self._rgb.redComponent() |
| 583 | | def _set_red(self, val): |
| 584 | | val = self._normalize(val) |
| 585 | | r, g, b, a = self._rgb.getRed_green_blue_alpha_() |
| 586 | | self._rgb = NSColor.colorWithDeviceRed_green_blue_alpha_(val, g, b, a) |
| 587 | | self._updateCmyk() |
| 588 | | r = red = property(_get_red, _set_red, doc="the red component of the color") |
| 589 | | |
| 590 | | def _get_green(self): |
| 591 | | return self._rgb.greenComponent() |
| 592 | | def _set_green(self, val): |
| 593 | | val = self._normalize(val) |
| 594 | | r, g, b, a = self._rgb.getRed_green_blue_alpha_() |
| 595 | | self._rgb = NSColor.colorWithDeviceRed_green_blue_alpha_(r, val, b, a) |
| 596 | | self._updateCmyk() |
| 597 | | g = green = property(_get_green, _set_green, doc="the green component of the color") |
| 598 | | |
| 599 | | def _get_blue(self): |
| 600 | | return self._rgb.blueComponent() |
| 601 | | def _set_blue(self, val): |
| 602 | | val = self._normalize(val) |
| 603 | | r, g, b, a = self._rgb.getRed_green_blue_alpha_() |
| 604 | | self._rgb = NSColor.colorWithDeviceRed_green_blue_alpha_(r, g, val, a) |
| 605 | | self._updateCmyk() |
| 606 | | b = blue = property(_get_blue, _set_blue, doc="the blue component of the color") |
| 607 | | |
| 608 | | def _get_alpha(self): |
| 609 | | return self._rgb.alphaComponent() |
| 610 | | def _set_alpha(self, val): |
| 611 | | val = self._normalize(val) |
| 612 | | r, g, b, a = self._rgb.getRed_green_blue_alpha_() |
| 613 | | self._rgb = NSColor.colorWithDeviceRed_green_blue_alpha_(r, g, b, val) |
| 614 | | self._updateCmyk() |
| 615 | | a = alpha = property(_get_alpha, _set_alpha, doc="the alpha component of the color") |
| 616 | | |
| 617 | | def _get_rgba(self): |
| 618 | | return self._rgb.getRed_green_blue_alpha_() |
| 619 | | def _set_rgba(self, val): |
| 620 | | val = self._normalizeList(val) |
| 621 | | r, g, b, a = val |
| 622 | | self._rgb = NSColor.colorWithDeviceRed_green_blue_alpha_(r, g, b, a) |
| 623 | | self._updateCmyk() |
| 624 | | rgba = property(_get_rgba, _set_rgba, doc="the red, green, blue and alpha values of the color") |
| 625 | | |
| 626 | | def _get_cyan(self): |
| 627 | | return self._cmyk.cyanComponent() |
| 628 | | def _set_cyan(self, val): |
| 629 | | val = self._normalize(val) |
| 630 | | c, m, y, k, a = self.cmyka # self._cmyk.getCyan_magenta_yellow_black_alpha_() |
| 631 | | self._cmyk = NSColor.colorWithDeviceCyan_magenta_yellow_black_alpha_(val, m, y, k, a) |
| 632 | | self._updateRgb() |
| 633 | | c = cyan = property(_get_cyan, _set_cyan, doc="the cyan component of the color") |
| 634 | | |
| 635 | | def _get_magenta(self): |
| 636 | | return self._cmyk.magentaComponent() |
| 637 | | def _set_magenta(self, val): |
| 638 | | val = self._normalize(val) |
| 639 | | c, m, y, k, a = self.cmyka # self._cmyk.getCyan_magenta_yellow_black_alpha_() |
| 640 | | self._cmyk = NSColor.colorWithDeviceCyan_magenta_yellow_black_alpha_(c, val, y, k, a) |
| 641 | | self._updateRgb() |
| 642 | | m = magenta = property(_get_magenta, _set_magenta, doc="the magenta component of the color") |
| 643 | | |
| 644 | | def _get_yellow(self): |
| 645 | | return self._cmyk.yellowComponent() |
| 646 | | def _set_yellow(self, val): |
| 647 | | val = self._normalize(val) |
| 648 | | c, m, y, k, a = self.cmyka # self._cmyk.getCyan_magenta_yellow_black_alpha_() |
| 649 | | self._cmyk = NSColor.colorWithDeviceCyan_magenta_yellow_black_alpha_(c, m, val, k, a) |
| 650 | | self._updateRgb() |
| 651 | | y = yellow = property(_get_yellow, _set_yellow, doc="the yellow component of the color") |
| 652 | | |
| 653 | | def _get_black(self): |
| 654 | | return self._cmyk.blackComponent() |
| 655 | | def _set_black(self, val): |
| 656 | | val = self._normalize(val) |
| 657 | | c, m, y, k, a = self.cmyka # self._cmyk.getCyan_magenta_yellow_black_alpha_() |
| 658 | | self._cmyk = NSColor.colorWithDeviceCyan_magenta_yellow_black_alpha_(c, m, y, val, a) |
| 659 | | self._updateRgb() |
| 660 | | k = black = property(_get_black, _set_black, doc="the black component of the color") |
| 661 | | |
| 662 | | def _get_cmyka(self): |
| 663 | | return (self._cmyk.cyanComponent(), self._cmyk.magentaComponent(), self._cmyk.yellowComponent(), self._cmyk.blackComponent(), self._cmyk.alphaComponent()) |
| 664 | | cmyka = property(_get_cmyka, doc="a tuple containing the CMYKA values for this color") |
| 665 | | |
| 666 | | def blend(self, otherColor, factor): |
| 667 | | """Blend the color with otherColor with a factor; return the new color. Factor |
| 668 | | is a float between 0.0 and 1.0. |
| 669 | | """ |
| 670 | | if hasattr(otherColor, "color"): |
| 671 | | otherColor = otherColor._rgb |
| 672 | | return self.__class__(color=self._rgb.blendedColorWithFraction_ofColor_( |
| 673 | | factor, otherColor)) |
| 674 | | |
| 675 | | def _normalize(self, v): |
| 676 | | """Bring the color into the 0-1 scale for the current colorrange""" |
| 677 | | return v / self._ctx._colorrange |
| 678 | | |
| 679 | | def _normalizeList(self, lst): |
| 680 | | """Bring the color into the 0-1 scale for the current colorrange""" |
| 681 | | return map(lambda x:x / self._ctx._colorrange, lst) |
| 682 | | |
| 683 | | color = Color |
| 684 | | |
| 685 | | class Transform(object): |
| 686 | | def __init__(self, transform=None): |
| 687 | | if transform is None: |
| 688 | | transform = NSAffineTransform.transform() |
| 689 | | elif not isinstance(transform, NSAffineTransform): |
| 690 | | matrix = tuple(transform) |
| 691 | | transform = NSAffineTransform.transform() |
| 692 | | transform.setTransformStruct_(matrix) |
| 693 | | self.transform = transform |
| 694 | | |
| 695 | | def set(self): |
| 696 | | self.transform.set() |
| 697 | | |
| 698 | | def concat(self): |
| 699 | | self.transform.concat() |
| 700 | | |
| 701 | | def copy(self): |
| 702 | | return self.__class__(self.transform.copy()) |
| 703 | | |
| 704 | | def __repr__(self): |
| 705 | | return "<%s [%.3f %.3f %.3f %.3f %.3f %.3f]>" % ((self.__class__.__name__,) |
| 706 | | + tuple(self)) |
| 707 | | |
| 708 | | def __iter__(self): |
| 709 | | for value in self.transform.transformStruct(): |
| 710 | | yield value |
| 711 | | |
| 712 | | def _get_matrix(self): |
| 713 | | return self.transform.transformStruct() |
| 714 | | def _set_matrix(self, value): |
| 715 | | self.transform.setTransformStruct_(value) |
| 716 | | matrix = property(_get_matrix, _set_matrix) |
| 717 | | |
| 718 | | def rotate(self, degrees=0, radians=0): |
| 719 | | if degrees: |
| 720 | | self.transform.rotateByDegrees_(degrees) |
| 721 | | else: |
| 722 | | self.transform.rotateByRadians_(radians) |
| 723 | | |
| 724 | | def translate(self, x=0, y=0): |
| 725 | | self.transform.translateXBy_yBy_(x, y) |
| 726 | | |
| 727 | | def scale(self, x=1, y=None): |
| 728 | | if y is None: |
| 729 | | y = x |
| 730 | | self.transform.scaleXBy_yBy_(x, y) |
| 731 | | |
| 732 | | def skew(self, x=0, y=0): |
| 733 | | import math |
| 734 | | x = math.pi * x / 180 |
| 735 | | y = math.pi * y / 180 |
| 736 | | t = Transform() |
| 737 | | t.matrix = 1, math.tan(y), -math.tan(x), 1, 0, 0 |
| 738 | | self.prepend(t) |
| 739 | | |
| 740 | | def invert(self): |
| 741 | | self.transform.invert() |
| 742 | |