2016-11-18 6 views
3

NSTextViewの横に行番号を表示するには、MacOSでNSRulerViewを使用しています。 両方のビューは同じフォントと同じフォントサイズを共有しますが、NSRulerViewではNSTextView文字列レンダリングが自動的に管理されますが、正しい行番号を計算する必要があります(この部分はうまくいきます)。drawHashMarksAndLabelsInRect内に文字列をレンダリングする必要があります。NSRulerView行番号をメインテキストに正しく整列する方法

私の問題は、2つのビュー間でテキストを正しく配置できないことです。いくつかのフォントではうまくいきますが、他のフォントでは目に見える違いがあります。

私が実際に使用していたコードは次のとおりです。

#define BTF_RULER_WIDTH  40.0f 
#define BTF_RULER_PADDING 5.0f 

static inline void drawLineNumber(NSUInteger lineNumber, CGFloat y, NSDictionary *attributes, CGFloat ruleThickness) { 
    NSString *string = [[NSNumber numberWithUnsignedInteger:lineNumber] stringValue]; 
    NSAttributedString *attString = [[NSAttributedString alloc] initWithString:string attributes:attributes]; 
    NSUInteger x = ruleThickness - BTF_RULER_PADDING - attString.size.width; 

    [attString drawAtPoint:NSMakePoint(x, y)]; 
} 

static inline NSUInteger countNewLines(NSString *s, NSUInteger location, NSUInteger length) { 
    CFStringInlineBuffer inlineBuffer; 
    CFStringInitInlineBuffer((__bridge CFStringRef)s, &inlineBuffer, CFRangeMake(location, length)); 

    NSUInteger counter = 0; 
    for (CFIndex i=0; i < length; ++i) { 
     UniChar c = CFStringGetCharacterFromInlineBuffer(&inlineBuffer, i); 
     if (c == (UniChar)'\n') ++counter; 
    } 
    return counter; 
} 

@implementation BTFRulerView 

- (instancetype)initWithBTFTextView:(BTFTextView *)textView { 
    self = [super initWithScrollView:textView.enclosingScrollView orientation:NSVerticalRuler]; 
    if (self) { 
     self.clientView = textView; 

     // default settings 
     self.ruleThickness = BTF_RULER_WIDTH; 
     self.textColor = [NSColor grayColor]; 
    } 
    return self; 
} 

- (void)drawHashMarksAndLabelsInRect:(NSRect)rect { 
    // do not use drawBackgroundInRect for background color otherwise a 1px right border with a different color appears 
    if (_backgroundColor) { 
     [_backgroundColor set]; 
     [NSBezierPath fillRect:rect]; 
    } 

    BTFTextView *textView = (BTFTextView *)self.clientView; 
    if (!textView) return; 

    NSLayoutManager *layoutManager = textView.layoutManager; 
    if (!layoutManager) return; 

    NSString *textString = textView.string; 
    if ((!textString) || (textString.length == 0)) return; 

    CGFloat insetHeight = textView.textContainerInset.height; 
    CGPoint relativePoint = [self convertPoint:NSZeroPoint fromView:textView]; 
    NSDictionary *lineNumberAttributes = @{NSFontAttributeName: textView.font, NSForegroundColorAttributeName: _textColor}; 

    NSRange visibleGlyphRange = [layoutManager glyphRangeForBoundingRect:textView.visibleRect inTextContainer:textView.textContainer]; 
    NSUInteger firstVisibleGlyphCharacterIndex = [layoutManager characterIndexForGlyphAtIndex:visibleGlyphRange.location]; 

    // line number for the first visible line 
    NSUInteger lineNumber = countNewLines(textString, 0, firstVisibleGlyphCharacterIndex)+1; 
    NSUInteger glyphIndexForStringLine = visibleGlyphRange.location; 

    // go through each line in the string 
    while (glyphIndexForStringLine < NSMaxRange(visibleGlyphRange)) { 
     // range of current line in the string 
     NSRange characterRangeForStringLine = [textString lineRangeForRange:NSMakeRange([layoutManager characterIndexForGlyphAtIndex:glyphIndexForStringLine], 0)]; 
     NSRange glyphRangeForStringLine = [layoutManager glyphRangeForCharacterRange: characterRangeForStringLine actualCharacterRange:nil]; 

     NSUInteger glyphIndexForGlyphLine = glyphIndexForStringLine; 
     NSUInteger glyphLineCount = 0; 

     while (glyphIndexForGlyphLine < NSMaxRange(glyphRangeForStringLine)) { 
      // check if the current line in the string spread across several lines of glyphs 
      NSRange effectiveRange = NSMakeRange(0, 0); 

      // range of current "line of glyphs". If a line is wrapped then it will have more than one "line of glyphs" 
      NSRect lineRect = [layoutManager lineFragmentRectForGlyphAtIndex:glyphIndexForGlyphLine effectiveRange:&effectiveRange withoutAdditionalLayout:YES]; 

      // compute Y for line number 
      CGFloat y = NSMinY(lineRect) + relativePoint.y + insetHeight; 

      // draw line number only if string does not spread across several lines 
      if (glyphLineCount == 0) { 
       drawLineNumber(lineNumber, y, lineNumberAttributes, self.ruleThickness); 
      } 

      // move to next glyph line 
      ++glyphLineCount; 
      glyphIndexForGlyphLine = NSMaxRange(effectiveRange); 
     } 

     glyphIndexForStringLine = NSMaxRange(glyphRangeForStringLine); 
     ++lineNumber; 
    } 

    // draw line number for the extra line at the end of the text 
    if (layoutManager.extraLineFragmentTextContainer) { 
     CGFloat y = NSMinY(layoutManager.extraLineFragmentRect) + relativePoint.y + insetHeight; 
     drawLineNumber(lineNumber, y, lineNumberAttributes, self.ruleThickness); 
    } 
} 

enter image description here

私は問題は、その後drawLineNumber関数に渡されたYの計算だと思います。どのように正しく計算するかについての任意のアイデアですか?

答えて

2

私は解決策を見つけたと私はそれが他の人に非常に有用である可能性を考える:

#define BTF_RULER_WIDTH  40.0f 
#define BTF_RULER_PADDING 5.0f 

static inline void drawLineNumberInRect(NSUInteger lineNumber, NSRect lineRect, NSDictionary *attributes, CGFloat ruleThickness) { 
    NSString *string = [[NSNumber numberWithUnsignedInteger:lineNumber] stringValue]; 
    NSAttributedString *attString = [[NSAttributedString alloc] initWithString:string attributes:attributes]; 
    NSUInteger x = ruleThickness - BTF_RULER_PADDING - attString.size.width; 

    // Offetting the drawing keeping into account the ascender (because we draw it without NSStringDrawingUsesLineFragmentOrigin) 
    NSFont *font = attributes[NSFontAttributeName]; 
    lineRect.origin.x = x; 
    lineRect.origin.y += font.ascender; 

    [attString drawWithRect:lineRect options:0 context:nil]; 
} 

static inline NSUInteger countNewLines(NSString *s, NSUInteger location, NSUInteger length) { 
    CFStringInlineBuffer inlineBuffer; 
    CFStringInitInlineBuffer((__bridge CFStringRef)s, &inlineBuffer, CFRangeMake(location, length)); 

    NSUInteger counter = 0; 
    for (CFIndex i=0; i < length; ++i) { 
     UniChar c = CFStringGetCharacterFromInlineBuffer(&inlineBuffer, i); 
     if (c == (UniChar)'\n') ++counter; 
    } 
    return counter; 
} 

@implementation BTFRulerView 

- (instancetype)initWithBTFTextView:(BTFTextView *)textView { 
    self = [super initWithScrollView:textView.enclosingScrollView orientation:NSVerticalRuler]; 
    if (self) { 
     self.clientView = textView; 

     // default settings 
     self.ruleThickness = BTF_RULER_WIDTH; 
     self.textColor = [NSColor grayColor]; 
    } 
    return self; 
} 

- (void)drawHashMarksAndLabelsInRect:(NSRect)rect { 
    // do not use drawBackgroundInRect for background color otherwise a 1px right border with a different color appears 
    if (_backgroundColor) { 
     [_backgroundColor set]; 
     [NSBezierPath fillRect:rect]; 
    } 

    BTFTextView *textView = (BTFTextView *)self.clientView; 
    if (!textView) return; 

    NSLayoutManager *layoutManager = textView.layoutManager; 
    if (!layoutManager) return; 

    NSString *textString = textView.string; 
    if ((!textString) || (textString.length == 0)) return; 

    CGFloat insetHeight = textView.textContainerInset.height; 
    CGPoint relativePoint = [self convertPoint:NSZeroPoint fromView:textView]; 

    // Gettign text attributes from the textview 
    NSMutableDictionary *lineNumberAttributes = [[textView.textStorage attributesAtIndex:0 effectiveRange:NULL] mutableCopy]; 
    lineNumberAttributes[NSForegroundColorAttributeName] = self.textColor; 

    NSRange visibleGlyphRange = [layoutManager glyphRangeForBoundingRect:textView.visibleRect inTextContainer:textView.textContainer]; 
    NSUInteger firstVisibleGlyphCharacterIndex = [layoutManager characterIndexForGlyphAtIndex:visibleGlyphRange.location]; 

    // line number for the first visible line 
    NSUInteger lineNumber = countNewLines(textString, 0, firstVisibleGlyphCharacterIndex)+1; 
    NSUInteger glyphIndexForStringLine = visibleGlyphRange.location; 

    // go through each line in the string 
    while (glyphIndexForStringLine < NSMaxRange(visibleGlyphRange)) { 
     // range of current line in the string 
     NSRange characterRangeForStringLine = [textString lineRangeForRange:NSMakeRange([layoutManager characterIndexForGlyphAtIndex:glyphIndexForStringLine], 0)]; 
     NSRange glyphRangeForStringLine = [layoutManager glyphRangeForCharacterRange: characterRangeForStringLine actualCharacterRange:nil]; 

     NSUInteger glyphIndexForGlyphLine = glyphIndexForStringLine; 
     NSUInteger glyphLineCount = 0; 

     while (glyphIndexForGlyphLine < NSMaxRange(glyphRangeForStringLine)) { 
      // check if the current line in the string spread across several lines of glyphs 
      NSRange effectiveRange = NSMakeRange(0, 0); 

      // range of current "line of glyphs". If a line is wrapped then it will have more than one "line of glyphs" 
      NSRect lineRect = [layoutManager lineFragmentRectForGlyphAtIndex:glyphIndexForGlyphLine effectiveRange:&effectiveRange withoutAdditionalLayout:YES]; 

      // compute Y for line number 
      CGFloat y = ceil(NSMinY(lineRect) + relativePoint.y + insetHeight); 
      lineRect.origin.y = y; 

      // draw line number only if string does not spread across several lines 
      if (glyphLineCount == 0) { 
       drawLineNumberInRect(lineNumber, lineRect, lineNumberAttributes, self.ruleThickness); 
      } 

      // move to next glyph line 
      ++glyphLineCount; 
      glyphIndexForGlyphLine = NSMaxRange(effectiveRange); 
     } 

     glyphIndexForStringLine = NSMaxRange(glyphRangeForStringLine); 
     ++lineNumber; 
    } 

    // draw line number for the extra line at the end of the text 
    if (layoutManager.extraLineFragmentTextContainer) { 
     NSRect lineRect = layoutManager.extraLineFragmentRect; 
     CGFloat y = ceil(NSMinY(lineRect) + relativePoint.y + insetHeight); 
     lineRect.origin.y = y; 
     drawLineNumberInRect(lineNumber, lineRect, lineNumberAttributes, self.ruleThickness); 
    } 
} 

私はdrawWithRectの代わりdrawAtPointを使用して、私は、接続のTextViewから直接属性を使用します。

enter image description here

関連する問題