def __init__(self, gradientType=None, start=None, end=None, colors=None, positions=None, startRadius=None, endRadius=None): if gradientType is None: return if gradientType not in ("linear", "radial"): raise PageBotError( "Gradient type must be either 'linear' or 'radial'") if not colors or len(colors) < 2: raise PageBotError("Gradient needs at least 2 colors") if positions is None: positions = [ i / float(len(colors) - 1) for i in range(len(colors)) ] if len(colors) != len(positions): raise PageBotError( "Gradient needs a correct position for each color") self.gradientType = gradientType self.colors = self._colorClass.getColorsFromList(colors) self.cmykColors = None self.positions = positions self.start = start self.end = end self.startRadius = startRadius self.endRadius = endRadius
def fontVariations(self, *args, **axes): """Picks a variation by axes values and return the current font variations settings. If no arguments are given `fontVariations()` will just return the current font variations settings.""" if args and axes: raise PageBotError("Can't combine positional arguments and keyword arguments") if args: if len(args) != 1: raise PageBotError("There can only be one positional argument") if args[0] is not None: raise PageBotError("First positional argument can only be None") logger.warning("fontVariations(None) is deprecated, use fontVariations(resetVariations=True) instead.") self._fontVariations.clear() else: if axes.pop("resetVariations", False): self._fontVariations.clear() self._fontVariations.update(axes) defaultVariations = self.listFontVariations() currentVariation = {axis: data["defaultValue"] for axis, data in defaultVariations.items()} currentVariation.update(self._fontVariations) return currentVariation
def _fontNameForPath(self, path): from fontTools.ttLib import TTFont, TTLibError try: font = TTFont(path, fontNumber=0) # in case of .ttc, use the first font psName = font["name"].getName(6, 1, 0) if psName is None: psName = font["name"].getName(6, 3, 1) font.close() except IOError: raise PageBotError("Font '%s' does not exist." % path) except TTLibError: raise PageBotError("Font '%s' is not a valid font." % path) if psName is not None: psName = psName.toUnicode() return psName
def getPath(self): graphic = self.getGraphic() if graphic.path is None: raise PageBotError("Create a new path first") return graphic.path
def colorSpace(self, colorSpace): if colorSpace is None: colorSpace = 'genericRGB' if colorSpace not in self._colorSpaceMap: raise PageBotError("'%s' is not a valid colorSpace, argument must be '%s'" % (colorSpace, "', '".join(self._colorSpaceMap.keys()))) colorSpace = self._colorSpaceMap[colorSpace] self._state.setColorSpace(colorSpace)
def getColor(cls, color): if isinstance(color, cls.__class__): return color elif isinstance(color, (tuple, list)): return cls(*color) elif isinstance(color, AppKit.NSColor): return cls(color) raise PageBotError("Not a valid color: %s" % color)
def _contoursForBooleanOperations(self): # contours are temporary objects # redirect drawToPointPen to drawPoints contours = self.contours for contour in contours: contour.drawPoints = contour.drawToPointPen if contour.open: raise PageBotError("open contours are not supported during boolean operations") return contours
def textBox(self, txt, box, font=_FALLBACKFONT, fontSize=10, align=None, hyphenation=None): """Draws a `txt` with a `font` and `fontSize` in a `box` in the Bézier path. If a font path is given the font will be installed and used directly. - Optionally an alignment can be set. - Possible `align` values are: `"left"`, `"center"` and `"right"`. - The default alignment is `left`. - Optionally `hyphenation` can be provided. - Optionally `txt` can be a `FormattedString`. - Optionally `box` can be a `BezierPath`. """ if align and align not in self._textAlignMap.keys(): raise PageBotError("align must be %s" % (", ".join(self._textAlignMap.keys()))) context = BaseContext() context.font(font, fontSize) context.hyphenation(hyphenation) path, (x, y) = context._getPathForFrameSetter(box) attributedString = context.attributedString(txt, align) setter = CoreText.CTFramesetterCreateWithAttributedString( attributedString) frame = CoreText.CTFramesetterCreateFrame(setter, (0, 0), path, None) ctLines = CoreText.CTFrameGetLines(frame) origins = CoreText.CTFrameGetLineOrigins(frame, (0, len(ctLines)), None) for i, (originX, originY) in enumerate(origins): ctLine = ctLines[i] ctRuns = CoreText.CTLineGetGlyphRuns(ctLine) for ctRun in ctRuns: attributes = CoreText.CTRunGetAttributes(ctRun) font = attributes.get(AppKit.NSFontAttributeName) baselineShift = attributes.get( AppKit.NSBaselineOffsetAttributeName, 0) glyphCount = CoreText.CTRunGetGlyphCount(ctRun) for i in range(glyphCount): glyph = CoreText.CTRunGetGlyphs(ctRun, (i, 1), None)[0] ax, ay = CoreText.CTRunGetPositions(ctRun, (i, 1), None)[0] if glyph: self._path.moveToPoint_( (x + originX + ax, y + originY + ay + baselineShift)) self._path.appendBezierPathWithGlyph_inFont_( glyph, font) self.optimizePath() return context.clippedText(txt, box, align)
def newPage(self, width=None, height=None): if self.width is None: if width is None: raise PageBotError("A page must have a width") self.width = width if self.height is None: if height is None: raise PageBotError("A page must have a height") self.height = height self.hasPage = True self.page = Canvas((0, 0, -0, -0), delegate=self, canvasSize=(self.width, self.height), acceptsMouseMoved=True, hasHorizontalScroller=True, hasVerticalScroller=True, autohidesScrollers=False, backgroundColor=None, drawsBackground=True, flipped=True) self._newPage(width, height) return self.page
def openTypeFeatures(self, *args, **features): """Enables OpenType features and return the current openType features settings. If no arguments are given `openTypeFeatures()` will just return the current openType features settings. .. downloadcode:: openTypeFeaturesFormattedString.py size(1000, 200) # create an empty formatted string object t = FormattedString() # set a font t.font("ACaslonPro-Regular") # set a font size t.fontSize(60) # add some text t += "0123456789 Hello" # enable some open type features t.openTypeFeatures(smcp=True, lnum=True) # add some text t += " 0123456789 Hello" # draw the formatted string text(t, (10, 80)) """ if args and features: raise PageBotError("Can't combine positional arguments and keyword arguments") if args: if len(args) != 1: raise PageBotError("There can only be one positional argument") if args[0] is not None: raise PageBotError("First positional argument can only be None") logger.warning("openTypeFeatures(None) is deprecated, use openTypeFeatures(resetFeatures=True) instead.") self._openTypeFeatures.clear() else: if features.pop("resetFeatures", False): self._openTypeFeatures.clear() self._openTypeFeatures.update(features) currentFeatures = self.listOpenTypeFeatures() currentFeatures.update(self._openTypeFeatures) return currentFeatures
def fallbackFont(self, font): """Sets a fallback font, used whenever a glyph is not available in the normal font. If a font path is given the font will be installed and used directly.""" if font: font = _tryInstallFontFromFontName(font) font = str(font) testFont = AppKit.NSFont.fontWithName_size_(font, self._fontSize) if testFont is None: raise PageBotError("Fallback font '%s' is not available" % font) self._fallbackFont = font return font
def text(self, txt, offset=None, font=_FALLBACKFONT, fontSize=10, align=None): """Draws a `txt` with a `font` and `fontSize` at an `offset` in the Bézier path. If a font path is given the font will be installed and used directly. - Optionally an alignment can be set. - Possible `align` values are: `"left"`, `"center"` and `"right"`. - The default alignment is `left`. - Optionally `txt` can be a `FormattedString`. """ context = BaseContext() if align and align not in self._textAlignMap.keys(): raise PageBotError("align must be %s" % (", ".join(self._textAlignMap.keys()))) context.font(font, fontSize) attributedString = context.attributedString(txt, align) w, h = attributedString.size() w *= 2 if offset: x, y = offset else: x = y = 0 if align == "right": x -= w elif align == "center": x -= w * .5 setter = CoreText.CTFramesetterCreateWithAttributedString( attributedString) path = Quartz.CGPathCreateMutable() Quartz.CGPathAddRect(path, None, Quartz.CGRectMake(x, y, w, h * 2)) frame = CoreText.CTFramesetterCreateFrame(setter, (0, 0), path, None) ctLines = CoreText.CTFrameGetLines(frame) origins = CoreText.CTFrameGetLineOrigins(frame, (0, len(ctLines)), None) if origins: y -= origins[0][1] self.textBox(txt, box=(x, y, w, h * 2), font=font, fontSize=fontSize, align=align)
def addPoint(self, point, segmentType=None, smooth=False, name=None, identifier=None, **kwargs): """Use the path as a point pen and add a point to the current subpath. `beginPath` must have been called prior to adding points with `addPoint` calls.""" if not hasattr(self, "_pointToSegmentPen"): msg = "path.beginPath() must be called before the path can be used as a point pen." raise PageBotError(msg) self._pointToSegmentPen.addPoint( point, segmentType=segmentType, smooth=smooth, name=name, identifier=identifier, **kwargs )
def endPath(self): """Ends the current subpath. Calling this method has two distinct meanings depending on the context: When the Bézier path is used as a segment pen (using `moveTo`, `lineTo`, etc.), the current subpath will be finished as an open contour. When the Bézier path is used as a point pen (using `beginPath`, `addPoint` and `endPath`), the path will process all the points added with `addPoint`, finishing the current subpath.""" if hasattr(self, "_pointToSegmentPen"): pointToSegmentPen = self._pointToSegmentPen del self._pointToSegmentPen pointToSegmentPen.endPath() else: msg = "path.beginPath() must be called before the path can be used as a point pen." raise PageBotError(msg)
def restore(self): if not self._stack: raise PageBotError("can't restore graphics state: no matching save()") self._state = self._stack.pop() #self._state.update(self) self._restore()
def saveImage(self, path, options): if not self.hasPage: raise PageBotError("can't save image when no page is set") self._saveImage(path, options)
def lineCap(self, cap): if cap is None: self._state.lineCap = None if cap not in self._lineCapStylesMap: raise PageBotError("lineCap() argument must be 'butt', 'square' or 'round'") self._state.lineCap = self._lineCapStylesMap[cap]
def lineJoin(self, join): if join is None: self._state.lineJoin = None if join not in self._lineJoinStylesMap: raise PageBotError("lineJoin() argument must be 'bevel', 'miter' or 'round'") self._state.lineJoin = self._lineJoinStylesMap[join]