From e08c7be13c2c69a3f3c23c437bc7ecf3fada52ce Mon Sep 17 00:00:00 2001 From: Simon Cozens Date: Thu, 3 Mar 2022 12:23:20 +0000 Subject: [PATCH 1/4] Line drawing function --- Lib/fontgoggles/mac/drawing.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/Lib/fontgoggles/mac/drawing.py b/Lib/fontgoggles/mac/drawing.py index 3212ce3b..b7bccdb9 100644 --- a/Lib/fontgoggles/mac/drawing.py +++ b/Lib/fontgoggles/mac/drawing.py @@ -59,3 +59,12 @@ def drawText(txt, pt, color, font): attrs = {AppKit.NSFontAttributeName: font, AppKit.NSForegroundColorAttributeName: color} AppKit.NSString.drawAtPoint_withAttributes_(txt, pt, attrs) + + +def drawLine(pt1, pt2, color, width): + line = AppKit.NSBezierPath.bezierPath() + line.moveToPoint_(AppKit.NSMakePoint(*pt1)) + line.lineToPoint_(AppKit.NSMakePoint(*pt2)) + line.setLineWidth_(width) + nsColorFromRGBA(color).set() + line.stroke() From 30029c30e46bd447c6e95a7a6c4815a1eef2ca31 Mon Sep 17 00:00:00 2001 From: Simon Cozens Date: Thu, 3 Mar 2022 12:25:30 +0000 Subject: [PATCH 2/4] Report metrics for each kind of font --- Lib/fontgoggles/font/baseFont.py | 23 +++++++++++++++++++++-- Lib/fontgoggles/font/dsFont.py | 17 +++++++++++++---- Lib/fontgoggles/font/ufoFont.py | 15 ++++++++++++--- 3 files changed, 46 insertions(+), 9 deletions(-) diff --git a/Lib/fontgoggles/font/baseFont.py b/Lib/fontgoggles/font/baseFont.py index d6f4c8ab..ea672406 100644 --- a/Lib/fontgoggles/font/baseFont.py +++ b/Lib/fontgoggles/font/baseFont.py @@ -1,8 +1,17 @@ +from typing import Any, NamedTuple + from ..misc.properties import cachedProperty from ..misc.hbShape import characterGlyphMapping from . import mergeScriptsAndLanguages +class FontMetrics(NamedTuple): + xHeight: int + capHeight: int + ascender: int + descender: int + + class BaseFont: def __init__(self, fontPath, fontNumber, dataProvider=None): @@ -53,6 +62,15 @@ def canReloadWithChange(self, externalFilePath): def unitsPerEm(self): return self.ttFont["head"].unitsPerEm + @cachedProperty + def fontMetrics(self): + return FontMetrics( + xHeight=self.ttFont["OS/2"].sxHeight, + capHeight=self.ttFont["OS/2"].sCapHeight, + ascender=self.ttFont["hhea"].ascent, + descender=self.ttFont["hhea"].descent, + ) + @cachedProperty def colorPalettes(self): return None @@ -102,7 +120,7 @@ def getGlyphRunFromTextInfo(self, textInfo, colorPalettesIndex=0, **kwargs): else: colorPalette = self.colorPalettes[colorPalettesIndex] - glyphs = GlyphsRun(len(text), self.unitsPerEm, direction in ("TTB", "BTT"), colorPalette) + glyphs = GlyphsRun(len(text), self.unitsPerEm, direction in ("TTB", "BTT"), self.fontMetrics, colorPalette) for segmentText, segmentScript, segmentBiDiLevel, firstCluster in textInfo.segments: if script is not None: @@ -172,10 +190,11 @@ def varLocationChanged(self, varLocation): class GlyphsRun(list): - def __init__(self, numChars, unitsPerEm, vertical, colorPalette=None): + def __init__(self, numChars, unitsPerEm, vertical, fontMetrics, colorPalette=None): self.numChars = numChars self.unitsPerEm = unitsPerEm self.vertical = vertical + self.fontMetrics = fontMetrics self._glyphToChars = None self._charToGlyphs = None self.endPos = (0, 0) diff --git a/Lib/fontgoggles/font/dsFont.py b/Lib/fontgoggles/font/dsFont.py index 81dc1974..b6225d24 100644 --- a/Lib/fontgoggles/font/dsFont.py +++ b/Lib/fontgoggles/font/dsFont.py @@ -15,7 +15,7 @@ from fontTools.ttLib import TTFont from fontTools.ufoLib import UFOReader from fontTools.varLib.models import normalizeValue -from .baseFont import BaseFont +from .baseFont import BaseFont, FontMetrics from .glyphDrawing import EmptyDrawing, GlyphDrawing from .ufoFont import Glyph, NotDefGlyph, UFOState, extractIncludedFeatureFiles from ..compile.compilerPool import compileUFOToPath, compileDSToBytes, CompilerError @@ -196,10 +196,19 @@ def defaultInfo(self): def unitsPerEm(self): return self.defaultInfo.unitsPerEm + @cachedProperty + def fontMetrics(self): + return FontMetrics( + xHeight = getattr(self.defaultInfo, "xHeight", None), + capHeight = getattr(self.defaultInfo, "capHeight", None), + ascender = getattr(self.defaultInfo, "ascender", None), + descender= getattr(self.defaultInfo, "descender", None) + ) + @cachedProperty def defaultVerticalAdvance(self): - ascender = getattr(self.defaultInfo, "ascender", None) - descender = getattr(self.defaultInfo, "descender", None) + ascender = self.fontMetrics.ascender + descender = self.fontMetrics.descender if ascender is None or descender is None: return self.defaultInfo.unitsPerEm else: @@ -207,7 +216,7 @@ def defaultVerticalAdvance(self): @cachedProperty def defaultVerticalOriginY(self): - ascender = getattr(self.defaultInfo, "ascender", None) + ascender = self.fontMetrics.ascender if ascender is None: return self.defaultInfo.unitsPerEm # ??? else: diff --git a/Lib/fontgoggles/font/ufoFont.py b/Lib/fontgoggles/font/ufoFont.py index c856a285..d8138a6e 100644 --- a/Lib/fontgoggles/font/ufoFont.py +++ b/Lib/fontgoggles/font/ufoFont.py @@ -129,6 +129,15 @@ def _getShaper(self, fontData): def unitsPerEm(self): return self.info.unitsPerEm + @cachedProperty + def fontMetrics(self): + return FontMetrics( + xHeight = getattr(self.info, "xHeight", None), + capHeight = getattr(self.info, "capHeight", None), + ascender = getattr(self.info, "ascender", None), + descender= getattr(self.info, "descender", None) + ) + def _getGlyph(self, glyphName, layerName=None): glyph = self._cachedGlyphs.get((layerName, glyphName)) if glyph is None: @@ -161,8 +170,8 @@ def _getHorizontalAdvance(self, glyphName): @cachedProperty def defaultVerticalAdvance(self): - ascender = getattr(self.info, "ascender", None) - descender = getattr(self.info, "descender", None) + ascender = self.fontMetrics.ascender + descender = self.fontMetrics.descender if ascender is None or descender is None: return self.info.unitsPerEm else: @@ -170,7 +179,7 @@ def defaultVerticalAdvance(self): @cachedProperty def defaultVerticalOriginY(self): - ascender = getattr(self.info, "ascender", None) + ascender = self.fontMetrics.ascender if ascender is None: return self.info.unitsPerEm # ??? else: From 8b0a3de76a1b062a7cdff19d59a2ad2905d681b3 Mon Sep 17 00:00:00 2001 From: Simon Cozens Date: Thu, 3 Mar 2022 12:26:14 +0000 Subject: [PATCH 3/4] Draw the actual metric lines --- Lib/fontgoggles/mac/fontList.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/Lib/fontgoggles/mac/fontList.py b/Lib/fontgoggles/mac/fontList.py index f29c9939..a6f6c57f 100644 --- a/Lib/fontgoggles/mac/fontList.py +++ b/Lib/fontgoggles/mac/fontList.py @@ -12,6 +12,7 @@ from fontgoggles.font import defaultSortSpec, sniffFontType, sortedFontPathsAndNumbers from fontgoggles.mac.drawing import ( blendRGBA, + drawLine, nsColorFromRGBA, nsRectFromRect, rgbaFromNSColor, @@ -1018,6 +1019,7 @@ class FGGlyphLineView(AppKit.NSView): relativeHBaseline = hookedProperty(_scheduleRedraw, default=0.25) relativeVBaseline = hookedProperty(_scheduleRedraw, default=0.5) relativeMargin = hookedProperty(_scheduleRedraw, default=0.1) + showMetrics = hookedProperty(_scheduleRedraw, default=True) def init(self): self = super().init() @@ -1250,6 +1252,18 @@ def drawRect_(self, rect): dx, dy = self.origin invScale = 1 / self.scaleFactor + + if self.showMetrics: + xHeight = dy + self._glyphs.fontMetrics.xHeight * self.scaleFactor + capHeight = dy + self._glyphs.fontMetrics.capHeight * self.scaleFactor + ascender = dy + self._glyphs.fontMetrics.ascender * self.scaleFactor + descender = dy + self._glyphs.fontMetrics.descender * self.scaleFactor + drawLine((0,dy), (self.bounds().size.width, dy), colors.selectedSpaceColor, 2) + drawLine((0,xHeight), (self.bounds().size.width, xHeight), colors.hoverSpaceColor, 2) + drawLine((0,capHeight), (self.bounds().size.width, capHeight), colors.hoverSpaceColor, 2) + drawLine((0,ascender), (self.bounds().size.width, ascender), colors.hoverColor, 1) + drawLine((0,descender), (self.bounds().size.width, descender), colors.hoverColor, 1) + rect = rectFromNSRect(rect) rect = scaleRect(offsetRect(rect, -dx, -dy), invScale, invScale) From 8b9b98d97dfce6e2c6e4b876b576226b41ca4834 Mon Sep 17 00:00:00 2001 From: Simon Cozens Date: Thu, 3 Mar 2022 12:26:23 +0000 Subject: [PATCH 4/4] Hook it up to a menu toggle --- .../English.lproj/MainMenu.nib/designable.nib | 9 +++++++-- .../MainMenu.nib/keyedobjects.nib | Bin 14540 -> 16363 bytes Lib/fontgoggles/mac/fontList.py | 17 ++++++++++++++--- Lib/fontgoggles/mac/mainWindow.py | 7 +++++++ Lib/fontgoggles/project.py | 1 + 5 files changed, 29 insertions(+), 5 deletions(-) diff --git a/App/Resources/English.lproj/MainMenu.nib/designable.nib b/App/Resources/English.lproj/MainMenu.nib/designable.nib index a8fd2e71..8d73f33c 100644 --- a/App/Resources/English.lproj/MainMenu.nib/designable.nib +++ b/App/Resources/English.lproj/MainMenu.nib/designable.nib @@ -1,8 +1,8 @@ - + - + @@ -273,6 +273,11 @@ CA + + + + + diff --git a/App/Resources/English.lproj/MainMenu.nib/keyedobjects.nib b/App/Resources/English.lproj/MainMenu.nib/keyedobjects.nib index fd354b6de5f9458a35bd93a4196933101df7c98b..d5a03a4b432fd9b4e4add618e6eaba78b0b95b0b 100644 GIT binary patch literal 16363 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