Optimize invisible glyph generation

This commit is contained in:
1024jp 2020-01-30 15:37:56 +09:00
parent 4a13ec7418
commit e4834ee48a
4 changed files with 35 additions and 21 deletions

View File

@ -5,6 +5,11 @@ Change Log
3.8.10 (unreleased)
--------------------------
### Improvements
- Optimize invisible character drawing.
### Fixes
- Fix an issue where the Unicode code point field in the document inspector displayed always “not selected.”

View File

@ -8,7 +8,7 @@
//
// ---------------------------------------------------------------------------
//
// © 2015-2019 1024jp
// © 2015-2020 1024jp
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@ -91,8 +91,8 @@ final class FindPanelLayoutManager: NSLayoutManager {
let fullwidthSpace = NSAttributedString(string: Invisible.fullwidthSpace.usedSymbol, attributes: fullwidthAttributes)
// draw invisibles glyph by glyph
for glyphIndex in glyphsToShow.location..<glyphsToShow.upperBound {
let charIndex = self.characterIndexForGlyph(at: glyphIndex)
let characterRange = self.characterRange(forGlyphRange: glyphsToShow, actualGlyphRange: nil)
for charIndex in characterRange.lowerBound..<characterRange.upperBound {
let codeUnit = (string as NSString).character(at: charIndex)
let invisible = Invisible(codeUnit: codeUnit)
@ -114,16 +114,15 @@ final class FindPanelLayoutManager: NSLayoutManager {
guard showsFullwidthSpace else { continue }
glyphString = fullwidthSpace
default:
case .otherControl:
guard showsOtherInvisibles else { continue }
guard
self.propertyForGlyph(at: glyphIndex) == .controlCharacter,
self.textStorage?.attribute(.glyphInfo, at: charIndex, effectiveRange: nil) == nil
else { continue }
let replaceFont = NSFont(named: .lucidaGrande, size: font.pointSize) ?? NSFont.systemFont(ofSize: font.pointSize)
let charRange = self.characterRange(forGlyphRange: NSRange(location: glyphIndex, length: 1), actualGlyphRange: nil)
let baseString = (string as NSString).substring(with: charRange)
let controlRange = NSRange(location: charIndex, length: 1)
let baseString = (string as NSString).substring(with: controlRange)
guard let glyphInfo = NSGlyphInfo(glyphName: "replacement", for: replaceFont, baseString: baseString) else { continue }
@ -132,11 +131,15 @@ final class FindPanelLayoutManager: NSLayoutManager {
// (2015-09 by 1024jp)
self.textStorage?.addAttributes([.glyphInfo: glyphInfo,
.font: replaceFont,
.foregroundColor: color], range: charRange)
.foregroundColor: color], range: controlRange)
continue
case .none:
continue
}
// calculate position to draw glyph
let glyphIndex = self.glyphIndexForCharacter(at: charIndex)
let lineOrigin = self.lineFragmentRect(forGlyphAt: glyphIndex, effectiveRange: nil, withoutAdditionalLayout: true).origin
let glyphLocation = self.location(forGlyphAt: glyphIndex)
let point = lineOrigin.offset(by: origin).offsetBy(dx: glyphLocation.x)

View File

@ -8,7 +8,7 @@
//
// ---------------------------------------------------------------------------
//
// © 2014-2018 1024jp
// © 2014-2020 1024jp
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@ -31,7 +31,7 @@ enum Invisible {
case tab
case newLine
case fullwidthSpace
case replacement
case otherControl
var candidates: [String] {
@ -45,7 +45,7 @@ enum Invisible {
return ["", "", "", ""]
case .fullwidthSpace:
return ["", "", "", ""]
case .replacement:
case .otherControl:
return ["<EFBFBD>"]
}
}
@ -82,8 +82,11 @@ extension Invisible {
self = .newLine
case 0x3000: // IDEOGRAPHIC SPACE a.k.a. full-width space (JP)
self = .fullwidthSpace
case 0x0000...0x001F, 0x0080...0x009F, 0x200B: // C0, C1, ZERO WIDTH SPACE
// -> NSGlyphGenerator generates NSControlGlyph for all characters
// in the Unicode General Category C* and U+200B (ZERO WIDTH SPACE).
self = .otherControl
default:
// `.replacement` cannot be determined only with code unit
return nil
}
}
@ -114,7 +117,7 @@ extension Invisible {
case .tab: return .invisibleTab
case .newLine: return .invisibleNewLine
case .fullwidthSpace: return .invisibleFullwidthSpace
case .replacement: return nil
case .otherControl: return nil
}
}

View File

@ -69,7 +69,7 @@ final class LayoutManager: NSLayoutManager, ValidationIgnorable {
self.spaceWidth = textFont.spaceWidth
self.invisibleLines = self.generateInvisibleLines()
self.replacementGlyphWidth = self.invisibleLines.replacement.bounds().width
self.replacementGlyphWidth = self.invisibleLines.otherControl.bounds().width
}
}
@ -106,7 +106,7 @@ final class LayoutManager: NSLayoutManager, ValidationIgnorable {
let tab: CTLine
let newLine: CTLine
let fullwidthSpace: CTLine
let replacement: CTLine
let otherControl: CTLine
}
@ -215,8 +215,8 @@ final class LayoutManager: NSLayoutManager, ValidationIgnorable {
}
// draw invisibles glyph by glyph
for glyphIndex in glyphsToShow.location..<glyphsToShow.upperBound {
let charIndex = self.characterIndexForGlyph(at: glyphIndex)
let characterRange = self.characterRange(forGlyphRange: glyphsToShow, actualGlyphRange: nil)
for charIndex in characterRange.lowerBound..<characterRange.upperBound {
let codeUnit = string.character(at: charIndex)
let invisible = Invisible(codeUnit: codeUnit)
@ -238,13 +238,16 @@ final class LayoutManager: NSLayoutManager, ValidationIgnorable {
guard self.showsFullwidthSpace else { continue }
line = self.invisibleLines.fullwidthSpace
default:
case .otherControl:
guard self.showsOtherInvisibles else { continue }
guard self.propertyForGlyph(at: glyphIndex) == .controlCharacter else { continue }
line = self.invisibleLines.replacement
line = self.invisibleLines.otherControl
case .none:
continue
}
// calculate position to draw glyph
let glyphIndex = self.glyphIndexForCharacter(at: charIndex)
let lineOrigin = self.lineFragmentRect(forGlyphAt: glyphIndex, effectiveRange: nil, withoutAdditionalLayout: true).origin
let glyphLocation = self.location(forGlyphAt: glyphIndex)
var point = lineOrigin.offset(by: origin).offsetBy(dx: glyphLocation.x,
@ -341,7 +344,7 @@ final class LayoutManager: NSLayoutManager, ValidationIgnorable {
tab: self.invisibleLine(.tab, font: font),
newLine: self.invisibleLine(.newLine, font: font),
fullwidthSpace: self.invisibleLine(.fullwidthSpace, font: fullWidthFont),
replacement: self.invisibleLine(.replacement, font: textFont))
otherControl: self.invisibleLine(.otherControl, font: textFont))
}