def interpolate(self, factor, minLayer, maxLayer, round=True, suppressError=True): """ Interpolate all possible data in the layer. :: >>> layer.interpolate(0.5, otherLayer1, otherLayer2) >>> layer.interpolate((0.5, 2.0), otherLayer1, otherLayer2, round=False) The interpolation occurs on a 0 to 1.0 range where **minLayer** is located at 0 and **maxLayer** is located at 1.0. **factor** is the interpolation value. It may be less than 0 and greater than 1.0. It may be a :ref:`type-int-float` or a tuple of two :ref:`type-int-float`. If it is a tuple, the first number indicates the x factor and the second number indicates the y factor. **round** indicates if the result should be rounded to integers. **suppressError** indicates if incompatible data should be ignored or if an error should be raised when such incompatibilities are found. """ factor = normalizers.normalizeInterpolationFactor(factor) if not isinstance(minLayer, BaseLayer): raise FontPartsError("Interpolation to an instance of %r can not be performed from an instance of %r." % (self.__class__.__name__, minLayer.__class__.__name__)) if not isinstance(maxLayer, BaseLayer): raise FontPartsError("Interpolation to an instance of %r can not be performed from an instance of %r." % (self.__class__.__name__, maxLayer.__class__.__name__)) round = normalizers.normalizeBoolean(round) suppressError = normalizers.normalizeBoolean(suppressError) self._interpolate(factor, minLayer, maxLayer, round=round, suppressError=suppressError)
def interpolate(self, factor, minInfo, maxInfo, round=True, suppressError=True): """ Interpolate all pairs between minInfo and maxInfo. The interpolation occurs on a 0 to 1.0 range where minInfo is located at 0 and maxInfo is located at 1.0. factor is the interpolation value. It may be less than 0 and greater than 1.0. It may be a number (integer, float) or a tuple of two numbers. If it is a tuple, the first number indicates the x factor and the second number indicates the y factor. round indicates if the result should be rounded to integers. suppressError indicates if incompatible data should be ignored or if an error should be raised when such incompatibilities are found. """ factor = normalizers.normalizeInterpolationFactor(factor) if not isinstance(minInfo, BaseInfo): raise FontPartsError("Interpolation to an instance of %r can not be performed from an instance of %r." % (self.__class__.__name__, minInfo.__class__.__name__)) if not isinstance(maxInfo, BaseInfo): raise FontPartsError("Interpolation to an instance of %r can not be performed from an instance of %r." % (self.__class__.__name__, maxInfo.__class__.__name__)) round = normalizers.normalizeBoolean(round) suppressError = normalizers.normalizeBoolean(suppressError) self._interpolate(factor, minInfo, maxInfo, round=round, suppressError=suppressError)
def _set_bcpOut(self, value): """ Subclasses may override this method. """ x, y = absoluteBCPOut(self.anchor, value) segment = self._segment nextSegment = self._nextSegment if nextSegment.type == "move" and value != (0, 0): raise FontPartsError(("Cannot set the bcpOut for the last " "point in an open contour.")) else: offCurves = nextSegment.offCurve if offCurves: # if the off curves are located at the anchor coordinates # we can switch to a "line" segment type if value == (0, 0) and self.bcpIn == (0, 0): segment.type = "line" segment.smooth = False else: offCurves[0].x = x offCurves[0].y = y elif value != (0, 0): nextSegment.type = "curve" offCurves = nextSegment.offCurve offCurves[0].x = x offCurves[0].y = y
def _set_type(self, newType): """ Subclasses may override this method. """ oldType = self.type if oldType == newType: return contour = self.contour if contour is None: raise FontPartsError("The segment does not belong to a contour.") # converting line <-> move if newType in ("move", "line") and oldType in ("move", "line"): pass # converting to a move or line elif newType not in ("curve", "qcurve"): offCurves = self.offCurve for point in offCurves: contour.removePoint(point) # converting a line/move to a curve/qcurve else: segments = contour.segments i = segments.index(self) prev = segments[i - 1].onCurve on = self.onCurve x = on.x y = on.y points = contour.points i = points.index(on) contour.insertPoint(i, (x, y), "offcurve") off2 = contour.points[i] contour.insertPoint(i, (prev.x, prev.y), "offcurve") off1 = contour.points[i] del self._points self._setPoints((off1, off2, on)) self.onCurve.type = newType
def _set_bcpIn(self, value): """ Subclasses may override this method. """ x, y = absoluteBCPIn(self.anchor, value) segment = self._segment if segment.type == "move" and value != (0, 0): raise FontPartsError( "Cannot set the bcpIn for the first point in an open contour.") else: offCurves = segment.offCurve if offCurves: # if the two off curves are located at the anchor # coordinates we can switch to a line segment type. if value == (0, 0) and self.bcpOut == (0, 0): segment.type = "line" segment.smooth = False else: offCurves[-1].x = x offCurves[-1].y = y elif value != (0, 0): segment.type = "curve" offCurves = segment.offCurve offCurves[-1].x = x offCurves[-1].y = y
def _getitem__points(self, index): index = normalizers.normalizeIndex(index) if index >= self._len__points(): raise FontPartsError("No point located at index %d." % index) point = self._getPoint(index) self._setContourInPoint(point) return point
def _loadFromGLIF(self, glifData): try: readGlyphFromString(aString=glifData, glyphObject=self.naked(), pointPen=self.getPointPen()) except GlifLibError: raise FontPartsError("Not valid glif data")
def _getitem__guidelines(self, index): index = normalizers.normalizeGuidelineIndex(index) if index >= self._len__guidelines(): raise FontPartsError("No guideline located at index %d." % index) guideline = self._getGuideline(index) self._setFontInGuideline(guideline) return guideline
def generate(self, format, path=None, **kwargs): """ Generate the font to another format. >>> font.generate("otfcff") >>> font.generate("otfcff", "/path/to/my/font.otf") **format** defines the file format to output. These are the standard format identifiers: %s Environments are not required to support all of these and environments may define their own format types. **path** defines the location where the new file should be created. If a file already exists at that location, it will be overwritten by the new file. If **path** defines a directory, the file will be output as the current file name, with the appropriate suffix for the format, into the given directory. If no **path** is given, the file will be output into the same directory as the source font with the file named with the current file name, with the appropriate suffix for the format. """ if format is None: raise FontPartsError("The format must be defined when generating.") elif not isinstance(format, basestring): raise FontPartsError("The format must be defined as a string.") ext = self.generateFormatToExtension(format, "." + format) if path is None and self.path is None: raise FontPartsError( "The file cannot be generated because an output path was not defined." ) elif path is None: path = os.path.splitext(self.path)[0] path += ext elif os.path.isdir(path): if self.path is None: raise FontPartsError( "The file cannot be generated because the file does not have a path." ) fileName = os.path.basename(self.path) fileName += ext path = os.path.join(path, fileName) path = normalizers.normalizeFilePath(path) return self._generate(format=format, path=path, **kwargs)
def _set_base_name(self, value): if value == self.name: return value = normalizers.normalizeLayerName(value) existing = self.font.layerOrder if value in existing: raise FontPartsError("A layer with the name '%s' already exists." % value) self._set_name(value)
def decompose(self): """ Decompose the component. """ glyph = self.glyph if glyph is None: raise FontPartsError("The component does not belong to a glyph.") self._decompose()
def isCompatible(self, other, cls): """ Evaluate interpolation compatibility with other. """ if not isinstance(other, cls): raise FontPartsError("Compatibility between an instance of %r and an instance of %r can not be checked." % (cls.__name__, other.__class__.__name__)) reporter = self.compatibilityReporterClass(self, other) self._isCompatible(other, reporter) return not reporter.fatal, reporter
def removeGlyph(self, name): """ Remove the glyph with name from the layer. :: >>> layer.removeGlyph("A") """ name = normalizers.normalizeGlyphName(name) if name not in self: raise FontPartsError("No glyph with the name '%s' exists." % name) self._removeGlyph(name)
def interpolate(self, factor, minKerning, maxKerning, round=True, suppressError=True): """ Interpolates all pairs between two :class:`BaseKerning` objects: **minKerning** and **maxKerning**. The interpolation occurs on a 0 to 1.0 range where **minKerning** is located at 0 and **maxKerning** is located at 1.0. The kerning data is replaced by the interpolated kerning. * **factor** is the interpolation value. It may be less than 0 and greater than 1.0. It may be an :ref:`type-int-float`, ``tuple`` or ``list``. If it is a ``tuple`` or ``list``, the first number indicates the x factor and the second number indicates the y factor. * **round** is a ``bool`` indicating if the result should be rounded to ``int``\s. The default behavior is to round interpolated kerning. * **suppressError** is a ``bool`` indicating if incompatible data should be ignored or if an error should be raised when such incompatibilities are found. The default behavior is to ignore incompatible data. >>> myKerning.interpolate(kerningOne, kerningTwo) """ factor = normalizers.normalizeInterpolationFactor(factor) if not isinstance(minKerning, BaseKerning): raise FontPartsError( "Interpolation to an instance of %r can not be performed from an instance of %r." % (self.__class__.__name__, minKerning.__class__.__name__)) if not isinstance(maxKerning, BaseKerning): raise FontPartsError( "Interpolation to an instance of %r can not be performed from an instance of %r." % (self.__class__.__name__, maxKerning.__class__.__name__)) round = normalizers.normalizeBoolean(round) suppressError = normalizers.normalizeBoolean(suppressError) self._interpolate(factor, minKerning, maxKerning, round=round, suppressError=suppressError)
def __get__(self, obj, cls): getter = getattr(obj, self.getterName, None) if getter is not None: return getter() else: # obj is None when the property is accessed # via the class instead of an instance if obj is None: return self raise FontPartsError("no getter for %r" % self.name)
def removeSegment(self, segment, **kwargs): """ Remove segment from the contour. """ if not isinstance(segment, int): segment = self.segments.index(segment) segment = normalizers.normalizeIndex(segment) if segment >= self._len__segments(): raise FontPartsError("No segment located at index %d." % segment) self._removeSegment(segment, **kwargs)
def removeLayer(self, name): """ Remove the layer with **name** from the font. >>> font.removeLayer("My Layer 3") """ name = normalizers.normalizeLayerName(name) if name not in self.layerOrder: raise FontPartsError("No layer with the name '%s' exists." % name) self._removeLayer(name)
def round(self, multiple=1): """ Rounds the kerning values to increments of **multiple**, which will be an ``int``. The default behavior is to round to increments of 1. """ if not isinstance(multiple, int): raise FontPartsError("The round multiple must be an int not %s." % multiple.__class__.__name__) self._round(multiple)
def removePoint(self, point, **kwargs): """ Remove the point from the contour. point can be a point object or an index. """ if not isinstance(point, int): point = self.points.index(point) point = normalizers.normalizeIndex(point) if point >= self._len__points(): raise FontPartsError("No point located at index %d." % point) self._removePoint(point, **kwargs)
def removeBPoint(self, bPoint, **kwargs): """ Remove the bpoint from the contour. bpoint can be a point object or an index. """ if not isinstance(bPoint, int): bPoint = bPoint.index bPoint = normalizers.normalizeIndex(bPoint) if bPoint >= self._len__points(): raise FontPartsError("No bPoint located at index %d." % bPoint) self._removeBPoint(bPoint, **kwargs)
def _set_base_index(self, value): glyph = self.glyph if glyph is None: raise FontPartsError("The contour does not belong to a glyph.") value = normalizers.normalizeIndex(value) contourCount = len(glyph.contours) if value < 0: value = -(value % contourCount) if value >= contourCount: value = contourCount self._set_index(value)
def __getitem__(self, name): """ Get the :class:`BaseGlyph` with name from the layer. :: >>> glyph = layer["A"] """ name = normalizers.normalizeGlyphName(name) if name not in self: raise FontPartsError("No glyph named '%s'." % name) glyph = self._getItem(name) self._setLayerInGlyph(glyph) return glyph
def getLayer(self, name): """ Get the :class:`BaseLayer` with **name**. >>> layer = font.getLayer("My Layer 2") """ name = normalizers.normalizeLayerName(name) if name not in self.layerOrder: raise FontPartsError("No layer with the name '%s' exists." % name) layer = self._getLayer(name) self._setFontInLayer(layer) return layer
def _get_type(self): """ Subclasses may override this method. """ point = self._point typ = point.type if typ == "curve" and point.smooth: bType = "curve" elif typ in ("move", "line", "curve"): bType = "corner" else: raise FontPartsError( "A %s point can not be converted to a bPoint." % typ) return bType
def _interpolate(self, factor, minInfo, maxInfo, round=True, suppressError=True): """ Subclasses may override this method. """ minInfo = minInfo._toMathInfo() maxInfo = maxInfo._toMathInfo() result = interpolate(minInfo, maxInfo, factor) if result is None and not suppressError: raise FontPartsError(("Info from font '%s' and font '%s' could not be " "interpolated.") % (minInfo.font.name, maxInfo.font.name)) if round: result = result.round() self._fromMathInfo(result)
def save(self, path=None, showProgress=False, formatVersion=None): """ Save the font to **path**. >>> font.save() >>> font.save("/path/to/my/font-2.ufo") If **path** is None, use the font's original location. The file type must be inferred from the file extension of the given path. If no file extension is given, the environment may fall back to the format of its choice. **showProgress** indicates if a progress indicator should be displayed during the operation. Environments may or may not implement this behavior. **formatVersion** indicates the format version that should be used for writing the given file type. For example, if 2 is given for formatVersion and the file type being written if UFO, the file is to be written in UFO 2 format. This value is not limited to UFO format versions. If no format version is given, the original format version of the file should be preserved. If there is no original format version it is implied that the format version is the latest version for the file type as supported by the environment. .. note:: Environments may define their own rules governing when a file should be saved into its original location and when it should not. For example, a font opened from a compiled OpenType font may not be written back into the original OpenType font. """ if path is None and self.path is None: raise FontPartsError( "The font cannot be saved because no file location has been given." ) if path is not None: path = normalizers.normalizeFilePath(path) showProgress = bool(showProgress) if formatVersion is not None: formatVersion = normalizers.normalizeFileFormatVersion( formatVersion) self._save(path=path, showProgress=showProgress, formatVersion=formatVersion)
def removeGuideline(self, guideline): """ Remove **guideline** from the font. >>> font.removeGuideline(guideline) >>> font.removeGuideline(2) **guideline** can be a guideline object or an integer representing the guideline index. """ if isinstance(guideline, int): index = guideline else: index = self._getGuidelineIndex(guideline) index = normalizers.normalizeGuidelineIndex(index) if index >= self._len__guidelines(): raise FontPartsError("No guideline located at index %d." % index) self._removeGuideline(index)
def setStartSegment(self, segment, **kwargs): """ Set the first segment on the contour. segment can be a segment object or an index. """ segments = self.segments if not isinstance(segment, int): segmentIndex = segments.index(segment) else: segmentIndex = segment if len(self.segments) < 2: return if segmentIndex == 0: return if segmentIndex >= len(segments): raise FontPartsError( "The contour does not contain a segment at index %d" % segmentIndex) self._setStartSegment(segmentIndex, **kwargs)
def _interpolate(self, factor, minInfo, maxInfo, round=True, suppressError=True): """ Subclasses may override this method. """ from fontMath.mathFunctions import setRoundIntegerFunction setRoundIntegerFunction(normalizers.normalizeVisualRounding) minInfo = minInfo._toMathInfo() maxInfo = maxInfo._toMathInfo() result = interpolate(minInfo, maxInfo, factor) if result is None and not suppressError: raise FontPartsError( ("Info from font '%s' and font '%s' could not be " "interpolated.") % (minInfo.font.name, maxInfo.font.name)) if round: result = result.round() self._fromMathInfo(result)
def _get_type(self): """ Subclasses may override this method. """ point = self._point typ = point.type bType = None if point.smooth: if typ == "curve": bType = "curve" elif typ == "line": nextSegment = self._nextSegment if nextSegment is not None and nextSegment.type == "curve": bType = "curve" else: bType = "corner" elif typ in ("move", "line", "curve"): bType = "corner" if bType is None: raise FontPartsError("A %s point can not be converted to a bPoint." % typ) return bType