classes.c16_glyph_inspector
1import drawBot 2from fontTools.ttLib import TTFont 3from fontTools.pens.basePen import BasePen 4from fontTools.pens.boundsPen import BoundsPen 5from typing import TypedDict, Any, Callable 6from loguru import logger 7from datatypes import DLineStyle 8from lib import graphics 9 10 11class TTGlyph(TypedDict): 12 """A simplified representation of a TrueType glyph for inspection purposes. This is not a full implementation of a TTGlyph but includes key properties.""" 13 14 name: str 15 width: int 16 height: int | None 17 lsb: int 18 tsb: int | None 19 draw: Callable 20 recalcBounds: bool 21 glyphSet: Any # fontTools glyph set object (private type in practice) 22 23 24def setFill(color=(0,)): 25 drawBot.fill(*color) 26 drawBot.stroke(None) 27 28 29def setStroke(width=0.5, pattern: graphics.LinePattern = "solid"): 30 DLineStyle( 31 strokeWidth=width, 32 strokeColor=(0,), 33 lineDash=5 if pattern == "dashed" else None, 34 lineCap="butt", 35 clearFill=True, 36 ).apply() 37 38 39class ShowBeziersPen(BasePen): 40 upscale = lambda self, value: value / self.scale 41 42 def __init__( 43 self, 44 glyphSet: TTGlyph, 45 anchorSize: float, 46 handleSize: float, 47 glyphStroke: float, 48 handleStroke: float, 49 scale: float = 1, 50 ): 51 super().__init__(glyphSet) 52 self.prevPt = None 53 self.scale = scale 54 self.anchorSize = anchorSize 55 self.handleSize = handleSize 56 self.glyphStroke = glyphStroke 57 self.handleStroke = handleStroke 58 59 @property 60 def _anchorSize(self): 61 return self.upscale(self.anchorSize) 62 63 @property 64 def _handleSize(self): 65 return self.upscale(self.handleSize) 66 67 def _drawAnchor(self, pt): 68 setFill() 69 x, y = pt 70 drawBot.rect( 71 x - self._anchorSize / 2, 72 y - self._anchorSize / 2, 73 self._anchorSize, 74 self._anchorSize, 75 ) 76 77 def _drawHandle(self, pt): 78 setFill() 79 x, y = pt 80 drawBot.oval( 81 x - self._handleSize / 2, 82 y - self._handleSize / 2, 83 self._handleSize, 84 self._handleSize, 85 ) 86 87 def _moveTo(self, pt): 88 self._drawAnchor(pt) 89 self.prevPt = pt 90 91 def _lineTo(self, pt): 92 self._drawAnchor(pt) 93 self.prevPt = pt 94 95 def _curveToOne(self, pt1, pt2, pt3): 96 # Handle lines 97 setStroke(self.upscale(self.handleStroke)) 98 drawBot.line(pt1, self.prevPt) 99 drawBot.line(pt2, pt3) 100 101 [self._drawHandle(pt) for pt in [pt1, pt2]] 102 self._drawAnchor(pt3) 103 self.prevPt = pt3 104 105 # Custom method to render the glyph with both outline and bezier handles 106 def render(self, glyph: TTGlyph): 107 with drawBot.savedState(): 108 drawBot.scale(self.scale) 109 110 # Draw glyph outline first 111 setStroke(self.upscale(self.glyphStroke)) 112 glyphPath = drawBot.BezierPath(glyphSet=glyph) 113 glyph.draw(glyphPath) 114 drawBot.drawPath(glyphPath) 115 116 # Draw bezier handles on top 117 glyph.draw(self) 118 119 120class KGlyphInspector: 121 TTGlyph = TTGlyph # Expose type alias as a class attribute for external use 122 123 """Inspect and draw font glyphs in DrawBot at a target point size.""" 124 125 def __init__(self, fontPath: str, fontSize: int): 126 """Load a font and prepare glyph access for the requested size. 127 128 Args: 129 fontPath: Absolute path to a font file. 130 fontSize: Target drawing size in points. 131 132 Notes: 133 `self.scale` converts glyph-space units (UPM) to page-space points. 134 """ 135 self.fontFile = TTFont(fontPath) 136 137 self.scale = float(fontSize / self.fontFile["head"].unitsPerEm) 138 139 self._cmap = self.fontFile.getBestCmap() 140 self._glyphSet = self.fontFile.getGlyphSet() 141 142 def getGlyphs(self, text: str) -> list[TTGlyph]: 143 """Return glyph objects for each character in a text string. 144 145 Args: 146 text: Text to convert into ordered glyphs. 147 148 Returns: 149 Glyph list in the same order as the input text. 150 151 Raises: 152 KeyError: If a character is missing from the font cmap. 153 """ 154 try: 155 chars = list(text) 156 glyphNames = [self._cmap[ord(char)] for char in chars] 157 return [self._glyphSet[name] for name in glyphNames] 158 except KeyError as e: 159 logger.error(f"Character '{e.args[0]}' not found in font's cmap.") 160 raise 161 162 def calcGlyphBounds(self, glyph: TTGlyph) -> tuple[int, int, int, int]: 163 """Measure raw glyph bounds in glyph-space units. 164 165 Useful when you need geometric extents for alignment or debugging. 166 """ 167 boundsPen = BoundsPen(glyph) 168 glyph.draw(boundsPen) 169 return boundsPen.bounds 170 171 def drawGlyph(self, glyph: TTGlyph): 172 """Draw a filled glyph outline using the inspector's scale.""" 173 with drawBot.savedState(): 174 drawBot.scale(self.scale) 175 glyphPath = drawBot.BezierPath(glyphSet=glyph) 176 glyph.draw(glyphPath) 177 drawBot.drawPath(glyphPath) 178 179 def drawGlyphBeziers( 180 self, 181 glyph: TTGlyph, 182 anchorSize: float = 5, 183 handleSize: float = 5, 184 glyphStroke: float = 1, 185 handleStroke: float = 0.5, 186 ): 187 """Draw glyph outline plus bezier anchors/handles for inspection. 188 189 Args: 190 glyph: Glyph object from `getGlyphs`. 191 anchorSize: Anchor marker size in page points. 192 handleSize: Handle marker size in page points. 193 glyphStroke: Outline stroke width in page points. 194 handleStroke: Handle line stroke width in page points. 195 """ 196 beziersPen = ShowBeziersPen( 197 glyph, 198 anchorSize=anchorSize, 199 handleSize=handleSize, 200 glyphStroke=glyphStroke, 201 handleStroke=handleStroke, 202 scale=self.scale, 203 ) 204 beziersPen.render(glyph)
12class TTGlyph(TypedDict): 13 """A simplified representation of a TrueType glyph for inspection purposes. This is not a full implementation of a TTGlyph but includes key properties.""" 14 15 name: str 16 width: int 17 height: int | None 18 lsb: int 19 tsb: int | None 20 draw: Callable 21 recalcBounds: bool 22 glyphSet: Any # fontTools glyph set object (private type in practice)
A simplified representation of a TrueType glyph for inspection purposes. This is not a full implementation of a TTGlyph but includes key properties.
40class ShowBeziersPen(BasePen): 41 upscale = lambda self, value: value / self.scale 42 43 def __init__( 44 self, 45 glyphSet: TTGlyph, 46 anchorSize: float, 47 handleSize: float, 48 glyphStroke: float, 49 handleStroke: float, 50 scale: float = 1, 51 ): 52 super().__init__(glyphSet) 53 self.prevPt = None 54 self.scale = scale 55 self.anchorSize = anchorSize 56 self.handleSize = handleSize 57 self.glyphStroke = glyphStroke 58 self.handleStroke = handleStroke 59 60 @property 61 def _anchorSize(self): 62 return self.upscale(self.anchorSize) 63 64 @property 65 def _handleSize(self): 66 return self.upscale(self.handleSize) 67 68 def _drawAnchor(self, pt): 69 setFill() 70 x, y = pt 71 drawBot.rect( 72 x - self._anchorSize / 2, 73 y - self._anchorSize / 2, 74 self._anchorSize, 75 self._anchorSize, 76 ) 77 78 def _drawHandle(self, pt): 79 setFill() 80 x, y = pt 81 drawBot.oval( 82 x - self._handleSize / 2, 83 y - self._handleSize / 2, 84 self._handleSize, 85 self._handleSize, 86 ) 87 88 def _moveTo(self, pt): 89 self._drawAnchor(pt) 90 self.prevPt = pt 91 92 def _lineTo(self, pt): 93 self._drawAnchor(pt) 94 self.prevPt = pt 95 96 def _curveToOne(self, pt1, pt2, pt3): 97 # Handle lines 98 setStroke(self.upscale(self.handleStroke)) 99 drawBot.line(pt1, self.prevPt) 100 drawBot.line(pt2, pt3) 101 102 [self._drawHandle(pt) for pt in [pt1, pt2]] 103 self._drawAnchor(pt3) 104 self.prevPt = pt3 105 106 # Custom method to render the glyph with both outline and bezier handles 107 def render(self, glyph: TTGlyph): 108 with drawBot.savedState(): 109 drawBot.scale(self.scale) 110 111 # Draw glyph outline first 112 setStroke(self.upscale(self.glyphStroke)) 113 glyphPath = drawBot.BezierPath(glyphSet=glyph) 114 glyph.draw(glyphPath) 115 drawBot.drawPath(glyphPath) 116 117 # Draw bezier handles on top 118 glyph.draw(self)
Base class for drawing pens. You must override _moveTo, _lineTo and _curveToOne. You may additionally override _closePath, _endPath, addComponent, addVarComponent, and/or _qCurveToOne. You should not override any other methods.
43 def __init__( 44 self, 45 glyphSet: TTGlyph, 46 anchorSize: float, 47 handleSize: float, 48 glyphStroke: float, 49 handleStroke: float, 50 scale: float = 1, 51 ): 52 super().__init__(glyphSet) 53 self.prevPt = None 54 self.scale = scale 55 self.anchorSize = anchorSize 56 self.handleSize = handleSize 57 self.glyphStroke = glyphStroke 58 self.handleStroke = handleStroke
Takes a 'glyphSet' argument (dict), in which the glyphs that are referenced as components are looked up by their name.
If the optional 'reverseFlipped' argument is True, components whose transformation matrix has a negative determinant will be decomposed with a reversed path direction to compensate for the flip.
The optional 'skipMissingComponents' argument can be set to True/False to override the homonymous class attribute for a given pen instance.
107 def render(self, glyph: TTGlyph): 108 with drawBot.savedState(): 109 drawBot.scale(self.scale) 110 111 # Draw glyph outline first 112 setStroke(self.upscale(self.glyphStroke)) 113 glyphPath = drawBot.BezierPath(glyphSet=glyph) 114 glyph.draw(glyphPath) 115 drawBot.drawPath(glyphPath) 116 117 # Draw bezier handles on top 118 glyph.draw(self)
121class KGlyphInspector: 122 TTGlyph = TTGlyph # Expose type alias as a class attribute for external use 123 124 """Inspect and draw font glyphs in DrawBot at a target point size.""" 125 126 def __init__(self, fontPath: str, fontSize: int): 127 """Load a font and prepare glyph access for the requested size. 128 129 Args: 130 fontPath: Absolute path to a font file. 131 fontSize: Target drawing size in points. 132 133 Notes: 134 `self.scale` converts glyph-space units (UPM) to page-space points. 135 """ 136 self.fontFile = TTFont(fontPath) 137 138 self.scale = float(fontSize / self.fontFile["head"].unitsPerEm) 139 140 self._cmap = self.fontFile.getBestCmap() 141 self._glyphSet = self.fontFile.getGlyphSet() 142 143 def getGlyphs(self, text: str) -> list[TTGlyph]: 144 """Return glyph objects for each character in a text string. 145 146 Args: 147 text: Text to convert into ordered glyphs. 148 149 Returns: 150 Glyph list in the same order as the input text. 151 152 Raises: 153 KeyError: If a character is missing from the font cmap. 154 """ 155 try: 156 chars = list(text) 157 glyphNames = [self._cmap[ord(char)] for char in chars] 158 return [self._glyphSet[name] for name in glyphNames] 159 except KeyError as e: 160 logger.error(f"Character '{e.args[0]}' not found in font's cmap.") 161 raise 162 163 def calcGlyphBounds(self, glyph: TTGlyph) -> tuple[int, int, int, int]: 164 """Measure raw glyph bounds in glyph-space units. 165 166 Useful when you need geometric extents for alignment or debugging. 167 """ 168 boundsPen = BoundsPen(glyph) 169 glyph.draw(boundsPen) 170 return boundsPen.bounds 171 172 def drawGlyph(self, glyph: TTGlyph): 173 """Draw a filled glyph outline using the inspector's scale.""" 174 with drawBot.savedState(): 175 drawBot.scale(self.scale) 176 glyphPath = drawBot.BezierPath(glyphSet=glyph) 177 glyph.draw(glyphPath) 178 drawBot.drawPath(glyphPath) 179 180 def drawGlyphBeziers( 181 self, 182 glyph: TTGlyph, 183 anchorSize: float = 5, 184 handleSize: float = 5, 185 glyphStroke: float = 1, 186 handleStroke: float = 0.5, 187 ): 188 """Draw glyph outline plus bezier anchors/handles for inspection. 189 190 Args: 191 glyph: Glyph object from `getGlyphs`. 192 anchorSize: Anchor marker size in page points. 193 handleSize: Handle marker size in page points. 194 glyphStroke: Outline stroke width in page points. 195 handleStroke: Handle line stroke width in page points. 196 """ 197 beziersPen = ShowBeziersPen( 198 glyph, 199 anchorSize=anchorSize, 200 handleSize=handleSize, 201 glyphStroke=glyphStroke, 202 handleStroke=handleStroke, 203 scale=self.scale, 204 ) 205 beziersPen.render(glyph)
126 def __init__(self, fontPath: str, fontSize: int): 127 """Load a font and prepare glyph access for the requested size. 128 129 Args: 130 fontPath: Absolute path to a font file. 131 fontSize: Target drawing size in points. 132 133 Notes: 134 `self.scale` converts glyph-space units (UPM) to page-space points. 135 """ 136 self.fontFile = TTFont(fontPath) 137 138 self.scale = float(fontSize / self.fontFile["head"].unitsPerEm) 139 140 self._cmap = self.fontFile.getBestCmap() 141 self._glyphSet = self.fontFile.getGlyphSet()
Load a font and prepare glyph access for the requested size.
Arguments:
- fontPath: Absolute path to a font file.
- fontSize: Target drawing size in points.
Notes:
self.scaleconverts glyph-space units (UPM) to page-space points.
143 def getGlyphs(self, text: str) -> list[TTGlyph]: 144 """Return glyph objects for each character in a text string. 145 146 Args: 147 text: Text to convert into ordered glyphs. 148 149 Returns: 150 Glyph list in the same order as the input text. 151 152 Raises: 153 KeyError: If a character is missing from the font cmap. 154 """ 155 try: 156 chars = list(text) 157 glyphNames = [self._cmap[ord(char)] for char in chars] 158 return [self._glyphSet[name] for name in glyphNames] 159 except KeyError as e: 160 logger.error(f"Character '{e.args[0]}' not found in font's cmap.") 161 raise
Return glyph objects for each character in a text string.
Arguments:
- text: Text to convert into ordered glyphs.
Returns:
Glyph list in the same order as the input text.
Raises:
- KeyError: If a character is missing from the font cmap.
163 def calcGlyphBounds(self, glyph: TTGlyph) -> tuple[int, int, int, int]: 164 """Measure raw glyph bounds in glyph-space units. 165 166 Useful when you need geometric extents for alignment or debugging. 167 """ 168 boundsPen = BoundsPen(glyph) 169 glyph.draw(boundsPen) 170 return boundsPen.bounds
Measure raw glyph bounds in glyph-space units.
Useful when you need geometric extents for alignment or debugging.
172 def drawGlyph(self, glyph: TTGlyph): 173 """Draw a filled glyph outline using the inspector's scale.""" 174 with drawBot.savedState(): 175 drawBot.scale(self.scale) 176 glyphPath = drawBot.BezierPath(glyphSet=glyph) 177 glyph.draw(glyphPath) 178 drawBot.drawPath(glyphPath)
Draw a filled glyph outline using the inspector's scale.
180 def drawGlyphBeziers( 181 self, 182 glyph: TTGlyph, 183 anchorSize: float = 5, 184 handleSize: float = 5, 185 glyphStroke: float = 1, 186 handleStroke: float = 0.5, 187 ): 188 """Draw glyph outline plus bezier anchors/handles for inspection. 189 190 Args: 191 glyph: Glyph object from `getGlyphs`. 192 anchorSize: Anchor marker size in page points. 193 handleSize: Handle marker size in page points. 194 glyphStroke: Outline stroke width in page points. 195 handleStroke: Handle line stroke width in page points. 196 """ 197 beziersPen = ShowBeziersPen( 198 glyph, 199 anchorSize=anchorSize, 200 handleSize=handleSize, 201 glyphStroke=glyphStroke, 202 handleStroke=handleStroke, 203 scale=self.scale, 204 ) 205 beziersPen.render(glyph)
Draw glyph outline plus bezier anchors/handles for inspection.
Arguments:
- glyph: Glyph object from
getGlyphs. - anchorSize: Anchor marker size in page points.
- handleSize: Handle marker size in page points.
- glyphStroke: Outline stroke width in page points.
- handleStroke: Handle line stroke width in page points.
12class TTGlyph(TypedDict): 13 """A simplified representation of a TrueType glyph for inspection purposes. This is not a full implementation of a TTGlyph but includes key properties.""" 14 15 name: str 16 width: int 17 height: int | None 18 lsb: int 19 tsb: int | None 20 draw: Callable 21 recalcBounds: bool 22 glyphSet: Any # fontTools glyph set object (private type in practice)
Inspect and draw font glyphs in DrawBot at a target point size.