from fontParts.base.base import BaseDict, dynamicProperty, reference
from fontParts.base import normalizers
from fontParts.base.deprecated import DeprecatedGroups, RemovedGroups
[docs]class BaseGroups(BaseDict, DeprecatedGroups, RemovedGroups):
"""
A Groups object. This object normally created as part of a
:class:`BaseFont`. An orphan Groups object can be created like this::
>>> groups = RGroups()
This object behaves like a Python dictionary. Most of the dictionary
functionality comes from :class:`BaseDict`, look at that object for the
required environment implementation details.
Groups uses :func:`normalizers.normalizeGroupKey` to normalize the key of
the ``dict``, and :func:`normalizers.normalizeGroupValue` to normalize the
value of the ``dict``.
"""
keyNormalizer = normalizers.normalizeGroupKey
valueNormalizer = normalizers.normalizeGroupValue
def _reprContents(self):
contents = []
if self.font is not None:
contents.append("for font")
contents += self.font._reprContents()
return contents
# -------
# Parents
# -------
# Font
_font = None
font = dynamicProperty("font", "The Groups' parent :class:`BaseFont`.")
def _get_font(self):
if self._font is None:
return None
return self._font()
def _set_font(self, font):
if self._font is not None and self._font != font:
raise AssertionError("font for groups already set and is not same as font")
if font is not None:
font = reference(font)
self._font = font
# ---------
# Searching
# ---------
[docs] def findGlyph(self, glyphName):
"""
Returns a ``list`` of the group or groups associated with
**glyphName**.
**glyphName** will be an :ref:`type-string`. If no group is found
to contain **glyphName** an empty ``list`` will be returned. ::
>>> font.groups.findGlyph("A")
["A_accented"]
"""
glyphName = normalizers.normalizeGlyphName(glyphName)
groupNames = self._findGlyph(glyphName)
groupNames = [self.keyNormalizer.__func__(
groupName) for groupName in groupNames]
return groupNames
[docs] def _findGlyph(self, glyphName):
"""
This is the environment implementation of
:meth:`BaseGroups.findGlyph`. **glyphName** will be
an :ref:`type-string`.
Subclasses may override this method.
"""
found = []
for key, groupList in self.items():
if glyphName in groupList:
found.append(key)
return found
# --------------
# Kerning Groups
# --------------
side1KerningGroups = dynamicProperty(
"base_side1KerningGroups",
"""
All groups marked as potential side 1
kerning members.
>>> side1Groups = groups.side1KerningGroups
The value will be a :ref:`dict` with
:ref:`string` keys representing group names
and :ref:`tuple` contaning glyph names.
"""
)
def _get_base_side1KerningGroups(self):
kerningGroups = self._get_side1KerningGroups()
normalized = {}
for name, members in kerningGroups.items():
name = normalizers.normalizeGroupKey(name)
members = normalizers.normalizeGroupValue(members)
normalized[name] = members
return normalized
def _get_side1KerningGroups(self):
"""
Subclasses may override this method.
"""
found = {}
for name, contents in self.items():
if name.startswith("public.kern1."):
found[name] = contents
return found
side2KerningGroups = dynamicProperty(
"base_side2KerningGroups",
"""
All groups marked as potential side 1
kerning members.
>>> side2Groups = groups.side2KerningGroups
The value will be a :ref:`dict` with
:ref:`string` keys representing group names
and :ref:`tuple` contaning glyph names.
"""
)
def _get_base_side2KerningGroups(self):
kerningGroups = self._get_side2KerningGroups()
normalized = {}
for name, members in kerningGroups.items():
name = normalizers.normalizeGroupKey(name)
members = normalizers.normalizeGroupValue(members)
normalized[name] = members
return normalized
def _get_side2KerningGroups(self):
"""
Subclasses may override this method.
"""
found = {}
for name, contents in self.items():
if name.startswith("public.kern2."):
found[name] = contents
return found
# ---------------------
# RoboFab Compatibility
# ---------------------
def remove(self, groupName):
"""
Removes a group from the Groups. **groupName** will be
a :ref:`type-string` that is the group name to
be removed.
This is a backwards compatibility method.
"""
del self[groupName]
def asDict(self):
"""
Return the Groups as a ``dict``.
This is a backwards compatibility method.
"""
d = {}
for k, v in self.items():
d[k] = v
return d
# -------------------
# Inherited Functions
# -------------------
[docs] def __contains__(self, groupName):
"""
Tests to see if a group name is in the Groups.
**groupName** will be a :ref:`type-string`.
This returns a ``bool`` indicating if the **groupName**
is in the Groups. ::
>>> "myGroup" in font.groups
True
"""
return super(BaseGroups, self).__contains__(groupName)
[docs] def __delitem__(self, groupName):
"""
Removes **groupName** from the Groups. **groupName** is a
:ref:`type-string`.::
>>> del font.groups["myGroup"]
"""
super(BaseGroups, self).__delitem__(groupName)
[docs] def __getitem__(self, groupName):
"""
Returns the contents of the named group. **groupName** is a
:ref:`type-string`. The returned value will be a
:ref:`type-immutable-list` of the group contents.::
>>> font.groups["myGroup"]
("A", "B", "C")
It is important to understand that any changes to the returned group
contents will not be reflected in the Groups object. If one wants to
make a change to the group contents, one should do the following::
>>> group = font.groups["myGroup"]
>>> group.remove("A")
>>> font.groups["myGroup"] = group
"""
return super(BaseGroups, self).__getitem__(groupName)
[docs] def __iter__(self):
"""
Iterates through the Groups, giving the key for each iteration. The
order that the Groups will iterate though is not fixed nor is it
ordered.::
>>> for groupName in font.groups:
>>> print groupName
"myGroup"
"myGroup3"
"myGroup2"
"""
return super(BaseGroups, self).__iter__()
[docs] def __len__(self):
"""
Returns the number of groups in Groups as an ``int``.::
>>> len(font.groups)
5
"""
return super(BaseGroups, self).__len__()
[docs] def __setitem__(self, groupName, glyphNames):
"""
Sets the **groupName** to the list of **glyphNames**. **groupName**
is the group name as a :ref:`type-string` and **glyphNames** is a
``list`` of glyph names as :ref:`type-string`.
>>> font.groups["myGroup"] = ["A", "B", "C"]
"""
super(BaseGroups, self).__setitem__(groupName, glyphNames)
[docs] def clear(self):
"""
Removes all group information from Groups,
resetting the Groups to an empty dictionary. ::
>>> font.groups.clear()
"""
super(BaseGroups, self).clear()
[docs] def get(self, groupName, default=None):
"""
Returns the contents of the named group.
**groupName** is a :ref:`type-string`, and the returned values will
either be :ref:`type-immutable-list` of group contents or ``None``
if no group was found. ::
>>> font.groups["myGroup"]
("A", "B", "C")
It is important to understand that any changes to the returned group
contents will not be reflected in the Groups object. If one wants to
make a change to the group contents, one should do the following::
>>> group = font.groups["myGroup"]
>>> group.remove("A")
>>> font.groups["myGroup"] = group
"""
return super(BaseGroups, self).get(groupName, default)
[docs] def items(self):
"""
Returns a list of ``tuple`` of each group name and group members.
Group names are :ref:`type-string` and group members are a
:ref:`type-immutable-list` of :ref:`type-string`. The initial
list will be unordered.
>>> font.groups.items()
[("myGroup", ("A", "B", "C"), ("myGroup2", ("D", "E", "F"))]
"""
return super(BaseGroups, self).items()
[docs] def keys(self):
"""
Returns a ``list`` of all the group names in Groups. This list will be
unordered.::
>>> font.groups.keys()
["myGroup4", "myGroup1", "myGroup5"]
"""
return super(BaseGroups, self).keys()
[docs] def pop(self, groupName, default=None):
"""
Removes the **groupName** from the Groups and returns the list of
group members. If no group is found, **default** is returned.
**groupName** is a :ref:`type-string`. This must return either
**default** or a :ref:`type-immutable-list` of glyph names as
:ref:`type-string`.
>>> font.groups.pop("myGroup")
("A", "B", "C")
"""
return super(BaseGroups, self).pop(groupName, default)
[docs] def update(self, otherGroups):
"""
Updates the Groups based on **otherGroups**. *otherGroups** is a
``dict`` of groups information. If a group from **otherGroups** is in
Groups, the group members will be replaced by the group members from
**otherGroups**. If a group from **otherGroups** is not in the Groups,
it is added to the Groups. If Groups contain a group name that is not
in *otherGroups**, it is not changed.
>>> font.groups.update(newGroups)
"""
super(BaseGroups, self).update(otherGroups)
[docs] def values(self):
"""
Returns a ``list`` of each named group's members.
This will be a list of lists, the group members will be a
:ref:`type-immutable-list` of :ref:`type-string`. The initial
list will be unordered.
>>> font.groups.items()
[("A", "B", "C"), ("D", "E", "F")]
"""
return super(BaseGroups, self).values()