diff --git a/Lib/fontgoggles/font/dsFont.py b/Lib/fontgoggles/font/dsFont.py index f4918dcf..72b29a42 100644 --- a/Lib/fontgoggles/font/dsFont.py +++ b/Lib/fontgoggles/font/dsFont.py @@ -23,7 +23,7 @@ from ..compile.dsCompiler import getTTPaths from ..misc.hbShape import HBShape from ..misc.properties import cachedProperty -from ..mac.makePathFromOutline import makePathFromArrays +from ..misc.platform import platform class DesignSpaceSourceError(CompilerError): @@ -443,7 +443,7 @@ def verticalOrigin(self): return self.getPoints()[-2] def getOutline(self): - return makePathFromArrays(self.getPoints(), self.tags, self.contours) + return platform.pathFromArrays(self, self.getPoints(), self.tags, self.contours) def draw(self, pen): ppen = PointToSegmentPen(pen) diff --git a/Lib/fontgoggles/font/glyphDrawing.py b/Lib/fontgoggles/font/glyphDrawing.py index ddccedeb..8492b27e 100644 --- a/Lib/fontgoggles/font/glyphDrawing.py +++ b/Lib/fontgoggles/font/glyphDrawing.py @@ -1,6 +1,5 @@ -from AppKit import NSGraphicsContext from fontTools.misc.arrayTools import unionRect -from ..mac.drawing import rectFromNSRect, nsColorFromRGBA +from ..misc.platform import platform from ..misc.properties import cachedProperty @@ -24,11 +23,11 @@ def __init__(self, path): def bounds(self): bounds = None if self.path.elementCount(): - bounds = rectFromNSRect(self.path.controlPointBounds()) + bounds = platform.convertRect(self.path.controlPointBounds()) return bounds def draw(self, colorPalette, defaultColor): - nsColorFromRGBA(defaultColor).set() + platform.convertColor(defaultColor).set() self.path.fill() def pointInside(self, pt): @@ -46,7 +45,7 @@ def bounds(self): for path, colorID in self.layers: if not path.elementCount(): continue - pathBounds = rectFromNSRect(path.controlPointBounds()) + pathBounds = platform.convertRect(path.controlPointBounds()) if bounds is None: bounds = pathBounds else: @@ -60,7 +59,7 @@ def draw(self, colorPalette, defaultColor): if colorID < len(colorPalette) else defaultColor ) - nsColorFromRGBA(color).set() + platform.convertColor(color).set() path.fill() def pointInside(self, pt): @@ -77,15 +76,7 @@ def bounds(self): return self.colorFont.getGlyphBounds(self.glyphName) def draw(self, colorPalette, defaultColor): - from blackrenderer.backends.coregraphics import CoreGraphicsCanvas - - cgContext = NSGraphicsContext.currentContext().CGContext() - self.colorFont.drawGlyph( - self.glyphName, - CoreGraphicsCanvas(cgContext), - palette=colorPalette, - textColor=defaultColor, - ) + platform.drawCOLRv1Glyph(self.colorFont, self.glyphName, colorPalette, defaultColor) def pointInside(self, pt): return False # TODO: implement diff --git a/Lib/fontgoggles/font/otfFont.py b/Lib/fontgoggles/font/otfFont.py index e8029e95..28ff8c57 100644 --- a/Lib/fontgoggles/font/otfFont.py +++ b/Lib/fontgoggles/font/otfFont.py @@ -3,20 +3,19 @@ from .baseFont import BaseFont from .glyphDrawing import GlyphDrawing, GlyphLayersDrawing, GlyphCOLRv1Drawing from ..compile.compilerPool import compileTTXToBytes -from ..mac.makePathFromOutline import makePathFromGlyph from ..misc.hbShape import HBShape from ..misc.properties import cachedProperty +from ..misc.platform import platform class _OTFBaseFont(BaseFont): def _getGlyphOutline(self, name): - return makePathFromGlyph(self.shaper.font, self.shaper.glyphMap[name]) + return platform.pathFromGlyph(self.shaper.font, self.shaper.glyphMap[name]) def _getGlyphDrawing(self, glyphName, colorLayers): if "VarC" in self.ttFont: - from fontTools.pens.cocoaPen import CocoaPen - pen = CocoaPen(None) + pen = platform.Pen(None) location = self._currentVarLocation or {} self.varcFont.drawGlyph(pen, glyphName, location) return GlyphDrawing(pen.path) diff --git a/Lib/fontgoggles/font/ufoFont.py b/Lib/fontgoggles/font/ufoFont.py index 412a7999..86e954c9 100644 --- a/Lib/fontgoggles/font/ufoFont.py +++ b/Lib/fontgoggles/font/ufoFont.py @@ -10,7 +10,6 @@ from fontTools.feaLib.ast import IncludeStatement from fontTools.feaLib.error import FeatureLibError from fontTools.fontBuilder import FontBuilder -from fontTools.pens.cocoaPen import CocoaPen # TODO: factor out mac-specific code from fontTools.ttLib import TTFont from fontTools.ufoLib import UFOReader, UFOFileStructure from fontTools.ufoLib import (FONTINFO_FILENAME, GROUPS_FILENAME, KERNING_FILENAME, @@ -23,6 +22,7 @@ from ..compile.ufoCompiler import fetchGlyphInfo from ..misc.hbShape import HBShape from ..misc.properties import cachedProperty +from ..misc.platform import platform class UFOFont(BaseFont): @@ -151,7 +151,7 @@ def _getGlyph(self, glyphName, layerName=None): return glyph def _addOutlinePathToGlyph(self, glyph): - pen = CocoaPen(self.glyphSet) + pen = platform.Pen(self.glyphSet) glyph.draw(pen) glyph.outline = pen.path @@ -258,7 +258,7 @@ def setVarLocation(self, varLocation): pass def getOutline(self): - pen = CocoaPen(None) # by now there are no more composites + pen = platform.Pen(None) self.draw(pen) return pen.path diff --git a/Lib/fontgoggles/misc/platform.py b/Lib/fontgoggles/misc/platform.py new file mode 100644 index 00000000..d69541b7 --- /dev/null +++ b/Lib/fontgoggles/misc/platform.py @@ -0,0 +1,108 @@ +from types import SimpleNamespace +from fontTools.pens.recordingPen import RecordingPen + +""" +An abstraction on top of CocoaPen / any Mac-specific operations +so that fontGoggles can function as a platform-agnostic library +""" + + +CAN_COCOA = True + +try: + import objc +except ImportError: + CAN_COCOA = False + + +class PlatformCocoa: + @staticmethod + def pathFromArrays(font, points, tags, contours): + from ..mac.makePathFromOutline import makePathFromArrays + + return makePathFromArrays(points, tags, contours) + + @staticmethod + def pathFromGlyph(font, gid): + from ..mac.makePathFromOutline import makePathFromGlyph + + return makePathFromGlyph(font, gid) + + @staticmethod + def convertRect(r): + from ..mac.drawing import rectFromNSRect + + return rectFromNSRect(r) + + @staticmethod + def convertColor(c): + from ..mac.drawing import nsColorFromRGBA + + return nsColorFromRGBA(c) + + @staticmethod + def drawCOLRv1Glyph(colorFont, glyphName, colorPalette, defaultColor): + from AppKit import NSGraphicsContext + from blackrenderer.backends.coregraphics import CoreGraphicsCanvas + + cgContext = NSGraphicsContext.currentContext().CGContext() + colorFont.drawGlyph( + glyphName, + CoreGraphicsCanvas(cgContext), + palette=colorPalette, + textColor=defaultColor, + ) + + Pen = CocoaPen + + +class PlatformGeneric: + @staticmethod + def pathFromArrays(font, points, tags, contours): + rp = RecordingPen() + font.draw(rp) + return rp + + @staticmethod + def pathFromGlyph(font, gid): + rp = RecordingPen() + font.draw_glyph_with_pen(gid, rp) + return rp + + @staticmethod + def convertRect(r): + raise NotImplementedError() + + @staticmethod + def convertColor(c): + raise NotImplementedError() + + @staticmethod + def drawCOLRv1Glyph(colorFont, glyphName, colorPalette, defaultColor): + raise NotImplementedError() + + class Pen(RecordingPen): + def __init__(self, glyphSet): # to match CocoaPen constructor + super().__init__() + + @property + def path(self): + return self + + +platform = SimpleNamespace() + + +def setUseCocoa(onOff): + global _platform + if onOff: + assert CAN_COCOA + _platform = PlatformCocoa if onOff else PlatformGeneric + platform.__dict__.update(**_platform.__dict__) + + +def getUseCocoa(): + return _platform is PlatformCocoa + + +setUseCocoa(CAN_COCOA) diff --git a/Tests/test_makeOutline.py b/Tests/test_makeOutline.py index acdf07ab..1ed59201 100644 --- a/Tests/test_makeOutline.py +++ b/Tests/test_makeOutline.py @@ -1,8 +1,8 @@ import sys import pytest -from fontTools.pens.cocoaPen import CocoaPen from fontTools.ttLib import TTFont from fontgoggles.font.otfFont import OTFFont +from fontgoggles.misc.platform import platform from testSupport import getFontPath @@ -19,7 +19,7 @@ async def test_getOutlinePath(): for glyphName in ["a", "B", "O", "period", "bar", "aring"]: p = font._getGlyphOutline(glyphName) - pen = CocoaPen(ttfGlyphSet) + pen = platform.Pen(ttfGlyphSet) ttfGlyphSet[glyphName].draw(pen) # The paths are not identical, due to different rounding # of the implied points, and different closepath behavior, diff --git a/Tests/test_noCocoa.py b/Tests/test_noCocoa.py new file mode 100644 index 00000000..57f85fc9 --- /dev/null +++ b/Tests/test_noCocoa.py @@ -0,0 +1,48 @@ +import pytest, sys +from asyncio import run +from fontgoggles.font import getOpener +from fontgoggles.misc.textInfo import TextInfo +from fontgoggles.misc.platform import setUseCocoa, getUseCocoa +from testSupport import getFontPath +from fontTools.pens.recordingPen import RecordingPen + + +font_paths = [ + "MutatorSans.ttf", + "MutatorSansBoldWide.ufo", + "MutatorSans.designspace", +] + + +def test_cocoaAndNoCocoa(): + def getDrawings(path): + fontPath = getFontPath(path) + _, opener, _ = getOpener(fontPath) + font = opener(fontPath, 0) + run(font.load(sys.stderr.write)) # to test support for non-async + + textInfo = TextInfo("abc") + glyphs = font.getGlyphRunFromTextInfo(textInfo) + glyphNames = [g.name for g in glyphs] + glyphDrawings = list(font.getGlyphDrawings(glyphNames, True)) + + assert len(glyphs) == 3 + assert len(glyphDrawings) == 3 + + return glyphDrawings + + assert getUseCocoa() == True + + for font_path in font_paths: + glyphDrawings = getDrawings(font_path) + for g in glyphDrawings: + assert "NSBezierPath" in str(type(g.path)) + + setUseCocoa(False) + + assert getUseCocoa() == False + + for font_path in font_paths: + glyphDrawings = getDrawings(font_path) + for g in glyphDrawings: + assert isinstance(g.path, RecordingPen) diff --git a/setup.py b/setup.py index e50eff63..c87c12c5 100644 --- a/setup.py +++ b/setup.py @@ -3,13 +3,18 @@ import re from setuptools import setup, find_packages import subprocess +import platform class build(_build): def run(self): - # Build our C library - subprocess.check_call(['./Turbo/build_lib.sh']) - _build.run(self) + if platform.system() == "Darwin": + try: + # Build our C library + subprocess.check_call(['./Turbo/build_lib.sh']) + _build.run(self) + except: + print("! Could not build turbo") _versionRE = re.compile(r'__version__\s*=\s*\"([^\"]+)\"') @@ -31,11 +36,18 @@ def run(self): packages=find_packages("Lib"), package_data={'fontgoggles.mac': ['*.dylib']}, install_requires=[ + "blackrenderer>=0.6.0", + "fonttools[woff,lxml,unicode,ufo,type1]>=4.53.1", + "uharfbuzz>=0.42.0", + "python-bidi==0.4.2", # pinned for non-forward-compatibility + "ufo2ft>=3.2.8", + "numpy", + "unicodedata2>=15.1.0", ], extras_require={ }, setup_requires=["setuptools_scm<8.0.0"], - python_requires=">=3.7", + python_requires=">=3.10", classifiers=[ ], cmdclass={'build': build},