def aat_features(spec): """Validate features in a Font spec and normalize settings values""" features = {} for k, v in spec.items(): if k == 'ss': # unpack & validate the ss arg (which might be a sequence of ints) ss = (int(v), ) if numlike(v) or isinstance(v, bool) else v if ss is None or ss == (0, ): features['ss'] = tuple() elif ss is all: features['ss'] = tuple(range(1, 21)) elif isinstance(ss, (list, tuple)): badvals = [n for n in ss if not (numlike(n) and 0 < n < 21)] if badvals: badset = 'The `ss` argument only accepts integers in the range 1-20 (not %r)' % badvals raise DeviceError(badset) features['ss'] = tuple(int(n) for n in ss) else: badset = 'The `ss` argument must be an integer in the range 1-20 or a list of them (not %r)' % ss raise DeviceError(badset) elif k in _aat_features: # with all the other features, just check that the arg value is in the dict try: _aat_features[k][v] features[k] = int(v) if not callable(v) else v except KeyError: badstyle = 'Bad `%s` argumeent: %r' % (k, v) raise DeviceError(badstyle) return features
def aat_features(spec): """Validate features in a Font spec and normalize settings values""" features = {} for k,v in spec.items(): if k=='ss': # unpack & validate the ss arg (which might be a sequence of ints) ss = (int(v),) if numlike(v) or isinstance(v, bool) else v if ss is None or ss==(0,): features['ss'] = tuple() elif ss is all: features['ss'] = tuple(range(1,21)) else: try: if not all([numlike(val) and 0<val<21 for val in ss]): raise TypeError() features['ss'] = tuple(set(int(n) for n in ss)) except TypeError: badset = 'The `ss` argument must be an integer in the range 1-20 or a list of them (not %s)' % repr(ss) raise DeviceError(badset) elif k in aat_options: # with all the other features, just check that the arg value is in the dict try: aat_options[k][v] # crash if argname or val is invalid features[k] = int(v) if not callable(v) else v except KeyError: badstyle = 'Bad `%s` argumeent: %r'%(k,v) raise DeviceError(badstyle) return features
def __init__(self, *colors, **kwargs): if colors and isinstance(colors[0], Gradient): _copy_attrs(colors[0], self, ('_gradient', '_steps', '_colors', '_center', '_angle')) return # parse colors and assemble an NSGradient colors = [Color(c) for c in colors] if len(colors) == 1: colors.append(Color(None)) num_c = len(colors) steps = kwargs.get('steps', [i/(num_c-1.0) for i in range(num_c)]) num_s = len(steps) if num_s!=num_c or not all(numlike(n) and 0<=n<=1 for n in steps): wrongstep = 'Gradient steps must equal in length to the number of colors (and lie in the range 0-1)' raise DeviceError(wrongstep) center = kwargs.get('center', [0,0]) if len(center)!=2 or not all(numlike(n) and -1<=n<=1 for n in center): offsides = 'Gradient center must be a 2-tuple or Point using relative values ranging from -1 to 1' raise DeviceError(offsides) self._steps = steps self._colors = colors self._center = center self._outputmode = None self._gradient = None # radial if None, otherwise convert angle from context units to degrees self._angle = None if 'angle' in kwargs: self._angle = _ctx._angle(kwargs['angle'], 'degrees')
def _lazyload(self, path=None, data=None): # loads either a `path` or `data` kwarg and returns an NSImage # `path` should be the path of a valid image file # `data` should be the bytestring contents of an image file, or base64-encoded # with the characters "base64," prepended to it NSDataBase64DecodingIgnoreUnknownCharacters = 1 _cache = _ctx._imagecache if data is not None: # convert the str into an NSData (possibly decoding along the way) if isinstance(data, str) and data.startswith('base64,'): data = NSData.alloc().initWithBase64EncodedString_options_( data[7:], NSDataBase64DecodingIgnoreUnknownCharacters) elif not isinstance(data, NSData): data = NSData.dataWithBytes_length_(data, len(data)) key, mtime, err_info = data.hash(), None, type(data) # return a cached image if possible... if key in _cache: return _cache[key][0] # ...or load from the data image = NSImage.alloc().initWithData_(data) elif path is not None: if re.match(r'https?:', path): # load from url key = err_info = path resp = HTTP.get(path) mtime = last_modified(resp) # return a cached image if possible... if path in _cache and _cache[path][1] >= mtime: return _cache[path][0] # ...or load from the data bytes = resp.content data = NSData.dataWithBytes_length_(bytes, len(bytes)) image = NSImage.alloc().initWithData_(data) else: # load from file path try: path = NSString.stringByExpandingTildeInPath(path) mtime = os.path.getmtime(path) # return a cached image if possible... if path in _cache and _cache[path][1] >= mtime: return _cache[path][0] except: notfound = 'Image "%s" not found.' % path raise DeviceError(notfound) key = err_info = path # ...or load from the file image = NSImage.alloc().initWithContentsOfFile_(path) # if we wound up with a valid image, configure and cache the NSImage # before returning it if image is None: invalid = "Doesn't seem to contain image data: %r" % err_info raise DeviceError(invalid) image.setFlipped_(True) image.setCacheMode_(NSImageCacheNever) _cache[key] = (image, mtime) return _cache[key][0]
def validate(cls, kwargs): known = LineLayout._fields + ('lineheight', ) remaining = [arg for arg in kwargs.keys() if arg not in known] if remaining: unknown = "Unknown Layout argument%s: %s" % ( '' if len(remaining) == 1 else 's', ", ".join(remaining)) raise DeviceError(unknown)
def validate(cls, kwargs): known = StyleMixin.fontOpts + StyleMixin.aatOpts remaining = [arg for arg in kwargs.keys() if arg not in known] if remaining: unknown = "Unknown Font argument%s: %s" % ( '' if len(remaining) == 1 else 's', ", ".join(remaining)) raise DeviceError(unknown)
def find(cls, like=None, encoding="western"): all_fams = family_names() if like: all_fams = [ name for name in all_fams if sanitized(like) in sanitized(name) ] if encoding is all: return all_fams regions = {} for fam in all_fams: fnt = family_members(fam, names=True)[0] enc = font_encoding(fnt) regions[enc] = regions.get(enc, []) + [fam] try: return regions[encoding.title()] except: if like: nosuchzone = "Couldn't find any fonts matching %r with an encoding of %r" % ( like, encoding) else: nosuchzone = "Couldn't find any fonts with an encoding of %r, choose from: %r" % ( encoding, regions.keys()) raise DeviceError(nosuchzone)
def _expat_error(self, e): # correct the column and line-string for our wrapper element col = e.offset err = u"\n".join(e.args) line = self._xml.decode('utf-8').split("\n")[e.lineno-1] if line.startswith(HEAD): line = line[len(HEAD):] col -= len(HEAD) err = re.sub(ur'column \d+', 'column %i'%col, err) if line.endswith(TAIL): line = line[:-len(TAIL)] # move the column range with the typo into `measure` chars measure = 80 snippet = line if col>measure: snippet = snippet[col-measure:] col -= col-measure snippet = snippet[:max(col+12, measure-col)] col = min(col, len(snippet)) # show which ends of the line are truncated clipped = [snippet] if not line.endswith(snippet): clipped.append(u'...') if not line.startswith(snippet): clipped.insert(0, u'...') col+=3 caret = u' '*(col-1) + u'^' # raise the exception msg = u'Text: ' + err stack = u'stack: ' + u" ".join(['<%s>'%elt.tag for elt in self.stack[1:]]) + u' ...' xmlfail = u"\n".join([msg, u"".join(clipped), caret, stack]) raise DeviceError(xmlfail)
def line_metrics(spec): # start with kwarg values as the canonical settings _canon = ('size','align','leading','tracking','indent','margin','spacing','hyphenate') spec = {k:v for k,v in spec.items() if k in _canon} # validate alignment if spec.get('align','left') not in ('left','right','center','justify'): chaoticneutral = 'Text alignment must be LEFT, RIGHT, CENTER, or JUSTIFY' raise DeviceError(chaoticneutral) # floatify dimensions and hyphenation (mapping bools to 0/1) for attr in 'size', 'leading', 'tracking', 'indent', 'hyphenate': if attr in spec: spec[attr] = float(spec[attr]) for attr in 'margin', 'spacing': if attr in spec: a,b = 0,0 if isinstance(spec[attr], (list, tuple)): a, b = spec[attr] elif spec[attr] is not None: a = float(spec[attr]) spec[attr] = (a, b) # be backward compatible with the old arg names if 'lineheight' in spec: spec.setdefault('leading', spec['lineheight']) return spec
def best_fam(self, word): """Returns a valid family name if the arg fuzzy matches any existing families""" self.refresh() # first try for an exact match word = re.sub(r' +',' ',word.strip()) if word in self._fams: return word if word not in self._fuzzy: # do a case-insensitive, no-whitespace comparison q = sanitized(word) corpus = sanitized(self._fams) if q in corpus: self._fuzzy[word] = self._fams[corpus.index(q)] elif q: # if still no match, compare against a list of names with all the noise words taken out corpus = debranded(self._fams, keep=branding(word)) if word in corpus: self._fuzzy[word] = self._fams[corpus.index(word)] elif q in sanitized(corpus): # case-insensitive with the de-noised names self._fuzzy[word] = self._fams[sanitized(corpus).index(q)] if word not in self._fuzzy: # give up but first do a broad search and suggest other names in the exception in_corpus = difflib.get_close_matches(q, corpus, 4, cutoff=0) matches = [self._fams[corpus.index(m)] for m in in_corpus] nomatch = "ambiguous font family name \"%s\""%word if matches: nomatch += '.\nDid you mean: %s'%[m.encode('utf-8') for m in matches] self._fuzzy[word] = DeviceError(nomatch) return self._fuzzy[word]
def poly(self, x, y, radius, sides=4, points=None): # if `points` is defined, draw a regularized star, otherwise draw # a regular polygon with the given number of `sides`. if points > 4: inner = radius * cos(pi * 2 / points) / cos(pi / points) return self.star(x, y, points, inner, radius) elif points is not None: # for 3-4 `points`, treat stars like a sided n-gon sides = points # special-case radius interpretation for squares if sides == 4: # draw with sides of 2*r (this seems less surprising than drawing a # square *inside* a circle at the given radius and winding up with # sides that are weird fractions of the radius) radius *= sqrt(2.0) elif sides < 3: badpoly = 'polygons must have at least 3 points' raise DeviceError(badpoly) # rotate the origin slightly so the polygon sits on an edge theta = pi / 2 + (0 if sides % 2 else 1 * pi / sides) angles = [2 * pi * i / sides - theta for i in xrange(sides)] # walk around the circle adding points with proper scale/origin points = [[radius * cos(theta) + x, radius * sin(theta) + y] for theta in angles] self._nsBezierPath.moveToPoint_(points[0]) for pt in points[1:]: self._nsBezierPath.lineToPoint_(pt) self._nsBezierPath.closePath() self._fulcrum = Point(x, y)
def __init__(self, path=None, **kwargs): super(Bezier, self).__init__(**kwargs) self._segment_cache = {} # used by pathmatics self._fulcrum = None # centerpoint (set only for center-based primitives) # path arg might contain a list of point tuples, a bezier to copy, or a raw # nsbezier reference to use as the backing store. otherwise start with a # fresh path with no points if path is None: self._nsBezierPath = NSBezierPath.bezierPath() elif isinstance(path, (list, tuple)): if isinstance(path[0], Curve): self._nsBezierPath = NSBezierPath.bezierPath() self.extend(path) else: p = pathmatics.findpath(path, 1.0 if kwargs.get('smooth') else 0.0) self._nsBezierPath = p._nsBezierPath elif isinstance(path, Bezier): _copy_attrs(path, self, Bezier.stateAttrs) elif isinstance(path, NSBezierPath): self._nsBezierPath = path.copy() else: badpath = "Don't know what to do with %s." % path raise DeviceError(badpath) # decide what needs to be done at the end of the `with` context self._needs_closure = kwargs.get('close', False)
def arrow(self, x, y, width=100, type=NORMAL): if type not in (NORMAL, FORTYFIVE): badtype = "available types for arrow() are NORMAL and FORTYFIVE" raise DeviceError(badtype) if type == NORMAL: head = width * .4 tail = width * .2 self.moveto(x, y) self.lineto(x - head, y + head) self.lineto(x - head, y + tail) self.lineto(x - width, y + tail) self.lineto(x - width, y - tail) self.lineto(x - head, y - tail) self.lineto(x - head, y - head) self.lineto(x, y) self.closepath() elif type == FORTYFIVE: head = .3 tail = 1 + head self.moveto(x, y) self.lineto(x, y + width * (1 - head)) self.lineto(x - width * head, y + width) self.lineto(x - width * head, y + width * tail * .4) self.lineto(x - width * tail * .6, y + width) self.lineto(x - width, y + width * tail * .6) self.lineto(x - width * tail * .4, y + width * head) self.lineto(x - width, y + width * head) self.lineto(x - width * (1 - head), y) self.lineto(x, y) self._fulcrum = Point(x, y)
def __init__(self, *args, **kwargs): """ Positional parameters: - src: the path to an image file, an existing Image object, or the `canvas` global - x & y: position of top-left corner - width & height: limit either or both dimensions to a maximum size If a width and height are both given, the narrower dimension is used If both are omitted default to full-size Optional keyword parameters: - data: a stream of bytes of image data. If the data begins with the characters "base64," the remainder of the stream will be decoded before loading - alpha: the image opacity (0-1.0) - blend: a blend mode name Example usage: x,y, w,h = 10,10, 200,200 Image("foo.png", x, y, w, h) Image(<Image object>, x, y, height=h) Image(x, y, data='<raw bytes from an image file>') Image(x, y, data='base64,<b64-encoded bytes>') Image(canvas, x, y) """ # look for a path or Image as the first arg, or a `data` kwarg (plus `image` for compat) args = list(args) data = kwargs.get('data', None) src = kwargs.get('path', kwargs.get('image', None)) if args and not (src or data): src = args.pop(0) # use first arg if image wasn't in kwargs elif args and args[0] is None: args.pop(0) # make image(None, 10,20, image=...) work properly for compat # get an NSImage reference (once way or another) if data: self._nsImage = self._lazyload(data=data) elif src: if isinstance(src, NSImage): self._nsImage = src.copy() self._nsImage.setFlipped_(True) elif hasattr(src, '_nsImage'): self._nsImage = src._nsImage elif isinstance(src, basestring): self._nsImage = self._lazyload(path=src) else: invalid = "Not a valid image source: %r" % type(src) raise DeviceError(invalid) # incorporate positional bounds args for attr, val in zip(['x','y','width','height'], args): kwargs.setdefault(attr, val) # incorporate existing bounds when working with an Image if isinstance(src, Image): for attr in ['x','y','width','height']: kwargs.setdefault(attr, getattr(src, attr)) # let the mixins handle bounds & effects super(Image, self).__init__(**kwargs)
def _expat_error(self, e, line): measure = 80 col = e.offset start, end = len('<%s>' % DEFAULT), -len('</%s>' % DEFAULT) line = line[start:end] col -= start # move the column range with the typo into `measure` chars snippet = line if col > measure: snippet = snippet[col - measure:] col -= col - measure snippet = snippet[:max(col + 12, measure - col)] col = min(col, len(snippet)) # show which ends of the line are truncated clipped = [snippet] if not line.endswith(snippet): clipped.append('...') if not line.startswith(snippet): clipped.insert(0, '...') col += 3 caret = ' ' * col + '^' # raise the exception msg = 'Text: ' + "\n".join(e.args) stack = 'stack: ' + " ".join(['<%s>' % tag for tag in self.stack[1:]]) + ' ...' xmlfail = "\n".join([msg, "".join(clipped), caret, stack]) raise DeviceError(xmlfail)
def find(self, regex, matches=0): """Find all matching portions of the text string using regular expressions Syntax: txt.find(re.compile(r'...', re.I)) # match a regex object txt.find(r'antidisest.*?ism') # match a pattern string txt.find(r'foo (.*?) baz') # match the pattern and capture a sub-group txt.find(r'the', 10) # find the first 10 occurrences of `the' Args: `regex` can be a pattern string or a regex object. Pattern strings without any uppercase characters will be case-insensitively matched. Patterns with mixed case will be case-sensitive. In addition, the re.DOTALL flag will be passed by default (meaning r'.' will match any character, including newlines). Compiled regexes can define their own flags. `matches` optionally set the maximum number of results to be returned. If omitted, find() will return a TextMatch object for every match that's visible in one of the Text object's TextFrames. Matches that lie in the overflow beyond the Text's bounds can be included however: pass the `all` keyword as the `matches` arg. Returns: a list of TextMatch objects """ if isinstance(regex, str): regex = regex.decode('utf-8') if isinstance(regex, unicode): flags = (re.I|re.S) if regex.lower()==regex else (re.S) regex = re.compile(regex, flags) if not hasattr(regex, 'pattern'): nonregex = "Text.find() must be called with an re.compile'd pattern object or a regular expression string" raise DeviceError(nonregex) return self._seek(regex.finditer(self.text), matches)
def page(self): """Clears the canvas, runs the code in the `with` block, then adds the canvas as a new pdf page. For example, to create a pdf with two pages, you could write: with export("multipage.pdf") as pdf: clear(all) ... # draw first page pdf.add() clear(all) ... # draw the next page pdf.add() With the `page` context manager it simplifies to: with export("multipage.pdf") as pdf: with pdf.page: ... # draw first page with pdf.page: ... # draw the next page """ if self.format != 'pdf': badform = 'The `page` property can only be used in PDF exports (not %r)'%self.format raise DeviceError(badform) self.opts['single'] = True return self.frame
def __init__(self, path=None, immediate=False, **kwargs): super(Bezier, self).__init__(**kwargs) self._segment_cache = {} # used by pathmatics self._fulcrum = None # centerpoint (set only for primitives) # path arg might contain a list of point tuples, a bezier to copy, or a raw # nsbezier reference to use as the backing store. otherwise start with a # fresh path with no points if path is None: self._nsBezierPath = NSBezierPath.bezierPath() elif isinstance(path, (list, tuple)): if isinstance(path[0], Curve): self._nsBezierPath = NSBezierPath.bezierPath() self.extend(path) else: p = pathmatics.findpath(path, 1.0 if kwargs.get('smooth') else 0.0) self._nsBezierPath = p._nsBezierPath elif isinstance(path, Bezier): self.inherit(path) elif isinstance(path, NSBezierPath): self._nsBezierPath = path else: badpath = "Don't know what to do with %s." % path raise DeviceError(badpath) # decide what needs to be done at the end of the `with` context self._autoclose = kwargs.get('close', False) self._autodraw = kwargs.get('draw', False) # finish the path (and potentially draw it) immediately if flagged to do so. # in practice, `immediate` is only passed when invoked by the `bezier()` command # with a preexisting point-set or bezier `path` argument. if immediate and kwargs.get('draw', True): self._autofinish()
def font_axes(*args, **kwargs): # start with kwarg values as the canonical settings _canon = ('family', 'size', 'weight', 'italic', 'width', 'variant', 'fontsize') spec = { k: v.decode('utf-8') if isinstance(v, char_type) else v for k, v in kwargs.items() if k in _canon } basis = kwargs.get('face', kwargs.get('fontname')) # be backward compatible with the old arg names if 'fontsize' in spec: spec.setdefault('size', spec.pop('fontsize')) # validate the nsfont-specific spec valuce if spec: if 'italic' in spec: spec['italic'] = bool(spec['italic']) if 'family' in spec: spec['family'] = family_name(spec['family']) # validate the weight and width args (if any) if 'weight' in spec and not weighty(spec['weight']): print 'Font: unknown weight "%s"' % spec.pop('weight') if spec.get('width') is not None and not widthy(spec['width']): print 'Font: unknown width "%s"' % spec.pop('width') # look for a postscript name passed as `face` or `fontname` and validate it if basis and not font_exists(basis): notfound = 'Font: no matches for Postscript name "%s"' % basis raise DeviceError(notfound) elif basis: # if the psname exists, inherit its attributes face = font_face(basis) for axis in ('family', 'weight', 'width', 'variant', 'italic'): spec.setdefault(axis, getattr(face, axis)) # search the positional args for either name/size or a Font object # we want the kwargs to have higher priority, so setdefault everywhere... for item in args: if hasattr(item, '_spec'): # existing Font object for k, v in item._spec.items(): spec.setdefault(k, v) elif isinstance(item, basestring): # name-like values item = item.decode('utf-8') if isinstance(item, char_type) else item if fammy(item): spec.setdefault('family', family_name(item)) elif widthy(item): spec.setdefault('width', item) elif weighty(item): spec.setdefault('weight', item) else: family_name(item) # raise an exception suggesting family names elif numlike(item) and 'size' not in kwargs: spec['size'] = float(item) return spec
def __setitem__(self, key, val): if val is None: del self[key] elif hasattr(val, 'items'): self.style(key, **val) else: badtype = 'Stylesheet: when directly assigning styles, pass them as dictionaries (not %s)'%type(val) raise DeviceError(badtype)
def __init__(self, famname=None, of=None): if of: famname = font_family(of) elif not famname: badarg = 'Family: requires either a name or a Font object' % famname raise DeviceError(badarg) q = famname.strip().lower().replace(' ', '') matches = [ fam for fam in family_names() if q == fam.lower().replace(' ', '') ] if not matches: notfound = 'Unknown font family "%s"' % famname raise DeviceError(notfound) self._name = matches[0] faces = family_members(self._name) self.encoding = font_encoding(faces[0].psname) self._faces = odict((f.psname, f) for f in faces) fam = {"weights": [], "widths": [], "variants": []} has_italic = False for f in sorted(faces, key=attrgetter('wgt')): for axis in ('weights', 'variants'): old, new = fam[axis], getattr(f, axis[:-1]) if new not in old: fam[axis].append(new) # has_italic = has_italic or 'italic' in f.traits has_italic = has_italic or f.italic self.has_italic = has_italic for f in sorted(faces, key=attrgetter('wid')): if f.width in fam['widths']: continue fam['widths'].append(f.width) for axis, vals in fam.items(): if axis in ('widths', 'variants') and any(vals) and None in vals: if axis == 'widths': pass # for widths, the default should be preserved in sort order else: # for variants, default should be first fam[axis] = [None] + filter(None, vals) else: fam[axis] = filter(None, vals) # otherwise wipe out the sole None setattr(self, axis, tuple(fam[axis]))
def validate(cls, kwargs): """Sanity check a potential set of constructor kwargs""" known = getattr(cls, '_opts', cls.opts) remaining = [arg for arg in kwargs.keys() if arg not in known] if remaining: unknown = "Unknown %s argument%s '%s'" % (cls.__name__, '' if len( remaining) == 1 else 's', ", ".join(remaining)) raise DeviceError(unknown)
def font_face(psname): """Return a Face tuple given a psname""" fam = LIBRARY.parent_fam(psname) for face in LIBRARY.list_fam(fam): if face.psname == psname: return face notfound = 'Font: no matches for Postscript name "%s"'%basis raise DeviceError(notfound)
def fontspec(*args, **kwargs): # convert any bytestrings to unicode (presuming utf8 everywhere) for k, v in kwargs.items(): if isinstance(v, str): kwargs[k] = v.decode('utf-8') args = [v.decode('utf-8') if isinstance(v, str) else v for v in args] # start with kwarg values as the canonical settings _canon = ('family', 'size', 'weight', 'italic', 'width', 'variant') spec = {k: v for k, v in kwargs.items() if k in _canon} # be backward compatible with the old arg names if 'fontsize' in kwargs: spec.setdefault('size', kwargs['fontsize']) if 'italic' in spec: spec['italic'] = bool(spec['italic']) if 'family' in spec: spec['family'] = family_name(spec['family']) # validate the weight and width args (if any) if not weighty(spec.get('weight', 'regular')): print 'Font: unknown weight "%s"' % spec.pop('weight') if not widthy(spec.get('width', 'condensed')): print 'Font: unknown width "%s"' % spec.pop('width') # look for a postscript name passed as `face` or `fontname` and validate it basis = kwargs.get('face', kwargs.get('fontname')) if basis and not font_exists(basis): notfound = 'Font: no matches for Postscript name "%s"' % basis raise DeviceError(notfound) elif basis: # if the psname exists, inherit its attributes face = font_face(basis) for axis in ('family', 'weight', 'width', 'variant', 'italic'): spec.setdefault(axis, getattr(face, axis)) # search the positional args for either name/size or a Font object # we want the kwargs to have higher priority, so setdefault everywhere... for item in args: if hasattr(item, '_spec'): for k, v in item._spec.items(): spec.setdefault(k, v) elif isinstance(item, unicode): if fammy(item): spec.setdefault('family', family_name(item)) elif widthy(item): spec.setdefault('width', item) elif weighty(item): spec.setdefault('weight', item) else: print 'Font: unrecognized weight or family name "%s"' % item elif numlike(item) and 'size' not in kwargs: spec['size'] = item # incorporate any typesetting features spec.update(aat_features(kwargs)) return spec
def _validate(self, eff, val): if val is None: pass elif eff=='alpha' and not (numlike(val) and 0<=val<=1): badalpha = 'alpha() value must be a number between 0 and 1.0' raise DeviceError(badalpha) elif eff=='blend': val = re.sub(r'[_\- ]','', val).lower() if val not in _BLEND: badblend = '"%s" is not a recognized blend mode.\nUse one of:\n%s'%(val, BLEND_MODES) raise DeviceError(badblend) elif eff=='shadow': if isinstance(val, Shadow): val = val.copy() else: val = Shadow(*val) return val
def apply(self, point_or_path): from .bezier import Bezier if isinstance(point_or_path, Bezier): return self.transformBezier(point_or_path) elif isinstance(point_or_path, Point): return self.transformPoint(point_or_path) else: wrongtype = "Can only transform Beziers or Points" raise DeviceError(wrongtype)
def points(self, amount=100): if len(self) == 0: empty = "The given path is empty" raise DeviceError(empty) count = int(amount) # make sure we don't choke on a float delta = 1.0 / max( 1, count - 1) # div by count-1 so the last point is at t=1.0 for i in xrange(count): yield pathmatics.point(self, delta * i)
def transformBezier(self, path): from .bezier import Bezier if isinstance(path, Bezier): path = path.copy() else: wrongtype = "Can only transform Beziers" raise DeviceError(wrongtype) path._nsBezierPath = self._nsAffineTransform.transformBezierPath_( path._nsBezierPath) return path
def _parse(cls, clrstr): """Returns an r/g/b/a tuple based on a css color name or a hex string of the form: RRGGBBAA, RRGGBB, RGBA, or RGB (with or without a leading #) """ if clrstr in _CSS_COLORS: # handle css color names clrstr = _CSS_COLORS[clrstr] if re.search(r'#?[0-9a-f]{3,8}', clrstr): # rgb & rgba hex strings hexclr = clrstr.lstrip('#') if len(hexclr) in (3,4): hexclr = "".join(map("".join, zip(hexclr,hexclr))) if len(hexclr) not in (6,8): invalid = "Don't know how to interpret hex color '#%s'." % hexclr raise DeviceError(invalid) r, g, b = [int(n, 16)/255.0 for n in (hexclr[0:2], hexclr[2:4], hexclr[4:6])] a = 1.0 if len(hexclr)!=8 else int(hexclr[6:], 16)/255.0 else: invalid = "Color strings must be 3/6/8-character hex codes or valid css-names (not %r)" % clrstr raise DeviceError(invalid) return r, g, b, a
def rect(self, x, y, width, height, radius=None): if radius is None: self._nsBezierPath.appendBezierPathWithRect_( ((x, y), (width, height))) else: if numlike(radius): radius = (radius, radius) elif not isinstance(radius, (list, tuple)) or len(radius) != 2: badradius = 'the radius for a rect must be either a number or an (x,y) tuple' raise DeviceError(badradius) self._nsBezierPath.appendBezierPathWithRoundedRect_xRadius_yRadius_( ((x, y), (width, height)), *radius)