From d5420f4b7848a9083bd3d0018ce33e1edae5226f Mon Sep 17 00:00:00 2001 From: Tae Won Ha Date: Sun, 19 Jun 2016 13:39:20 +0200 Subject: [PATCH] Use CoreText's font metric --- SwiftNeoVim/Grid.swift | 1 + SwiftNeoVim/NeoVimView.swift | 42 ++++++++++++++++++++---------------- SwiftNeoVim/TextDrawer.h | 3 ++- SwiftNeoVim/TextDrawer.m | 41 +++++++++++++++++++++++------------ 4 files changed, 54 insertions(+), 33 deletions(-) diff --git a/SwiftNeoVim/Grid.swift b/SwiftNeoVim/Grid.swift index 7db1cfc3..b1cd576c 100644 --- a/SwiftNeoVim/Grid.swift +++ b/SwiftNeoVim/Grid.swift @@ -157,6 +157,7 @@ class Grid: CustomStringConvertible { } private func clearRegion(region: Region) { + // FIXME: sometimes clearRegion gets called without first resizing the Grid. Should we handle this? guard self.hasData else { return } diff --git a/SwiftNeoVim/NeoVimView.swift b/SwiftNeoVim/NeoVimView.swift index f0283ea0..6331600f 100644 --- a/SwiftNeoVim/NeoVimView.swift +++ b/SwiftNeoVim/NeoVimView.swift @@ -48,7 +48,8 @@ public class NeoVimView: NSView { didSet { self.drawer.font = self.font self.cellSize = self.drawer.cellSize - self.lineSpace = self.drawer.lineSpace + self.descent = self.drawer.descent + self.leading = self.drawer.leading // FIXME: resize and redraw } @@ -58,21 +59,23 @@ public class NeoVimView: NSView { private let drawer: TextDrawer private var cellSize = CGSize.zero - private var lineSpace = CGFloat(0) + private var descent = CGFloat(0) + private var leading = CGFloat(0) private let grid = Grid() init(frame rect: NSRect = CGRect.zero, xpc: NeoVimXpc) { self.xpc = xpc - self.font = NSFont(name: "Menlo", size: 13)! + self.font = NSFont(name: "Menlo", size: 16)! self.drawer = TextDrawer(font: font) super.init(frame: rect) self.wantsLayer = true self.cellSize = self.drawer.cellSize - self.lineSpace = self.drawer.lineSpace + self.descent = self.drawer.descent + self.leading = self.drawer.leading } override public func keyDown(theEvent: NSEvent) { @@ -100,7 +103,7 @@ public class NeoVimView: NSView { self.drawBackground(positions: positions, background: rowFrag.attrs.background) let string = self.grid.cells[rowFrag.row][rowFrag.range].reduce("") { $0 + $1.string } - let glyphPositions = positions.map { CGPoint(x: $0.x, y: $0.y + self.lineSpace) } + let glyphPositions = positions.map { CGPoint(x: $0.x, y: $0.y + self.descent + self.leading) } self.drawer.drawString(string, positions: UnsafeMutablePointer(glyphPositions), positionsCount: positions.count, highlightAttrs: rowFrag.attrs, @@ -120,23 +123,26 @@ public class NeoVimView: NSView { private func rowRunIntersecting(rects rects: [CGRect]) -> [RowRun] { return rects .map { rect -> Region in + // Get all Regions that intersects with the given rects. There can be overlaps between the Regions, but for the + // time being we ignore them; probably not necessary to optimize them away. let rowStart = Int(floor((self.frame.height - (rect.origin.y + rect.size.height)) / self.cellSize.height)) let rowEnd = Int(ceil((self.frame.height - rect.origin.y) / self.cellSize.height)) - 1 let columnStart = Int(floor(rect.origin.x / self.cellSize.width)) let columnEnd = Int(ceil((rect.origin.x + rect.size.width) / self.cellSize.width)) - 1 + return Region(top: rowStart, bottom: rowEnd, left: columnStart, right: columnEnd) - } // There can be overlaps between the Regions, but for the time being we ignore them. + } .map { region -> [RowRun] in - return (region.rowRange) + // Map Regions to RowRuns for drawing. + return region.rowRange + // Map each row in a Region to RowRuns .map { row -> [RowRun] in - let range = region.columnRange + let columns = region.columnRange let rowCells = self.grid.cells[row] - let startIndex = range.startIndex + let startIndex = columns.startIndex - var result = [ - RowRun(row: row, range: startIndex...startIndex, attrs: rowCells[startIndex].attrs) - ] - range.forEach { idx in + var result = [ RowRun(row: row, range: startIndex...startIndex, attrs: rowCells[startIndex].attrs) ] + columns.forEach { idx in if rowCells[idx].attrs == result.last!.attrs { let last = result.popLast()! result.append(RowRun(row: row, range: last.range.startIndex...idx, attrs: last.attrs)) @@ -145,11 +151,11 @@ public class NeoVimView: NSView { } } - return result - } // -> [[RowRun]] - .flatMap { $0 } // -> [RowRun] - } // -> [[RowRun]] - .flatMap { $0 } // -> [RowRun] + return result // All RowRuns for a row in a Region. + } // All RowRuns for all rows in a Region grouped by row. + .flatMap { $0 } // Flattened RowRuns for a Region. + } // All RowRuns for all Regions grouped by Region. + .flatMap { $0 } // Flattened RowRuns for all Regions. } private func positionOnView(row: Int, column: Int) -> CGPoint { diff --git a/SwiftNeoVim/TextDrawer.h b/SwiftNeoVim/TextDrawer.h index 070d6eb3..5662b7f8 100644 --- a/SwiftNeoVim/TextDrawer.h +++ b/SwiftNeoVim/TextDrawer.h @@ -11,7 +11,8 @@ @interface TextDrawer : NSObject @property (nonatomic, nonnull, retain) NSFont *font; -@property (nonatomic, readonly) CGFloat lineSpace; +@property (nonatomic, readonly) CGFloat leading; +@property (nonatomic, readonly) CGFloat descent; @property (nonatomic, readonly) CGSize cellSize; - (instancetype _Nonnull)initWithFont:(NSFont *_Nonnull)font; diff --git a/SwiftNeoVim/TextDrawer.m b/SwiftNeoVim/TextDrawer.m index e83a499c..26c62509 100644 --- a/SwiftNeoVim/TextDrawer.m +++ b/SwiftNeoVim/TextDrawer.m @@ -19,8 +19,8 @@ NSLayoutManager *_layoutManager; NSFont *_font; - CGFloat _fontDescent; - CGFloat _lineGap; + CGFloat _underlinePosition; + CGFloat _underlineThickness; NSMutableArray *_fontLookupCache; NSMutableDictionary *_fontTraitCache; @@ -30,13 +30,23 @@ [_font autorelease]; _font = [font retain]; + // cf. https://developer.apple.com/library/mac/documentation/TextFonts/Conceptual/CocoaTextArchitecture/FontHandling/FontHandling.html + CGFloat ascent = CTFontGetAscent((CTFontRef) _font); + CGFloat descent = CTFontGetDescent((CTFontRef) _font); + CGFloat leading = CTFontGetLeading((CTFontRef) _font); + CGFloat underlinePosition = CTFontGetUnderlinePosition((CTFontRef) _font); + CGFloat underlineThickness = CTFontGetUnderlineThickness((CTFontRef) _font); + _cellSize = CGSizeMake( round([@"m" sizeWithAttributes:@{ NSFontAttributeName : _font }].width), - [_layoutManager defaultLineHeightForFont:_font] + _lineSpace + ceil(ascent + descent + leading) ); - // https://developer.apple.com/library/mac/documentation/TextFonts/Conceptual/CocoaTextArchitecture/FontHandling/FontHandling.html - _lineGap = _cellSize.height - _font.ascender - _font.descender; - _fontDescent = CTFontGetDescent((CTFontRef) _font); + + _leading = leading; + _descent = descent; + _underlinePosition = underlinePosition; // This seems to take the thickness into account + // TODO: Maybe we should use 0.5 or 1 as minimum thickness for Retina and non-Retina, respectively. + _underlineThickness = underlineThickness; } - (instancetype)initWithFont:(NSFont *_Nonnull)font { @@ -49,7 +59,6 @@ _fontLookupCache = [[NSMutableArray alloc] init]; _fontTraitCache = [[NSMutableDictionary alloc] init]; - _lineSpace = 4; self.font = font; return self; @@ -79,11 +88,7 @@ CGContextSaveGState(context); if (attrs.fontTrait & FontTraitUnderline) { - CGRect rect = { - {positions[0].x, positions[0].y - 1}, - {positions[0].x + positions[positionsCount - 1].x + _cellSize.width, 1} - }; - [self drawUnderline:rect color:attrs.special context:context]; + [self drawUnderline:positions count:positionsCount color:attrs.special context:context]; } [self drawString:string positions:positions @@ -93,8 +98,16 @@ CGContextRestoreGState(context); } -- (void)drawUnderline:(CGRect)rect color:(unsigned int)color context:(CGContextRef _Nonnull)context { +- (void)drawUnderline:(const CGPoint *_Nonnull)positions + count:(NSInteger)count + color:(unsigned int)color + context:(CGContextRef _Nonnull)context +{ CGContextSetRGBFillColor(context, RED(color), GREEN(color), BLUE(color), ALPHA(color)); + CGRect rect = { + {positions[0].x, positions[0].y + _underlinePosition}, + {positions[0].x + positions[count - 1].x + _cellSize.width, _underlineThickness} + }; CGContextFillRect(context, rect); } @@ -115,7 +128,7 @@ unichars = unibuffer; } - CGGlyph *glyphs = malloc(unilength * sizeof(UniChar)); + CGGlyph *glyphs = malloc(unilength * sizeof(CGGlyph)); CTFontRef fontWithTraits = [self fontWithTrait:fontTrait]; CGContextSetRGBFillColor(context, RED(foreground), GREEN(foreground), BLUE(foreground), 1.0);