Skip to content

Support strikethrough/underdouble/underdashed/underdotted styles #1287

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Sep 15, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion src/MacVim/MMCoreTextView.h
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,9 @@
// From NSTextView
NSSize insetSize;

float fontDescent;
CGFloat fontDescent;
CGFloat fontAscent;
CGFloat fontXHeight;
BOOL antialias;
BOOL ligatures;
BOOL thinStrokes;
Expand Down
133 changes: 119 additions & 14 deletions src/MacVim/MMCoreTextView.m
Original file line number Diff line number Diff line change
Expand Up @@ -36,14 +36,18 @@
// TODO: What does DRAW_TRANSP flag do? If the background isn't drawn when
// this flag is set, then sometimes the character after the cursor becomes
// blank. Everything seems to work fine by just ignoring this flag.
#define DRAW_TRANSP 0x01 /* draw with transparent bg */
#define DRAW_BOLD 0x02 /* draw bold text */
#define DRAW_UNDERL 0x04 /* draw underline text */
#define DRAW_UNDERC 0x08 /* draw undercurl text */
#define DRAW_ITALIC 0x10 /* draw italic text */
#define DRAW_TRANSP 0x01 // draw with transparent bg
#define DRAW_BOLD 0x02 // draw bold text
#define DRAW_UNDERL 0x04 // draw underline text
#define DRAW_UNDERC 0x08 // draw undercurl text
#define DRAW_ITALIC 0x10 // draw italic text
#define DRAW_CURSOR 0x20
#define DRAW_WIDE 0x80 /* draw wide text */
#define DRAW_COMP 0x100 /* drawing composing char */
#define DRAW_STRIKE 0x40 // draw strikethrough text
#define DRAW_UNDERDOUBLE 0x80 // draw double underline
#define DRAW_UNDERDOTTED 0x100 // draw dotted underline
#define DRAW_UNDERDASHED 0x200 // draw dashed underline
#define DRAW_WIDE 0x1000 // (MacVim only) draw wide text
#define DRAW_COMP 0x2000 // (MacVim only) drawing composing char

#if MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_X_VERSION_10_13
typedef NSString * NSAttributedStringKey;
Expand Down Expand Up @@ -431,6 +435,8 @@ - (void)setFont:(NSFont *)newFont
font = [newFont retain];
}
fontDescent = ceil(CTFontGetDescent((CTFontRef)font));
fontAscent = CTFontGetAscent((CTFontRef)font);
fontXHeight = CTFontGetXHeight((CTFontRef)font);

// NOTE! Even though NSFontFixedAdvanceAttribute is a float, it will
// only render at integer sizes. Hence, we restrict the cell width to
Expand Down Expand Up @@ -884,6 +890,9 @@ - (void)drawRect:(NSRect)rect
CGContextSetBlendMode(ctx, kCGBlendModeCopy);
[lineString deleteCharactersInRange:NSMakeRange(0, lineString.length)];
};

BOOL hasStrikeThrough = NO;

for (size_t c = 0; c < grid.cols; c++) {
GridCell cell = *grid_cell(&grid, r, c);
CGRect cellRect = {{rowRect.origin.x + cellSize.width * c, rowRect.origin.y}, cellSize};
Expand Down Expand Up @@ -945,19 +954,90 @@ - (void)drawRect:(NSRect)rect
}
}

// Text underline styles
if (cell.textFlags & DRAW_UNDERL) {
CGRect rect = CGRectMake(cellRect.origin.x, cellRect.origin.y+0.4*fontDescent, cellRect.size.width, 1);
CGContextSetFillColor(ctx, COMPONENTS(cell.sp));
CGContextFillRect(ctx, rect);
} else if (cell.textFlags & DRAW_UNDERC) {
const float x = cellRect.origin.x, y = cellRect.origin.y+1, w = cellSize.width, h = 0.5*fontDescent;
// Text underline styles. We only allow one of them to be active.
// Note: We are not currently using underlineThickness or underlinePosition. Should fix to use them.
const CGFloat underlineY = 0.4*fontDescent; // Just a hard-coded value for now. Should fix to use underlinePosition.
if (cell.textFlags & DRAW_UNDERC) {
const CGFloat x = cellRect.origin.x, y = cellRect.origin.y+1, w = cellSize.width, h = 0.5*fontDescent;
CGContextMoveToPoint(ctx, x, y);
CGContextAddCurveToPoint(ctx, x+0.25*w, y, x+0.25*w, y+h, x+0.5*w, y+h);
CGContextAddCurveToPoint(ctx, x+0.75*w, y+h, x+0.75*w, y, x+w, y);
if (cell.textFlags & DRAW_WIDE) {
// Need to draw another set for double-width characters
const CGFloat x2 = x + cellSize.width;
CGContextAddCurveToPoint(ctx, x2+0.25*w, y, x2+0.25*w, y+h, x2+0.5*w, y+h);
CGContextAddCurveToPoint(ctx, x2+0.75*w, y+h, x2+0.75*w, y, x2+w, y);
}
CGContextSetRGBStrokeColor(ctx, RED(cell.sp), GREEN(cell.sp), BLUE(cell.sp), ALPHA(cell.sp));
CGContextStrokePath(ctx);
}
else if (cell.textFlags & DRAW_UNDERDASHED) {
const CGFloat dashLengths[] = {cellSize.width / 4, cellSize.width / 4};

const CGFloat x = cellRect.origin.x;
const CGFloat y = cellRect.origin.y+underlineY;
CGContextMoveToPoint(ctx, x, y);
CGContextAddLineToPoint(ctx, x + cellRect.size.width, y);
CGContextSetRGBStrokeColor(ctx, RED(cell.sp), GREEN(cell.sp), BLUE(cell.sp), ALPHA(cell.sp));
CGContextSetLineDash(ctx, 0, dashLengths, 2);
CGContextStrokePath(ctx);
}
else if (cell.textFlags & DRAW_UNDERDOTTED) {
// Calculate dot size to use. Normally, just do 1-pixel dots/gaps, since the line is one pixel thick.
CGFloat dotSize = 1, gapSize = 1;
if (fmod(cellSize.width, 2) != 0) {
// Width is not even number, so spacing them would look weird. Find another way.
if (fmod(cellSize.width, 3) == 0) {
// Width is divisible by 3, so just make the gap twice as long so they can be spaced out.
dotSize = 1;
gapSize = 2;
}
else {
// Not disible by 2 or 3. Just Re-calculate dot size so be slightly larger than 1 so we can exactly
// equal number of dots and gaps. This does mean we have a non-integer size, so we are relying
// on anti-aliasing here to help this not look too bad, but it will still look slightly blurry.
dotSize = cellSize.width / (ceil(cellSize.width / 2) * 2);
gapSize = dotSize;
}
}
const CGFloat dashLengths[] = {dotSize, gapSize};

const CGFloat x = cellRect.origin.x;
const CGFloat y = cellRect.origin.y+underlineY;
CGContextMoveToPoint(ctx, x, y);
CGContextAddLineToPoint(ctx, x + cellRect.size.width, y);
CGContextSetRGBStrokeColor(ctx, RED(cell.sp), GREEN(cell.sp), BLUE(cell.sp), ALPHA(cell.sp));
CGContextSetLineDash(ctx, 0, dashLengths, 2);
CGContextStrokePath(ctx);
}
else if (cell.textFlags & DRAW_UNDERDOUBLE) {
CGRect rect = CGRectMake(cellRect.origin.x, cellRect.origin.y+underlineY, cellRect.size.width, 1);
CGContextSetFillColor(ctx, COMPONENTS(cell.sp));
CGContextFillRect(ctx, rect);

// Draw second underline
if (underlineY - 3 < 0) {
// Not enough fontDescent to draw another line below, just draw above. This is not the desired
// solution but works.
rect = CGRectMake(cellRect.origin.x, cellRect.origin.y+underlineY + 3, cellRect.size.width, 1);
} else {
// Nominal situation. Just a second one below first one.
rect = CGRectMake(cellRect.origin.x, cellRect.origin.y+underlineY - 3, cellRect.size.width, 1);
}
CGContextSetFillColor(ctx, COMPONENTS(cell.sp));
CGContextFillRect(ctx, rect);
} else if (cell.textFlags & DRAW_UNDERL) {
CGRect rect = CGRectMake(cellRect.origin.x, cellRect.origin.y+underlineY, cellRect.size.width, 1);
CGContextSetFillColor(ctx, COMPONENTS(cell.sp));
CGContextFillRect(ctx, rect);
}

// Text strikethrough
// We delay the rendering of strikethrough and only do it as a second-pass since we want to draw them on top
// of text, and text rendering is currently delayed via flushLineString().
if (cell.textFlags & DRAW_STRIKE) {
hasStrikeThrough = YES;
}

// Draw the actual text
if (cell.string) {
Expand All @@ -981,6 +1061,31 @@ - (void)drawRect:(NSRect)rect
}
flushLineString();
[lineString release];

if (hasStrikeThrough) {
// Second pass to render strikethrough. Unfortunately have to duplicate a little bit of code here to loop
// through the cells.
for (size_t c = 0; c < grid.cols; c++) {
GridCell cell = *grid_cell(&grid, r, c);
CGRect cellRect = {{rowRect.origin.x + cellSize.width * c, rowRect.origin.y}, cellSize};
if (cell.textFlags & DRAW_WIDE)
cellRect.size.width *= 2;
if (cell.inverted) {
cell.bg ^= 0xFFFFFF;
cell.fg ^= 0xFFFFFF;
cell.sp ^= 0xFFFFFF;
}

// Text strikethrough
if (cell.textFlags & DRAW_STRIKE) {
CGRect rect = CGRectMake(cellRect.origin.x, cellRect.origin.y + fontDescent + fontXHeight / 2, cellRect.size.width, 1);
CGContextSetFillColor(ctx, COMPONENTS(cell.sp));
CGContextFillRect(ctx, rect);
}

}
}

CGContextRestoreGState(ctx);
}
if (thinStrokes) {
Expand Down
9 changes: 8 additions & 1 deletion src/gui.c
Original file line number Diff line number Diff line change
Expand Up @@ -2527,7 +2527,14 @@ gui_outstr_nowrap(
if (hl_mask_todo & HL_UNDERCURL)
draw_flags |= DRAW_UNDERC;

// TODO: HL_UNDERDOUBLE, HL_UNDERDOTTED, HL_UNDERDASHED
// MacVim note: underdouble/underdotted/underdashed are not implemented in Vim yet.
// These are MacVim-only for now.
if (hl_mask_todo & HL_UNDERDOUBLE)
draw_flags |= DRAW_UNDERDOUBLE;
if (hl_mask_todo & HL_UNDERDOTTED)
draw_flags |= DRAW_UNDERDOTTED;
if (hl_mask_todo & HL_UNDERDASHED)
draw_flags |= DRAW_UNDERDASHED;

// Do we strikethrough the text?
if (hl_mask_todo & HL_STRIKETHROUGH)
Expand Down
14 changes: 12 additions & 2 deletions src/gui.h
Original file line number Diff line number Diff line change
Expand Up @@ -128,8 +128,18 @@
#endif
#define DRAW_CURSOR 0x20 // drawing block cursor (win32)
#define DRAW_STRIKE 0x40 // strikethrough
#define DRAW_WIDE 0x80 // drawing wide char (MacVim)
#define DRAW_COMP 0x100 // drawing composing char (MacVim)
// MacVim note: underdouble/underdotted/underdashed are not implemented in Vim yet.
// These are MacVim-only for now.
// IMPORTANT: If resolving a merge conflict when merging from upstream, if Vim decided
// to use different values for these constants, MMCoreTextView.m would need
// to be updated to reflect them as well, or the renderer won't understand
// these values.
#define DRAW_UNDERDOUBLE 0x80 // draw double underline
#define DRAW_UNDERDOTTED 0x100 // draw dotted underline
#define DRAW_UNDERDASHED 0x200 // draw dashed underline

#define DRAW_WIDE 0x1000 // drawing wide char (MacVim)
#define DRAW_COMP 0x2000 // drawing composing char (MacVim)

// For our own tearoff menu item
#define TEAR_STRING "-->Detach"
Expand Down