typoman/fontgadgets
GitHub: typoman/fontgadgets
一个通过猴子补丁技术扩展 defcon 和 FontParts 对象的 Python 库,为字体设计工作流提供高级操作方法和便捷的自定义扩展机制。
Stars: 3 | Forks: 0
# FontGadgets
[许可证](https://github.com/typoman/fontgadgets/blob/main/LICENSE)
一个通过高级方法扩展 `defcon` 和 `FontParts` 对象能力的库。
## 为什么?
你是否厌倦了为每个字体项目编写或复制相同的函数?FontGadgets 将常见的高级字体操作任务集中起来,直接附加到你已经使用的 defcon 和 FontParts 对象上。这能让你的脚本更整洁,工作流程更有条理。我也开发了一些用于处理复杂字体的工具,其中一些工具共享相同的功能。所以我决定将所有内容放在一个地方。
## 什么是 FontGadgets?
FontGadgets 是一个 Python 包,它使用一种称为“猴子补丁(monkey patching)”的技术向 `defcon` 和 `FontParts` 的核心对象添加新功能或“扩展”。这种方法允许在不修改这些库的原始源代码的情况下添加这些新方法。我将其视为我在提出新想法或添加内容之前扩展其能力的演练场。
## 安装
要以可编辑模式从代码库安装最新版本,请按照以下步骤操作:
```
git clone https://github.com/typoman/fontgadgets
cd fontgadgets
pip3 install -e .
```
## 添加你自己的扩展
你可以使用 FontGadgets 提供的装饰器,轻松地向 `defcon` 和 `FontParts` 对象添加你自己的方法。函数的第一个参数应始终是你打算修改的对象的实例(例如 `font`、`glyph`)。
### 可用的装饰器
* `@font_method`:向类添加常规方法。
* `@font_property`:向类添加只读动态属性(property)。
* `@font_property_setter`:为现有属性添加 setter。
* `@font_cached_method`:添加结果被缓存的方法。缓存会根据你添加方法的对象所拥有的破坏性通知(destructive notifications)进行失效。例如,要查找字形对象的破坏性通知,可以运行 `help(defcon.Glyph)`。
* `@font_cached_property`:添加缓存属性。你还需要像 `font_cached_method` 一样将破坏性通知列表作为参数传递给装饰器。
### 示例
#### 添加动态属性
以下是如何向字形对象添加 `isComposite` 属性,如果字形是复合字形,该属性将为 `True`。
```
from fontgadgets.decorators import font_property
@font_property
def isComposite(glyph):
"""
Returns True if the glyph is a composite, False otherwise.
"""
return len(glyph.contours) == 0 and len(glyph.components) > 0
```
现在,你可以在任何字形对象上将其作为属性访问:
```
>>> myGlyph.isComposite
True
```
#### 添加缓存方法
对于性能消耗较大的操作,可以使用缓存方法。以下示例创建字形的描边版本。结果会被缓存,并且只有在字形的轮廓或组件发生变化时才会重新计算。
```
from fontgadgets.decorators import font_cached_method
from drawBot import BezierPath
from defcon import Glyph
from fontTools.pens.cocoaPen import CocoaPen
@font_cached_method("Glyph.ContoursChanged", "Glyph.ComponentsChanged", "Component.BaseGlyphChanged") # You can find the list of notifications from each defcon object by using for example, help(defcon.Glyph)
def getStroked(glyph, strokeWidth):
"""
Returns a stroked copy of the glyph with the defined stroke width.
"""
print("Executing the stroking logic...")
pen = CocoaPen(glyph.font)
glyph.draw(pen)
bezierPath = BezierPath(pen.path)
bezierPath.removeOverlap()
newPath = bezierPath.expandStroke(strokeWidth).removeOverlap()
union = newPath.union(bezierPath)
result = Glyph()
pen = result.getPen()
union.drawToPen(pen)
return result
# 为了确保缓存持久化,请在单独的 module/file 中运行此代码:
g = font['a']
g.getStroked(10)
# 打印了 "Executing the stroking logic..."
g.getStroked(10)
# 该消息未打印,因为缓存结果被立即返回,且函数体未被执行。
```
### 高级用法:类型提示
你可以使用 Python 的类型提示来控制将方法添加到哪个库(`defcon` 或 `FontParts`)。
#### 定位特定对象
要仅向 `fontParts.fontshell.RSegment` 对象添加方法(`defcon` 没有轮廓段对象),你可以执行以下操作:
```
from fontgadgets.decorators import font_method
from fontParts.fontshell import RSegment
@font_method
def myFunction(segment: RSegment):
# This logic will only be available on RSegment objects
pass
```
#### 控制返回类型
如果你的方法返回一个 `defcon` 对象,当从 `FontParts` 实例调用该方法时,FontGadgets 可以自动将其包装在相应的 `FontParts` 对象中。你只需要在函数定义中添加返回类型提示。
```
from fontgadgets.decorators import font_method
import defcon
@font_method
def subset(font) -> defcon.Font: # Note the return type hint
# ... subsetting logic that returns a new defcon.Font
return new_defcon_font
```
当你在 `RFont` 对象上调用 `my_rfont_object.subset()` 时,返回的 `defcon.Font` 将自动转换为 `fontParts.fontshell.RFont` 对象。
### 扩展先前的实现
有时你想完全替换类上已经存在的方法,但仍保留旧的实现。你的新函数会接收到一个名为 `original` 的对象,其行为与旧方法完全一样:
```
from fontgadgets import patch
@patch.method(MyClass)
def draw(self, pen):
original().draw(pen) # call the existing implementation
# now do other things you want to add to the previous mechanism
```
如果你只想*添加*一个全新的方法,并且绝不触及任何已存在的内容,请设置 `override=False`。任何冲突都会引发错误(或者如果你已经设置了 `patch.DEBUG=True`,则只会发出警告),这样你就不会意外地覆盖某些内容。
### 替换或扩展属性
同样的场景也适用于属性。替换掉 getter、setter 或两者,并仍然通过 `original()` 访问原始实现:
```
from fontgadgets import patch
@patch.property_getter(MyClass, property_name='width')
def _width_getter(self):
return round(original().width / 10) * 10
@patch.property_setter(MyClass, property_name='width')
def _width_setter(self, value):
original().width = value
```
同样,如果你只想提供 getter 或 setter 而不替换任何现有属性,请在装饰器的参数中使用 `override=False`;或者当你打算替换已经存在的属性时,保留它(默认值为 `True`)。
## 可用的扩展
FontGadgets 已经附带预构建的扩展。以下是一些可用的模块及其添加的功能:
- Features:编译字距(kerning)和标记(mark)特性,重命名以及子集化特性。
- Font:缩放、子集化。
- Unicode:Unicode 属性(脚本、方向)。
- Glyph:布尔运算、在字形之间复制数据。
- Kerning:使用以字形为中心的 API 管理字距。
- Groups:使用以字形为中心的 API 操作字距分组。
- Interpolation:应用字形交换规则生成实例。
- Git:选择性地将字形数据(例如轮廓、宽度等)还原到某个 git commit。
## 警告
该软件包目前处于开发的 alpha 阶段。虽然它足够稳定,可以向字体对象添加方法,但公共 API 可能会在未来的版本中发生变化。请在代码库的 issue tracker 中报告你遇到的任何问题。
标签:API, defcon, FontParts, Monkey Patching, Python, ufo, 元编程, 威胁情报, 字体修改, 字体设计, 字形, 工作流优化, 工具集, 开发者工具, 开源库, 扩展库, 排版, 搜索引擎爬虫, 无后门, 类型设计, 缓存, 网络调试, 网络连接监控, 自动化, 逆向工具, 面向对象编程