From 2cecea6df2c947d5015f329ef47d4d267c7613b0 Mon Sep 17 00:00:00 2001 From: Nikolaus Waxweiler Date: Tue, 25 May 2021 12:43:55 +0100 Subject: [PATCH 1/5] Experimental loading with readwrite_ufo_glif --- src/ufoLib2/objects/layer.py | 68 ++++++++++++++++++++++++++++++------ 1 file changed, 58 insertions(+), 10 deletions(-) diff --git a/src/ufoLib2/objects/layer.py b/src/ufoLib2/objects/layer.py index f1cb1585..868b85ed 100644 --- a/src/ufoLib2/objects/layer.py +++ b/src/ufoLib2/objects/layer.py @@ -12,9 +12,13 @@ import attr from fontTools.ufoLib.glifLib import GlyphSet +import readwrite_ufo_glif from ufoLib2.constants import DEFAULT_LAYER_NAME from ufoLib2.objects.glyph import Glyph +from ufoLib2.objects.contour import Contour +from ufoLib2.objects.point import Point +from ufoLib2.objects.component import Component from ufoLib2.objects.misc import ( _NOT_LOADED, BoundingBox, @@ -125,16 +129,12 @@ def read(cls, name: str, glyphSet: GlyphSet, lazy: bool = True) -> "Layer": lazy: If True, load glyphs as they are accessed. If False, load everything up front. """ - glyphNames = glyphSet.keys() glyphs: Dict[str, Union[Glyph, Placeholder]] if lazy: - glyphs = {gn: _NOT_LOADED for gn in glyphNames} + glyphs = {gn: _NOT_LOADED for gn in glyphSet.keys()} else: - glyphs = {} - for glyphName in glyphNames: - glyph = Glyph(glyphName) - glyphSet.readGlyph(glyphName, glyph, glyph.getPointPen()) - glyphs[glyphName] = glyph + layer_path = glyphSet.fs.getsyspath(".") + glyphs = _read_layer(layer_path) self = cls(name, glyphs) if lazy: self._glyphSet = glyphSet @@ -285,9 +285,8 @@ def insertGlyph( def loadGlyph(self, name: str) -> Glyph: """Load and return Glyph object.""" # XXX: Remove and let __getitem__ do it? - glyph = Glyph(name) - self._glyphSet.readGlyph(name, glyph, glyph.getPointPen()) - self._glyphs[name] = glyph + glif_path = self._glyphSet.fs.getsyspath(self._glyphSet.contents[name]) + self._glyphs[name] = glyph = _read_glyph(glif_path, name) return glyph def newGlyph(self, name: str) -> Glyph: @@ -373,3 +372,52 @@ def _fetch_glyph_identifiers(glyph: Glyph) -> Set[str]: if component.identifier is not None: identifiers.add(component.identifier) return identifiers + + +def _read_glyph(glif_path: str, name: str) -> Glyph: + data = readwrite_ufo_glif.read_glyph(glif_path) + return Glyph( + name, + height=data.get("height", 0), + width=data.get("width", 0), + unicodes=data.get("unicodes", []), + image=data.get("image"), + anchors=data.get("anchors", []), + guidelines=data.get("guidelines", []), + lib=data.get("lib", {}), + note=data.get("note", {}), + contours=[ + Contour( + points=[Point(**kwargs) for kwargs in contour["points"]], + identifier=contour.get("identifier"), + ) + for contour in data["contours"] + ], + components=[Component(**kwargs) for kwargs in data["components"]], + ) + + +def _read_layer(layer_path: str) -> Dict[str, Glyph]: + all_data: Dict[str, Dict[str, Any]] = readwrite_ufo_glif.read_layer(layer_path) + return { + name: Glyph( + name, + height=data.get("height", 0), + width=data.get("width", 0), + unicodes=data.get("unicodes", []), + image=data.get("image"), + anchors=data.get("anchors", []), + guidelines=data.get("guidelines", []), + lib=data.get("lib", {}), + note=data.get("note", {}), + contours=[ + Contour( + points=[Point(**kwargs) for kwargs in contour["points"]], + identifier=contour.get("identifier"), + ) + for contour in data["contours"] + ], + components=[Component(**kwargs) for kwargs in data["components"]], + ) + for name, data in all_data.items() + } From 609143b229f70637811dc15b820c96f4cf06c5bc Mon Sep 17 00:00:00 2001 From: Nikolaus Waxweiler Date: Tue, 25 May 2021 15:11:25 +0100 Subject: [PATCH 2/5] Actually properly instantiate all Glpyh attrs --- src/ufoLib2/objects/layer.py | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/src/ufoLib2/objects/layer.py b/src/ufoLib2/objects/layer.py index 868b85ed..07aabca2 100644 --- a/src/ufoLib2/objects/layer.py +++ b/src/ufoLib2/objects/layer.py @@ -11,14 +11,16 @@ ) import attr -from fontTools.ufoLib.glifLib import GlyphSet import readwrite_ufo_glif +from fontTools.ufoLib.glifLib import GlyphSet from ufoLib2.constants import DEFAULT_LAYER_NAME -from ufoLib2.objects.glyph import Glyph -from ufoLib2.objects.contour import Contour -from ufoLib2.objects.point import Point +from ufoLib2.objects.anchor import Anchor from ufoLib2.objects.component import Component +from ufoLib2.objects.contour import Contour +from ufoLib2.objects.glyph import Glyph +from ufoLib2.objects.guideline import Guideline +from ufoLib2.objects.image import Image from ufoLib2.objects.misc import ( _NOT_LOADED, BoundingBox, @@ -27,6 +29,7 @@ _prune_object_libs, unionBounds, ) +from ufoLib2.objects.point import Point from ufoLib2.typing import T @@ -381,9 +384,9 @@ def _read_glyph(glif_path: str, name: str) -> Glyph: height=data.get("height", 0), width=data.get("width", 0), unicodes=data.get("unicodes", []), - image=data.get("image"), - anchors=data.get("anchors", []), - guidelines=data.get("guidelines", []), + image=Image(**data["image"]) if "image" in data else Image(), + anchors=[Anchor(**kwargs) for kwargs in data.get("anchors", [])], + guidelines=[Guideline(**kwargs) for kwargs in data.get("guidelines", [])], lib=data.get("lib", {}), note=data.get("note", {}), contours=[ @@ -405,9 +408,9 @@ def _read_layer(layer_path: str) -> Dict[str, Glyph]: height=data.get("height", 0), width=data.get("width", 0), unicodes=data.get("unicodes", []), - image=data.get("image"), - anchors=data.get("anchors", []), - guidelines=data.get("guidelines", []), + image=Image(**data["image"]) if "image" in data else Image(), + anchors=[Anchor(**kwargs) for kwargs in data.get("anchors", [])], + guidelines=[Guideline(**kwargs) for kwargs in data.get("guidelines", [])], lib=data.get("lib", {}), note=data.get("note", {}), contours=[ From b638b7dcfea8d71612179596da6698cb89f790a4 Mon Sep 17 00:00:00 2001 From: Nikolaus Waxweiler Date: Fri, 11 Jun 2021 23:21:25 +0100 Subject: [PATCH 3/5] WIP: make contour and component data optional --- src/ufoLib2/objects/layer.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/ufoLib2/objects/layer.py b/src/ufoLib2/objects/layer.py index 07aabca2..1ce131d6 100644 --- a/src/ufoLib2/objects/layer.py +++ b/src/ufoLib2/objects/layer.py @@ -394,9 +394,9 @@ def _read_glyph(glif_path: str, name: str) -> Glyph: points=[Point(**kwargs) for kwargs in contour["points"]], identifier=contour.get("identifier"), ) - for contour in data["contours"] + for contour in data.get("contours", []) ], - components=[Component(**kwargs) for kwargs in data["components"]], + components=[Component(**kwargs) for kwargs in data.get("components", [])], ) @@ -418,9 +418,9 @@ def _read_layer(layer_path: str) -> Dict[str, Glyph]: points=[Point(**kwargs) for kwargs in contour["points"]], identifier=contour.get("identifier"), ) - for contour in data["contours"] + for contour in data.get("contours", []) ], - components=[Component(**kwargs) for kwargs in data["components"]], + components=[Component(**kwargs) for kwargs in data.get("components", [])], ) for name, data in all_data.items() } From a7b4a10e32afe39085b9a02d6ea693092a54eff3 Mon Sep 17 00:00:00 2001 From: Nikolaus Waxweiler Date: Tue, 15 Jun 2021 23:14:50 +0100 Subject: [PATCH 4/5] Try to get rid of GlyphSet --- src/ufoLib2/objects/layer.py | 49 ++++++++++++++++++++++++--------- src/ufoLib2/objects/layerSet.py | 40 +++++++++++++++++++++++---- 2 files changed, 70 insertions(+), 19 deletions(-) diff --git a/src/ufoLib2/objects/layer.py b/src/ufoLib2/objects/layer.py index 1ce131d6..047c176f 100644 --- a/src/ufoLib2/objects/layer.py +++ b/src/ufoLib2/objects/layer.py @@ -1,3 +1,4 @@ +from pathlib import Path from typing import ( Any, Dict, @@ -6,6 +7,7 @@ Optional, Sequence, Set, + Tuple, Union, overload, ) @@ -71,6 +73,16 @@ def _convert_glyphs( return result +@attr.s(auto_attribs=True, slots=True, repr=False) +class MinimalGlyphSet: + path: Path + contents: Dict[str, str] + + @classmethod + def from_path(cls, path: Path) -> "MinimalGlyphSet": + return cls(path, readwrite_ufo_glif.read_layer_contents(str(path))) + + @attr.s(auto_attribs=True, slots=True, repr=False) class Layer: """Represents a Layer that holds Glyph objects. @@ -122,7 +134,7 @@ class Layer: _glyphSet: Any = attr.ib(default=None, init=False, eq=False) @classmethod - def read(cls, name: str, glyphSet: GlyphSet, lazy: bool = True) -> "Layer": + def read(cls, name: str, path: Path, lazy: bool = True) -> "Layer": """Instantiates a Layer object from a :class:`fontTools.ufoLib.glifLib.GlyphSet`. @@ -132,16 +144,18 @@ def read(cls, name: str, glyphSet: GlyphSet, lazy: bool = True) -> "Layer": lazy: If True, load glyphs as they are accessed. If False, load everything up front. """ + glyphs: Dict[str, Union[Glyph, Placeholder]] if lazy: - glyphs = {gn: _NOT_LOADED for gn in glyphSet.keys()} + glyphset = MinimalGlyphSet.from_path(path) + glyphs = {gn: _NOT_LOADED for gn in glyphset.contents.keys()} + color, lib = readwrite_ufo_glif.read_layerinfo_maybe(str(path)) + self = cls(name, glyphs, color=color, lib=lib) + self._glyphSet = glyphset else: - layer_path = glyphSet.fs.getsyspath(".") - glyphs = _read_layer(layer_path) - self = cls(name, glyphs) - if lazy: - self._glyphSet = glyphSet - glyphSet.readLayerInfo(self) + glyphs, (color, lib) = _read_layer(path) + self = cls(name, glyphs, color=color, lib=lib) + return self def unlazify(self) -> None: @@ -288,7 +302,8 @@ def insertGlyph( def loadGlyph(self, name: str) -> Glyph: """Load and return Glyph object.""" # XXX: Remove and let __getitem__ do it? - glif_path = self._glyphSet.fs.getsyspath(self._glyphSet.contents[name]) + layer_path: Path = self._glyphSet.path + glif_path = layer_path / self._glyphSet.contents[name] self._glyphs[name] = glyph = _read_glyph(glif_path, name) return glyph @@ -325,6 +340,7 @@ def instantiateGlyphObject(self) -> Glyph: """ return Glyph() + # where does writer glyphset come from? same as layer._glyphSet? def write(self, glyphSet: GlyphSet, saveAs: bool = True) -> None: """Write Layer to a :class:`fontTools.ufoLib.glifLib.GlyphSet`. @@ -378,7 +394,8 @@ def _fetch_glyph_identifiers(glyph: Glyph) -> Set[str]: def _read_glyph(glif_path: str, name: str) -> Glyph: - data = readwrite_ufo_glif.read_glyph(glif_path) + # data = pickle.loads(readwrite_ufo_glif.read_glyph(glif_path)) + data = readwrite_ufo_glif.read_glyph(str(glif_path)) return Glyph( name, height=data.get("height", 0), @@ -400,9 +417,13 @@ def _read_glyph(glif_path: str, name: str) -> Glyph: ) -def _read_layer(layer_path: str) -> Dict[str, Glyph]: - all_data: Dict[str, Dict[str, Any]] = readwrite_ufo_glif.read_layer(layer_path) - return { +def _read_layer(layer_path: str) -> Tuple[Dict[str, Glyph], Dict[str, Any]]: + # all_data: Dict[str, Dict[str, Any]] = pickle.loads(readwrite_ufo_glif.read_layer(layer_path)) + all_data: Dict[str, Dict[str, Any]] + layerinfo: Dict[str, Any] + all_data, layerinfo = readwrite_ufo_glif.read_layer(str(layer_path)) + + glyphs = { name: Glyph( name, height=data.get("height", 0), @@ -424,3 +445,5 @@ def _read_layer(layer_path: str) -> Dict[str, Glyph]: ) for name, data in all_data.items() } + + return glyphs, layerinfo diff --git a/src/ufoLib2/objects/layerSet.py b/src/ufoLib2/objects/layerSet.py index e8a2d744..388fcb35 100644 --- a/src/ufoLib2/objects/layerSet.py +++ b/src/ufoLib2/objects/layerSet.py @@ -1,5 +1,16 @@ from collections import OrderedDict -from typing import AbstractSet, Any, Iterable, Iterator, List, Optional, Sized, Union +from pathlib import Path +from typing import ( + AbstractSet, + Any, + Iterable, + Iterator, + List, + Optional, + Sized, + Tuple, + Union, +) import attr from fontTools.ufoLib import UFOReader, UFOWriter @@ -112,10 +123,13 @@ def read(cls, reader: UFOReader, lazy: bool = True) -> "LayerSet": defaultLayerName = reader.getDefaultLayerName() + base_path = Path(reader.fs.getsyspath(".")) + layer_contents = reader._readLayerContents(validate=True) for layerName in reader.getLayerNames(): isDefault = layerName == defaultLayerName if isDefault or not lazy: - layer = cls._loadLayer(reader, layerName, lazy) + layer_path = base_path / cls._layer_dir(layerName, layer_contents) + layer = cls._loadLayer(layer_path, layerName, lazy) if isDefault: defaultLayer = layer layers[layerName] = layer @@ -138,9 +152,9 @@ def unlazify(self) -> None: __deepcopy__ = _deepcopy_unlazify_attrs @staticmethod - def _loadLayer(reader: UFOReader, layerName: str, lazy: bool = True) -> Layer: - glyphSet = reader.getGlyphSet(layerName) - return Layer.read(layerName, glyphSet, lazy=lazy) + def _loadLayer(layer_path: Path, layerName: str, lazy: bool = True) -> Layer: + # glyphSet = reader.getGlyphSet(layerName) + return Layer.read(layerName, layer_path, lazy=lazy) def loadLayer(self, layerName: str, lazy: bool = True) -> Layer: # XXX: Remove this method and do business via _loadLayer or take this one @@ -148,10 +162,24 @@ def loadLayer(self, layerName: str, lazy: bool = True) -> Layer: assert self._reader is not None if layerName not in self._layers: raise KeyError(layerName) - layer = self._loadLayer(self._reader, layerName, lazy) + base_path = Path(self._reader.fs.getsyspath(".")) + layer_contents = self._reader._readLayerContents(validate=True) + layer_path = base_path / self._layer_dir(layerName, layer_contents) + layer = self._loadLayer(layer_path, layerName, lazy) self._layers[layerName] = layer return layer + @staticmethod + def _layer_dir(layer_name: str, layer_contents: List[Tuple[str, str]]) -> str: + directory = None + for stored_layer_name, stored_layer_directory in layer_contents: + if layer_name == stored_layer_name: + directory = stored_layer_directory + break + if directory is None: + raise Error(f'No glyphs directory is mapped to "{layer_name}".') + return directory + def __contains__(self, name: str) -> bool: return name in self._layers From a381fbbafcda7ae5b6083070346686dc8ad27b68 Mon Sep 17 00:00:00 2001 From: Nikolaus Waxweiler Date: Tue, 15 Jun 2021 23:35:16 +0100 Subject: [PATCH 5/5] Fix dict destructuring oopsie --- src/ufoLib2/objects/layer.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/ufoLib2/objects/layer.py b/src/ufoLib2/objects/layer.py index 047c176f..9cc84205 100644 --- a/src/ufoLib2/objects/layer.py +++ b/src/ufoLib2/objects/layer.py @@ -153,8 +153,10 @@ def read(cls, name: str, path: Path, lazy: bool = True) -> "Layer": self = cls(name, glyphs, color=color, lib=lib) self._glyphSet = glyphset else: - glyphs, (color, lib) = _read_layer(path) - self = cls(name, glyphs, color=color, lib=lib) + glyphs, layerinfo = _read_layer(path) + self = cls( + name, glyphs, color=layerinfo.get("color"), lib=layerinfo.get("lib") + ) return self