Skip to content

Commit 882944f

Browse files
committed
Support strikethrough/underdouble/underdashed/underdotted styles
Add support for all the missing text styles for MacVim for Vim parity. For strikethrough, this needed to be done as a second pass to make sure they get drawn on top of the text. This is necessary because currently the logic buffers texts up before dispatching them later in a line, so it's just easier to loop through the line a second time if we detected strikethrough. For the strikethrough position, we simply use the half of xheight which seems to work best in looking good. For underdouble, the logic is a little tricky because sometimes we don't have space below the line. When that's the case, simply draw the second line on top of the first line. For underdotted, need to do something smart to space out the dots. When the width is divisible by 2, they get spaced out evenly. If they are not, try to make it work if divisible by 3. If that's not the case, we just readjust the size of dot/gap a little bit to make it fit, even though now we have non-integer sizes (from experimentation, the antialising works well enough that it's not too jarring). Also fix rendering of undercurl to work for double-width characters as well. Note that underdouble/underdotted/underdashed are not supported in regular gVim yet, and so I had to add the ifdef for those in gui.c. These may cause merge conflicts later which should be easily resolved. Known issue 1: Note that currently underline is not respecting the font's underline thickness and position. We always use a thickness of 1 pt, and hard-code a 0.4*descent position, which are not great. Thickness in particular should scale with the font size. They should be fixed in a future commit. Known issue 2: There are some current clipping bugs in the renderer. This is because the line height returned by NSLayoutManager is sometimes smaller than ascent+descent+leading, *and* MMCoreTextView for some reason takes the `ceil(descent)` (presumably to make the rendering grid-aligned). This should be fixed later. Fix #1034
1 parent b3cd8a5 commit 882944f

File tree

4 files changed

+142
-18
lines changed

4 files changed

+142
-18
lines changed

src/MacVim/MMCoreTextView.h

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,9 @@
2727
// From NSTextView
2828
NSSize insetSize;
2929

30-
float fontDescent;
30+
CGFloat fontDescent;
31+
CGFloat fontAscent;
32+
CGFloat fontXHeight;
3133
BOOL antialias;
3234
BOOL ligatures;
3335
BOOL thinStrokes;

src/MacVim/MMCoreTextView.m

Lines changed: 119 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -36,14 +36,18 @@
3636
// TODO: What does DRAW_TRANSP flag do? If the background isn't drawn when
3737
// this flag is set, then sometimes the character after the cursor becomes
3838
// blank. Everything seems to work fine by just ignoring this flag.
39-
#define DRAW_TRANSP 0x01 /* draw with transparent bg */
40-
#define DRAW_BOLD 0x02 /* draw bold text */
41-
#define DRAW_UNDERL 0x04 /* draw underline text */
42-
#define DRAW_UNDERC 0x08 /* draw undercurl text */
43-
#define DRAW_ITALIC 0x10 /* draw italic text */
39+
#define DRAW_TRANSP 0x01 // draw with transparent bg
40+
#define DRAW_BOLD 0x02 // draw bold text
41+
#define DRAW_UNDERL 0x04 // draw underline text
42+
#define DRAW_UNDERC 0x08 // draw undercurl text
43+
#define DRAW_ITALIC 0x10 // draw italic text
4444
#define DRAW_CURSOR 0x20
45-
#define DRAW_WIDE 0x80 /* draw wide text */
46-
#define DRAW_COMP 0x100 /* drawing composing char */
45+
#define DRAW_STRIKE 0x40 // draw strikethrough text
46+
#define DRAW_UNDERDOUBLE 0x80 // draw double underline
47+
#define DRAW_UNDERDOTTED 0x100 // draw dotted underline
48+
#define DRAW_UNDERDASHED 0x200 // draw dashed underline
49+
#define DRAW_WIDE 0x1000 // (MacVim only) draw wide text
50+
#define DRAW_COMP 0x2000 // (MacVim only) drawing composing char
4751

4852
#if MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_X_VERSION_10_13
4953
typedef NSString * NSAttributedStringKey;
@@ -431,6 +435,8 @@ - (void)setFont:(NSFont *)newFont
431435
font = [newFont retain];
432436
}
433437
fontDescent = ceil(CTFontGetDescent((CTFontRef)font));
438+
fontAscent = CTFontGetAscent((CTFontRef)font);
439+
fontXHeight = CTFontGetXHeight((CTFontRef)font);
434440

435441
// NOTE! Even though NSFontFixedAdvanceAttribute is a float, it will
436442
// only render at integer sizes. Hence, we restrict the cell width to
@@ -884,6 +890,9 @@ - (void)drawRect:(NSRect)rect
884890
CGContextSetBlendMode(ctx, kCGBlendModeCopy);
885891
[lineString deleteCharactersInRange:NSMakeRange(0, lineString.length)];
886892
};
893+
894+
BOOL hasStrikeThrough = NO;
895+
887896
for (size_t c = 0; c < grid.cols; c++) {
888897
GridCell cell = *grid_cell(&grid, r, c);
889898
CGRect cellRect = {{rowRect.origin.x + cellSize.width * c, rowRect.origin.y}, cellSize};
@@ -945,19 +954,90 @@ - (void)drawRect:(NSRect)rect
945954
}
946955
}
947956

948-
// Text underline styles
949-
if (cell.textFlags & DRAW_UNDERL) {
950-
CGRect rect = CGRectMake(cellRect.origin.x, cellRect.origin.y+0.4*fontDescent, cellRect.size.width, 1);
951-
CGContextSetFillColor(ctx, COMPONENTS(cell.sp));
952-
CGContextFillRect(ctx, rect);
953-
} else if (cell.textFlags & DRAW_UNDERC) {
954-
const float x = cellRect.origin.x, y = cellRect.origin.y+1, w = cellSize.width, h = 0.5*fontDescent;
957+
// Text underline styles. We only allow one of them to be active.
958+
// Note: We are not currently using underlineThickness or underlinePosition. Should fix to use them.
959+
const CGFloat underlineY = 0.4*fontDescent; // Just a hard-coded value for now. Should fix to use underlinePosition.
960+
if (cell.textFlags & DRAW_UNDERC) {
961+
const CGFloat x = cellRect.origin.x, y = cellRect.origin.y+1, w = cellSize.width, h = 0.5*fontDescent;
955962
CGContextMoveToPoint(ctx, x, y);
956963
CGContextAddCurveToPoint(ctx, x+0.25*w, y, x+0.25*w, y+h, x+0.5*w, y+h);
957964
CGContextAddCurveToPoint(ctx, x+0.75*w, y+h, x+0.75*w, y, x+w, y);
965+
if (cell.textFlags & DRAW_WIDE) {
966+
// Need to draw another set for double-width characters
967+
const CGFloat x2 = x + cellSize.width;
968+
CGContextAddCurveToPoint(ctx, x2+0.25*w, y, x2+0.25*w, y+h, x2+0.5*w, y+h);
969+
CGContextAddCurveToPoint(ctx, x2+0.75*w, y+h, x2+0.75*w, y, x2+w, y);
970+
}
971+
CGContextSetRGBStrokeColor(ctx, RED(cell.sp), GREEN(cell.sp), BLUE(cell.sp), ALPHA(cell.sp));
972+
CGContextStrokePath(ctx);
973+
}
974+
else if (cell.textFlags & DRAW_UNDERDASHED) {
975+
const CGFloat dashLengths[] = {cellSize.width / 4, cellSize.width / 4};
976+
977+
const CGFloat x = cellRect.origin.x;
978+
const CGFloat y = cellRect.origin.y+underlineY;
979+
CGContextMoveToPoint(ctx, x, y);
980+
CGContextAddLineToPoint(ctx, x + cellRect.size.width, y);
958981
CGContextSetRGBStrokeColor(ctx, RED(cell.sp), GREEN(cell.sp), BLUE(cell.sp), ALPHA(cell.sp));
982+
CGContextSetLineDash(ctx, 0, dashLengths, 2);
959983
CGContextStrokePath(ctx);
960984
}
985+
else if (cell.textFlags & DRAW_UNDERDOTTED) {
986+
// Calculate dot size to use. Normally, just do 1-pixel dots/gaps, since the line is one pixel thick.
987+
CGFloat dotSize = 1, gapSize = 1;
988+
if (fmod(cellSize.width, 2) != 0) {
989+
// Width is not even number, so spacing them would look weird. Find another way.
990+
if (fmod(cellSize.width, 3) == 0) {
991+
// Width is divisible by 3, so just make the gap twice as long so they can be spaced out.
992+
dotSize = 1;
993+
gapSize = 2;
994+
}
995+
else {
996+
// Not disible by 2 or 3. Just Re-calculate dot size so be slightly larger than 1 so we can exactly
997+
// equal number of dots and gaps. This does mean we have a non-integer size, so we are relying
998+
// on anti-aliasing here to help this not look too bad, but it will still look slightly blurry.
999+
dotSize = cellSize.width / (ceil(cellSize.width / 2) * 2);
1000+
gapSize = dotSize;
1001+
}
1002+
}
1003+
const CGFloat dashLengths[] = {dotSize, gapSize};
1004+
1005+
const CGFloat x = cellRect.origin.x;
1006+
const CGFloat y = cellRect.origin.y+underlineY;
1007+
CGContextMoveToPoint(ctx, x, y);
1008+
CGContextAddLineToPoint(ctx, x + cellRect.size.width, y);
1009+
CGContextSetRGBStrokeColor(ctx, RED(cell.sp), GREEN(cell.sp), BLUE(cell.sp), ALPHA(cell.sp));
1010+
CGContextSetLineDash(ctx, 0, dashLengths, 2);
1011+
CGContextStrokePath(ctx);
1012+
}
1013+
else if (cell.textFlags & DRAW_UNDERDOUBLE) {
1014+
CGRect rect = CGRectMake(cellRect.origin.x, cellRect.origin.y+underlineY, cellRect.size.width, 1);
1015+
CGContextSetFillColor(ctx, COMPONENTS(cell.sp));
1016+
CGContextFillRect(ctx, rect);
1017+
1018+
// Draw second underline
1019+
if (underlineY - 3 < 0) {
1020+
// Not enough fontDescent to draw another line below, just draw above. This is not the desired
1021+
// solution but works.
1022+
rect = CGRectMake(cellRect.origin.x, cellRect.origin.y+underlineY + 3, cellRect.size.width, 1);
1023+
} else {
1024+
// Nominal situation. Just a second one below first one.
1025+
rect = CGRectMake(cellRect.origin.x, cellRect.origin.y+underlineY - 3, cellRect.size.width, 1);
1026+
}
1027+
CGContextSetFillColor(ctx, COMPONENTS(cell.sp));
1028+
CGContextFillRect(ctx, rect);
1029+
} else if (cell.textFlags & DRAW_UNDERL) {
1030+
CGRect rect = CGRectMake(cellRect.origin.x, cellRect.origin.y+underlineY, cellRect.size.width, 1);
1031+
CGContextSetFillColor(ctx, COMPONENTS(cell.sp));
1032+
CGContextFillRect(ctx, rect);
1033+
}
1034+
1035+
// Text strikethrough
1036+
// We delay the rendering of strikethrough and only do it as a second-pass since we want to draw them on top
1037+
// of text, and text rendering is currently delayed via flushLineString().
1038+
if (cell.textFlags & DRAW_STRIKE) {
1039+
hasStrikeThrough = YES;
1040+
}
9611041

9621042
// Draw the actual text
9631043
if (cell.string) {
@@ -981,6 +1061,31 @@ - (void)drawRect:(NSRect)rect
9811061
}
9821062
flushLineString();
9831063
[lineString release];
1064+
1065+
if (hasStrikeThrough) {
1066+
// Second pass to render strikethrough. Unfortunately have to duplicate a little bit of code here to loop
1067+
// through the cells.
1068+
for (size_t c = 0; c < grid.cols; c++) {
1069+
GridCell cell = *grid_cell(&grid, r, c);
1070+
CGRect cellRect = {{rowRect.origin.x + cellSize.width * c, rowRect.origin.y}, cellSize};
1071+
if (cell.textFlags & DRAW_WIDE)
1072+
cellRect.size.width *= 2;
1073+
if (cell.inverted) {
1074+
cell.bg ^= 0xFFFFFF;
1075+
cell.fg ^= 0xFFFFFF;
1076+
cell.sp ^= 0xFFFFFF;
1077+
}
1078+
1079+
// Text strikethrough
1080+
if (cell.textFlags & DRAW_STRIKE) {
1081+
CGRect rect = CGRectMake(cellRect.origin.x, cellRect.origin.y + fontDescent + fontXHeight / 2, cellRect.size.width, 1);
1082+
CGContextSetFillColor(ctx, COMPONENTS(cell.sp));
1083+
CGContextFillRect(ctx, rect);
1084+
}
1085+
1086+
}
1087+
}
1088+
9841089
CGContextRestoreGState(ctx);
9851090
}
9861091
if (thinStrokes) {

src/gui.c

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2527,7 +2527,14 @@ gui_outstr_nowrap(
25272527
if (hl_mask_todo & HL_UNDERCURL)
25282528
draw_flags |= DRAW_UNDERC;
25292529

2530-
// TODO: HL_UNDERDOUBLE, HL_UNDERDOTTED, HL_UNDERDASHED
2530+
// MacVim note: underdouble/underdotted/underdashed are not implemented in Vim yet.
2531+
// These are MacVim-only for now.
2532+
if (hl_mask_todo & HL_UNDERDOUBLE)
2533+
draw_flags |= DRAW_UNDERDOUBLE;
2534+
if (hl_mask_todo & HL_UNDERDOTTED)
2535+
draw_flags |= DRAW_UNDERDOTTED;
2536+
if (hl_mask_todo & HL_UNDERDASHED)
2537+
draw_flags |= DRAW_UNDERDASHED;
25312538

25322539
// Do we strikethrough the text?
25332540
if (hl_mask_todo & HL_STRIKETHROUGH)

src/gui.h

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -128,8 +128,18 @@
128128
#endif
129129
#define DRAW_CURSOR 0x20 // drawing block cursor (win32)
130130
#define DRAW_STRIKE 0x40 // strikethrough
131-
#define DRAW_WIDE 0x80 // drawing wide char (MacVim)
132-
#define DRAW_COMP 0x100 // drawing composing char (MacVim)
131+
// MacVim note: underdouble/underdotted/underdashed are not implemented in Vim yet.
132+
// These are MacVim-only for now.
133+
// IMPORTANT: If resolving a merge conflict when merging from upstream, if Vim decided
134+
// to use different values for these constants, MMCoreTextView.m would need
135+
// to be updated to reflect them as well, or the renderer won't understand
136+
// these values.
137+
#define DRAW_UNDERDOUBLE 0x80 // draw double underline
138+
#define DRAW_UNDERDOTTED 0x100 // draw dotted underline
139+
#define DRAW_UNDERDASHED 0x200 // draw dashed underline
140+
141+
#define DRAW_WIDE 0x1000 // drawing wide char (MacVim)
142+
#define DRAW_COMP 0x2000 // drawing composing char (MacVim)
133143

134144
// For our own tearoff menu item
135145
#define TEAR_STRING "-->Detach"

0 commit comments

Comments
 (0)