From 92f0d852fa33e6cfef979710707b6f289acbb0be Mon Sep 17 00:00:00 2001 From: Just van Rossum Date: Sun, 28 Apr 2024 22:04:49 +0200 Subject: [PATCH 1/2] Implement get/put features --- src/fontra_rcjk/backend_fs.py | 30 +++++++++++++++++++++++++++++- src/fontra_rcjk/backend_mysql.py | 7 +++++++ src/fontra_rcjk/base.py | 4 ++-- tests/test_font.py | 14 ++++++++++++++ 4 files changed, 52 insertions(+), 3 deletions(-) diff --git a/src/fontra_rcjk/backend_fs.py b/src/fontra_rcjk/backend_fs.py index b5108b2..9d2b713 100644 --- a/src/fontra_rcjk/backend_fs.py +++ b/src/fontra_rcjk/backend_fs.py @@ -10,7 +10,14 @@ from fontra.backends.filewatcher import Change, FileWatcher from fontra.backends.ufo_utils import extractGlyphNameAndCodePoints -from fontra.core.classes import Axes, Font, FontInfo, FontSource, VariableGlyph +from fontra.core.classes import ( + Axes, + Font, + FontInfo, + FontSource, + OpenTypeFeatures, + VariableGlyph, +) from fontra.core.instancer import mapLocationFromUserToSource from fontra.core.protocols import WritableFontBackend from fontTools.ufoLib.filenames import userNameToFileName @@ -32,6 +39,7 @@ FILE_DELETED_TOKEN = object() DS_FILENAME = "designspace.json" +FEA_FILENAME = "features.fea" FONTLIB_FILENAME = "fontLib.json" @@ -228,6 +236,26 @@ async def deleteGlyph(self, glyphName): del self._glyphMap[glyphName] + async def getFeatures(self) -> OpenTypeFeatures: + featuresPath = self.path / FEA_FILENAME + featureText = ( + featuresPath.read_text(encoding="utf-8") if featuresPath.is_file() else "" + ) + return OpenTypeFeatures(text=featureText) + + async def putFeatures(self, features: OpenTypeFeatures) -> None: + if features.language != "fea": + logger.warning( + f"skip writing features in unsupported language: {features.language!r}" + ) + return + + featuresPath = self.path / FEA_FILENAME + if features.text: + featuresPath.write_text(features.text, encoding="utf-8") + elif featuresPath.is_file(): + featuresPath.unlink() + async def getCustomData(self) -> dict[str, Any]: customData = {} customDataPath = self.path / FONTLIB_FILENAME diff --git a/src/fontra_rcjk/backend_mysql.py b/src/fontra_rcjk/backend_mysql.py index 04b1b25..4ebd213 100644 --- a/src/fontra_rcjk/backend_mysql.py +++ b/src/fontra_rcjk/backend_mysql.py @@ -13,6 +13,7 @@ Font, FontInfo, FontSource, + OpenTypeFeatures, VariableGlyph, structure, unstructure, @@ -195,6 +196,12 @@ async def putUnitsPerEm(self, value: int) -> None: designspace.unitsPerEm = value await self._writeDesignspace(designspace) + async def getFeatures(self) -> OpenTypeFeatures: + return OpenTypeFeatures() + + async def putFeatures(self, features: OpenTypeFeatures) -> None: + pass + async def getCustomData(self) -> dict[str, Any]: customData = self._tempFontItemsCache.get("customData") if customData is None: diff --git a/src/fontra_rcjk/base.py b/src/fontra_rcjk/base.py index 1c3ca7f..9df98c3 100644 --- a/src/fontra_rcjk/base.py +++ b/src/fontra_rcjk/base.py @@ -544,8 +544,8 @@ def structureDesignspaceData(designspaceData: dict[str, Any]) -> Font: def unstructureDesignspaceData(designspace: Font) -> dict[str, Any]: designspaceData = unstructure(designspace) - del designspaceData["glyphs"] - del designspaceData["glyphMap"] + designspaceData.pop("glyphs", None) + designspaceData.pop("glyphMap", None) return designspaceData diff --git a/tests/test_font.py b/tests/test_font.py index ebb4456..5dae37a 100644 --- a/tests/test_font.py +++ b/tests/test_font.py @@ -10,6 +10,7 @@ GlyphAxis, GlyphSource, Layer, + OpenTypeFeatures, PackedPath, StaticGlyph, VariableGlyph, @@ -1023,3 +1024,16 @@ async def test_putUnitsPerEm(writableTestFont): assert 1000 == await writableTestFont.getUnitsPerEm() await writableTestFont.putUnitsPerEm(2000) assert 2000 == await writableTestFont.getUnitsPerEm() + + +async def test_getFeatures(writableTestFont): + font = getTestFont("rcjk") + features = await font.getFeatures() + assert "languagesystem DFLT dflt" in features.text + + +async def test_putFeatures(writableTestFont): + featureText = "# feature text" + async with contextlib.aclosing(writableTestFont): + await writableTestFont.putFeatures(OpenTypeFeatures(text=featureText)) + assert (await writableTestFont.getFeatures()).text == featureText From aed3e7e9c21cea8fe51ce6fd6c415d58dfa1a889 Mon Sep 17 00:00:00 2001 From: Just van Rossum Date: Sun, 28 Apr 2024 22:14:25 +0200 Subject: [PATCH 2/2] Implement get/put features for mysql backend --- src/fontra_rcjk/backend_mysql.py | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/src/fontra_rcjk/backend_mysql.py b/src/fontra_rcjk/backend_mysql.py index 4ebd213..400e9f7 100644 --- a/src/fontra_rcjk/backend_mysql.py +++ b/src/fontra_rcjk/backend_mysql.py @@ -127,6 +127,9 @@ async def taskFunc(): self._tempFontItemsCache["designspace"] = structureDesignspaceData( font_data["data"].get("designspace", {}) ) + self._tempFontItemsCache["features"] = font_data["data"].get( + "features", "" + ) self._tempFontItemsCache["customData"] = ( font_data["data"].get("fontlib", {}) | standardCustomDataItems ) @@ -197,10 +200,20 @@ async def putUnitsPerEm(self, value: int) -> None: await self._writeDesignspace(designspace) async def getFeatures(self) -> OpenTypeFeatures: - return OpenTypeFeatures() + await self._getMiscFontItems() + featureText = self._tempFontItemsCache["features"] + return OpenTypeFeatures(text=featureText) async def putFeatures(self, features: OpenTypeFeatures) -> None: - pass + if features.language != "fea": + logger.warning( + f"skip writing features in unsupported language: {features.language!r}" + ) + return + + await self._getMiscFontItems() + self._tempFontItemsCache["features"] = features.text + _ = await self.client.font_update(self.fontUID, features=features.text) async def getCustomData(self) -> dict[str, Any]: customData = self._tempFontItemsCache.get("customData")