forked from gnachman/iTerm2
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathCharacterRun.m
230 lines (207 loc) · 7.87 KB
/
CharacterRun.m
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
//
// CharacterRun.m
// iTerm
//
// Created by George Nachman on 12/16/12.
//
//
#import "CharacterRun.h"
#import "ScreenChar.h"
@implementation CharacterRun
@synthesize antiAlias = antiAlias_;
@synthesize color = color_;
@synthesize runType = runType_;
@synthesize fakeBold = fakeBold_;
@synthesize x = x_;
@synthesize fontInfo = fontInfo_;
@synthesize sharedData = sharedData_;
@synthesize range = range_;
- (id)init {
self = [super init];
if (self) {
runType_ = kCharacterRunMultipleSimpleChars;
}
return self;
}
- (void)dealloc {
[color_ release];
[fontInfo_ release];
[sharedData_ release];
[super dealloc];
}
- (id)copyWithZone:(NSZone *)zone {
CharacterRun *theCopy = [[CharacterRun alloc] init];
theCopy.antiAlias = antiAlias_;
theCopy.runType = runType_;
theCopy.fontInfo = fontInfo_;
theCopy.color = color_;
theCopy.fakeBold = fakeBold_;
theCopy.x = x_;
theCopy.sharedData = sharedData_;
theCopy.range = range_;
return theCopy;
}
- (NSString *)description {
NSMutableString *d = [NSMutableString string];
[d appendFormat:@"<CharacterRun: %p codes=\"", self];
unichar *c = [self codes];
for (int i = 0; i < range_.length; i++) {
[d appendFormat:@"%x ", (((int)c[i]) & 0xffff)];
}
[d appendFormat:@"\">"];
return d;
}
- (unichar *)codes {
return [sharedData_ codesInRange:range_];
}
- (CGSize *)advances {
return [sharedData_ advancesInRange:range_];
}
- (CGGlyph *)glyphs {
return [sharedData_ glyphsInRange:range_];
}
// Given a run with codes x1,x2,...,xn, change self to have codes x1,x2,...,x(i-1).
// and return a new run (with the same attributes as self) with codes xi,x(i+1),...,xn.
- (CharacterRun *)splitBeforeIndex:(int)truncateBeforeIndex
{
CharacterRun *tailRun = [[self copy] autorelease];
assert(range_.length >= truncateBeforeIndex);
CGSize *advances = [self advances];
for (int i = 0; i < truncateBeforeIndex; ++i) {
tailRun.x += advances[i].width;
}
[sharedData_ advanceAllocation:&tailRun->range_ by:truncateBeforeIndex];
[sharedData_ truncateAllocation:&range_ toSize:truncateBeforeIndex];
return tailRun;
}
// Returns the index of the first glyph in [self glyphs] valued 0, or -1 if there is none.
- (int)indexOfFirstMissingGlyph {
CGGlyph *glyphs = [self glyphs];
for (int i = 0; i < range_.length; i++) {
if (!glyphs[i]) {
return i;
}
}
return -1;
}
- (void)appendRunsWithGlyphsToArray:(NSMutableArray *)newRuns {
[newRuns addObject:self];
if (runType_ == kCharacterRunSingleCharWithCombiningMarks) {
// These don't actually need glyphs. This algorithm is incompatible
// with surrogate pairs (they have expected 0-valued glyphs), so it
// must never be used in this case. Anyway, it's useless because
// CoreText can't render combining marks sanely.
return;
}
// This algorithm works by trying to convert a whole run into glyphs. If a bad glyph is found,
// the current run is split before and after the bad glyph. The bad glyph will be set to type
// kCharacterRunSingleCharWithCombiningMarks and left for NSAttributedString to deal with.
//
// Example (numbers are valid glyphs, letters are errors):
// 12a34bc5d
// [12,a34bc5d]
// [12,a,34bc5d]
// [12,a,34,bc5d]
// [12,a,34,b,c5d]
// [12,a,34,b,c,5d]
// [12,a,34,b,c,5,d]
//
// In the while loop, the invariant is maintained that currentRun always equals the last glyph
// in the newRuns array.
BOOL isOk = CTFontGetGlyphsForCharacters((CTFontRef)fontInfo_.font,
[self codes],
[self glyphs],
range_.length);
CharacterRun *currentRun = self;
while (!isOk) {
// As long as this loop is running there are bogus glyphs in the current
// run. We split the prefix of good glyphs off and then split the suffix
// after the bad glyph off, isolating it.
//
// A faster algorithm would be possible if the font substitution
// algorithm were a bit smarter, but sometimes it goes down dead ends
// so the only way to make sure that it can render the max number of
// glyphs in a string is to substitute fonts for one glyph at a time.
//
// Example: given "U+239c U+23b7" in AndaleMono, the font substitution
// algorithm suggests HiraganoKaguGothicProN, which cannot render both
// glyphs. Ask it one at a time and you get Apple Symbols, which can
// render both.
int i = [currentRun indexOfFirstMissingGlyph];
while (i >= 0) {
if (i == 0) {
// The first glyph is bad. Truncate the current run to 1
// glyph and convert it to a
// kCharacterRunSingleCharWithCombiningMarks (though it's
// obviously only one code point, it uses NSAttributedString to
// render which is slow but can find the right font).
CharacterRun *suffixRun = [currentRun splitBeforeIndex:1];
currentRun.runType = kCharacterRunSingleCharWithCombiningMarks;
if (suffixRun.range.length > 0) {
// Append the remainder of the original run to the array of
// runs and have the outer loop begin working on the suffix.
[newRuns addObject:suffixRun];
currentRun = suffixRun;
// break to try getting glyphs again.
break;
} else {
// This was the last glyph.
return;
}
} else if (i > 0) {
// Some glyph after the first is bad. Truncate the current
// run to just the good glyphs. Set the currentRun to the
// second half. This allows us to have a long run of type kCharacterRunMultipleSimpleChars.
currentRun = [currentRun splitBeforeIndex:i];
[newRuns addObject:currentRun];
// Now currentRun has a bad first glyph.
}
i = [currentRun indexOfFirstMissingGlyph];
}
if (i >= 0) {
isOk = CTFontGetGlyphsForCharacters((CTFontRef)currentRun.fontInfo.font,
[self codes],
[self glyphs],
currentRun.range.length);
} else {
break;
}
}
}
- (NSArray *)runsWithGlyphs
{
if (!range_.length) {
return nil;
}
NSMutableArray *newRuns = [NSMutableArray array];
[self appendRunsWithGlyphsToArray:newRuns];
return newRuns;
}
- (BOOL)isCompatibleWith:(CharacterRun *)otherRun {
return (otherRun.runType != kCharacterRunSingleCharWithCombiningMarks &&
runType_ != kCharacterRunSingleCharWithCombiningMarks &&
fontInfo_ == otherRun.fontInfo &&
color_ == otherRun.color &&
fakeBold_ == otherRun.fakeBold &&
antiAlias_ == otherRun.antiAlias);
}
- (void)appendCode:(unichar)code withAdvance:(CGFloat)advance {
[sharedData_ growAllocation:&range_ by:1];
unichar *codes = [self codes];
CGSize *advances = [self advances];
codes[range_.length - 1] = code;
advances[range_.length - 1] = CGSizeMake(advance, 0);
}
- (void)appendCodesFromString:(NSString *)string withAdvance:(CGFloat)advance {
int offset = range_.length;
int length = [string length];
[sharedData_ growAllocation:&range_ by:length];
[string getCharacters:[self codes] + offset
range:NSMakeRange(0, length)];
CGSize *advances = [self advances];
advances[offset] = CGSizeMake(advance, 0);
}
- (void)clearRange {
range_ = NSMakeRange(0, 0);
}
@end