コード例 #1
0
ファイル: foundry.py プロジェクト: x-raizor/plotdevice
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
コード例 #2
0
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
コード例 #3
0
    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')
コード例 #4
0
ファイル: image.py プロジェクト: vasily-kartashov/plotdevice
    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]
コード例 #5
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)
コード例 #6
0
 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)
コード例 #7
0
    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)
コード例 #8
0
    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)
コード例 #9
0
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
コード例 #10
0
    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]
コード例 #11
0
ファイル: bezier.py プロジェクト: poke1024/plotdevice
    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)
コード例 #12
0
ファイル: bezier.py プロジェクト: poke1024/plotdevice
    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)
コード例 #13
0
ファイル: bezier.py プロジェクト: poke1024/plotdevice
    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)
コード例 #14
0
ファイル: image.py プロジェクト: x-raizor/plotdevice
    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)
コード例 #15
0
    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)
コード例 #16
0
ファイル: text.py プロジェクト: poke1024/plotdevice
    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)
コード例 #17
0
ファイル: image.py プロジェクト: x-raizor/plotdevice
    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
コード例 #18
0
    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()
コード例 #19
0
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
コード例 #20
0
 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)
コード例 #21
0
ファイル: typography.py プロジェクト: x-raizor/plotdevice
    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]))
コード例 #22
0
ファイル: atoms.py プロジェクト: vasily-kartashov/plotdevice
 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)
コード例 #23
0
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)
コード例 #24
0
ファイル: foundry.py プロジェクト: x-raizor/plotdevice
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
コード例 #25
0
    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
コード例 #26
0
 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)
コード例 #27
0
ファイル: bezier.py プロジェクト: poke1024/plotdevice
    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)
コード例 #28
0
 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
コード例 #29
0
    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
コード例 #30
0
ファイル: bezier.py プロジェクト: poke1024/plotdevice
 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)