Implemented CoreText renderer
[MacVim.git] / src / MacVim / MMCoreTextView.m
blob2c1027119fff2a34926cfbfecbe63314e055b61c
1 /* vi:set ts=8 sts=4 sw=4 ft=objc:
2  *
3  * VIM - Vi IMproved            by Bram Moolenaar
4  *                              MacVim GUI port by Bjorn Winckler
5  *
6  * Do ":help uganda"  in Vim to read copying and usage conditions.
7  * Do ":help credits" in Vim to see a list of people who contributed.
8  * See README.txt for an overview of the Vim source code.
9  */
11  * MMCoreTextView
12  *
13  * Dispatches keyboard and mouse input to the backend.  Handles drag-n-drop of
14  * files onto window.  The rendering is done using CoreText.
15  *
16  * The text view area consists of two parts:
17  *   1. The text area - this is where text is rendered; the size is governed by
18  *      the current number of rows and columns.
19  *   2. The inset area - this is a border around the text area; the size is
20  *      governed by the user defaults MMTextInset[Left|Right|Top|Bottom].
21  *
22  * The current size of the text view frame does not always match the desired
23  * area, i.e. the area determined by the number of rows, columns plus text
24  * inset.  This distinction is particularly important when the view is being
25  * resized.
26  */
28 #import "MMAppController.h"
29 #import "MMCoreTextView.h"
30 #import "MMTextViewHelper.h"
31 #import "MMVimController.h"
32 #import "MMWindowController.h"
33 #import "Miscellaneous.h"
36 // TODO: What does DRAW_TRANSP flag do?  If the background isn't drawn when
37 // this flag is set, then sometimes the character after the cursor becomes
38 // 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 */
44 #define DRAW_CURSOR               0x20
45 #define DRAW_WIDE                 0x40    /* draw wide text */
48 #define BLUE(argb)      ((argb & 0xff)/255.0f)
49 #define GREEN(argb)     (((argb>>8) & 0xff)/255.0f)
50 #define RED(argb)       (((argb>>16) & 0xff)/255.0f)
51 #define ALPHA(argb)     (((argb>>24) & 0xff)/255.0f)
55 @interface MMCoreTextView (Private)
56 - (MMWindowController *)windowController;
57 - (MMVimController *)vimController;
58 @end
61 @interface MMCoreTextView (Drawing)
62 - (NSPoint)pointForRow:(int)row column:(int)column;
63 - (NSRect)rectForRow:(int)row column:(int)column numRows:(int)nr
64           numColumns:(int)nc;
65 - (NSRect)rectFromRow:(int)row1 column:(int)col1
66                 toRow:(int)row2 column:(int)col2;
67 - (NSSize)textAreaSize;
68 - (void)batchDrawData:(NSData *)data;
69 - (void)drawString:(const UniChar *)chars length:(UniCharCount)length
70              atRow:(int)row column:(int)col cells:(int)cells
71          withFlags:(int)flags foregroundColor:(int)fg
72    backgroundColor:(int)bg specialColor:(int)sp;
73 - (void)deleteLinesFromRow:(int)row lineCount:(int)count
74               scrollBottom:(int)bottom left:(int)left right:(int)right
75                      color:(int)color;
76 - (void)insertLinesAtRow:(int)row lineCount:(int)count
77             scrollBottom:(int)bottom left:(int)left right:(int)right
78                    color:(int)color;
79 - (void)clearBlockFromRow:(int)row1 column:(int)col1 toRow:(int)row2
80                    column:(int)col2 color:(int)color;
81 - (void)clearAll;
82 - (void)drawInsertionPointAtRow:(int)row column:(int)col shape:(int)shape
83                        fraction:(int)percent color:(int)color;
84 - (void)drawInvertedRectAtRow:(int)row column:(int)col numRows:(int)nrows
85                    numColumns:(int)ncols;
86 @end
90     static float
91 defaultLineHeightForFont(NSFont *font)
93     // HACK: -[NSFont defaultLineHeightForFont] is deprecated but since the
94     // CoreText renderer does not use NSLayoutManager we create one
95     // temporarily.
96     NSLayoutManager *lm = [[NSLayoutManager alloc] init];
97     float height = [lm defaultLineHeightForFont:font];
98     [lm release];
100     return height;
103     static double
104 defaultAdvanceForFont(CTFontRef fontRef)
106     // We measure the default advance of a font as the advance of its glyph for
107     // 'm'.
108     double advance = 0.0;
109     UniChar characters[] = { 'm' };
110     int count = 1;
111     CGGlyph glyphs[] = { 0 };
113     if (CTFontGetGlyphsForCharacters(fontRef, characters, glyphs, count)) {
114         advance = CTFontGetAdvancesForGlyphs(fontRef,
115                                              kCTFontDefaultOrientation,
116                                              glyphs,
117                                              NULL,
118                                              count);
119     }
121     if (advance < 1.0) {
122         ASLogWarn(@"Could not determine default advance for current font");
123         advance = 10.0f;
124     }
126     return advance;
129 @implementation MMCoreTextView
131 - (id)initWithFrame:(NSRect)frame
133     if (!(self = [super initWithFrame:frame]))
134         return nil;
136     // NOTE!  It does not matter which font is set here, Vim will set its
137     // own font on startup anyway.  Just set some bogus values.
138     font = [[NSFont userFixedPitchFontOfSize:0] retain];
139     cellSize.width = cellSize.height = 1;
141     // NOTE: If the default changes to 'NO' then the intialization of
142     // p_antialias in option.c must change as well.
143     antialias = YES;
145     drawData = [[NSMutableArray alloc] init];
147     helper = [[MMTextViewHelper alloc] init];
148     [helper setTextView:self];
150     [self registerForDraggedTypes:[NSArray arrayWithObjects:
151             NSFilenamesPboardType, NSStringPboardType, nil]];
153     return self;
156 - (void)dealloc
158     [font release];  font = nil;
159     [defaultBackgroundColor release];  defaultBackgroundColor = nil;
160     [defaultForegroundColor release];  defaultForegroundColor = nil;
161     [drawData release];  drawData = nil;
163     [helper setTextView:nil];
164     [helper release];  helper = nil;
166     if (glyphs) { free(glyphs); glyphs = NULL; }
167     if (advances) { free(advances); advances = NULL; }
169     [super dealloc];
172 - (int)maxRows
174     return maxRows;
177 - (int)maxColumns
179     return maxColumns;
182 - (void)getMaxRows:(int*)rows columns:(int*)cols
184     if (rows) *rows = maxRows;
185     if (cols) *cols = maxColumns;
188 - (void)setMaxRows:(int)rows columns:(int)cols
190     // NOTE: Just remember the new values, the actual resizing is done lazily.
191     maxRows = rows;
192     maxColumns = cols;
195 - (void)setDefaultColorsBackground:(NSColor *)bgColor
196                         foreground:(NSColor *)fgColor
198     if (defaultBackgroundColor != bgColor) {
199         [defaultBackgroundColor release];
200         defaultBackgroundColor = bgColor ? [bgColor retain] : nil;
201     }
203     // NOTE: The default foreground color isn't actually used for anything, but
204     // other class instances might want to be able to access it so it is stored
205     // here.
206     if (defaultForegroundColor != fgColor) {
207         [defaultForegroundColor release];
208         defaultForegroundColor = fgColor ? [fgColor retain] : nil;
209     }
212 - (NSColor *)defaultBackgroundColor
214     return defaultBackgroundColor;
217 - (NSColor *)defaultForegroundColor
219     return defaultForegroundColor;
222 - (void)setTextContainerInset:(NSSize)size
224     insetSize = size;
227 - (NSRect)rectForRowsInRange:(NSRange)range
229     NSRect rect = { {0, 0}, {0, 0} };
230     unsigned start = range.location > maxRows ? maxRows : range.location;
231     unsigned length = range.length;
233     if (start + length > maxRows)
234         length = maxRows - start;
236     rect.origin.y = cellSize.height * start + insetSize.height;
237     rect.size.height = cellSize.height * length;
239     return rect;
242 - (NSRect)rectForColumnsInRange:(NSRange)range
244     NSRect rect = { {0, 0}, {0, 0} };
245     unsigned start = range.location > maxColumns ? maxColumns : range.location;
246     unsigned length = range.length;
248     if (start+length > maxColumns)
249         length = maxColumns - start;
251     rect.origin.x = cellSize.width * start + insetSize.width;
252     rect.size.width = cellSize.width * length;
254     return rect;
258 - (void)setFont:(NSFont *)newFont
260 #if 0
261     if (newFont && font != newFont) {
262         [font release];
263         font = [newFont retain];
265         float em = 7.0; //[newFont widthOfString:@"m"];
266         float cellWidthMultiplier = [[NSUserDefaults standardUserDefaults]
267                 floatForKey:MMCellWidthMultiplierKey];
269         // NOTE! Even though NSFontFixedAdvanceAttribute is a float, it will
270         // only render at integer sizes.  Hence, we restrict the cell width to
271         // an integer here, otherwise the window width and the actual text
272         // width will not match.
273         cellSize.width = ceilf(em * cellWidthMultiplier);
274         cellSize.height = linespace + 15.0; //[newFont defaultLineHeightForFont];
275     }
276 #else
277     if (!(newFont && font != newFont))
278         return;
280     double em = round(defaultAdvanceForFont((CTFontRef)newFont));
281     double pt = round([newFont pointSize]);
283     NSDictionary *attr = [NSDictionary dictionaryWithObjectsAndKeys:
284         [newFont displayName], (NSString*)kCTFontNameAttribute,
285         [NSNumber numberWithFloat:pt], (NSString*)kCTFontSizeAttribute,
286         //[NSNumber numberWithFloat:em], (NSString*)kCTFontFixedAdvanceAttribute,
287         nil];
288     CTFontDescriptorRef desc = CTFontDescriptorCreateWithAttributes(
289             (CFDictionaryRef)attr);
290     CTFontRef fontRef = CTFontCreateWithFontDescriptor(desc, pt, NULL);
291     CFRelease(desc);
293     [font release];
294     font = (NSFont*)fontRef;
296     float cellWidthMultiplier = [[NSUserDefaults standardUserDefaults]
297             floatForKey:MMCellWidthMultiplierKey];
299     // NOTE! Even though NSFontFixedAdvanceAttribute is a float, it will
300     // only render at integer sizes.  Hence, we restrict the cell width to
301     // an integer here, otherwise the window width and the actual text
302     // width will not match.
303     cellSize.width = ceil(em * cellWidthMultiplier);
304     cellSize.height = linespace + defaultLineHeightForFont(font);
306     fontDescent = ceil(CTFontGetDescent(fontRef));
307 #endif
310 - (void)setWideFont:(NSFont *)newFont
314 - (NSFont *)font
316     return font;
319 - (NSFont *)fontWide
321     return nil;
324 - (NSSize)cellSize
326     return cellSize;
329 - (void)setLinespace:(float)newLinespace
331     linespace = newLinespace;
333     // NOTE: The linespace is added to the cell height in order for a multiline
334     // selection not to have white (background color) gaps between lines.  Also
335     // this simplifies the code a lot because there is no need to check the
336     // linespace when calculating the size of the text view etc.  When the
337     // linespace is non-zero the baseline will be adjusted as well; check
338     // MMTypesetter.
339     cellSize.height = linespace + defaultLineHeightForFont(font);
345 - (void)setShouldDrawInsertionPoint:(BOOL)on
349 - (void)setPreEditRow:(int)row column:(int)col
353 - (void)setMouseShape:(int)shape
355     [helper setMouseShape:shape];
358 - (void)setAntialias:(BOOL)state
360     antialias = state;
363 - (void)setImControl:(BOOL)enable
365     [helper setImControl:enable];
368 - (void)activateIm:(BOOL)enable
370     [helper activateIm:enable];
376 - (void)keyDown:(NSEvent *)event
378     [helper keyDown:event];
381 - (void)insertText:(id)string
383     [helper insertText:string];
386 - (void)doCommandBySelector:(SEL)selector
388     [helper doCommandBySelector:selector];
391 - (BOOL)performKeyEquivalent:(NSEvent *)event
393     return [helper performKeyEquivalent:event];
396 - (BOOL)hasMarkedText
398     return [helper hasMarkedText];
401 - (NSRange)markedRange
403     return [helper markedRange];
406 - (NSDictionary *)markedTextAttributes
408     return [helper markedTextAttributes];
411 - (void)setMarkedTextAttributes:(NSDictionary *)attr
413     [helper setMarkedTextAttributes:attr];
416 - (void)setMarkedText:(id)text selectedRange:(NSRange)range
418     [helper setMarkedText:text selectedRange:range];
421 - (void)unmarkText
423     [helper unmarkText];
426 - (void)scrollWheel:(NSEvent *)event
428     [helper scrollWheel:event];
431 - (void)mouseDown:(NSEvent *)event
433     [helper mouseDown:event];
436 - (void)rightMouseDown:(NSEvent *)event
438     [helper mouseDown:event];
441 - (void)otherMouseDown:(NSEvent *)event
443     [helper mouseDown:event];
446 - (void)mouseUp:(NSEvent *)event
448     [helper mouseUp:event];
451 - (void)rightMouseUp:(NSEvent *)event
453     [helper mouseUp:event];
456 - (void)otherMouseUp:(NSEvent *)event
458     [helper mouseUp:event];
461 - (void)mouseDragged:(NSEvent *)event
463     [helper mouseDragged:event];
466 - (void)rightMouseDragged:(NSEvent *)event
468     [helper mouseDragged:event];
471 - (void)otherMouseDragged:(NSEvent *)event
473     [helper mouseDragged:event];
476 - (void)mouseMoved:(NSEvent *)event
478     [helper mouseMoved:event];
481 - (void)mouseEntered:(NSEvent *)event
483     [helper mouseEntered:event];
486 - (void)mouseExited:(NSEvent *)event
488     [helper mouseExited:event];
491 - (void)setFrame:(NSRect)frame
493     [super setFrame:frame];
494     [helper setFrame:frame];
497 - (void)viewDidMoveToWindow
499     [helper viewDidMoveToWindow];
502 - (void)viewWillMoveToWindow:(NSWindow *)newWindow
504     [helper viewWillMoveToWindow:newWindow];
507 - (NSMenu*)menuForEvent:(NSEvent *)event
509     // HACK! Return nil to disable default popup menus (Vim provides its own).
510     // Called when user Ctrl-clicks in the view (this is already handled in
511     // rightMouseDown:).
512     return nil;
515 - (BOOL)performDragOperation:(id <NSDraggingInfo>)sender
517     return [helper performDragOperation:sender];
520 - (NSDragOperation)draggingEntered:(id <NSDraggingInfo>)sender
522     return [helper draggingEntered:sender];
525 - (NSDragOperation)draggingUpdated:(id <NSDraggingInfo>)sender
527     return [helper draggingUpdated:sender];
532 - (BOOL)mouseDownCanMoveWindow
534     return NO;
537 - (BOOL)isOpaque
539     return YES;
542 - (BOOL)acceptsFirstResponder
544     return YES;
547 - (BOOL)isFlipped
549     return NO;
552 - (void)drawRect:(NSRect)rect
554     //ASLogNotice(@"drawData count=%d", [drawData count]);
556     NSGraphicsContext *context = [NSGraphicsContext currentContext];
557     [context setShouldAntialias:antialias];
559     NSRect frame = [self frame];
560     if (!NSEqualRects(lastClearRect, frame)) {
561         // HACK! If the view frame changes then clear the entire frame
562         // otherwise the screen flickers e.g. when the window is resized.
563 #if 1
564         [self clearAll];
565 #else
566         float y = frame.size.height - lastClearRect.size.height;
567         if (y > 0 && y < frame.size.height) {
568             NSCopyBits(0, lastClearRect, NSMakePoint(0, y));
569         }
570 #endif
571         lastClearRect = frame;
572     }
574     id data;
575     NSEnumerator *e = [drawData objectEnumerator];
576     while ((data = [e nextObject]))
577         [self batchDrawData:data];
579     [drawData removeAllObjects];
582 - (void)performBatchDrawWithData:(NSData *)data
584     [drawData addObject:data];
585     [self setNeedsDisplay:YES];
587     // NOTE: During resizing, Cocoa only sends draw messages before Vim's rows
588     // and columns are changed (due to ipc delays). Force a redraw here.
589     if ([self inLiveResize])
590         [self display];
593 - (NSSize)constrainRows:(int *)rows columns:(int *)cols toSize:(NSSize)size
595     // TODO:
596     // - Rounding errors may cause size change when there should be none
597     // - Desired rows/columns shold not be 'too small'
599     // Constrain the desired size to the given size.  Values for the minimum
600     // rows and columns is taken from Vim.
601     NSSize desiredSize = [self desiredSize];
602     int desiredRows = maxRows;
603     int desiredCols = maxColumns;
604     NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
605     int right = [ud integerForKey:MMTextInsetRightKey];
606     int bot = [ud integerForKey:MMTextInsetBottomKey];
608     if (size.height != desiredSize.height) {
609         float fh = cellSize.height;
610         float ih = insetSize.height + bot;
611         if (fh < 1.0f) fh = 1.0f;
613         desiredRows = floor((size.height - ih)/fh);
614         desiredSize.height = fh*desiredRows + ih;
615     }
617     if (size.width != desiredSize.width) {
618         float fw = cellSize.width;
619         float iw = insetSize.width + right;
620         if (fw < 1.0f) fw = 1.0f;
622         desiredCols = floor((size.width - iw)/fw);
623         desiredSize.width = fw*desiredCols + iw;
624     }
626     if (rows) *rows = desiredRows;
627     if (cols) *cols = desiredCols;
629     return desiredSize;
632 - (NSSize)desiredSize
634     // Compute the size the text view should be for the entire text area and
635     // inset area to be visible with the present number of rows and columns.
636     NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
637     int right = [ud integerForKey:MMTextInsetRightKey];
638     int bot = [ud integerForKey:MMTextInsetBottomKey];
640     return NSMakeSize(maxColumns * cellSize.width + insetSize.width + right,
641                       maxRows * cellSize.height + insetSize.height + bot);
644 - (NSSize)minSize
646     // Compute the smallest size the text view is allowed to be.
647     NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
648     int right = [ud integerForKey:MMTextInsetRightKey];
649     int bot = [ud integerForKey:MMTextInsetBottomKey];
651     return NSMakeSize(MMMinColumns * cellSize.width + insetSize.width + right,
652                       MMMinRows * cellSize.height + insetSize.height + bot);
655 - (void)changeFont:(id)sender
657     NSFont *newFont = [sender convertFont:font];
659     if (newFont) {
660         NSString *name = [newFont displayName];
661         unsigned len = [name lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
662         if (len > 0) {
663             NSMutableData *data = [NSMutableData data];
664             float pointSize = [newFont pointSize];
666             [data appendBytes:&pointSize length:sizeof(float)];
668             ++len;  // include NUL byte
669             [data appendBytes:&len length:sizeof(unsigned)];
670             [data appendBytes:[name UTF8String] length:len];
672             [[self vimController] sendMessage:SetFontMsgID data:data];
673         }
674     }
679 // NOTE: The menu items cut/copy/paste/undo/redo/select all/... must be bound
680 // to the same actions as in IB otherwise they will not work with dialogs.  All
681 // we do here is forward these actions to the Vim process.
683 - (IBAction)cut:(id)sender
685     [[self windowController] vimMenuItemAction:sender];
688 - (IBAction)copy:(id)sender
690     [[self windowController] vimMenuItemAction:sender];
693 - (IBAction)paste:(id)sender
695     [[self windowController] vimMenuItemAction:sender];
698 - (IBAction)undo:(id)sender
700     [[self windowController] vimMenuItemAction:sender];
703 - (IBAction)redo:(id)sender
705     [[self windowController] vimMenuItemAction:sender];
708 - (IBAction)selectAll:(id)sender
710     [[self windowController] vimMenuItemAction:sender];
713 - (BOOL)convertPoint:(NSPoint)point toRow:(int *)row column:(int *)column
715     // View is not flipped, instead the atsui code draws to a flipped image;
716     // thus we need to 'flip' the coordinate here since the column number
717     // increases in an up-to-down order.
718     point.y = [self frame].size.height - point.y;
720     NSPoint origin = { insetSize.width, insetSize.height };
722     if (!(cellSize.width > 0 && cellSize.height > 0))
723         return NO;
725     if (row) *row = floor((point.y-origin.y-1) / cellSize.height);
726     if (column) *column = floor((point.x-origin.x-1) / cellSize.width);
728     //ASLogDebug(@"point=%@ row=%d col=%d",
729     //      NSStringFromPoint(point), *row, *column);
731     return YES;
734 - (NSArray *)validAttributesForMarkedText
736     return nil;
739 - (NSAttributedString *)attributedSubstringFromRange:(NSRange)range
741     return nil;
744 - (NSUInteger)characterIndexForPoint:(NSPoint)point
746     return NSNotFound;
749 - (NSInteger)conversationIdentifier
751     return (NSInteger)self;
754 - (NSRange)selectedRange
756     return [helper imRange];
759 - (NSRect)firstRectForCharacterRange:(NSRange)range
761     return [helper firstRectForCharacterRange:range];
764 @end // MMCoreTextView
769 @implementation MMCoreTextView (Private)
771 - (MMWindowController *)windowController
773     id windowController = [[self window] windowController];
774     if ([windowController isKindOfClass:[MMWindowController class]])
775         return (MMWindowController*)windowController;
776     return nil;
779 - (MMVimController *)vimController
781     return [[self windowController] vimController];
784 @end // MMCoreTextView (Private)
789 @implementation MMCoreTextView (Drawing)
791 - (NSPoint)pointForRow:(int)row column:(int)col
793     NSRect frame = [self frame];
794     return NSMakePoint(
795             col*cellSize.width + insetSize.width,
796             frame.size.height - (row+1)*cellSize.height - insetSize.height);
799 - (NSRect)rectForRow:(int)row column:(int)col numRows:(int)nr
800           numColumns:(int)nc
802     NSRect rect;
803     NSRect frame = [self frame];
805     rect.origin.x = col*cellSize.width + insetSize.width;
806     rect.origin.y = frame.size.height - (row+nr)*cellSize.height -
807                     insetSize.height;
808     rect.size.width = nc*cellSize.width;
809     rect.size.height = nr*cellSize.height;
811     return rect;
814 - (NSRect)rectFromRow:(int)row1 column:(int)col1
815                 toRow:(int)row2 column:(int)col2
817     NSRect frame = [self frame];
818     return NSMakeRect(
819             insetSize.width + col1*cellSize.width,
820             frame.size.height - insetSize.height - (row2+1)*cellSize.height,
821             (col2 + 1 - col1) * cellSize.width,
822             (row2 + 1 - row1) * cellSize.height);
825 - (NSSize)textAreaSize
827     // Calculate the (desired) size of the text area, i.e. the text view area
828     // minus the inset area.
829     return NSMakeSize(maxColumns * cellSize.width, maxRows * cellSize.height);
832 #define MM_DEBUG_DRAWING 0
834 - (void)batchDrawData:(NSData *)data
836     const void *bytes = [data bytes];
837     const void *end = bytes + [data length];
839 #if MM_DEBUG_DRAWING
840     ASLogNotice(@"====> BEGIN %s", _cmd);
841 #endif
842     // TODO: Sanity check input
844     while (bytes < end) {
845         int type = *((int*)bytes);  bytes += sizeof(int);
847         if (ClearAllDrawType == type) {
848 #if MM_DEBUG_DRAWING
849             ASLogNotice(@"   Clear all");
850 #endif
851             [self clearAll];
852         } else if (ClearBlockDrawType == type) {
853             unsigned color = *((unsigned*)bytes);  bytes += sizeof(unsigned);
854             int row1 = *((int*)bytes);  bytes += sizeof(int);
855             int col1 = *((int*)bytes);  bytes += sizeof(int);
856             int row2 = *((int*)bytes);  bytes += sizeof(int);
857             int col2 = *((int*)bytes);  bytes += sizeof(int);
859 #if MM_DEBUG_DRAWING
860             ASLogNotice(@"   Clear block (%d,%d) -> (%d,%d)", row1, col1,
861                     row2,col2);
862 #endif
863             [self clearBlockFromRow:row1 column:col1
864                     toRow:row2 column:col2
865                     color:color];
866         } else if (DeleteLinesDrawType == type) {
867             unsigned color = *((unsigned*)bytes);  bytes += sizeof(unsigned);
868             int row = *((int*)bytes);  bytes += sizeof(int);
869             int count = *((int*)bytes);  bytes += sizeof(int);
870             int bot = *((int*)bytes);  bytes += sizeof(int);
871             int left = *((int*)bytes);  bytes += sizeof(int);
872             int right = *((int*)bytes);  bytes += sizeof(int);
874 #if MM_DEBUG_DRAWING
875             ASLogNotice(@"   Delete %d line(s) from %d", count, row);
876 #endif
877             [self deleteLinesFromRow:row lineCount:count
878                     scrollBottom:bot left:left right:right
879                            color:color];
880         } else if (DrawStringDrawType == type) {
881             int bg = *((int*)bytes);  bytes += sizeof(int);
882             int fg = *((int*)bytes);  bytes += sizeof(int);
883             int sp = *((int*)bytes);  bytes += sizeof(int);
884             int row = *((int*)bytes);  bytes += sizeof(int);
885             int col = *((int*)bytes);  bytes += sizeof(int);
886             int cells = *((int*)bytes);  bytes += sizeof(int);
887             int flags = *((int*)bytes);  bytes += sizeof(int);
888             int len = *((int*)bytes);  bytes += sizeof(int);
889             UInt8 *s = (UInt8 *)bytes;  bytes += len;
891 #if MM_DEBUG_DRAWING
892             ASLogNotice(@"   Draw string len=%d row=%d col=%d flags=%#x",
893                     len, row, col, flags);
894 #endif
896             // Convert UTF-8 chars to UTF-16
897             CFStringRef sref = CFStringCreateWithBytesNoCopy(NULL, s, len,
898                                 kCFStringEncodingUTF8, false, kCFAllocatorNull);
899             CFIndex unilength = CFStringGetLength(sref);
900             const UniChar *unichars = CFStringGetCharactersPtr(sref);
901             UniChar *buffer = NULL;
902             if (unichars == NULL) {
903                 buffer = malloc(unilength * sizeof(UniChar));
904                 CFStringGetCharacters(sref, CFRangeMake(0, unilength), buffer);
905                 unichars = buffer;
906             }
908             [self drawString:unichars length:unilength
909                        atRow:row column:col cells:cells
910                               withFlags:flags
911                         foregroundColor:fg
912                         backgroundColor:bg
913                            specialColor:sp];
915             if (buffer) {
916                 free(buffer);
917                 buffer = NULL;
918             }
919             CFRelease(sref);
920         } else if (InsertLinesDrawType == type) {
921             unsigned color = *((unsigned*)bytes);  bytes += sizeof(unsigned);
922             int row = *((int*)bytes);  bytes += sizeof(int);
923             int count = *((int*)bytes);  bytes += sizeof(int);
924             int bot = *((int*)bytes);  bytes += sizeof(int);
925             int left = *((int*)bytes);  bytes += sizeof(int);
926             int right = *((int*)bytes);  bytes += sizeof(int);
928 #if MM_DEBUG_DRAWING
929             ASLogNotice(@"   Insert %d line(s) at row %d", count, row);
930 #endif
931             [self insertLinesAtRow:row lineCount:count
932                              scrollBottom:bot left:left right:right
933                                     color:color];
934         } else if (DrawCursorDrawType == type) {
935             unsigned color = *((unsigned*)bytes);  bytes += sizeof(unsigned);
936             int row = *((int*)bytes);  bytes += sizeof(int);
937             int col = *((int*)bytes);  bytes += sizeof(int);
938             int shape = *((int*)bytes);  bytes += sizeof(int);
939             int percent = *((int*)bytes);  bytes += sizeof(int);
941 #if MM_DEBUG_DRAWING
942             ASLogNotice(@"   Draw cursor at (%d,%d)", row, col);
943 #endif
944             [self drawInsertionPointAtRow:row column:col shape:shape
945                                      fraction:percent
946                                         color:color];
947         } else if (DrawInvertedRectDrawType == type) {
948             int row = *((int*)bytes);  bytes += sizeof(int);
949             int col = *((int*)bytes);  bytes += sizeof(int);
950             int nr = *((int*)bytes);  bytes += sizeof(int);
951             int nc = *((int*)bytes);  bytes += sizeof(int);
952             /*int invert = *((int*)bytes);*/  bytes += sizeof(int);
954 #if MM_DEBUG_DRAWING
955             ASLogNotice(@"   Draw inverted rect: row=%d col=%d nrows=%d "
956                    "ncols=%d", row, col, nr, nc);
957 #endif
958             [self drawInvertedRectAtRow:row column:col numRows:nr
959                              numColumns:nc];
960         } else if (SetCursorPosDrawType == type) {
961             // TODO: This is used for Voice Over support in MMTextView,
962             // MMCoreTextView currently does not support Voice Over.
963 #if MM_DEBUG_DRAWING
964             int row = *((int*)bytes);  bytes += sizeof(int);
965             int col = *((int*)bytes);  bytes += sizeof(int);
966             ASLogNotice(@"   Set cursor row=%d col=%d", row, col);
967 #else
968             /*cursorRow = *((int*)bytes);*/  bytes += sizeof(int);
969             /*cursorCol = *((int*)bytes);*/  bytes += sizeof(int);
970 #endif
971         } else {
972             ASLogWarn(@"Unknown draw type (type=%d)", type);
973         }
974     }
976 #if MM_DEBUG_DRAWING
977     ASLogNotice(@"<==== END   %s", _cmd);
978 #endif
981     static void
982 recurseDraw(const unichar *chars, CGGlyph *glyphs, CGSize *advances,
983             UniCharCount length, CGContextRef context, CTFontRef fontRef,
984             float x, float y)
986     CGFontRef cgFontRef = CTFontCopyGraphicsFont(fontRef, NULL);
988     if (CTFontGetGlyphsForCharacters(fontRef, chars, glyphs, length)) {
989         // All chars were mapped to glyphs, so draw all at once and return.
990         CGContextSetFont(context, cgFontRef);
991         CGContextSetTextPosition(context, x, y);
992         CGContextShowGlyphsWithAdvances(context, glyphs, advances, length);
993         CGFontRelease(cgFontRef);
994         return;
995     }
997     CGGlyph *glyphsEnd = glyphs+length, *g = glyphs;
998     CGSize *a = advances;
999     const unichar *c = chars;
1000     float x0 = x;
1001     while (glyphs < glyphsEnd) {
1002         if (*g) {
1003             // Draw as many consecutive glyphs as possible in the current font
1004             // (if a glyph is 0 that means it does not exist in the current
1005             // font).
1006             while (*g && g < glyphsEnd) {
1007                 ++g;
1008                 ++c;
1009                 x += a->width;
1010                 ++a;
1011             }
1013             int count = g-glyphs;
1014             CGContextSetFont(context, cgFontRef);
1015             CGContextSetTextPosition(context, x0, y);
1016             CGContextShowGlyphsWithAdvances(context, glyphs, advances, count);
1017         } else {
1018             // Skip past as many consecutive chars as possible which cannot be
1019             // drawn in the current font.
1020             while (0 == *g && g < glyphsEnd) {
1021                 ++g;
1022                 ++c;
1023                 x += a->width;
1024                 ++a;
1025             }
1027             // Figure out which font to draw these chars with.
1028             UniCharCount count = c - chars;
1029             CFRange r = { 0, count };
1030             CFStringRef strRef = CFStringCreateWithCharactersNoCopy(
1031                     NULL, chars, count, kCFAllocatorNull);
1032             CTFontRef newFontRef = CTFontCreateForString(fontRef, strRef, r);
1033             if (!newFontRef) {
1034                 ASLogNotice(@"Cannot find font to draw chars: %@", strRef);
1035                 CGFontRelease(cgFontRef);
1036                 return;
1037             }
1038             CFRelease(strRef);
1040             recurseDraw(chars, glyphs, advances, count, context, newFontRef,
1041                         x0, y);
1043             CFRelease(newFontRef);
1044         }
1046         chars = c;
1047         glyphs = g;
1048         advances = a;
1049         x0 = x;
1050     }
1052     CGFontRelease(cgFontRef);
1055 - (void)drawString:(const UniChar *)chars length:(UniCharCount)length
1056              atRow:(int)row column:(int)col cells:(int)cells
1057          withFlags:(int)flags foregroundColor:(int)fg
1058    backgroundColor:(int)bg specialColor:(int)sp
1060     CGContextRef context = [[NSGraphicsContext currentContext] graphicsPort];
1061     NSRect frame = [self frame];
1062     float x = col*cellSize.width + insetSize.width;
1063     float y = frame.size.height - insetSize.height - (1+row)*cellSize.height;
1064     float w = cellSize.width;
1066     if (flags & DRAW_WIDE) {
1067         // NOTE: It is assumed that either all characters in 'chars' are wide
1068         // or all are normal width.
1069         w *= 2;
1070     }
1072     CGContextSaveGState(context);
1074     // NOTE!  'cells' is zero if we're drawing a composing character
1075     CGFloat clipWidth = cells > 0 ? cells*cellSize.width : w;
1076     CGRect clipRect = { {x, y}, {clipWidth, cellSize.height} };
1077     CGContextClipToRect(context, clipRect);
1079     if (!(flags & DRAW_TRANSP)) {
1080         // Draw the background of the text.  Note that if we ignore the
1081         // DRAW_TRANSP flag and always draw the background, then the insert
1082         // mode cursor is drawn over.
1083         CGRect rect = { {x, y}, {cells*cellSize.width, cellSize.height} };
1084         CGContextSetRGBFillColor(context, RED(bg), GREEN(bg), BLUE(bg),
1085                                  ALPHA(bg));
1087         // Antialiasing may cause bleeding effects which are highly undesirable
1088         // when clearing the background (this code is also called to draw the
1089         // cursor sometimes) so disable it temporarily.
1090         CGContextSetShouldAntialias(context, NO);
1091         CGContextFillRect(context, rect);
1092         CGContextSetShouldAntialias(context, antialias);
1093     }
1096     if (flags & DRAW_UNDERL) {
1097         // Draw underline
1098         CGRect rect = { {x, y+0.4*fontDescent}, {cells*cellSize.width, 1} };
1099         CGContextSetRGBFillColor(context, RED(sp), GREEN(sp), BLUE(sp),
1100                                  ALPHA(sp));
1101         CGContextFillRect(context, rect);
1102     } else if (flags & DRAW_UNDERC) {
1103         // Draw curly underline
1104         int k;
1105         float x0 = x, y0 = y+1, w = cellSize.width, h = 0.5*fontDescent;
1107         CGContextMoveToPoint(context, x0, y0);
1108         for (k = 0; k < cells; ++k) {
1109             CGContextAddCurveToPoint(context, x0+0.25*w, y0, x0+0.25*w, y0+h,
1110                                      x0+0.5*w, y0+h);
1111             CGContextAddCurveToPoint(context, x0+0.75*w, y0+h, x0+0.75*w, y0,
1112                                      x0+w, y0);
1113             x0 += w;
1114         }
1116         CGContextSetRGBStrokeColor(context, RED(sp), GREEN(sp), BLUE(sp),
1117                                  ALPHA(sp));
1118         CGContextStrokePath(context);
1119     }
1121     if (length > maxlen) {
1122         if (glyphs) free(glyphs);
1123         if (advances) free(advances);
1124         glyphs = (CGGlyph*)malloc(length*sizeof(CGGlyph));
1125         advances = (CGSize*)calloc(length, sizeof(CGSize));
1126         maxlen = length;
1127     }
1129     CGContextSetTextMatrix(context, CGAffineTransformIdentity);
1130     CGContextSetTextDrawingMode(context, kCGTextFill);
1131     CGContextSetRGBFillColor(context, RED(fg), GREEN(fg), BLUE(fg), ALPHA(fg));
1132     CGContextSetFontSize(context, [font pointSize]);
1134     NSUInteger i;
1135     for (i = 0; i < length; ++i)
1136         advances[i].width = w;
1138     CTFontRef fontRef = (CTFontRef)[font retain];
1140     unsigned traits = 0;
1141     if (flags & DRAW_ITALIC)
1142         traits |= kCTFontItalicTrait;
1143     if (flags & DRAW_BOLD)
1144         traits |= kCTFontBoldTrait;
1146     if (traits) {
1147         CTFontRef fr = CTFontCreateCopyWithSymbolicTraits(fontRef, 0.0, NULL,
1148                 traits, traits);
1149         if (fr) {
1150             CFRelease(fontRef);
1151             fontRef = fr;
1152         }
1153     }
1155     recurseDraw(chars, glyphs, advances, length, context, fontRef, x,
1156                 y+fontDescent);
1158     CFRelease(fontRef);
1159     CGContextRestoreGState(context);
1162 - (void)scrollRect:(NSRect)rect lineCount:(int)count
1164     NSPoint destPoint = rect.origin;
1165     destPoint.y -= count * cellSize.height;
1167     NSCopyBits(0, rect, destPoint);
1170 - (void)deleteLinesFromRow:(int)row lineCount:(int)count
1171               scrollBottom:(int)bottom left:(int)left right:(int)right
1172                      color:(int)color
1174     NSRect rect = [self rectFromRow:row + count
1175                              column:left
1176                               toRow:bottom
1177                              column:right];
1179     // move rect up for count lines
1180     [self scrollRect:rect lineCount:-count];
1181     [self clearBlockFromRow:bottom - count + 1
1182                      column:left
1183                       toRow:bottom
1184                      column:right
1185                       color:color];
1188 - (void)insertLinesAtRow:(int)row lineCount:(int)count
1189             scrollBottom:(int)bottom left:(int)left right:(int)right
1190                    color:(int)color
1192     NSRect rect = [self rectFromRow:row
1193                              column:left
1194                               toRow:bottom - count
1195                              column:right];
1197     // move rect down for count lines
1198     [self scrollRect:rect lineCount:count];
1199     [self clearBlockFromRow:row
1200                      column:left
1201                       toRow:row + count - 1
1202                      column:right
1203                       color:color];
1206 - (void)clearBlockFromRow:(int)row1 column:(int)col1 toRow:(int)row2
1207                    column:(int)col2 color:(int)color
1209     CGContextRef context = [[NSGraphicsContext currentContext] graphicsPort];
1210     NSRect rect = [self rectFromRow:row1 column:col1 toRow:row2 column:col2];
1212     CGContextSetRGBFillColor(context, RED(color), GREEN(color), BLUE(color),
1213                              ALPHA(color));
1214     CGContextFillRect(context, *(CGRect*)&rect);
1217 - (void)clearAll
1219     CGContextRef context = [[NSGraphicsContext currentContext] graphicsPort];
1220     NSRect rect = [self frame];
1221     float r = [defaultBackgroundColor redComponent];
1222     float g = [defaultBackgroundColor greenComponent];
1223     float b = [defaultBackgroundColor blueComponent];
1224     float a = [defaultBackgroundColor alphaComponent];
1226     CGContextSetRGBFillColor(context, r, g, b, a);
1227     CGContextFillRect(context, *(CGRect*)&rect);
1230 - (void)drawInsertionPointAtRow:(int)row column:(int)col shape:(int)shape
1231                        fraction:(int)percent color:(int)color
1233     CGContextRef context = [[NSGraphicsContext currentContext] graphicsPort];
1234     NSRect rect = [self rectForRow:row column:col numRows:1 numColumns:1];
1236     if (MMInsertionPointHorizontal == shape) {
1237         int frac = (cellSize.height * percent + 99)/100;
1238         rect.size.height = frac;
1239     } else if (MMInsertionPointVertical == shape) {
1240         int frac = (cellSize.width * percent + 99)/100;
1241         rect.size.width = frac;
1242     } else if (MMInsertionPointVerticalRight == shape) {
1243         int frac = (cellSize.width * percent + 99)/100;
1244         rect.origin.x += rect.size.width - frac;
1245         rect.size.width = frac;
1246     }
1248     // Temporarily disable antialiasing since we are only drawing square
1249     // cursors.  Failing to disable antialiasing can cause the cursor to bleed
1250     // over into adjacent display cells and it may look ugly.
1251     CGContextSetShouldAntialias(context, NO);
1253     if (MMInsertionPointHollow == shape) {
1254         // When stroking a rect its size is effectively 1 pixel wider/higher
1255         // than we want so make it smaller to avoid having it bleed over into
1256         // the adjacent display cell.
1257         rect.size.width -= 1;
1258         rect.size.height -= 1;
1260         CGContextSetRGBStrokeColor(context, RED(color), GREEN(color),
1261                                    BLUE(color), ALPHA(color));
1262         CGContextStrokeRect(context, *(CGRect*)&rect);
1263     } else {
1264         CGContextSetRGBFillColor(context, RED(color), GREEN(color), BLUE(color),
1265                                  ALPHA(color));
1266         CGContextFillRect(context, *(CGRect*)&rect);
1267     }
1269     CGContextSetShouldAntialias(context, antialias);
1272 - (void)drawInvertedRectAtRow:(int)row column:(int)col numRows:(int)nrows
1273                    numColumns:(int)ncols
1275     // TODO: THIS CODE HAS NOT BEEN TESTED!
1276     CGContextRef cgctx = [[NSGraphicsContext currentContext] graphicsPort];
1277     CGContextSaveGState(cgctx);
1278     CGContextSetBlendMode(cgctx, kCGBlendModeDifference);
1279     CGContextSetRGBFillColor(cgctx, 1.0, 1.0, 1.0, 1.0);
1281     NSRect rect = [self rectForRow:row column:col numRows:nrows
1282                         numColumns:ncols];
1283     CGContextFillRect(cgctx, *(CGRect*)&rect);
1285     CGContextRestoreGState(cgctx);
1288 @end // MMCoreTextView (Drawing)