Display hidden characters in NSTextView

前端 未结 6 1536
孤城傲影
孤城傲影 2020-12-15 01:14

I am writing a text editor for Mac OS X. I need to display hidden characters in an NSTextView (such as spaces, tabs, and special characters). I have spent a lot of time se

相关标签:
6条回答
  • 2020-12-15 01:47

    Here is Pol's solution in Swift:

    class MyLayoutManager: NSLayoutManager {
        override func drawGlyphsForGlyphRange(glyphsToShow: NSRange, atPoint origin: NSPoint) {
            if let storage = self.textStorage {
                let s = storage.string
                let startIndex = s.startIndex
                for var glyphIndex = glyphsToShow.location; glyphIndex < glyphsToShow.location + glyphsToShow.length; glyphIndex++ {
                    let characterIndex = self.characterIndexForGlyphAtIndex(glyphIndex)
                    let ch = s[startIndex.advancedBy(characterIndex)]
                    switch ch {
                    case " ":
                        let attrs = storage.attributesAtIndex(characterIndex, effectiveRange: nil)
                        if let font = attrs[NSFontAttributeName] {
                            let g = font.glyphWithName("periodcentered")
                            self.replaceGlyphAtIndex(glyphIndex, withGlyph: g)
                        }
                    case "\n":
                        let attrs = storage.attributesAtIndex(characterIndex, effectiveRange: nil)
                        if let font = attrs[NSFontAttributeName] {
    //                        let g = font.glyphWithName("carriagereturn")
                            let g = font.glyphWithName("paragraph")
                            self.replaceGlyphAtIndex(glyphIndex, withGlyph: g)
                        }
                    case "\t":
                        let attrs = storage.attributesAtIndex(characterIndex, effectiveRange: nil)
                        if let font = attrs[NSFontAttributeName] {
                            let g = font.glyphWithName("arrowdblright")
                            self.replaceGlyphAtIndex(glyphIndex, withGlyph: g)
                        }
                    default:
                        break
                    }
                }
            }
            super.drawGlyphsForGlyphRange(glyphsToShow, atPoint: origin)
        }
    }
    

    And to list the glyph names:

       func listFonts() {
            let font = CGFontCreateWithFontName("Menlo-Regular")
            for var i:UInt16 = 0; i < UInt16(CGFontGetNumberOfGlyphs(font)); i++ {
                if let name = CGFontCopyGlyphNameForGlyph(font, i) {
                    print("name: \(name) at index \(i)")
                }
            }
        }
    
    0 讨论(0)
  • 2020-12-15 01:48

    Have a look at the NSLayoutManager class. Your NSTextView will have a layout manager associated with it, and the layout manager is responsible for associating a character (space, tab, etc.) with a glyph (the image of that character drawn on the screen).

    In your case, you would probably be most interested in the replaceGlyphAtIndex:withGlyph: method, which would allow you to replace individual glyphs.

    0 讨论(0)
  • 2020-12-15 01:53

    Perhaps -[NSLayoutManager setShowsControlCharacters:] and/or -[NSLayoutManager setShowsInvisibleCharacters:] will do what you want.

    0 讨论(0)
  • 2020-12-15 02:02

    Here's a fully working and clean implementation

    @interface GILayoutManager : NSLayoutManager
    @end
    
    @implementation GILayoutManager
    
    - (void)drawGlyphsForGlyphRange:(NSRange)range atPoint:(NSPoint)point {
      NSTextStorage* storage = self.textStorage;
      NSString* string = storage.string;
      for (NSUInteger glyphIndex = range.location; glyphIndex < range.location + range.length; glyphIndex++) {
        NSUInteger characterIndex = [self characterIndexForGlyphAtIndex: glyphIndex];
        switch ([string characterAtIndex:characterIndex]) {
    
          case ' ': {
            NSFont* font = [storage attribute:NSFontAttributeName atIndex:characterIndex effectiveRange:NULL];
            [self replaceGlyphAtIndex:glyphIndex withGlyph:[font glyphWithName:@"periodcentered"]];
            break;
          }
    
          case '\n': {
            NSFont* font = [storage attribute:NSFontAttributeName atIndex:characterIndex effectiveRange:NULL];
            [self replaceGlyphAtIndex:glyphIndex withGlyph:[font glyphWithName:@"carriagereturn"]];
            break;
          }
    
        }
      }
    
      [super drawGlyphsForGlyphRange:range atPoint:point];
    }
    
    @end
    

    To install, use:

    [myTextView.textContainer replaceLayoutManager:[[GILayoutManager alloc] init]];
    

    To find font glyph names, you have to go to CoreGraphics:

    CGFontRef font = CGFontCreateWithFontName(CFSTR("Menlo-Regular"));
    for (size_t i = 0; i < CGFontGetNumberOfGlyphs(font); ++i) {
      printf("%s\n", [CFBridgingRelease(CGFontCopyGlyphNameForGlyph(font, i)) UTF8String]);
    }
    
    0 讨论(0)
  • 2020-12-15 02:09

    I solved the problem of converting between NSGlyphs and the corresponding unichar in the NSTextView. The code below works beautifully and replaces spaces with bullets for visible text:

    - (void)drawGlyphsForGlyphRange:(NSRange)range atPoint:(NSPoint)origin
    {
        NSFont *font = [[CURRENT_TEXT_VIEW typingAttributes]
                           objectForKey:NSFontAttributeName];
    
        NSGlyph bullet = [font glyphWithName:@"bullet"];
    
        for (int i = range.location; i != range.location + range.length; i++)
        {
            unsigned charIndex = [self characterIndexForGlyphAtIndex:i];
    
            unichar c =[[[self textStorage] string] characterAtIndex:charIndex];
    
            if (c == ' ')
                [self replaceGlyphAtIndex:charIndex withGlyph:bullet];
        }
    
        [super drawGlyphsForGlyphRange:range atPoint:origin];
    }
    0 讨论(0)
  • 2020-12-15 02:10

    I wrote a text editor a few years back - here's some meaningless code that should get you looking in (hopefully) the right direction (this is an NSLayoutManager subclass btw - and yes I know it's leaking like the proverbial kitchen sink):

    - (void)drawGlyphsForGlyphRange:(NSRange)glyphRange atPoint:(NSPoint)containerOrigin
    {
        if ([[[[MJDocumentController sharedDocumentController] currentDocument] editor] showInvisibles])
        {
            //init glyphs
            unichar crlf = 0x00B6; 
            NSString *CRLF = [[NSString alloc] initWithCharacters:&crlf length:1];
            unichar space = 0x00B7;
            NSString *SPACE = [[NSString alloc] initWithCharacters:&space length:1];
            unichar tab = 0x2192; 
            NSString *TAB = [[NSString alloc] initWithCharacters:&tab length:1];
    
            NSString *docContents = [[self textStorage] string];
            NSString *glyph;
            NSPoint glyphPoint;
            NSRect glyphRect;
            NSDictionary *attr = [[NSDictionary alloc] initWithObjectsAndKeys:[NSUnarchiver unarchiveObjectWithData:[[NSUserDefaults standardUserDefaults] objectForKey:@"invisiblesColor"]], NSForegroundColorAttributeName, nil];
    
            //loop thru current range, drawing glyphs
            int i;
            for (i = glyphRange.location; i < NSMaxRange(glyphRange); i++)
            {
                glyph = @"";
    
                //look for special chars
                switch ([docContents characterAtIndex:i])
                {
                    //space
                    case ' ':
                        glyph = SPACE;
                        break;
    
                    //tab
                    case '\t':
                        glyph = TAB;
                        break;
    
                    //eol
                    case 0x2028:
                    case 0x2029:
                    case '\n':
                    case '\r':
                        glyph = CRLF;
                        break;
    
                    //do nothing
                    default:
                        glyph = @"";
                        break;                  
                }
    
                //should we draw?
                if ([glyph length])
                {
                    glyphPoint = [self locationForGlyphAtIndex:i];
                    glyphRect = [self lineFragmentRectForGlyphAtIndex:i effectiveRange:NULL];
                    glyphPoint.x += glyphRect.origin.x;
                    glyphPoint.y = glyphRect.origin.y;
                    [glyph drawAtPoint:glyphPoint withAttributes:attr];
                }
            }
        }
    
        [super drawGlyphsForGlyphRange:glyphRange atPoint:containerOrigin];
    }
    
    0 讨论(0)
提交回复
热议问题