git.y1.nz

SameBoy

Accurate GB/GBC emulator
download: https://git.y1.nz/archives/sameboy.tar.gz
README | Files | Log | Refs | LICENSE

HexFiend/HFRepresenterTextView.m

      1 //
      2 //  HFRepresenterTextView.m
      3 //  HexFiend_2
      4 //
      5 //  Copyright 2007 ridiculous_fish. All rights reserved.
      6 //
      7 
      8 #import <HexFiend/HFRepresenterTextView_Internal.h>
      9 #import <HexFiend/HFTextRepresenter_Internal.h>
     10 #import <HexFiend/HFTextVisualStyleRun.h>
     11 #import <HexFiend/HFFunctions.h>
     12 #import <HexFiend/HFRepresenterTextViewCallout.h>
     13 #import <objc/message.h>
     14 
     15 static const NSTimeInterval HFCaretBlinkFrequency = 0.56;
     16 
     17 @implementation HFRepresenterTextView
     18 
     19 - (NSUInteger)_getGlyphs:(CGGlyph *)glyphs forString:(NSString *)string font:(NSFont *)inputFont {
     20     NSUInteger length = [string length];
     21     UniChar chars[256];
     22     HFASSERT(length <= sizeof chars / sizeof *chars);
     23     HFASSERT(inputFont != nil);
     24     [string getCharacters:chars range:NSMakeRange(0, length)];
     25     if (! CTFontGetGlyphsForCharacters((CTFontRef)inputFont, chars, glyphs, length)) {
     26         /* Some or all characters were not mapped.  This is OK.  We'll use the replacement glyph. */
     27     }
     28     return length;
     29 }
     30 
     31 - (NSUInteger)_glyphsForString:(NSString *)string withGeneratingLayoutManager:(NSLayoutManager *)layoutManager glyphs:(CGGlyph *)glyphs {
     32     HFASSERT(layoutManager != NULL);
     33     HFASSERT(string != NULL);
     34     NSGlyph nsglyphs[GLYPH_BUFFER_SIZE];
     35     [[[layoutManager textStorage] mutableString] setString:string];
     36     NSUInteger glyphIndex, glyphCount = [layoutManager getGlyphs:nsglyphs range:NSMakeRange(0, MIN(GLYPH_BUFFER_SIZE, [layoutManager numberOfGlyphs]))];
     37     if (glyphs != NULL) {
     38         /* Convert from unsigned int NSGlyphs to unsigned short CGGlyphs */
     39         for (glyphIndex = 0; glyphIndex < glyphCount; glyphIndex++) {
     40             /* Get rid of NSControlGlyph */
     41             NSGlyph modifiedGlyph = nsglyphs[glyphIndex] == NSControlGlyph ? NSNullGlyph : nsglyphs[glyphIndex];
     42             HFASSERT(modifiedGlyph <= USHRT_MAX);
     43             glyphs[glyphIndex] = (CGGlyph)modifiedGlyph;
     44         }
     45     }
     46     return glyphCount;    
     47 }
     48 
     49 /* Returns the number of glyphs for the given string, using the given text view, and generating the glyphs if the glyphs parameter is not NULL */
     50 - (NSUInteger)_glyphsForString:(NSString *)string withGeneratingTextView:(NSTextView *)textView glyphs:(CGGlyph *)glyphs {
     51     HFASSERT(string != NULL);
     52     HFASSERT(textView != NULL);
     53     [textView setString:string];
     54     [textView setNeedsDisplay:YES]; //ligature generation doesn't seem to happen without this, for some reason.  This seems very fragile!  We should find a better way to get this ligature information!!
     55     return [self _glyphsForString:string withGeneratingLayoutManager:[textView layoutManager] glyphs:glyphs];
     56 }
     57 
     58 - (NSArray *)displayedSelectedContentsRanges {
     59     if (! cachedSelectedRanges) {
     60         cachedSelectedRanges = [[[self representer] displayedSelectedContentsRanges] copy];
     61     }
     62     return cachedSelectedRanges;
     63 }
     64 
     65 - (BOOL)_shouldHaveCaretTimer {
     66     NSWindow *window = [self window];
     67     if (window == NULL) return NO;
     68     if (! [window isKeyWindow]) return NO;
     69     if (self != [window firstResponder]) return NO;
     70     if (! _hftvflags.editable) return NO;
     71     NSArray *ranges = [self displayedSelectedContentsRanges];
     72     if ([ranges count] != 1) return NO;
     73     NSRange range = [ranges[0] rangeValue];
     74     if (range.length != 0) return NO;
     75     return YES;
     76 }
     77 
     78 - (NSUInteger)_effectiveBytesPerColumn {
     79     /* returns the bytesPerColumn, unless it's larger than the bytes per character, in which case it returns 0 */
     80     NSUInteger bytesPerColumn = [self bytesPerColumn], bytesPerCharacter = [self bytesPerCharacter];
     81     return bytesPerColumn >= bytesPerCharacter ? bytesPerColumn : 0;
     82 }
     83 
     84 // note: index may be negative
     85 - (NSPoint)originForCharacterAtByteIndex:(NSInteger)index {
     86     NSPoint result;
     87     NSInteger bytesPerLine = (NSInteger)[self bytesPerLine];
     88     
     89     // We want a nonnegative remainder
     90     NSInteger lineIndex = index / bytesPerLine;
     91     NSInteger byteIndexIntoLine = index % bytesPerLine;
     92     while (byteIndexIntoLine < 0) {
     93         byteIndexIntoLine += bytesPerLine;
     94         lineIndex--;
     95     }
     96 
     97     NSUInteger bytesPerColumn = [self _effectiveBytesPerColumn];
     98     NSUInteger numConsumedColumns = (bytesPerColumn ? byteIndexIntoLine / bytesPerColumn : 0);
     99     NSUInteger characterIndexIntoLine = byteIndexIntoLine / [self bytesPerCharacter];
    100     
    101     result.x = [self horizontalContainerInset] + characterIndexIntoLine * [self advancePerCharacter] + numConsumedColumns * [self advanceBetweenColumns];
    102     result.y = (lineIndex - [self verticalOffset]) * [self lineHeight];
    103     
    104     return result;
    105 }
    106 
    107 - (NSUInteger)indexOfCharacterAtPoint:(NSPoint)point {
    108     NSUInteger bytesPerLine = [self bytesPerLine];
    109     NSUInteger bytesPerCharacter = [self bytesPerCharacter];
    110     HFASSERT(bytesPerLine % bytesPerCharacter == 0);
    111     CGFloat advancePerCharacter = [self advancePerCharacter];
    112     NSUInteger bytesPerColumn = [self _effectiveBytesPerColumn];
    113     CGFloat floatRow = (CGFloat)floor([self verticalOffset] + point.y / [self lineHeight]);
    114     NSUInteger byteIndexWithinRow;
    115     
    116     // to compute the column, we need to solve for byteIndexIntoLine in something like this: point.x = [self advancePerCharacter] * charIndexIntoLine + [self spaceBetweenColumns] * floor(byteIndexIntoLine / [self bytesPerColumn]).  Start by computing the column (or if bytesPerColumn is 0, we don't have columns)
    117     CGFloat insetX = point.x - [self horizontalContainerInset];
    118     if (insetX < 0) {
    119         //handle the case of dragging within the container inset
    120         byteIndexWithinRow = 0;
    121     }
    122     else if (bytesPerColumn == 0) {
    123         /* We don't have columns */
    124         byteIndexWithinRow = bytesPerCharacter * (NSUInteger)(insetX / advancePerCharacter);
    125     }
    126     else {
    127         CGFloat advancePerColumn = [self advancePerColumn];
    128         HFASSERT(advancePerColumn > 0);
    129         CGFloat floatColumn = insetX / advancePerColumn;
    130         HFASSERT(floatColumn >= 0 && floatColumn <= NSUIntegerMax);
    131         CGFloat startOfColumn = advancePerColumn * HFFloor(floatColumn);
    132         HFASSERT(startOfColumn <= insetX);
    133         CGFloat xOffsetWithinColumn = insetX - startOfColumn;
    134         CGFloat charIndexWithinColumn = xOffsetWithinColumn / advancePerCharacter; //charIndexWithinColumn may be larger than bytesPerColumn if the user clicked on the space between columns
    135         HFASSERT(charIndexWithinColumn >= 0 && charIndexWithinColumn <= NSUIntegerMax / bytesPerCharacter);
    136         NSUInteger byteIndexWithinColumn = bytesPerCharacter * (NSUInteger)charIndexWithinColumn;
    137         byteIndexWithinRow = bytesPerColumn * (NSUInteger)floatColumn + byteIndexWithinColumn; //this may trigger overflow to the next column, but that's OK
    138         byteIndexWithinRow = MIN(byteIndexWithinRow, bytesPerLine); //don't let clicking to the right of the line overflow to the next line
    139     }
    140     HFASSERT(floatRow >= 0 && floatRow <= NSUIntegerMax);
    141     NSUInteger row = (NSUInteger)floatRow;
    142     return (row * bytesPerLine + byteIndexWithinRow) / bytesPerCharacter;
    143 }
    144 
    145 - (NSRect)caretRect {
    146     NSArray *ranges = [self displayedSelectedContentsRanges];
    147     HFASSERT([ranges count] == 1);
    148     NSRange range = [ranges[0] rangeValue];
    149     HFASSERT(range.length == 0);
    150     
    151     NSPoint caretBaseline = [self originForCharacterAtByteIndex:range.location];
    152     return NSMakeRect(caretBaseline.x - 1, caretBaseline.y, 1, [self lineHeight]);
    153 }
    154 
    155 - (void)_blinkCaret:(NSTimer *)timer {
    156     HFASSERT(timer == caretTimer);
    157     if (_hftvflags.caretVisible) {
    158         _hftvflags.caretVisible = NO;
    159         [self setNeedsDisplayInRect:lastDrawnCaretRect];
    160         caretRectToDraw = NSZeroRect;
    161     }
    162     else {
    163         _hftvflags.caretVisible = YES;
    164         caretRectToDraw = [self caretRect];
    165         [self setNeedsDisplayInRect:caretRectToDraw];
    166     }
    167 }
    168 
    169 - (void)_updateCaretTimerWithFirstResponderStatus:(BOOL)treatAsHavingFirstResponder {
    170     BOOL hasCaretTimer = !! caretTimer;
    171     BOOL shouldHaveCaretTimer = treatAsHavingFirstResponder && [self _shouldHaveCaretTimer];
    172     if (shouldHaveCaretTimer == YES && hasCaretTimer == NO) {
    173         caretTimer = [[NSTimer timerWithTimeInterval:HFCaretBlinkFrequency target:self selector:@selector(_blinkCaret:) userInfo:nil repeats:YES] retain];
    174         NSRunLoop *loop = [NSRunLoop currentRunLoop];
    175         [loop addTimer:caretTimer forMode:NSDefaultRunLoopMode];
    176         [loop addTimer:caretTimer forMode:NSModalPanelRunLoopMode];
    177         if ([self enclosingMenuItem] != NULL) {
    178             [loop addTimer:caretTimer forMode:NSEventTrackingRunLoopMode];            
    179         }
    180     }
    181     else if (shouldHaveCaretTimer == NO && hasCaretTimer == YES) {
    182         [caretTimer invalidate];
    183         [caretTimer release];
    184         caretTimer = nil;
    185         caretRectToDraw = NSZeroRect;
    186         if (! NSIsEmptyRect(lastDrawnCaretRect)) {
    187             [self setNeedsDisplayInRect:lastDrawnCaretRect];
    188         }
    189     }
    190     HFASSERT(shouldHaveCaretTimer == !! caretTimer);
    191 }
    192 
    193 - (void)_updateCaretTimer {
    194     [self _updateCaretTimerWithFirstResponderStatus: self == [[self window] firstResponder]];
    195 }
    196 
    197 /* When you click or type, the caret appears immediately - do that here */
    198 - (void)_forceCaretOnIfHasCaretTimer {
    199     if (caretTimer) {
    200         [caretTimer invalidate];
    201         [caretTimer release];
    202         caretTimer = nil;
    203         [self _updateCaretTimer];
    204         
    205         _hftvflags.caretVisible = YES;
    206         caretRectToDraw = [self caretRect];
    207         [self setNeedsDisplayInRect:caretRectToDraw];
    208     }
    209 }
    210 
    211 /* Returns the range of lines containing the selected contents ranges (as NSValues containing NSRanges), or {NSNotFound, 0} if ranges is nil or empty */
    212 - (NSRange)_lineRangeForContentsRanges:(NSArray *)ranges {
    213     NSUInteger minLine = NSUIntegerMax;
    214     NSUInteger maxLine = 0;
    215     NSUInteger bytesPerLine = [self bytesPerLine];
    216     FOREACH(NSValue *, rangeValue, ranges) {
    217         NSRange range = [rangeValue rangeValue];
    218         if (range.length > 0) {
    219             NSUInteger lineForRangeStart = range.location / bytesPerLine;
    220             NSUInteger lineForRangeEnd = NSMaxRange(range) / bytesPerLine;
    221             HFASSERT(lineForRangeStart <= lineForRangeEnd);
    222             minLine = MIN(minLine, lineForRangeStart);
    223             maxLine = MAX(maxLine, lineForRangeEnd);
    224         }
    225     }
    226     if (minLine > maxLine) return NSMakeRange(NSNotFound, 0);
    227     else return NSMakeRange(minLine, maxLine - minLine + 1);
    228 }
    229 
    230 - (NSRect)_rectForLineRange:(NSRange)lineRange {
    231     HFASSERT(lineRange.location != NSNotFound);
    232     NSUInteger bytesPerLine = [self bytesPerLine];
    233     NSRect bounds = [self bounds];
    234     NSRect result;
    235     result.origin.x = NSMinX(bounds);
    236     result.size.width = NSWidth(bounds);
    237     result.origin.y = [self originForCharacterAtByteIndex:lineRange.location * bytesPerLine].y;
    238     result.size.height = [self lineHeight] * lineRange.length;
    239     return result;
    240 }
    241 
    242 static int range_compare(const void *ap, const void *bp) {
    243     const NSRange *a = ap;
    244     const NSRange *b = bp;
    245     if (a->location < b->location) return -1;
    246     if (a->location > b->location) return 1;
    247     if (a->length < b->length) return -1;
    248     if (a->length > b->length) return 1;
    249     return 0;
    250 }
    251 
    252 enum LineCoverage_t {
    253     eCoverageNone,
    254     eCoveragePartial,
    255     eCoverageFull
    256 };
    257 
    258 - (void)_linesWithParityChangesFromRanges:(const NSRange *)oldRanges count:(NSUInteger)oldRangeCount toRanges:(const NSRange *)newRanges count:(NSUInteger)newRangeCount intoIndexSet:(NSMutableIndexSet *)result {
    259     NSUInteger bytesPerLine = [self bytesPerLine];
    260     NSUInteger oldParity=0, newParity=0;
    261     NSUInteger oldRangeIndex = 0, newRangeIndex = 0;
    262     NSUInteger currentCharacterIndex = MIN(oldRanges[oldRangeIndex].location, newRanges[newRangeIndex].location);
    263     oldParity = (currentCharacterIndex >= oldRanges[oldRangeIndex].location);
    264     newParity = (currentCharacterIndex >= newRanges[newRangeIndex].location);
    265     //    NSLog(@"Old %s, new %s at %u (%u, %u)", oldParity ? "on" : "off", newParity ? "on" : "off", currentCharacterIndex, oldRanges[oldRangeIndex].location, newRanges[newRangeIndex].location);
    266     for (;;) {
    267         NSUInteger oldDivision = NSUIntegerMax, newDivision = NSUIntegerMax;
    268         /* Move up to the next parity change */
    269         if (oldRangeIndex < oldRangeCount) {
    270             const NSRange oldRange = oldRanges[oldRangeIndex];
    271             oldDivision = oldRange.location + (oldParity ? oldRange.length : 0);
    272         }
    273         if (newRangeIndex < newRangeCount) {
    274             const NSRange newRange = newRanges[newRangeIndex];            
    275             newDivision = newRange.location + (newParity ? newRange.length : 0);
    276         }
    277         
    278         NSUInteger division = MIN(oldDivision, newDivision);
    279         HFASSERT(division > currentCharacterIndex);
    280         
    281         //        NSLog(@"Division %u", division);
    282         
    283         if (division == NSUIntegerMax) break;
    284         
    285         if (oldParity != newParity) {
    286             /* The parities did not match through this entire range, so add all intersected lines to the result index set */
    287             NSUInteger startLine = currentCharacterIndex / bytesPerLine;
    288             NSUInteger endLine = HFDivideULRoundingUp(division, bytesPerLine);
    289             HFASSERT(endLine >= startLine);
    290             //            NSLog(@"Adding lines %u -> %u", startLine, endLine);
    291             [result addIndexesInRange:NSMakeRange(startLine, endLine - startLine)];
    292         }
    293         if (division == oldDivision) {
    294             oldRangeIndex += oldParity;
    295             oldParity = ! oldParity;
    296             //            NSLog(@"Old range switching %s at %u", oldParity ? "on" : "off", division);
    297         }
    298         if (division == newDivision) {
    299             newRangeIndex += newParity;
    300             newParity = ! newParity;
    301             //            NSLog(@"New range switching %s at %u", newParity ? "on" : "off", division);
    302         }
    303         currentCharacterIndex = division;
    304     }
    305 }
    306 
    307 - (void)_addLinesFromRanges:(const NSRange *)ranges count:(NSUInteger)count toIndexSet:(NSMutableIndexSet *)set {
    308     NSUInteger bytesPerLine = [self bytesPerLine];
    309     NSUInteger i;
    310     for (i=0; i < count; i++) {
    311         NSUInteger firstLine = ranges[i].location / bytesPerLine;
    312         NSUInteger lastLine = HFDivideULRoundingUp(NSMaxRange(ranges[i]), bytesPerLine);
    313         [set addIndexesInRange:NSMakeRange(firstLine, lastLine - firstLine)];
    314     }
    315 }
    316 
    317 - (NSIndexSet *)_indexSetOfLinesNeedingRedrawWhenChangingSelectionFromRanges:(NSArray *)oldSelectedRangeArray toRanges:(NSArray *)newSelectedRangeArray {
    318     NSUInteger oldRangeCount = 0, newRangeCount = 0;
    319     
    320     NEW_ARRAY(NSRange, oldRanges, [oldSelectedRangeArray count]);
    321     NEW_ARRAY(NSRange, newRanges, [newSelectedRangeArray count]);
    322     
    323     NSMutableIndexSet *result = [NSMutableIndexSet indexSet];
    324     
    325     /* Extract all the ranges into a local array */
    326     FOREACH(NSValue *, rangeValue1, oldSelectedRangeArray) {
    327         NSRange range = [rangeValue1 rangeValue];
    328         if (range.length > 0) {
    329             oldRanges[oldRangeCount++] = range;
    330         }
    331     }
    332     FOREACH(NSValue *, rangeValue2, newSelectedRangeArray) {
    333         NSRange range = [rangeValue2 rangeValue];
    334         if (range.length > 0) {
    335             newRanges[newRangeCount++] = range;
    336         }
    337     }
    338     
    339 #if ! NDEBUG
    340     /* Assert that ranges of arrays do not have any self-intersection; this is supposed to be enforced by our HFController.  Also assert that they aren't "just touching"; if they are they should be merged into a single range. */
    341     for (NSUInteger i=0; i < oldRangeCount; i++) {
    342         for (NSUInteger j=i+1; j < oldRangeCount; j++) {
    343             HFASSERT(NSIntersectionRange(oldRanges[i], oldRanges[j]).length == 0);
    344             HFASSERT(NSMaxRange(oldRanges[i]) != oldRanges[j].location && NSMaxRange(oldRanges[j]) != oldRanges[i].location);
    345         }
    346     }
    347     for (NSUInteger i=0; i < newRangeCount; i++) {
    348         for (NSUInteger j=i+1; j < newRangeCount; j++) {
    349             HFASSERT(NSIntersectionRange(newRanges[i], newRanges[j]).length == 0);
    350             HFASSERT(NSMaxRange(newRanges[i]) != newRanges[j].location && NSMaxRange(newRanges[j]) != newRanges[i].location);
    351         }
    352     }
    353 #endif
    354     
    355     if (newRangeCount == 0) {
    356         [self _addLinesFromRanges:oldRanges count:oldRangeCount toIndexSet:result];
    357     }
    358     else if (oldRangeCount == 0) {
    359         [self _addLinesFromRanges:newRanges count:newRangeCount toIndexSet:result];
    360     }
    361     else {
    362         /* Sort the arrays, since _linesWithParityChangesFromRanges needs it */
    363         qsort(oldRanges, oldRangeCount, sizeof *oldRanges, range_compare);
    364         qsort(newRanges, newRangeCount, sizeof *newRanges, range_compare);
    365         
    366         [self _linesWithParityChangesFromRanges:oldRanges count:oldRangeCount toRanges:newRanges count:newRangeCount intoIndexSet:result];
    367     }
    368     
    369     FREE_ARRAY(oldRanges);
    370     FREE_ARRAY(newRanges);
    371     
    372     return result;
    373 }
    374 
    375 - (void)updateSelectedRanges {
    376     NSArray *oldSelectedRanges = cachedSelectedRanges;
    377     cachedSelectedRanges = [[[self representer] displayedSelectedContentsRanges] copy];
    378     NSIndexSet *indexSet = [self _indexSetOfLinesNeedingRedrawWhenChangingSelectionFromRanges:oldSelectedRanges toRanges:cachedSelectedRanges];
    379     BOOL lastCaretRectNeedsRedraw = ! NSIsEmptyRect(lastDrawnCaretRect);
    380     NSRange lineRangeToInvalidate = NSMakeRange(NSUIntegerMax, 0);
    381     for (NSUInteger lineIndex = [indexSet firstIndex]; ; lineIndex = [indexSet indexGreaterThanIndex:lineIndex]) {
    382         if (lineIndex != NSNotFound && NSMaxRange(lineRangeToInvalidate) == lineIndex) {
    383             lineRangeToInvalidate.length++;
    384         }
    385         else {
    386             if (lineRangeToInvalidate.length > 0) {
    387                 NSRect rectToInvalidate = [self _rectForLineRange:lineRangeToInvalidate];
    388                 [self setNeedsDisplayInRect:rectToInvalidate];
    389                 lastCaretRectNeedsRedraw = lastCaretRectNeedsRedraw && ! NSContainsRect(rectToInvalidate, lastDrawnCaretRect);
    390             }
    391             lineRangeToInvalidate = NSMakeRange(lineIndex, 1);
    392         }
    393         if (lineIndex == NSNotFound) break;
    394     }
    395     
    396     if (lastCaretRectNeedsRedraw) [self setNeedsDisplayInRect:lastDrawnCaretRect];
    397     [oldSelectedRanges release]; //balance the retain we borrowed from the ivar
    398     [self _updateCaretTimer];
    399     [self _forceCaretOnIfHasCaretTimer];
    400     
    401     // A new pulse window will be created at the new selected range if necessary.
    402     [self terminateSelectionPulse];
    403 }
    404 
    405 - (void)drawPulseBackgroundInRect:(NSRect)pulseRect {
    406     [[NSColor yellowColor] set];
    407     CGContextRef ctx = [[NSGraphicsContext currentContext] graphicsPort];
    408     CGContextSaveGState(ctx);
    409     [[NSBezierPath bezierPathWithRoundedRect:pulseRect xRadius:25 yRadius:25] addClip];
    410     NSGradient *gradient = [[NSGradient alloc] initWithStartingColor:[NSColor yellowColor] endingColor:[NSColor colorWithCalibratedRed:(CGFloat)1. green:(CGFloat).75 blue:0 alpha:1]];
    411     [gradient drawInRect:pulseRect angle:90];
    412     [gradient release];
    413     CGContextRestoreGState(ctx);
    414 }
    415 
    416 - (void)fadePulseWindowTimer:(NSTimer *)timer {
    417     // TODO: close & invalidate immediatley if view scrolls.
    418     NSWindow *window = [timer userInfo];
    419     CGFloat alpha = [window alphaValue];
    420     alpha -= (CGFloat)(3. / 30.);
    421     if (alpha < 0) {
    422         [window close];
    423         [timer invalidate];
    424     }
    425     else {
    426         [window setAlphaValue:alpha];
    427     }
    428 }
    429 
    430 - (void)terminateSelectionPulse {
    431     if (pulseWindow) {
    432         [[self window] removeChildWindow:pulseWindow];
    433         [pulseWindow setFrame:pulseWindowBaseFrameInScreenCoordinates display:YES animate:NO];
    434         [NSTimer scheduledTimerWithTimeInterval:1. / 30. target:self selector:@selector(fadePulseWindowTimer:) userInfo:pulseWindow repeats:YES];
    435         //release is not necessary, since it relases when closed by default
    436         pulseWindow = nil;
    437         pulseWindowBaseFrameInScreenCoordinates = NSZeroRect;
    438     }
    439 }
    440 
    441 - (void)drawCaretIfNecessaryWithClip:(NSRect)clipRect {
    442     NSRect caretRect = NSIntersectionRect(caretRectToDraw, clipRect);
    443     if (! NSIsEmptyRect(caretRect)) {
    444         [[NSColor controlTextColor] set];
    445         NSRectFill(caretRect);
    446         lastDrawnCaretRect = caretRect;
    447     }
    448     if (NSIsEmptyRect(caretRectToDraw)) lastDrawnCaretRect = NSZeroRect;
    449 }
    450 
    451 
    452 /* This is the color when we are the first responder in the key window */
    453 - (NSColor *)primaryTextSelectionColor {
    454     return [NSColor selectedTextBackgroundColor];
    455 }
    456 
    457 /* This is the color when we are not in the key window */
    458 - (NSColor *)inactiveTextSelectionColor {
    459     if (@available(macOS 10.14, *)) {
    460         return [NSColor unemphasizedSelectedTextBackgroundColor];
    461     }
    462     return [NSColor colorWithCalibratedWhite: (CGFloat)(212./255.) alpha:1];
    463 }
    464 
    465 /* This is the color when we are not the first responder, but we are in the key window */
    466 - (NSColor *)secondaryTextSelectionColor {
    467     if (@available(macOS 10.14, *)) {
    468         return [NSColor unemphasizedSelectedTextBackgroundColor];
    469     }
    470     return [NSColor colorWithCalibratedWhite: (CGFloat)(212./255.) alpha:1];
    471 }
    472 
    473 - (NSColor *)textSelectionColor {
    474     NSWindow *window = [self window];
    475     if (window == nil) return [self primaryTextSelectionColor];
    476     else if (! [window isKeyWindow]) return [self inactiveTextSelectionColor];
    477     else if (self != [window firstResponder]) return [self secondaryTextSelectionColor];
    478     else return [self primaryTextSelectionColor];
    479 }
    480 
    481 - (void)drawSelectionIfNecessaryWithClip:(NSRect)clipRect {
    482     NSArray *ranges = [self displayedSelectedContentsRanges];
    483     NSUInteger bytesPerLine = [self bytesPerLine];
    484     [[self textSelectionColor] set];
    485     CGFloat lineHeight = [self lineHeight];
    486     FOREACH(NSValue *, rangeValue, ranges) {
    487         NSRange range = [rangeValue rangeValue];
    488         if (range.length > 0) {
    489             NSUInteger startByteIndex = range.location;
    490             NSUInteger endByteIndexForThisRange = range.location + range.length - 1;
    491             NSUInteger byteIndex = startByteIndex;
    492             while (byteIndex <= endByteIndexForThisRange) {
    493                 NSUInteger endByteIndexForLine = ((byteIndex / bytesPerLine) + 1) * bytesPerLine - 1;
    494                 NSUInteger endByteForThisLineOfRange = MIN(endByteIndexForThisRange, endByteIndexForLine);
    495                 NSPoint startPoint = [self originForCharacterAtByteIndex:byteIndex];
    496                 NSPoint endPoint = [self originForCharacterAtByteIndex:endByteForThisLineOfRange];
    497                 NSRect selectionRect = NSMakeRect(startPoint.x, startPoint.y, endPoint.x + [self advancePerCharacter] - startPoint.x, lineHeight);
    498                 NSRect clippedSelectionRect = NSIntersectionRect(selectionRect, clipRect);
    499                 if (! NSIsEmptyRect(clippedSelectionRect)) {
    500                     NSRectFill(clippedSelectionRect);
    501                 }
    502                 byteIndex = endByteForThisLineOfRange + 1;
    503             }
    504         }
    505     }
    506 }
    507 
    508 - (BOOL)acceptsFirstResponder {
    509     return YES;
    510 }
    511 
    512 - (BOOL)hasVisibleDisplayedSelectedContentsRange {
    513     FOREACH(NSValue *, rangeValue, [self displayedSelectedContentsRanges]) {
    514         NSRange range = [rangeValue rangeValue];
    515         if (range.length > 0) {
    516             return YES;
    517         }
    518     }
    519     return NO;
    520 }
    521 
    522 - (BOOL)becomeFirstResponder {
    523     BOOL result = [super becomeFirstResponder];
    524     [self _updateCaretTimerWithFirstResponderStatus:YES];
    525     if ([self showsFocusRing] || [self hasVisibleDisplayedSelectedContentsRange]) {
    526         [self setNeedsDisplay:YES];
    527     }
    528     return result;
    529 }
    530 
    531 - (BOOL)resignFirstResponder {
    532     BOOL result = [super resignFirstResponder];
    533     [self _updateCaretTimerWithFirstResponderStatus:NO];
    534     BOOL needsRedisplay = NO;
    535     if ([self showsFocusRing]) needsRedisplay = YES;
    536     else if (! NSIsEmptyRect(lastDrawnCaretRect)) needsRedisplay = YES;
    537     else if ([self hasVisibleDisplayedSelectedContentsRange]) needsRedisplay = YES;
    538     if (needsRedisplay) [self setNeedsDisplay:YES];
    539     return result;
    540 }
    541 
    542 - (instancetype)initWithRepresenter:(HFTextRepresenter *)rep {
    543     self = [super initWithFrame:NSMakeRect(0, 0, 1, 1)];
    544     horizontalContainerInset = 4;
    545     representer = rep;
    546     _hftvflags.editable = YES;
    547     
    548     return self;
    549 }
    550 
    551 - (void)clearRepresenter {
    552     representer = nil;
    553 }
    554 
    555 - (void)encodeWithCoder:(NSCoder *)coder {
    556     HFASSERT([coder allowsKeyedCoding]);
    557     [super encodeWithCoder:coder];
    558     [coder encodeObject:representer forKey:@"HFRepresenter"];
    559     [coder encodeObject:_font forKey:@"HFFont"];
    560     [coder encodeObject:_data forKey:@"HFData"];
    561     [coder encodeDouble:verticalOffset forKey:@"HFVerticalOffset"];
    562     [coder encodeDouble:horizontalContainerInset forKey:@"HFHorizontalContainerOffset"];
    563     [coder encodeDouble:defaultLineHeight forKey:@"HFDefaultLineHeight"];
    564     [coder encodeInt64:bytesBetweenVerticalGuides forKey:@"HFBytesBetweenVerticalGuides"];
    565     [coder encodeInt64:startingLineBackgroundColorIndex forKey:@"HFStartingLineBackgroundColorIndex"];
    566     [coder encodeObject:rowBackgroundColors forKey:@"HFRowBackgroundColors"];
    567     [coder encodeBool:_hftvflags.antialias forKey:@"HFAntialias"];
    568     [coder encodeBool:_hftvflags.drawCallouts forKey:@"HFDrawCallouts"];
    569     [coder encodeBool:_hftvflags.editable forKey:@"HFEditable"];
    570 }
    571 
    572 - (instancetype)initWithCoder:(NSCoder *)coder {
    573     HFASSERT([coder allowsKeyedCoding]);
    574     self = [super initWithCoder:coder];
    575     representer = [coder decodeObjectForKey:@"HFRepresenter"];
    576     _font = [[coder decodeObjectForKey:@"HFFont"] retain];
    577     _data = [[coder decodeObjectForKey:@"HFData"] retain];
    578     verticalOffset = (CGFloat)[coder decodeDoubleForKey:@"HFVerticalOffset"];
    579     horizontalContainerInset = (CGFloat)[coder decodeDoubleForKey:@"HFHorizontalContainerOffset"];
    580     defaultLineHeight = (CGFloat)[coder decodeDoubleForKey:@"HFDefaultLineHeight"];
    581     bytesBetweenVerticalGuides = (NSUInteger)[coder decodeInt64ForKey:@"HFBytesBetweenVerticalGuides"];
    582     startingLineBackgroundColorIndex = (NSUInteger)[coder decodeInt64ForKey:@"HFStartingLineBackgroundColorIndex"];
    583     rowBackgroundColors = [[coder decodeObjectForKey:@"HFRowBackgroundColors"] retain];
    584     _hftvflags.antialias = [coder decodeBoolForKey:@"HFAntialias"];
    585     _hftvflags.drawCallouts = [coder decodeBoolForKey:@"HFDrawCallouts"];
    586     _hftvflags.editable = [coder decodeBoolForKey:@"HFEditable"];
    587     return self;
    588 }
    589 
    590 - (CGFloat)horizontalContainerInset {
    591     return horizontalContainerInset;
    592 }
    593 
    594 - (void)setHorizontalContainerInset:(CGFloat)inset {
    595     horizontalContainerInset = inset;
    596 }
    597 
    598 - (void)setBytesBetweenVerticalGuides:(NSUInteger)val {
    599     bytesBetweenVerticalGuides = val;
    600 }
    601 
    602 - (NSUInteger)bytesBetweenVerticalGuides {
    603     return bytesBetweenVerticalGuides;
    604 }
    605 
    606 
    607 - (void)setFont:(NSFont *)val {
    608     if (val != _font) {
    609         [_font release];
    610         _font = [val retain];
    611         NSLayoutManager *manager = [[NSLayoutManager alloc] init];
    612         defaultLineHeight = [manager defaultLineHeightForFont:_font];
    613         [manager release];
    614         [self setNeedsDisplay:YES];
    615     }
    616 }
    617 
    618 - (CGFloat)lineHeight {
    619     return defaultLineHeight;
    620 }
    621 
    622 /* The base implementation does not support font substitution, so we require that it be the base font. */
    623 - (NSFont *)fontAtSubstitutionIndex:(uint16_t)idx {
    624     HFASSERT(idx == 0);
    625     USE(idx);
    626     return _font;
    627 }
    628 
    629 - (NSRange)roundPartialByteRange:(NSRange)byteRange {
    630     NSUInteger bytesPerCharacter = [self bytesPerCharacter];
    631     /* Get the left and right edges of the range */
    632     NSUInteger left = byteRange.location, right = NSMaxRange(byteRange);
    633     
    634     /* Round both to the left.  This may make the range bigger or smaller, or empty! */
    635     left -= left % bytesPerCharacter;
    636     right -= right % bytesPerCharacter;
    637     
    638     /* Done */
    639     HFASSERT(right >= left);
    640     return NSMakeRange(left, right - left);
    641     
    642 }
    643 
    644 - (void)setNeedsDisplayForLinesInRange:(NSRange)lineRange {
    645     // redisplay the lines in the given range
    646     if (lineRange.length == 0) return;
    647     NSUInteger firstLine = lineRange.location, lastLine = NSMaxRange(lineRange);
    648     CGFloat lineHeight = [self lineHeight];
    649     CGFloat vertOffset = [self verticalOffset];
    650     CGFloat yOrigin = (firstLine - vertOffset) * lineHeight;
    651     CGFloat lastLineBottom = (lastLine - vertOffset) * lineHeight;
    652     NSRect bounds = [self bounds];
    653     NSRect dirtyRect = NSMakeRect(bounds.origin.x, bounds.origin.y + yOrigin, NSWidth(bounds), lastLineBottom - yOrigin);
    654     [self setNeedsDisplayInRect:dirtyRect];
    655 }
    656 
    657 - (void)setData:(NSData *)val {
    658     if (val != _data) {
    659         NSUInteger oldLength = [_data length];
    660         NSUInteger newLength = [val length];
    661         const unsigned char *oldBytes = (const unsigned char *)[_data bytes];
    662         const unsigned char *newBytes = (const unsigned char *)[val bytes];
    663         NSUInteger firstDifferingIndex = HFIndexOfFirstByteThatDiffers(oldBytes, oldLength, newBytes, newLength);
    664         if (firstDifferingIndex == NSUIntegerMax) {
    665             /* Nothing to do!  Data is identical! */
    666         }
    667         else {
    668             NSUInteger lastDifferingIndex = HFIndexOfLastByteThatDiffers(oldBytes, oldLength, newBytes, newLength);
    669             HFASSERT(lastDifferingIndex != NSUIntegerMax); //if we have a first different byte, we must have a last different byte
    670             /* Expand to encompass characters that they touch */
    671             NSUInteger bytesPerCharacter = [self bytesPerCharacter];
    672             firstDifferingIndex -= firstDifferingIndex % bytesPerCharacter;
    673             lastDifferingIndex = HFRoundUpToMultipleInt(lastDifferingIndex, bytesPerCharacter);
    674             
    675             /* Now figure out the line range they touch */
    676             const NSUInteger bytesPerLine = [self bytesPerLine];
    677             NSUInteger firstLine = firstDifferingIndex / bytesPerLine;
    678             NSUInteger lastLine = HFDivideULRoundingUp(MAX(oldLength, newLength), bytesPerLine);
    679             /* The +1 is for the following case - if we change the last character, then it may push the caret into the next line (even though there's no text there).  This last line may have a background color, so we need to make it draw if it did not draw before (or vice versa - when deleting the last character which pulls the caret from the last line). */
    680             NSUInteger lastDifferingLine = (lastDifferingIndex == NSNotFound ? lastLine : HFDivideULRoundingUp(lastDifferingIndex + 1, bytesPerLine));
    681             if (lastDifferingLine > firstLine) {
    682                 [self setNeedsDisplayForLinesInRange:NSMakeRange(firstLine, lastDifferingLine - firstLine)];
    683             }
    684         }
    685         [_data release];
    686         _data = [val copy];
    687         [self _updateCaretTimer];
    688     }
    689 }
    690 
    691 - (void)setStyles:(NSArray *)newStyles {
    692     if (! [_styles isEqual:newStyles]) {
    693         
    694         /* Figure out which styles changed - that is, we want to compute those objects that are not in oldStyles or newStyles, but not both. */
    695         NSMutableSet *changedStyles = _styles ? [[NSMutableSet alloc] initWithArray:_styles] : [[NSMutableSet alloc] init];
    696         FOREACH(HFTextVisualStyleRun *, run, newStyles) {
    697             if ([changedStyles containsObject:run]) {
    698                 [changedStyles removeObject:run];
    699             }
    700             else {
    701                 [changedStyles addObject:run];
    702             }
    703         }
    704         
    705         /* Now figure out the first and last indexes of changed ranges. */
    706         NSUInteger firstChangedIndex = NSUIntegerMax, lastChangedIndex = 0;
    707         FOREACH(HFTextVisualStyleRun *, changedRun, changedStyles) {
    708             NSRange range = [changedRun range];
    709             if (range.length > 0) {
    710                 firstChangedIndex = MIN(firstChangedIndex, range.location);
    711                 lastChangedIndex = MAX(lastChangedIndex, NSMaxRange(range) - 1);
    712             }
    713         }
    714         
    715         /* Don't need this any more */
    716         [changedStyles release];
    717         
    718         /* Expand to cover all touched characters */
    719         NSUInteger bytesPerCharacter = [self bytesPerCharacter];
    720         firstChangedIndex -= firstChangedIndex % bytesPerCharacter;
    721         lastChangedIndex = HFRoundUpToMultipleInt(lastChangedIndex, bytesPerCharacter);        
    722         
    723         /* Figure out the changed lines, and trigger redisplay */
    724         if (firstChangedIndex <= lastChangedIndex) {
    725             const NSUInteger bytesPerLine = [self bytesPerLine];
    726             NSUInteger firstLine = firstChangedIndex / bytesPerLine;
    727             NSUInteger lastLine = HFDivideULRoundingUp(lastChangedIndex, bytesPerLine);
    728             [self setNeedsDisplayForLinesInRange:NSMakeRange(firstLine, lastLine - firstLine + 1)];   
    729         }
    730         
    731         /* Do the usual Cocoa thing */
    732         [_styles release];
    733         _styles = [newStyles copy];
    734     }
    735 }
    736 
    737 - (void)setVerticalOffset:(CGFloat)val {
    738     if (val != verticalOffset) {
    739         verticalOffset = val;
    740         [self setNeedsDisplay:YES];
    741     }
    742 }
    743 
    744 - (CGFloat)verticalOffset {
    745     return verticalOffset;
    746 }
    747 
    748 - (NSUInteger)startingLineBackgroundColorIndex {
    749     return startingLineBackgroundColorIndex;
    750 }
    751 
    752 - (void)setStartingLineBackgroundColorIndex:(NSUInteger)val {
    753     startingLineBackgroundColorIndex = val;
    754 }
    755 
    756 - (BOOL)isFlipped {
    757     return YES;
    758 }
    759 
    760 - (HFTextRepresenter *)representer {
    761     return representer;
    762 }
    763 
    764 - (void)dealloc {
    765     HFUnregisterViewForWindowAppearanceChanges(self, _hftvflags.registeredForAppNotifications /* appToo */);
    766     [caretTimer invalidate];
    767     [caretTimer release];
    768     [_font release];
    769     [_data release];
    770     [_styles release];
    771     [cachedSelectedRanges release];
    772     [callouts release];
    773     if(byteColoring) Block_release(byteColoring);
    774     [super dealloc];
    775 }
    776 
    777 - (NSColor *)backgroundColorForEmptySpace {
    778     NSArray *colors = [[self representer] rowBackgroundColors];
    779     if (! [colors count]) return [NSColor clearColor]; 
    780     else return colors[0];
    781 }
    782 
    783 - (NSColor *)backgroundColorForLine:(NSUInteger)line {
    784     NSArray *colors = [[self representer] rowBackgroundColors];
    785     NSUInteger colorCount = [colors count];
    786     if (colorCount == 0) return [NSColor clearColor];
    787     NSUInteger colorIndex = (line + startingLineBackgroundColorIndex) % colorCount;
    788     if (colorIndex == 0) return nil; //will be drawn by empty space
    789     else return colors[colorIndex]; 
    790 }
    791 
    792 - (NSUInteger)bytesPerLine {
    793     HFASSERT([self representer] != nil);
    794     return [[self representer] bytesPerLine];
    795 }
    796 
    797 - (NSUInteger)bytesPerColumn {
    798     HFASSERT([self representer] != nil);
    799     return [[self representer] bytesPerColumn];
    800 }
    801 
    802 - (void)_drawDefaultLineBackgrounds:(NSRect)clip withLineHeight:(CGFloat)lineHeight maxLines:(NSUInteger)maxLines {
    803     NSRect bounds = [self bounds];
    804     NSUInteger lineIndex;
    805     NSRect lineRect = NSMakeRect(NSMinX(bounds), NSMinY(bounds), NSWidth(bounds), lineHeight);
    806     if ([self showsFocusRing]) lineRect = NSInsetRect(lineRect, 2, 0);
    807     lineRect.origin.y -= [self verticalOffset] * [self lineHeight];
    808     NSUInteger drawableLineIndex = 0;
    809     NEW_ARRAY(NSRect, lineRects, maxLines);
    810     NEW_ARRAY(NSColor*, lineColors, maxLines);
    811     for (lineIndex = 0; lineIndex < maxLines; lineIndex++) {
    812         NSRect clippedLineRect = NSIntersectionRect(lineRect, clip);
    813         if (! NSIsEmptyRect(clippedLineRect)) {
    814             NSColor *lineColor = [self backgroundColorForLine:lineIndex];
    815             if (lineColor) {
    816                 lineColors[drawableLineIndex] = lineColor;
    817                 lineRects[drawableLineIndex] = clippedLineRect;
    818                 drawableLineIndex++;
    819             }
    820         }
    821         lineRect.origin.y += lineHeight;
    822     }
    823     
    824     if (drawableLineIndex > 0) {
    825         NSRectFillListWithColorsUsingOperation(lineRects, lineColors, drawableLineIndex, NSCompositeSourceOver);
    826     }
    827     
    828     FREE_ARRAY(lineRects);
    829     FREE_ARRAY(lineColors);
    830 }
    831 
    832 - (HFTextVisualStyleRun *)styleRunForByteAtIndex:(NSUInteger)byteIndex {
    833     HFTextVisualStyleRun *run = [[HFTextVisualStyleRun alloc] init];
    834     [run setRange:NSMakeRange(0, NSUIntegerMax)];
    835     [run setForegroundColor:[NSColor textColor]];
    836     return [run autorelease];
    837 }
    838 
    839 /* Given a list of rects and a parallel list of values, find cases of equal adjacent values, and union together their corresponding rects, deleting the second element from the list.  Next, delete all nil values.  Returns the new count of the list. */
    840 static size_t unionAndCleanLists(NSRect *rectList, id *valueList, size_t count) {
    841     size_t trailing = 0, leading = 0;
    842     while (leading < count) {
    843         /* Copy our value left */
    844         valueList[trailing] = valueList[leading];
    845         rectList[trailing] = rectList[leading];
    846         
    847         /* Skip one - no point unioning with ourselves */
    848         leading += 1;
    849         
    850         /* Sweep right, unioning until we reach a different value or the end */
    851         id targetValue = valueList[trailing];
    852         for (; leading < count; leading++) {
    853             id testValue = valueList[leading];
    854             if (targetValue == testValue || (testValue && [targetValue isEqual:testValue])) {
    855                 /* Values match, so union the two rects */
    856                 rectList[trailing] = NSUnionRect(rectList[trailing], rectList[leading]);
    857             }
    858             else {
    859                 /* Values don't match, we're done sweeping */
    860                 break;
    861             }
    862         }
    863         
    864         /* We're done with this index */
    865         trailing += 1;
    866     }
    867     
    868     /* trailing keeps track of how many values we have */
    869     count = trailing;
    870     
    871     /* Now do the same thing, except delete nil values */
    872     for (trailing = leading = 0; leading < count; leading++) {
    873         if (valueList[leading] != nil) {
    874             valueList[trailing] = valueList[leading];
    875             rectList[trailing] = rectList[leading];
    876             trailing += 1;
    877         }
    878     }
    879     count = trailing;    
    880     
    881     /* All done */
    882     return count;
    883 }
    884 
    885 /* Draw vertical guidelines every four bytes */
    886 - (void)drawVerticalGuideLines:(NSRect)clip {
    887     if (bytesBetweenVerticalGuides == 0) return;
    888     
    889     NSUInteger bytesPerLine = [self bytesPerLine];
    890     NSRect bounds = [self bounds];
    891     CGFloat advancePerCharacter = [self advancePerCharacter];
    892     CGFloat spaceAdvancement = advancePerCharacter / 2;
    893     CGFloat advanceAmount = (advancePerCharacter + spaceAdvancement) * bytesBetweenVerticalGuides;
    894     CGFloat lineOffset = (CGFloat)(NSMinX(bounds) + [self horizontalContainerInset] + advanceAmount - spaceAdvancement / 2.);
    895     CGFloat endOffset = NSMaxX(bounds) - [self horizontalContainerInset];
    896     
    897     NSUInteger numGuides = (bytesPerLine - 1) / bytesBetweenVerticalGuides; // -1 is a trick to avoid drawing the last line
    898     NSUInteger guideIndex = 0, rectIndex = 0;
    899     NEW_ARRAY(NSRect, lineRects, numGuides);
    900     
    901     while (lineOffset < endOffset && guideIndex < numGuides) {
    902         NSRect lineRect = NSMakeRect(lineOffset - 1, NSMinY(bounds), 1, NSHeight(bounds));
    903         NSRect clippedLineRect = NSIntersectionRect(lineRect, clip);
    904         if (! NSIsEmptyRect(clippedLineRect)) {
    905             lineRects[rectIndex++] = clippedLineRect;
    906         }
    907         lineOffset += advanceAmount;
    908         guideIndex++;
    909     }
    910     if (rectIndex > 0) {
    911         [[NSColor gridColor] set];
    912         NSRectFillListUsingOperation(lineRects, rectIndex, NSCompositeSourceOver);
    913     }
    914     FREE_ARRAY(lineRects);
    915 }
    916 
    917 - (NSUInteger)maximumGlyphCountForByteCount:(NSUInteger)byteCount {
    918     USE(byteCount);
    919     UNIMPLEMENTED();
    920 }
    921 
    922 - (void)setByteColoring:(void (^)(uint8_t, uint8_t*, uint8_t*, uint8_t*, uint8_t*))coloring {
    923     Block_release(byteColoring);
    924     byteColoring = coloring ? Block_copy(coloring) : NULL;
    925     [self setNeedsDisplay:YES];
    926 }
    927 
    928 - (void)drawByteColoringBackground:(NSRange)range inRect:(NSRect)rect {
    929     if(!byteColoring) return;
    930     
    931     size_t width = (size_t)rect.size.width;
    932     
    933     // A rgba, 8-bit, single row image.
    934     // +1 in case messing around with floats makes us overshoot a bit.
    935     uint32_t *buffer = calloc(width+1, 4);
    936     
    937     const uint8_t *bytes = [_data bytes];
    938     bytes += range.location;
    939     
    940     NSUInteger bytesPerColumn = [self _effectiveBytesPerColumn];
    941     CGFloat advancePerCharacter = [self advancePerCharacter];
    942     CGFloat advanceBetweenColumns = [self advanceBetweenColumns];
    943     
    944     // For each character, draw the corresponding part of the image
    945     CGFloat offset = [self horizontalContainerInset];
    946     for(NSUInteger i = 0; i < range.length; i++) {
    947         uint8_t r, g, b, a;
    948         byteColoring(bytes[i], &r, &g, &b, &a);
    949         uint32_t c = ((uint32_t)r<<0) | ((uint32_t)g<<8) | ((uint32_t)b<<16) | ((uint32_t)a<<24);
    950         memset_pattern4(&buffer[(size_t)offset], &c, 4*(size_t)(advancePerCharacter+1));
    951         offset += advancePerCharacter;
    952         if(bytesPerColumn && (i+1) % bytesPerColumn == 0)
    953             offset += advanceBetweenColumns;
    954     }
    955     
    956     // Do a CGImage dance to draw the buffer
    957     CGDataProviderRef provider = CGDataProviderCreateWithData(NULL, buffer, 4 * width, NULL);
    958     CGColorSpaceRef cgcolorspace = CGColorSpaceCreateWithName(kCGColorSpaceGenericRGB);
    959     CGImageRef image = CGImageCreate(width, 1, 8, 32, 4 * width, cgcolorspace,
    960                                      (CGBitmapInfo)kCGImageAlphaLast, provider, NULL, false, kCGRenderingIntentDefault);
    961     CGContextDrawImage([[NSGraphicsContext currentContext] graphicsPort], NSRectToCGRect(rect), image);
    962     CGColorSpaceRelease(cgcolorspace);
    963     CGImageRelease(image);
    964     CGDataProviderRelease(provider);
    965     free(buffer);
    966 }
    967 
    968 - (void)drawStyledBackgroundsForByteRange:(NSRange)range inRect:(NSRect)rect {
    969     NSRect remainingRunRect = rect;
    970     NSRange remainingRange = range;
    971     
    972     /* Our caller lies to us a little */
    973     remainingRunRect.origin.x += [self horizontalContainerInset];
    974     
    975     const NSUInteger bytesPerColumn = [self _effectiveBytesPerColumn];
    976     
    977     /* Here are the properties we care about */
    978     struct PropertyInfo_t {
    979         SEL stylePropertyAccessor; // the selector we use to get the property
    980         NSRect *rectList; // the list of rects corresponding to the property values
    981         id *propertyValueList; // the list of the property values
    982         size_t count; //list count, only gets set after cleaning up our lists
    983     } propertyInfos[] = {
    984         {.stylePropertyAccessor = @selector(backgroundColor)},
    985         {.stylePropertyAccessor = @selector(bookmarkStarts)},
    986         {.stylePropertyAccessor = @selector(bookmarkExtents)},
    987         {.stylePropertyAccessor = @selector(bookmarkEnds)}
    988     };
    989     
    990     /* Each list has the same capacity, and (initially) the same count */
    991     size_t listCount = 0, listCapacity = 0;
    992     
    993     /* The function pointer we use to get our property values */
    994     id (* const funcPtr)(id, SEL) = (id (*)(id, SEL))objc_msgSend;
    995     
    996     size_t propertyIndex;
    997     const size_t propertyInfoCount = sizeof propertyInfos / sizeof *propertyInfos;
    998     
    999     while (remainingRange.length > 0) {
   1000         /* Get the next run for the remaining range. */
   1001         HFTextVisualStyleRun *styleRun = [self styleRunForByteAtIndex:remainingRange.location];
   1002         
   1003         /* The length of the run is the end of the style run or the end of the range we're given (whichever is smaller), minus the beginning of the range we care about. */
   1004         NSUInteger runStart = remainingRange.location;
   1005         NSUInteger runLength = MIN(NSMaxRange(range), NSMaxRange([styleRun range])) - runStart;
   1006         
   1007         /* Get the width of this run and use it to compute the rect */
   1008         CGFloat runRectWidth = [self totalAdvanceForBytesInRange:NSMakeRange(remainingRange.location, runLength)];
   1009         NSRect runRect = remainingRunRect;
   1010         runRect.size.width = runRectWidth;
   1011         
   1012         /* Update runRect and remainingRunRect based on what we just learned */
   1013         remainingRunRect.origin.x += runRectWidth;
   1014         remainingRunRect.size.width -= runRectWidth;
   1015 		
   1016         /* Do a hack - if we end at a column boundary, subtract the advance between columns.  If the next run has the same value for this property, then we'll end up unioning the rects together and the column gap will be filled.  This is the primary purpose of this function. */
   1017         if (bytesPerColumn > 0 && (runStart + runLength) % bytesPerColumn == 0) {
   1018             runRect.size.width -= MIN([self advanceBetweenColumns], runRect.size.width);
   1019         }
   1020         
   1021         /* Extend our lists if necessary */
   1022         if (listCount == listCapacity) {
   1023             /* Our list is too small, extend it */
   1024             listCapacity = listCapacity + 16;
   1025             
   1026             for (propertyIndex = 0; propertyIndex < propertyInfoCount; propertyIndex++) {
   1027                 struct PropertyInfo_t *p = propertyInfos + propertyIndex;
   1028                 p->rectList = check_realloc(p->rectList, listCapacity * sizeof *p->rectList);
   1029                 p->propertyValueList = check_realloc(p->propertyValueList, listCapacity * sizeof *p->propertyValueList);
   1030             }
   1031         }
   1032         
   1033         /* Now append our values to our lists, even if it's nil */
   1034         for (propertyIndex = 0; propertyIndex < propertyInfoCount; propertyIndex++) {
   1035             struct PropertyInfo_t *p = propertyInfos + propertyIndex;
   1036             id value = funcPtr(styleRun, p->stylePropertyAccessor);
   1037             p->rectList[listCount] = runRect;
   1038             p->propertyValueList[listCount] = value;
   1039         }
   1040         
   1041         listCount++;
   1042 		
   1043         /* Update remainingRange */
   1044         remainingRange.location += runLength;
   1045         remainingRange.length -= runLength;		
   1046         
   1047     }
   1048     
   1049     /* Now clean up our lists, to delete the gaps we may have introduced */
   1050     for (propertyIndex = 0; propertyIndex < propertyInfoCount; propertyIndex++) {
   1051         struct PropertyInfo_t *p = propertyInfos + propertyIndex;
   1052         p->count = unionAndCleanLists(p->rectList, p->propertyValueList, listCount);
   1053     }
   1054     
   1055     /* Finally we can draw them! First, draw byte backgrounds. */
   1056     [self drawByteColoringBackground:range inRect:rect];
   1057     
   1058     const struct PropertyInfo_t *p;
   1059     
   1060     /* Draw backgrounds */
   1061     p = propertyInfos + 0;
   1062     if (p->count > 0) NSRectFillListWithColorsUsingOperation(p->rectList, p->propertyValueList, p->count, NSCompositeSourceOver);
   1063 
   1064     /* Clean up */
   1065     for (propertyIndex = 0; propertyIndex < propertyInfoCount; propertyIndex++) {
   1066         p = propertyInfos + propertyIndex;
   1067         free(p->rectList);
   1068         free(p->propertyValueList);
   1069     }    
   1070 }
   1071 
   1072 - (void)drawGlyphs:(const struct HFGlyph_t *)glyphs atPoint:(NSPoint)point withAdvances:(const CGSize *)advances withStyleRun:(HFTextVisualStyleRun *)styleRun count:(NSUInteger)glyphCount {
   1073     HFASSERT(glyphs != NULL);
   1074     HFASSERT(advances != NULL);
   1075     HFASSERT(glyphCount > 0);
   1076     if ([styleRun shouldDraw]) {
   1077         [styleRun set];
   1078         CGContextRef ctx =  [[NSGraphicsContext currentContext] graphicsPort];
   1079         
   1080         /* Get all the CGGlyphs together */
   1081         NEW_ARRAY(CGGlyph, cgglyphs, glyphCount);
   1082         for (NSUInteger j=0; j < glyphCount; j++) {
   1083             cgglyphs[j] = glyphs[j].glyph;
   1084         }
   1085         
   1086         NSUInteger runStart = 0;
   1087         HFGlyphFontIndex runFontIndex = glyphs[0].fontIndex;
   1088         CGFloat runAdvance = 0;
   1089         for (NSUInteger i=1; i <= glyphCount; i++) {
   1090             /* Check if this run is finished, or if we are using a substitution font */
   1091             if (i == glyphCount || glyphs[i].fontIndex != runFontIndex || runFontIndex > 0) {
   1092                 /* Draw this run */
   1093                 NSFont *fontToUse = [self fontAtSubstitutionIndex:runFontIndex];
   1094                 [[fontToUse screenFont] set];
   1095                 CGContextSetTextPosition(ctx, point.x + runAdvance, point.y);
   1096                 
   1097                 if (runFontIndex > 0) {
   1098                     /* A substitution font.  Here we should only have one glyph */
   1099                     HFASSERT(i - runStart == 1);
   1100                     /* Get the advance for this glyph. */
   1101                     NSSize nativeAdvance;
   1102                     NSGlyph nativeGlyph = cgglyphs[runStart];
   1103                     [fontToUse getAdvancements:&nativeAdvance forGlyphs:&nativeGlyph count:1];
   1104                     if (nativeAdvance.width > advances[runStart].width) {
   1105                         /* This glyph is too wide!  We'll have to scale it.  Here we only scale horizontally. */
   1106                         CGFloat horizontalScale = advances[runStart].width / nativeAdvance.width;
   1107                         CGAffineTransform textCTM = CGContextGetTextMatrix(ctx);
   1108                         textCTM.a *= horizontalScale;
   1109                         CGContextSetTextMatrix(ctx, textCTM);
   1110                         /* Note that we don't have to restore the text matrix, because the next call to set the font will overwrite it. */
   1111                     }
   1112                 }
   1113                 
   1114                 /* Draw the glyphs */
   1115                 CGContextShowGlyphsWithAdvances(ctx, cgglyphs + runStart, advances + runStart, i - runStart);
   1116                 
   1117                 /* Record the new run */
   1118                 if (i < glyphCount) {                    
   1119                     /* Sum the advances */
   1120                     for (NSUInteger j = runStart; j < i; j++) {
   1121                         runAdvance += advances[j].width;
   1122                     }
   1123                     
   1124                     /* Record the new run start and index */
   1125                     runStart = i;
   1126                     runFontIndex = glyphs[i].fontIndex;
   1127                     HFASSERT(runFontIndex != kHFGlyphFontIndexInvalid);
   1128                 }
   1129             }
   1130         }
   1131     }
   1132 }
   1133 
   1134 
   1135 - (void)extractGlyphsForBytes:(const unsigned char *)bytes count:(NSUInteger)numBytes offsetIntoLine:(NSUInteger)offsetIntoLine intoArray:(struct HFGlyph_t *)glyphs advances:(CGSize *)advances resultingGlyphCount:(NSUInteger *)resultGlyphCount {
   1136     USE(bytes);
   1137     USE(numBytes);
   1138     USE(offsetIntoLine);
   1139     USE(glyphs);
   1140     USE(advances);
   1141     USE(resultGlyphCount);
   1142     UNIMPLEMENTED_VOID();
   1143 }
   1144 
   1145 - (void)extractGlyphsForBytes:(const unsigned char *)bytePtr range:(NSRange)byteRange intoArray:(struct HFGlyph_t *)glyphs advances:(CGSize *)advances withInclusionRanges:(NSArray *)restrictingToRanges initialTextOffset:(CGFloat *)initialTextOffset resultingGlyphCount:(NSUInteger *)resultingGlyphCount {
   1146     NSParameterAssert(glyphs != NULL && advances != NULL && restrictingToRanges != nil && bytePtr != NULL);
   1147     NSRange priorIntersectionRange = {NSUIntegerMax, NSUIntegerMax};
   1148     NSUInteger glyphBufferIndex = 0;
   1149     NSUInteger bytesPerLine = [self bytesPerLine];
   1150     NSUInteger restrictionRangeCount = [restrictingToRanges count];
   1151     for (NSUInteger rangeIndex = 0; rangeIndex < restrictionRangeCount; rangeIndex++) {
   1152         NSRange inclusionRange = [restrictingToRanges[rangeIndex] rangeValue];
   1153         NSRange intersectionRange = NSIntersectionRange(inclusionRange, byteRange);
   1154         if (intersectionRange.length == 0) continue;
   1155         
   1156         NSUInteger offsetIntoLine = intersectionRange.location % bytesPerLine;
   1157         
   1158         NSRange byteRangeToSkip;
   1159         if (priorIntersectionRange.location == NSUIntegerMax) {
   1160             byteRangeToSkip = NSMakeRange(byteRange.location, intersectionRange.location - byteRange.location);
   1161         }
   1162         else {
   1163             HFASSERT(intersectionRange.location >= NSMaxRange(priorIntersectionRange));
   1164             byteRangeToSkip.location = NSMaxRange(priorIntersectionRange);
   1165             byteRangeToSkip.length = intersectionRange.location - byteRangeToSkip.location;
   1166         }
   1167         
   1168         if (byteRangeToSkip.length > 0) {
   1169             CGFloat additionalAdvance = [self totalAdvanceForBytesInRange:byteRangeToSkip];
   1170             if (glyphBufferIndex == 0) {
   1171                 *initialTextOffset = *initialTextOffset + additionalAdvance;
   1172             }
   1173             else {
   1174                 advances[glyphBufferIndex - 1].width += additionalAdvance;
   1175             }
   1176         }
   1177         
   1178         NSUInteger glyphCountForRange = NSUIntegerMax;
   1179         [self extractGlyphsForBytes:bytePtr + intersectionRange.location count:intersectionRange.length offsetIntoLine:offsetIntoLine intoArray:glyphs + glyphBufferIndex advances:advances + glyphBufferIndex resultingGlyphCount:&glyphCountForRange];
   1180         HFASSERT(glyphCountForRange != NSUIntegerMax);
   1181         glyphBufferIndex += glyphCountForRange;
   1182         priorIntersectionRange = intersectionRange;
   1183     }
   1184     if (resultingGlyphCount) *resultingGlyphCount = glyphBufferIndex;
   1185 }
   1186 
   1187 - (void)drawTextWithClip:(NSRect)clip restrictingToTextInRanges:(NSArray *)restrictingToRanges {
   1188     CGContextRef ctx = [[NSGraphicsContext currentContext] graphicsPort];
   1189     NSRect bounds = [self bounds];
   1190     CGFloat lineHeight = [self lineHeight];
   1191     
   1192     CGAffineTransform textTransform = CGContextGetTextMatrix(ctx);
   1193     CGContextSetTextDrawingMode(ctx, kCGTextFill);
   1194     
   1195     NSUInteger lineStartIndex, bytesPerLine = [self bytesPerLine];
   1196     NSData *dataObject = [self data];
   1197     NSFont *fontObject = [[self font] screenFont];
   1198     //const NSUInteger bytesPerChar = [self bytesPerCharacter];
   1199     const NSUInteger byteCount = [dataObject length];
   1200     
   1201     const unsigned char * const bytePtr = [dataObject bytes];
   1202     
   1203     NSRect lineRectInBoundsSpace = NSMakeRect(NSMinX(bounds), NSMinY(bounds), NSWidth(bounds), lineHeight);
   1204     lineRectInBoundsSpace.origin.y -= [self verticalOffset] * lineHeight;
   1205     
   1206     /* Start us off with the horizontal inset and move the baseline down by the ascender so our glyphs just graze the top of our view */
   1207     textTransform.tx += [self horizontalContainerInset];
   1208     textTransform.ty += [fontObject ascender] - lineHeight * [self verticalOffset];
   1209     const NSUInteger maxGlyphCount = [self maximumGlyphCountForByteCount:bytesPerLine];
   1210     NEW_ARRAY(struct HFGlyph_t, glyphs, maxGlyphCount);
   1211     NEW_ARRAY(CGSize, advances, maxGlyphCount);
   1212     for (lineStartIndex = 0; lineStartIndex < byteCount; lineStartIndex += bytesPerLine) {
   1213         if (lineStartIndex > 0) {
   1214             textTransform.ty += lineHeight;
   1215             lineRectInBoundsSpace.origin.y += lineHeight;
   1216         }
   1217         if (NSIntersectsRect(lineRectInBoundsSpace, clip)) {	    
   1218             const NSUInteger bytesInThisLine = MIN(bytesPerLine, byteCount - lineStartIndex);
   1219             
   1220             /* Draw the backgrounds of any styles. */
   1221             [self drawStyledBackgroundsForByteRange:NSMakeRange(lineStartIndex, bytesInThisLine) inRect:lineRectInBoundsSpace];
   1222             
   1223             NSUInteger byteIndexInLine = 0;
   1224             CGFloat advanceIntoLine = 0;
   1225             while (byteIndexInLine < bytesInThisLine) {
   1226                 const NSUInteger byteIndex = lineStartIndex + byteIndexInLine;
   1227                 HFTextVisualStyleRun *styleRun = [self styleRunForByteAtIndex:byteIndex];
   1228                 HFASSERT(styleRun != nil);
   1229                 HFASSERT(byteIndex >= [styleRun range].location);
   1230                 const NSUInteger bytesInThisRun = MIN(NSMaxRange([styleRun range]) - byteIndex, bytesInThisLine - byteIndexInLine);
   1231                 const NSRange characterRange = [self roundPartialByteRange:NSMakeRange(byteIndex, bytesInThisRun)];
   1232                 if (characterRange.length > 0) {
   1233                     NSUInteger resultGlyphCount = 0;
   1234                     CGFloat initialTextOffset = 0;
   1235                     if (restrictingToRanges == nil) {
   1236                         [self extractGlyphsForBytes:bytePtr + characterRange.location count:characterRange.length offsetIntoLine:byteIndexInLine intoArray:glyphs advances:advances resultingGlyphCount:&resultGlyphCount];
   1237                     }
   1238                     else {
   1239                         [self extractGlyphsForBytes:bytePtr range:NSMakeRange(byteIndex, bytesInThisRun) intoArray:glyphs advances:advances withInclusionRanges:restrictingToRanges initialTextOffset:&initialTextOffset resultingGlyphCount:&resultGlyphCount];
   1240                     }
   1241                     HFASSERT(resultGlyphCount <= maxGlyphCount);
   1242                     
   1243 #if ! NDEBUG
   1244                     for (NSUInteger q=0; q < resultGlyphCount; q++) {
   1245                         HFASSERT(glyphs[q].fontIndex != kHFGlyphFontIndexInvalid);
   1246                     }
   1247 #endif
   1248                     
   1249                     if (resultGlyphCount > 0) {
   1250                         textTransform.tx += initialTextOffset + advanceIntoLine;
   1251                         CGContextSetTextMatrix(ctx, textTransform);
   1252                         /* Draw them */
   1253                         [self drawGlyphs:glyphs atPoint:NSMakePoint(textTransform.tx, textTransform.ty) withAdvances:advances withStyleRun:styleRun count:resultGlyphCount];
   1254                         
   1255                         /* Undo the work we did before so as not to screw up the next run */
   1256                         textTransform.tx -= initialTextOffset + advanceIntoLine;
   1257                         
   1258                         /* Record how far into our line this made us move */
   1259                         NSUInteger glyphIndex;
   1260                         for (glyphIndex = 0; glyphIndex < resultGlyphCount; glyphIndex++) {
   1261                             advanceIntoLine += advances[glyphIndex].width;
   1262                         }
   1263                     }
   1264                 }
   1265                 byteIndexInLine += bytesInThisRun;
   1266             }
   1267         }
   1268         else if (NSMinY(lineRectInBoundsSpace) > NSMaxY(clip)) {
   1269             break;
   1270         }
   1271     }
   1272     FREE_ARRAY(glyphs);
   1273     FREE_ARRAY(advances);
   1274 }
   1275 
   1276 
   1277 - (void)drawFocusRingWithClip:(NSRect)clip {
   1278     USE(clip);
   1279     [NSGraphicsContext saveGraphicsState];
   1280     NSSetFocusRingStyle(NSFocusRingOnly);
   1281     [[NSColor clearColor] set];
   1282     NSRectFill([self bounds]);
   1283     [NSGraphicsContext restoreGraphicsState];
   1284 }
   1285 
   1286 - (BOOL)shouldDrawCallouts {
   1287     return _hftvflags.drawCallouts;
   1288 }
   1289 
   1290 - (void)setShouldDrawCallouts:(BOOL)val {
   1291     _hftvflags.drawCallouts = val;
   1292     [self setNeedsDisplay:YES];
   1293 }
   1294 
   1295 - (void)drawBookmarksWithClip:(NSRect)clip {
   1296     if([self shouldDrawCallouts]) {
   1297         /* Figure out which callouts we're going to draw */
   1298         NSRect allCalloutsRect = NSZeroRect;
   1299         NSMutableArray *localCallouts = [[NSMutableArray alloc] initWithCapacity:[callouts count]];
   1300         FOREACH(HFRepresenterTextViewCallout *, callout, [callouts objectEnumerator]) {
   1301             NSRect calloutRect = [callout rect];
   1302             if (NSIntersectsRect(clip, calloutRect)) {
   1303                 [localCallouts addObject:callout];
   1304                 allCalloutsRect = NSUnionRect(allCalloutsRect, calloutRect);
   1305             }
   1306         }
   1307         allCalloutsRect = NSIntersectionRect(allCalloutsRect, clip);
   1308         
   1309         if ([localCallouts count]) {
   1310             /* Draw shadows first */
   1311             CGContextRef ctx = [[NSGraphicsContext currentContext] graphicsPort];
   1312             CGContextBeginTransparencyLayerWithRect(ctx, NSRectToCGRect(allCalloutsRect), NULL);
   1313             FOREACH(HFRepresenterTextViewCallout *, callout, localCallouts) {
   1314                 [callout drawShadowWithClip:clip];
   1315             }
   1316             CGContextEndTransparencyLayer(ctx);
   1317             
   1318             FOREACH(HFRepresenterTextViewCallout *, newCallout, localCallouts) {
   1319                 // NSRect rect = [callout rect];
   1320                 // [[NSColor greenColor] set];
   1321                 // NSFrameRect(rect);
   1322                 [newCallout drawWithClip:clip];
   1323             }
   1324         }
   1325         [localCallouts release];
   1326     }
   1327 }
   1328 
   1329 - (void)drawRect:(NSRect)clip {
   1330     [[self backgroundColorForEmptySpace] set];
   1331     NSRectFillUsingOperation(clip, NSCompositeSourceOver);
   1332     BOOL antialias = [self shouldAntialias];
   1333     CGContextRef ctx = [[NSGraphicsContext currentContext] graphicsPort];
   1334     
   1335     [[self.font screenFont] set];
   1336     
   1337     if ([self showsFocusRing]) {
   1338         NSWindow *window = [self window];
   1339         if (self == [window firstResponder] && [window isKeyWindow]) {
   1340             [self drawFocusRingWithClip:clip];
   1341         }
   1342     }
   1343     
   1344     NSUInteger bytesPerLine = [self bytesPerLine];
   1345     if (bytesPerLine == 0) return;
   1346     NSUInteger byteCount = [_data length];
   1347     
   1348     [self _drawDefaultLineBackgrounds:clip withLineHeight:[self lineHeight] maxLines:ll2l(HFRoundUpToNextMultipleSaturate(byteCount, bytesPerLine) / bytesPerLine)];
   1349     [self drawSelectionIfNecessaryWithClip:clip];
   1350     
   1351     NSColor *textColor = [NSColor textColor];
   1352     [textColor set];
   1353     
   1354     if (! antialias) {
   1355         CGContextSaveGState(ctx);
   1356         CGContextSetShouldAntialias(ctx, NO);
   1357     }
   1358     [self drawTextWithClip:clip restrictingToTextInRanges:nil];
   1359     if (! antialias) {
   1360         CGContextRestoreGState(ctx);
   1361     }
   1362     
   1363     // Vertical dividers only make sense in single byte mode.
   1364     if ([self _effectiveBytesPerColumn] == 1) {
   1365         [self drawVerticalGuideLines:clip];
   1366     }
   1367     
   1368     [self drawCaretIfNecessaryWithClip:clip];
   1369     
   1370     [self drawBookmarksWithClip:clip];
   1371 }
   1372 
   1373 - (NSRect)furthestRectOnEdge:(NSRectEdge)edge forRange:(NSRange)byteRange {
   1374     HFASSERT(edge == NSMinXEdge || edge == NSMaxXEdge || edge == NSMinYEdge || edge == NSMaxYEdge);
   1375     const NSUInteger bytesPerLine = [self bytesPerLine];
   1376     CGFloat lineHeight = [self lineHeight];
   1377     CGFloat vertOffset = [self verticalOffset];
   1378     NSUInteger firstLine = byteRange.location / bytesPerLine, lastLine = (NSMaxRange(byteRange) - 1) / bytesPerLine;
   1379     NSRect result = NSZeroRect;
   1380     
   1381     if (edge == NSMinYEdge || edge == NSMaxYEdge) {
   1382         /* This is the top (MinY) or bottom (MaxY).  We only have to look at one line. */
   1383         NSUInteger lineIndex = (edge == NSMinYEdge ? firstLine : lastLine);
   1384         NSRange lineRange = NSMakeRange(lineIndex * bytesPerLine, bytesPerLine);
   1385         NSRange intersection = NSIntersectionRange(lineRange, byteRange);
   1386         HFASSERT(intersection.length > 0);
   1387         CGFloat yOrigin = (lineIndex - vertOffset) * lineHeight;
   1388         CGFloat xStart = [self originForCharacterAtByteIndex:intersection.location].x;
   1389         CGFloat xEnd = [self originForCharacterAtByteIndex:NSMaxRange(intersection) - 1].x + [self advancePerCharacter];
   1390         result = NSMakeRect(xStart, yOrigin, xEnd - xStart, 0);
   1391     }
   1392     else {
   1393         if (firstLine == lastLine) {
   1394             /* We only need to consider this one line */
   1395             NSRange lineRange = NSMakeRange(firstLine * bytesPerLine, bytesPerLine);
   1396             NSRange intersection = NSIntersectionRange(lineRange, byteRange);
   1397             HFASSERT(intersection.length > 0);
   1398             CGFloat yOrigin = (firstLine - vertOffset) * lineHeight;
   1399             CGFloat xCoord;
   1400             if (edge == NSMinXEdge) {
   1401                 xCoord = [self originForCharacterAtByteIndex:intersection.location].x;
   1402             }
   1403             else {
   1404                 xCoord = [self originForCharacterAtByteIndex:NSMaxRange(intersection) - 1].x + [self advancePerCharacter];
   1405             }
   1406             result = NSMakeRect(xCoord, yOrigin, 0, lineHeight);            
   1407         }
   1408         else {
   1409             /* We have more than one line.  If we are asking for the left edge, sum up the left edge of every line but the first, and handle the first specially.  Likewise for the right edge (except handle the last specially) */
   1410             BOOL includeFirstLine, includeLastLine;
   1411             CGFloat xCoord;
   1412             if (edge == NSMinXEdge) {
   1413                 /* Left edge, include the first line only if it starts at the beginning of the line or there's only one line */
   1414                 includeFirstLine = (byteRange.location % bytesPerLine == 0);
   1415                 includeLastLine = YES;
   1416                 xCoord = [self horizontalContainerInset];
   1417             }
   1418             else {
   1419                 /* Right edge, include the last line only if it starts at the beginning of the line or there's only one line */
   1420                 includeFirstLine = YES;
   1421                 includeLastLine = (NSMaxRange(byteRange) % bytesPerLine == 0);
   1422                 NSUInteger bytesPerColumn = [self _effectiveBytesPerColumn];
   1423                 /* Don't add in space for the advance after the last column, hence subtract 1. */
   1424                 NSUInteger numColumns = (bytesPerColumn ? (bytesPerLine / bytesPerColumn - 1) : 0);
   1425                 xCoord = [self horizontalContainerInset] + ([self advancePerCharacter] * bytesPerLine / [self bytesPerCharacter]) + [self advanceBetweenColumns] * numColumns;
   1426             }
   1427             NSUInteger firstLineToInclude = (includeFirstLine ? firstLine : firstLine + 1), lastLineToInclude = (includeLastLine ? lastLine : lastLine - 1);
   1428             result = NSMakeRect(xCoord, (firstLineToInclude - [self verticalOffset]) * lineHeight, 0, (lastLineToInclude - firstLineToInclude + 1) * lineHeight);
   1429         }
   1430     }
   1431     return result;
   1432 }
   1433 
   1434 - (NSUInteger)availableLineCount {
   1435     CGFloat result = (CGFloat)ceil(NSHeight([self bounds]) / [self lineHeight]);
   1436     HFASSERT(result >= 0.);
   1437     HFASSERT(result <= NSUIntegerMax);
   1438     return (NSUInteger)result;
   1439 }
   1440 
   1441 - (double)maximumAvailableLinesForViewHeight:(CGFloat)viewHeight {
   1442     return viewHeight / [self lineHeight];
   1443 }
   1444 
   1445 - (void)setFrameSize:(NSSize)size {
   1446     NSUInteger currentBytesPerLine = [self bytesPerLine];
   1447     double currentLineCount = [self maximumAvailableLinesForViewHeight:NSHeight([self bounds])];
   1448     [super setFrameSize:size];
   1449     NSUInteger newBytesPerLine = [self maximumBytesPerLineForViewWidth:size.width];
   1450     double newLineCount = [self maximumAvailableLinesForViewHeight:NSHeight([self bounds])];
   1451     HFControllerPropertyBits bits = 0;
   1452     if (newBytesPerLine != currentBytesPerLine) bits |= (HFControllerBytesPerLine | HFControllerDisplayedLineRange);
   1453     if (newLineCount != currentLineCount) bits |= HFControllerDisplayedLineRange;
   1454     if (bits) [[self representer] representerChangedProperties:bits];
   1455 }
   1456 
   1457 - (CGFloat)advanceBetweenColumns {
   1458     UNIMPLEMENTED();
   1459 }
   1460 
   1461 - (CGFloat)advancePerCharacter {
   1462     UNIMPLEMENTED();
   1463 }
   1464 
   1465 - (CGFloat)advancePerColumn {
   1466     NSUInteger bytesPerColumn = [self _effectiveBytesPerColumn];
   1467     if (bytesPerColumn == 0) {
   1468         return 0;
   1469     }
   1470     else {
   1471         return [self advancePerCharacter] * (bytesPerColumn / [self bytesPerCharacter]) + [self advanceBetweenColumns];
   1472     }
   1473 }
   1474 
   1475 - (CGFloat)totalAdvanceForBytesInRange:(NSRange)range {
   1476     if (range.length == 0) return 0;
   1477     NSUInteger bytesPerColumn = [self _effectiveBytesPerColumn];
   1478     HFASSERT(bytesPerColumn == 0 || [self bytesPerLine] % bytesPerColumn == 0);
   1479     CGFloat result = (range.length * [self advancePerCharacter] / [self bytesPerCharacter]) ;
   1480     if (bytesPerColumn > 0) {
   1481         NSUInteger numColumnSpaces = NSMaxRange(range) / bytesPerColumn - range.location / bytesPerColumn; //note that integer division does not distribute
   1482         result += numColumnSpaces * [self advanceBetweenColumns];
   1483     }
   1484     return result;
   1485 }
   1486 
   1487 /* Returns the number of bytes in a character, e.g. if we are UTF-16 this would be 2. */
   1488 - (NSUInteger)bytesPerCharacter {
   1489     return 1;
   1490 }
   1491 
   1492 - (NSUInteger)maximumBytesPerLineForViewWidth:(CGFloat)viewWidth {
   1493     CGFloat availableSpace = (CGFloat)(viewWidth - 2. * [self horizontalContainerInset]);
   1494     NSUInteger bytesPerColumn = [self _effectiveBytesPerColumn], bytesPerCharacter = [self bytesPerCharacter];    
   1495     if (bytesPerColumn == 0) {
   1496         /* No columns */
   1497         NSUInteger numChars = (NSUInteger)(availableSpace / [self advancePerCharacter]);
   1498         /* Return it, except it's at least one character */
   1499         return MAX(numChars, 1u) * bytesPerCharacter;
   1500     }
   1501     else {
   1502         /* We have some columns */
   1503         CGFloat advancePerColumn = [self advancePerColumn];
   1504         //spaceRequiredForNColumns = N * (advancePerColumn) - spaceBetweenColumns
   1505         CGFloat fractionalColumns = (availableSpace + [self advanceBetweenColumns]) / advancePerColumn;
   1506         NSUInteger columnCount = (NSUInteger)fmax(1., HFFloor(fractionalColumns));
   1507         return columnCount * bytesPerColumn;
   1508     }
   1509 }
   1510 
   1511 
   1512 - (CGFloat)minimumViewWidthForBytesPerLine:(NSUInteger)bytesPerLine {
   1513     HFASSERT(bytesPerLine > 0);
   1514     NSUInteger bytesPerColumn = [self _effectiveBytesPerColumn];
   1515     CGFloat result;
   1516     if (bytesPerColumn == 0) {
   1517         result = (CGFloat)((2. * [self horizontalContainerInset]) + [self advancePerCharacter] * (bytesPerLine / [self bytesPerCharacter]));
   1518     }
   1519     else {
   1520         HFASSERT(bytesPerLine % bytesPerColumn == 0);
   1521         result = (CGFloat)((2. * [self horizontalContainerInset]) + [self advancePerColumn] * (bytesPerLine / bytesPerColumn) - [self advanceBetweenColumns]);
   1522     }
   1523     return result;
   1524 }
   1525 
   1526 - (BOOL)isEditable {
   1527     return _hftvflags.editable;
   1528 }
   1529 
   1530 - (void)setEditable:(BOOL)val {
   1531     if (val != _hftvflags.editable) {
   1532         _hftvflags.editable = val;
   1533         [self _updateCaretTimer];
   1534     }
   1535 }
   1536 
   1537 - (BOOL)shouldAntialias {
   1538     return _hftvflags.antialias;
   1539 }
   1540 
   1541 - (void)setShouldAntialias:(BOOL)val {
   1542     _hftvflags.antialias = !!val;
   1543     [self setNeedsDisplay:YES];
   1544 }
   1545 
   1546 - (BOOL)behavesAsTextField {
   1547     return [[self representer] behavesAsTextField];
   1548 }
   1549 
   1550 - (BOOL)showsFocusRing {
   1551     return [[self representer] behavesAsTextField];
   1552 }
   1553 
   1554 - (BOOL)isWithinMouseDown {
   1555     return _hftvflags.withinMouseDown;
   1556 }
   1557 
   1558 - (void)_windowDidChangeKeyStatus:(NSNotification *)note {
   1559     USE(note);
   1560     [self _updateCaretTimer];
   1561     if ([[note name] isEqualToString:NSWindowDidBecomeKeyNotification]) {
   1562         [self _forceCaretOnIfHasCaretTimer];
   1563     }
   1564     if ([self showsFocusRing] && self == [[self window] firstResponder]) {
   1565         [[self superview] setNeedsDisplayInRect:NSInsetRect([self frame], -6, -6)];
   1566     }
   1567     [self setNeedsDisplay:YES];
   1568 }
   1569 
   1570 - (void)viewDidMoveToWindow {
   1571     [self _updateCaretTimer];
   1572     HFRegisterViewForWindowAppearanceChanges(self, @selector(_windowDidChangeKeyStatus:), ! _hftvflags.registeredForAppNotifications);
   1573     _hftvflags.registeredForAppNotifications = YES;
   1574     [super viewDidMoveToWindow];
   1575 }
   1576 
   1577 - (void)viewWillMoveToWindow:(NSWindow *)newWindow {
   1578     HFUnregisterViewForWindowAppearanceChanges(self, NO /* appToo */);
   1579     [super viewWillMoveToWindow:newWindow];
   1580 }
   1581 
   1582 /* Computes the character at the given index for selection, properly handling the case where the point is outside the bounds */
   1583 - (NSUInteger)characterAtPointForSelection:(NSPoint)point {
   1584     NSPoint mungedPoint = point;
   1585     // shift us right by half an advance so that we trigger at the midpoint of each character, rather than at the x origin
   1586     mungedPoint.x += [self advancePerCharacter] / (CGFloat)2.;
   1587     // make sure we're inside the bounds
   1588     const NSRect bounds = [self bounds];
   1589     mungedPoint.x = HFMax(NSMinX(bounds), mungedPoint.x);
   1590     mungedPoint.x = HFMin(NSMaxX(bounds), mungedPoint.x);
   1591     mungedPoint.y = HFMax(NSMinY(bounds), mungedPoint.y);
   1592     mungedPoint.y = HFMin(NSMaxY(bounds), mungedPoint.y);
   1593     return [self indexOfCharacterAtPoint:mungedPoint];
   1594 }
   1595 
   1596 - (NSUInteger)maximumCharacterIndex {
   1597     //returns the maximum character index that the selection may lie on.  It is one beyond the last byte index, to represent the cursor at the end of the document.
   1598     return [[self data] length] / [self bytesPerCharacter];
   1599 }
   1600 
   1601 - (void)mouseDown:(NSEvent *)event {
   1602     HFASSERT(_hftvflags.withinMouseDown == 0);
   1603     _hftvflags.withinMouseDown = 1;
   1604     [self _forceCaretOnIfHasCaretTimer];
   1605     NSPoint mouseDownLocation = [self convertPoint:[event locationInWindow] fromView:nil];
   1606     NSUInteger characterIndex = [self characterAtPointForSelection:mouseDownLocation];
   1607     
   1608     characterIndex = MIN(characterIndex, [self maximumCharacterIndex]); //characterIndex may be one beyond the last index, to represent the cursor at the end of the document
   1609     [[self representer] beginSelectionWithEvent:event forCharacterIndex:characterIndex];
   1610     
   1611     /* Drive the event loop in event tracking mode until we're done */
   1612     HFASSERT(_hftvflags.receivedMouseUp == NO); //paranoia - detect any weird recursive invocations
   1613     NSDate *endDate = [NSDate distantFuture];
   1614     
   1615     /* Start periodic events for autoscroll */
   1616     [NSEvent startPeriodicEventsAfterDelay:0.1 withPeriod:0.05];
   1617     
   1618     NSPoint autoscrollLocation = mouseDownLocation;
   1619     while (! _hftvflags.receivedMouseUp) {
   1620         @autoreleasepool {
   1621         NSEvent *ev = [NSApp nextEventMatchingMask: NSLeftMouseUpMask | NSLeftMouseDraggedMask | NSPeriodicMask untilDate:endDate inMode:NSEventTrackingRunLoopMode dequeue:YES];
   1622         
   1623         if ([ev type] == NSPeriodic) {
   1624             // autoscroll if drag is out of view bounds
   1625             CGFloat amountToScroll = 0;
   1626             NSRect bounds = [self bounds];
   1627             if (autoscrollLocation.y < NSMinY(bounds)) {
   1628                 amountToScroll = (autoscrollLocation.y - NSMinY(bounds)) / [self lineHeight];
   1629             }
   1630             else if (autoscrollLocation.y > NSMaxY(bounds)) {
   1631                 amountToScroll = (autoscrollLocation.y - NSMaxY(bounds)) / [self lineHeight];
   1632             }
   1633             if (amountToScroll != 0.) {
   1634                 [[[self representer] controller] scrollByLines:amountToScroll];
   1635                 characterIndex = [self characterAtPointForSelection:autoscrollLocation];
   1636                 characterIndex = MIN(characterIndex, [self maximumCharacterIndex]);
   1637                 [[self representer] continueSelectionWithEvent:ev forCharacterIndex:characterIndex];
   1638             }
   1639         }
   1640         else if ([ev type] == NSLeftMouseDragged) {
   1641             autoscrollLocation = [self convertPoint:[ev locationInWindow] fromView:nil];
   1642         }
   1643         
   1644         [NSApp sendEvent:ev];
   1645         } // @autoreleasepool
   1646     }
   1647     
   1648     [NSEvent stopPeriodicEvents];
   1649     
   1650     _hftvflags.receivedMouseUp = NO;
   1651     _hftvflags.withinMouseDown = 0;
   1652 }
   1653 
   1654 - (void)mouseDragged:(NSEvent *)event {
   1655     if (! _hftvflags.withinMouseDown) return;
   1656     NSPoint location = [self convertPoint:[event locationInWindow] fromView:nil];
   1657     NSUInteger characterIndex = [self characterAtPointForSelection:location];
   1658     characterIndex = MIN(characterIndex, [self maximumCharacterIndex]);
   1659     [[self representer] continueSelectionWithEvent:event forCharacterIndex:characterIndex];    
   1660 }
   1661 
   1662 - (void)mouseUp:(NSEvent *)event {
   1663     if (! _hftvflags.withinMouseDown) return;
   1664     NSPoint location = [self convertPoint:[event locationInWindow] fromView:nil];
   1665     NSUInteger characterIndex = [self characterAtPointForSelection:location];
   1666     characterIndex = MIN(characterIndex, [self maximumCharacterIndex]);
   1667     [[self representer] endSelectionWithEvent:event forCharacterIndex:characterIndex];
   1668     _hftvflags.receivedMouseUp = YES;
   1669 }
   1670 
   1671 - (void)keyDown:(NSEvent *)event {
   1672     HFASSERT(event != NULL);
   1673     [self interpretKeyEvents:@[event]];
   1674 }
   1675 
   1676 - (void)scrollWheel:(NSEvent *)event {
   1677     [[self representer] scrollWheel:event];
   1678 }
   1679 
   1680 - (void)insertText:(id)string {
   1681     if (! [self isEditable]) {
   1682         NSBeep();
   1683     }
   1684     else {
   1685         if ([string isKindOfClass:[NSAttributedString class]]) string = [string string];
   1686         [NSCursor setHiddenUntilMouseMoves:YES];
   1687         [[self representer] insertText:string];
   1688     }
   1689 }
   1690 
   1691 - (BOOL)handleCommand:(SEL)sel {
   1692     if (sel == @selector(insertTabIgnoringFieldEditor:)) {
   1693         [self insertText:@"\t"];
   1694     }
   1695     else if ([self respondsToSelector:sel]) {
   1696         [self performSelector:sel withObject:nil];
   1697     }
   1698     else {
   1699         return NO;
   1700     }
   1701     return YES;
   1702 }
   1703 
   1704 - (void)doCommandBySelector:(SEL)sel {
   1705     HFRepresenter *rep = [self representer];
   1706     //    NSLog(@"%s%s", _cmd, sel);
   1707     if ([self handleCommand:sel]) {
   1708         /* Nothing to do */
   1709     }
   1710     else if ([rep respondsToSelector:sel]) {
   1711         [rep performSelector:sel withObject:self];
   1712     }
   1713     else {
   1714         [super doCommandBySelector:sel];
   1715     }
   1716 }
   1717 
   1718 - (IBAction)selectAll:sender {
   1719     [[self representer] selectAll:sender];
   1720 }
   1721 
   1722 /* Indicates whether at least one byte is selected */
   1723 - (BOOL)_selectionIsNonEmpty {
   1724     NSArray *selection = [[[self representer] controller] selectedContentsRanges];
   1725     FOREACH(HFRangeWrapper *, rangeWrapper, selection) {
   1726         if ([rangeWrapper HFRange].length > 0) return YES;
   1727     }
   1728     return NO;
   1729 }
   1730 
   1731 - (SEL)_pasteboardOwnerStringTypeWritingSelector {
   1732     UNIMPLEMENTED();
   1733 }
   1734 
   1735 - (void)paste:sender {
   1736     if (! [self isEditable]) {
   1737         NSBeep();
   1738     }
   1739     else {
   1740         USE(sender);
   1741         [[self representer] pasteBytesFromPasteboard:[NSPasteboard generalPasteboard]];
   1742     }
   1743 }
   1744 
   1745 - (void)copy:sender {
   1746     USE(sender);
   1747     [[self representer] copySelectedBytesToPasteboard:[NSPasteboard generalPasteboard]];
   1748 }
   1749 
   1750 - (void)cut:sender {
   1751     USE(sender);
   1752     [[self representer] cutSelectedBytesToPasteboard:[NSPasteboard generalPasteboard]];
   1753 }
   1754 
   1755 - (BOOL)validateMenuItem:(NSMenuItem *)item {
   1756     SEL action = [item action];
   1757     if (action == @selector(selectAll:)) return YES;
   1758     else if (action == @selector(cut:)) return [[self representer] canCut];
   1759     else if (action == @selector(copy:)) return [self _selectionIsNonEmpty];
   1760     else if (action == @selector(paste:)) return [[self representer] canPasteFromPasteboard:[NSPasteboard generalPasteboard]];
   1761     else return YES;
   1762 }
   1763 
   1764 /* Compatibility with Sonoma */
   1765 + (bool)clipsToBounds
   1766 {
   1767     return true;
   1768 }
   1769 
   1770 @end

This webpage is intended to be an accessible preview of this repository. To get a fuller picture, clone it and use the git CLI.