@@ -225,6 +225,16 @@ @implementation MMCoreTextView {
225225
226226 BOOL alignCmdLineToBottom; // /< Whether to pin the Vim command-line to the bottom of the window
227227 int cmdlineRow; // /< Row number (0-indexed) where the cmdline starts. Used for pinning it to the bottom if desired.
228+
229+ // / Number of rows to expand when redrawing to make sure we don't clip tall
230+ // / characters whose glyphs extend beyond the bottom/top of the cell.
231+ // /
232+ // / Note: This is a short-term hacky solution as it permanently increases
233+ // / the number of rows to expand every time we redraw. Eventually we should
234+ // / calculate each line's glyphs' bounds before issuing a redraw and use
235+ // / that to determine the accurate redraw bounds instead. Currently we
236+ // / calculate the glyph run too late (inside the draw call itself).
237+ unsigned int redrawExpandRows;
228238}
229239
230240- (instancetype )initWithFrame : (NSRect )frame
@@ -255,6 +265,7 @@ - (instancetype)initWithFrame:(NSRect)frame
255265
256266 alignCmdLineToBottom = NO ; // this would be updated to the user preferences later
257267 cmdlineRow = -1 ; // this would be updated by Vim
268+ redrawExpandRows = 0 ; // start at 0, until we see a tall character. and then we expand it.
258269
259270 return self;
260271}
@@ -739,11 +750,16 @@ - (BOOL)isFlipped
739750
740751- (void )setNeedsDisplayFromRow : (int )row column : (int )col toRow : (int )row2
741752 column : (int )col2 {
753+ row -= redrawExpandRows;
754+ row2 += redrawExpandRows;
742755 [self setNeedsDisplayInRect: [self rectForRow: row column: 0 numRows: row2-row+1 numColumns: maxColumns]];
743756}
744757
745758- (void )drawRect : (NSRect )rect
746759{
760+ NSUserDefaults *ud = [NSUserDefaults standardUserDefaults ];
761+ const BOOL clipTextToRow = [ud boolForKey: MMRendererClipToRowKey]; // Specify whether to clip tall characters by the row boundary.
762+
747763 NSGraphicsContext *context = [NSGraphicsContext currentContext ];
748764#if MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_10
749765 CGContextRef ctx = context.CGContext ;
@@ -777,14 +793,101 @@ - (void)drawRect:(NSRect)rect
777793
778794 CGContextFillRect (ctx, rect);
779795
780- for (size_t r = 0 ; r < grid.rows ; r++) {
781- const CGRect rowRect = [self rectForRow: r column: 0 numRows: 1 numColumns: grid.cols];
782- const CGRect rowClipRect = CGRectIntersection (rowRect, rect);
783- if (CGRectIsNull (rowClipRect))
784- continue ;
785- CGContextSaveGState (ctx);
786- CGContextClipToRect (ctx, rowClipRect);
787-
796+ // Function to draw all rows
797+ void (^drawAllRows)(void (^)(CGContextRef,CGRect,int )) = ^(void (^drawFunc)(CGContextRef,CGRect,int )){
798+ for (size_t r = 0 ; r < grid.rows ; r++) {
799+ const CGRect rowRect = [self rectForRow: (int )r
800+ column: 0
801+ numRows: 1
802+ numColumns: grid.cols];
803+
804+ // Expand the clip rect to include some above/below rows in case we have tall characters.
805+ const CGRect rowExpandedRect = [self rectForRow: (int )(r-redrawExpandRows)
806+ column: 0
807+ numRows: (1 +redrawExpandRows*2 )
808+ numColumns: grid.cols];
809+
810+ const CGRect rowClipRect = CGRectIntersection (rowExpandedRect, rect);
811+ if (CGRectIsNull (rowClipRect))
812+ continue ;
813+ CGContextSaveGState (ctx);
814+ if (clipTextToRow)
815+ CGContextClipToRect (ctx, rowClipRect);
816+
817+ drawFunc (ctx, rowRect, (int )r);
818+
819+ CGContextRestoreGState (ctx);
820+ }
821+ };
822+
823+ // Function to draw a row of background colors, signs, and cursor rect. These should go below
824+ // any text.
825+ void (^drawBackgroundAndCursorFunc)(CGContextRef,CGRect,int ) = ^(CGContextRef ctx, CGRect rowRect, int r){
826+ for (int c = 0 ; c < grid.cols ; c++) {
827+ GridCell cell = *grid_cell (&grid, r, c);
828+ CGRect cellRect = {{rowRect.origin .x + cellSize.width * c, rowRect.origin .y }, cellSize};
829+ if (cell.textFlags & DRAW_WIDE)
830+ cellRect.size .width *= 2 ;
831+ if (cell.inverted ) {
832+ cell.bg ^= 0xFFFFFF ;
833+ cell.fg ^= 0xFFFFFF ;
834+ cell.sp ^= 0xFFFFFF ;
835+ }
836+
837+ // Fill background
838+ if (cell.bg != defaultBg && ALPHA (cell.bg ) > 0 ) {
839+ CGRect fillCellRect = cellRect;
840+
841+ if (c == grid.cols - 1 || (c == grid.cols - 2 && (cell.textFlags & DRAW_WIDE))) {
842+ // Fill a little extra to the right if this is the last
843+ // column, and the frame size isn't exactly the same size
844+ // as the grid (due to smooth resizing, etc). This makes it
845+ // look less ugly and more consisten. See rectForRow:'s
846+ // implementation for extra comments.
847+ CGFloat extraWidth = rowRect.origin .x + rowRect.size .width - (cellRect.size .width + cellRect.origin .x );
848+ fillCellRect.size .width += extraWidth;
849+ }
850+
851+ CGContextSetFillColor (ctx, COMPONENTS (cell.bg ));
852+ CGContextFillRect (ctx, fillCellRect);
853+ }
854+
855+ // Handle signs
856+ if (cell.sign ) {
857+ CGRect signRect = cellRect;
858+ signRect.size .width *= 2 ;
859+ [cell.sign drawInRect: signRect
860+ fromRect: (NSRect ){{0 , 0 }, cell.sign .size }
861+ operation: (cell.inverted ? NSCompositingOperationDifference : NSCompositingOperationSourceOver)
862+ fraction: 1.0 ];
863+ }
864+
865+ // Insertion point (cursor)
866+ if (cell.insertionPoint .color && cell.insertionPoint .fraction ) {
867+ float frac = cell.insertionPoint .fraction / 100.0 ;
868+ NSRect rect = cellRect;
869+ if (MMInsertionPointHorizontal == cell.insertionPoint .shape ) {
870+ rect.size .height = cellSize.height * frac;
871+ } else if (MMInsertionPointVertical == cell.insertionPoint .shape ) {
872+ rect.size .width = cellSize.width * frac;
873+ } else if (MMInsertionPointVerticalRight == cell.insertionPoint .shape ) {
874+ rect.size .width = cellSize.width * frac;
875+ rect.origin .x += cellRect.size .width - rect.size .width ;
876+ }
877+ rect = [self backingAlignedRect: rect options: NSAlignAllEdgesInward];
878+
879+ [[NSColor colorWithArgbInt: cell.insertionPoint.color] set ];
880+ if (MMInsertionPointHollow == cell.insertionPoint .shape ) {
881+ [NSBezierPath strokeRect: NSInsetRect (rect, 0.5 , 0.5 )];
882+ } else {
883+ NSRectFill (rect);
884+ }
885+ }
886+ }
887+ };
888+
889+ // Function to draw a row of text with their corresponding text styles.
890+ void (^drawTextFunc)(CGContextRef,CGRect,int ) = ^(CGContextRef ctx, CGRect rowRect, int r){
788891 __block NSMutableString *lineString = nil ;
789892 __block CGFloat lineStringStart = 0 ;
790893 __block CFRange lineStringRange = {};
@@ -797,7 +900,7 @@ - (void)drawRect:(NSRect)rect
797900 if (!lineString.length )
798901 return ;
799902 size_t cellOffsetByIndex[lineString.length];
800- for (size_t i = 0 , stringIndex = 0 ; i < lineStringRange.length ; i++) {
903+ for (int i = 0 , stringIndex = 0 ; i < lineStringRange.length ; i++) {
801904 GridCell cell = *grid_cell (&grid, r, lineStringRange.location + i);
802905 size_t cell_length = cell.string .length ;
803906 for (size_t j = 0 ; j < cell_length; j++) {
@@ -860,6 +963,38 @@ - (void)drawRect:(NSRect)rect
860963 layoutPositions = layoutPositions_storage;
861964 }
862965
966+ const int maxRedrawExpandRows = clipTextToRow ? 0 : 3 ; // Hard-code a sane maximum for now to prevent degenerate edge cases
967+ if (redrawExpandRows < maxRedrawExpandRows) {
968+ // Check if we encounter any glyphs in this line that are too tall and would be
969+ // clipped / not redrawn properly. If we encounter that, increase
970+ // redrawExpandRows and redraw.
971+ // Note: This is kind of a hacky solution. See comments for redrawExpandRows.
972+ CGRect lineBounds = CTRunGetImageBounds (run, ctx, CFRangeMake (0 ,0 ));
973+ if (!CGRectIsNull (lineBounds)) {
974+ unsigned int newRedrawExpandRows = 0 ;
975+ if (lineBounds.origin .y < rowRect.origin .y ) {
976+ newRedrawExpandRows = (int )ceil ((rowRect.origin .y - lineBounds.origin .y ) / cellSize.height );
977+ }
978+ if (lineBounds.origin .y + lineBounds.size .height > rowRect.origin .y + cellSize.height ) {
979+ int rowsAbove = (int )ceil (((lineBounds.origin .y + lineBounds.size .height ) - (rowRect.origin .y + cellSize.height )) / cellSize.height );
980+ if (rowsAbove > newRedrawExpandRows) {
981+ newRedrawExpandRows = rowsAbove;
982+ }
983+ }
984+
985+ if (newRedrawExpandRows > redrawExpandRows) {
986+ redrawExpandRows = newRedrawExpandRows;
987+ if (redrawExpandRows > maxRedrawExpandRows) {
988+ redrawExpandRows = maxRedrawExpandRows;
989+ }
990+ [self setNeedsDisplay: YES ];
991+ }
992+ }
993+ }
994+ else {
995+ redrawExpandRows = maxRedrawExpandRows;
996+ }
997+
863998 for (CFIndex i = 0 ; i < glyphCount; i++) {
864999 if (indices[i] >= lineStringLength) {
8651000 ASLogDebug (@" Invalid glyph pos index: %ld , len: %lu " , (long )indices[i], (unsigned long )lineStringLength);
@@ -916,7 +1051,7 @@ - (void)drawRect:(NSRect)rect
9161051
9171052 BOOL hasStrikeThrough = NO ;
9181053
919- for (size_t c = 0 ; c < grid.cols ; c++) {
1054+ for (int c = 0 ; c < grid.cols ; c++) {
9201055 GridCell cell = *grid_cell (&grid, r, c);
9211056 CGRect cellRect = {{rowRect.origin .x + cellSize.width * c, rowRect.origin .y }, cellSize};
9221057 if (cell.textFlags & DRAW_WIDE)
@@ -927,56 +1062,6 @@ - (void)drawRect:(NSRect)rect
9271062 cell.sp ^= 0xFFFFFF ;
9281063 }
9291064
930- // Fill background
931- if (cell.bg != defaultBg && ALPHA (cell.bg ) > 0 ) {
932- CGRect fillCellRect = cellRect;
933-
934- if (c == grid.cols - 1 || (c == grid.cols - 2 && (cell.textFlags & DRAW_WIDE))) {
935- // Fill a little extra to the right if this is the last
936- // column, and the frame size isn't exactly the same size
937- // as the grid (due to smooth resizing, etc). This makes it
938- // look less ugly and more consisten. See rectForRow:'s
939- // implementation for extra comments.
940- CGFloat extraWidth = rowRect.origin .x + rowRect.size .width - (cellRect.size .width + cellRect.origin .x );
941- fillCellRect.size .width += extraWidth;
942- }
943-
944- CGContextSetFillColor (ctx, COMPONENTS (cell.bg ));
945- CGContextFillRect (ctx, fillCellRect);
946- }
947-
948- // Handle signs
949- if (cell.sign ) {
950- CGRect signRect = cellRect;
951- signRect.size .width *= 2 ;
952- [cell.sign drawInRect: signRect
953- fromRect: (NSRect ){{0 , 0 }, cell.sign .size }
954- operation: (cell.inverted ? NSCompositingOperationDifference : NSCompositingOperationSourceOver)
955- fraction: 1.0 ];
956- }
957-
958- // Insertion point (cursor)
959- if (cell.insertionPoint .color && cell.insertionPoint .fraction ) {
960- float frac = cell.insertionPoint .fraction / 100.0 ;
961- NSRect rect = cellRect;
962- if (MMInsertionPointHorizontal == cell.insertionPoint .shape ) {
963- rect.size .height = cellSize.height * frac;
964- } else if (MMInsertionPointVertical == cell.insertionPoint .shape ) {
965- rect.size .width = cellSize.width * frac;
966- } else if (MMInsertionPointVerticalRight == cell.insertionPoint .shape ) {
967- rect.size .width = cellSize.width * frac;
968- rect.origin .x += cellRect.size .width - rect.size .width ;
969- }
970- rect = [self backingAlignedRect: rect options: NSAlignAllEdgesInward];
971-
972- [[NSColor colorWithArgbInt: cell.insertionPoint.color] set ];
973- if (MMInsertionPointHollow == cell.insertionPoint .shape ) {
974- [NSBezierPath strokeRect: NSInsetRect (rect, 0.5 , 0.5 )];
975- } else {
976- NSRectFill (rect);
977- }
978- }
979-
9801065 // Text underline styles. We only allow one of them to be active.
9811066 // Note: We are not currently using underlineThickness or underlinePosition. Should fix to use them.
9821067 const CGFloat underlineY = 0.4 *fontDescent; // Just a hard-coded value for now. Should fix to use underlinePosition.
@@ -1089,7 +1174,7 @@ - (void)drawRect:(NSRect)rect
10891174 if (hasStrikeThrough) {
10901175 // Second pass to render strikethrough. Unfortunately have to duplicate a little bit of code here to loop
10911176 // through the cells.
1092- for (size_t c = 0 ; c < grid.cols ; c++) {
1177+ for (int c = 0 ; c < grid.cols ; c++) {
10931178 GridCell cell = *grid_cell (&grid, r, c);
10941179 CGRect cellRect = {{rowRect.origin .x + cellSize.width * c, rowRect.origin .y }, cellSize};
10951180 if (cell.textFlags & DRAW_WIDE)
@@ -1109,9 +1194,21 @@ - (void)drawRect:(NSRect)rect
11091194
11101195 }
11111196 }
1197+ };
1198+
1199+ // Render passes:
1200+
1201+ // 1. Draw background color and cursor rect.
1202+ drawAllRows (drawBackgroundAndCursorFunc);
1203+
1204+ // 2. Draw text.
1205+ // We need to do this in a separate pass in case some characters are taller than a cell. This
1206+ // could easily happen when we have composed characters (e.g. T゙̂⃗) that either goes below or above
1207+ // the cell boundary. We draw the background colors in 1st pass to make sure all the texts will
1208+ // be drawn on top of them. Also see redrawExpandRows which handles making such tall characters
1209+ // redraw/clip correctly.
1210+ drawAllRows (drawTextFunc);
11121211
1113- CGContextRestoreGState (ctx);
1114- }
11151212 if (thinStrokes) {
11161213 CGContextSetFontSmoothingStyle (ctx, originalSmoothingStyle);
11171214 }
0 commit comments