-
Notifications
You must be signed in to change notification settings - Fork 21
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Background image for tracing #1707
Changes from all commits
47808ae
6ed0bce
78c5461
a913743
430f607
0e281b5
01bd9c4
c70c679
8d3eae7
08768c8
07bc39b
81f646c
c6da347
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -7,3 +7,4 @@ ufomerge==1.8.2 | |
unicodedata2==15.1.0 | ||
watchfiles==0.24.0 | ||
skia-pathops==0.8.0.post2 | ||
pybase64==1.4.0 | ||
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,6 +1,7 @@ | ||
from __future__ import annotations | ||
|
||
import asyncio | ||
import base64 | ||
import logging | ||
import os | ||
import pathlib | ||
|
@@ -22,7 +23,7 @@ | |
DiscreteAxisDescriptor, | ||
SourceDescriptor, | ||
) | ||
from fontTools.misc.transform import DecomposedTransform | ||
from fontTools.misc.transform import DecomposedTransform, Transform | ||
from fontTools.pens.pointPen import AbstractPointPen | ||
from fontTools.pens.recordingPen import RecordingPointPen | ||
from fontTools.ufoLib import UFOReaderWriter | ||
|
@@ -42,6 +43,7 @@ | |
GlyphAxis, | ||
GlyphSource, | ||
Guideline, | ||
Image, | ||
Kerning, | ||
Layer, | ||
LineMetric, | ||
|
@@ -344,7 +346,9 @@ async def getGlyph(self, glyphName: str) -> VariableGlyph | None: | |
if glyphName not in ufoLayer.glyphSet: | ||
continue | ||
|
||
staticGlyph, ufoGlyph = ufoLayerToStaticGlyph(ufoLayer.glyphSet, glyphName) | ||
staticGlyph, ufoGlyph = ufoLayerToStaticGlyph( | ||
ufoLayer.glyphSet, glyphName, ufoDir=ufoLayer.path | ||
) | ||
if ufoLayer == self.defaultUFOLayer: | ||
localDS = ufoGlyph.lib.get(GLYPH_DESIGNSPACE_LIB_KEY) | ||
if localDS is not None: | ||
|
@@ -1072,6 +1076,40 @@ def _writeDesignSpaceDocument(self): | |
self.dsDoc.write(self.dsDoc.path) | ||
self.dsDocModTime = os.stat(self.dsDoc.path).st_mtime | ||
|
||
async def getBinaryData(self) -> dict[str, bytes]: | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. So this would return all binary data for all layers. That's insanely inefficient: we always load "big things" on demand. But see the general comment, you ignored the method signature that I prescribed. |
||
binaryData = {} | ||
|
||
for ufoLayer in self.ufoLayers: | ||
folderPath = pathlib.Path(ufoLayer.path) / "images" | ||
if not folderPath.is_dir(): | ||
continue | ||
for filePath in folderPath.iterdir(): | ||
if filePath.is_file(): | ||
binaryData[filePath.name] = base64.b64encode( | ||
filePath.read_bytes() | ||
).decode("utf-8") | ||
|
||
return binaryData | ||
|
||
async def putBinaryData( | ||
self, name: str, fontraLayerName: str | None, binaryData: bytes | ||
) -> None: | ||
if fontraLayerName is not None: | ||
ufoDir = self.ufoLayers.findItem(fontraLayerName=fontraLayerName).path | ||
filePath = pathlib.Path(ufoDir) / "images" / name | ||
else: | ||
# loop through all layers and return the first found image | ||
for ufoLayer in self.ufoLayers: | ||
ufoDir = ufoLayer.path | ||
filePath = pathlib.Path(ufoDir) / "images" / name | ||
if filePath.is_file(): | ||
break | ||
|
||
if not filePath.is_file(): | ||
filePath = pathlib.Path(self.defaultUFOLayer.path) / "images" / name | ||
|
||
filePath.write_bytes(binaryData) | ||
|
||
async def watchExternalChanges( | ||
self, callback: Callable[[Any], Awaitable[None]] | ||
) -> None: | ||
|
@@ -1397,6 +1435,7 @@ class UFOGlyph: | |
height: float | None = None | ||
anchors: list = [] | ||
guidelines: list = [] | ||
image: Image | None = None | ||
note: str | None = None | ||
lib: dict | ||
|
||
|
@@ -1566,7 +1605,9 @@ def iterAttrs(self, attrName): | |
yield getattr(item, attrName) | ||
|
||
|
||
def ufoLayerToStaticGlyph(glyphSet, glyphName, penClass=PackedPathPointPen): | ||
def ufoLayerToStaticGlyph( | ||
glyphSet, glyphName, penClass=PackedPathPointPen, ufoDir=None | ||
): | ||
glyph = UFOGlyph() | ||
glyph.lib = {} | ||
pen = penClass() | ||
|
@@ -1583,6 +1624,7 @@ def ufoLayerToStaticGlyph(glyphSet, glyphName, penClass=PackedPathPointPen): | |
verticalOrigin=verticalOrigin, | ||
anchors=unpackAnchors(glyph.anchors), | ||
guidelines=unpackGuidelines(glyph.guidelines), | ||
image=unpackImage(glyph.image), | ||
) | ||
|
||
return staticGlyph, glyph | ||
|
@@ -1605,6 +1647,27 @@ def unpackAnchors(anchors): | |
return [Anchor(name=a.get("name"), x=a["x"], y=a["y"]) for a in anchors] | ||
|
||
|
||
def unpackImage(image): | ||
if image is None: | ||
return None | ||
|
||
xx = image.get("xScale", 1) | ||
xy = image.get("xyScale", 0) | ||
yx = image.get("yxScale", 0) | ||
yy = image.get("yScale", 1) | ||
dx = image.get("xOffset", 0) | ||
dy = image.get("yOffset", 0) | ||
transformation = Transform(xx, xy, yx, yy, dx, dy) | ||
decomposedTransform = DecomposedTransform.fromTransform(transformation) | ||
|
||
return Image( | ||
fileName=image["fileName"], | ||
transformation=decomposedTransform, | ||
color=image.get("color", None), | ||
customData=image.get("customData", {}), | ||
) | ||
|
||
|
||
def unpackGuidelines(guidelines): | ||
return [ | ||
Guideline( | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,5 @@ | ||
import asyncio | ||
import base64 | ||
import csv | ||
import json | ||
import logging | ||
|
@@ -92,6 +93,13 @@ def glyphInfoPath(self): | |
def glyphsDir(self): | ||
return self.path / self.glyphsDirName | ||
|
||
@property | ||
def binaryDataPath(self): | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Please do not work on the Fontra backlend until we've actually designed how that data should be stored in there. |
||
binaryDataPath = self.path / "binaryData" | ||
if not binaryDataPath.is_dir(): | ||
binaryDataPath.mkdir() | ||
return binaryDataPath | ||
|
||
async def aclose(self): | ||
self.flush() | ||
|
||
|
@@ -188,6 +196,21 @@ async def putCustomData(self, customData: dict[str, Any]) -> None: | |
self.fontData.customData = deepcopy(customData) | ||
self._scheduler.schedule(self._writeFontData) | ||
|
||
async def getBinaryData(self) -> bytes | None: | ||
binaryData = {} | ||
if not self.binaryDataPath.is_dir(): | ||
return binaryData | ||
for filePath in self.binaryDataPath.iterdir(): | ||
if filePath.is_file(): | ||
binaryData[filePath.name] = base64.b64encode( | ||
filePath.read_bytes() | ||
).decode("utf-8") | ||
return binaryData | ||
|
||
async def putBinaryData(self, name: str, binaryData: bytes) -> None: | ||
filePath = self.binaryDataPath / name | ||
filePath.write_bytes(binaryData) | ||
|
||
def _readGlyphInfo(self) -> None: | ||
with self.glyphInfoPath.open("r", encoding="utf-8", newline="") as file: | ||
reader = csv.reader(file, delimiter=";") | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -220,6 +220,8 @@ async def _getData(self, key: str) -> Any: | |
value = await self.backend.getCustomData() | ||
case "unitsPerEm": | ||
value = await self.backend.getUnitsPerEm() | ||
case "binaryData": | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. No, "binaryData" is not a font attribute like "unitsPerEm" is. See general comment. |
||
value = await self.backend.getBinaryData() | ||
case _: | ||
raise KeyError(key) | ||
|
||
|
@@ -268,6 +270,13 @@ async def getUnitsPerEm(self, *, connection): | |
async def getCustomData(self, *, connection): | ||
return await self.getData("customData") | ||
|
||
# Then add getBinaryData() on FontHandler as a "remotemethod" | ||
@remoteMethod | ||
async def getBinaryData(self, *, connection): | ||
print("FontHandler getBinaryData") | ||
# and do the base64 conversion there. | ||
return await self.getData("binaryData") | ||
|
||
def _getClientData(self, connection, key, default=None): | ||
return self.clientData[connection.clientUUID].get(key, default) | ||
|
||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What is this for? The
base64
module comes with Python.