SameBoy | Accurate GB/GBC emulator |
| download: https://git.y1.nz/archives/sameboy.tar.gz | |
| README | Files | Log | Refs | LICENSE |
HexFiend/HFRepresenterStringEncodingTextView.m
1 //
2 // HFRepresenterStringEncodingTextView.m
3 // HexFiend_2
4 //
5 // Copyright 2007 ridiculous_fish. All rights reserved.
6 //
7
8 #import <HexFiend/HFRepresenterStringEncodingTextView.h>
9 #import <HexFiend/HFRepresenterTextView_Internal.h>
10 #include <malloc/malloc.h>
11
12 @implementation HFRepresenterStringEncodingTextView
13
14 static NSString *copy1CharStringForByteValue(unsigned long long byteValue, NSUInteger bytesPerChar, NSStringEncoding encoding) {
15 NSString *result = nil;
16 unsigned char bytes[sizeof byteValue];
17 /* If we are little endian, then the bytesPerChar doesn't matter, because it will all come out the same. If we are big endian, then it does matter. */
18 #if ! __BIG_ENDIAN__
19 *(unsigned long long *)bytes = byteValue;
20 #else
21 if (bytesPerChar == sizeof(uint8_t)) {
22 *(uint8_t *)bytes = (uint8_t)byteValue;
23 } else if (bytesPerChar == sizeof(uint16_t)) {
24 *(uint16_t *)bytes = (uint16_t)byteValue;
25 } else if (bytesPerChar == sizeof(uint32_t)) {
26 *(uint32_t *)bytes = (uint32_t)byteValue;
27 } else if (bytesPerChar == sizeof(uint64_t)) {
28 *(uint64_t *)bytes = (uint64_t)byteValue;
29 } else {
30 [NSException raise:NSInvalidArgumentException format:@"Unsupported bytesPerChar of %u", bytesPerChar];
31 }
32 #endif
33
34 /* ASCII is mishandled :( */
35 BOOL encodingOK = YES;
36 if (encoding == NSASCIIStringEncoding && bytesPerChar == 1 && bytes[0] > 0x7F) {
37 encodingOK = NO;
38 }
39
40
41
42 /* Now create a string from these bytes */
43 if (encodingOK) {
44 result = [[NSString alloc] initWithBytes:bytes length:bytesPerChar encoding:encoding];
45
46 if ([result length] > 1) {
47 /* Try precomposing it */
48 NSString *temp = [[result precomposedStringWithCompatibilityMapping] copy];
49 [result release];
50 result = temp;
51 }
52
53 /* Ensure it has exactly one character */
54 if ([result length] != 1) {
55 [result release];
56 result = nil;
57 }
58 }
59
60 /* All done */
61 return result;
62 }
63
64 static BOOL getGlyphs(CGGlyph *glyphs, NSString *string, NSFont *inputFont) {
65 NSUInteger length = [string length];
66 HFASSERT(inputFont != nil);
67 NEW_ARRAY(UniChar, chars, length);
68 [string getCharacters:chars range:NSMakeRange(0, length)];
69 bool result = CTFontGetGlyphsForCharacters((CTFontRef)inputFont, chars, glyphs, length);
70 /* A NO return means some or all characters were not mapped. This is OK. We'll use the replacement glyph. Unless we're calculating the replacement glyph! Hmm...maybe we should have a series of replacement glyphs that we try? */
71
72 ////////////////////////
73 // Workaround for a Mavericks bug. Still present as of 10.9.5
74 // TODO: Hmm, still? Should look into this again, either it's not a bug or Apple needs a poke.
75 if(!result) for(NSUInteger i = 0; i < length; i+=15) {
76 CFIndex x = length-i;
77 if(x > 15) x = 15;
78 result = CTFontGetGlyphsForCharacters((CTFontRef)inputFont, chars+i, glyphs+i, x);
79 if(!result) break;
80 }
81 ////////////////////////
82
83 FREE_ARRAY(chars);
84 return result;
85 }
86
87 static void generateGlyphs(NSFont *baseFont, NSMutableArray *fonts, struct HFGlyph_t *outGlyphs, NSInteger bytesPerChar, NSStringEncoding encoding, const NSUInteger *charactersToLoad, NSUInteger charactersToLoadCount, CGFloat *outMaxAdvance) {
88 /* If the caller wants the advance, initialize it to 0 */
89 if (outMaxAdvance) *outMaxAdvance = 0;
90
91 /* Invalid glyph marker */
92 const struct HFGlyph_t invalidGlyph = {.fontIndex = kHFGlyphFontIndexInvalid, .glyph = -1};
93
94 NSCharacterSet *coveredSet = [baseFont coveredCharacterSet];
95 NSMutableString *coveredGlyphFetchingString = [[NSMutableString alloc] init];
96 NSMutableIndexSet *coveredGlyphIndexes = [[NSMutableIndexSet alloc] init];
97 NSMutableString *substitutionFontsGlyphFetchingString = [[NSMutableString alloc] init];
98 NSMutableIndexSet *substitutionGlyphIndexes = [[NSMutableIndexSet alloc] init];
99
100 /* Loop over all the characters, appending them to our glyph fetching string */
101 NSUInteger idx;
102 for (idx = 0; idx < charactersToLoadCount; idx++) {
103 NSString *string = copy1CharStringForByteValue(charactersToLoad[idx], bytesPerChar, encoding);
104 if (string == nil) {
105 /* This byte value is not represented in this char set (e.g. upper 128 in ASCII) */
106 outGlyphs[idx] = invalidGlyph;
107 } else {
108 if ([coveredSet characterIsMember:[string characterAtIndex:0]]) {
109 /* It's covered by our base font */
110 [coveredGlyphFetchingString appendString:string];
111 [coveredGlyphIndexes addIndex:idx];
112 } else {
113 /* Maybe there's a substitution font */
114 [substitutionFontsGlyphFetchingString appendString:string];
115 [substitutionGlyphIndexes addIndex:idx];
116 }
117 }
118 [string release];
119 }
120
121
122 /* Fetch the non-substitute glyphs */
123 {
124 NEW_ARRAY(CGGlyph, cgglyphs, [coveredGlyphFetchingString length]);
125 BOOL success = getGlyphs(cgglyphs, coveredGlyphFetchingString, baseFont);
126 HFASSERT(success == YES);
127 NSUInteger numGlyphs = [coveredGlyphFetchingString length];
128
129 /* Fill in our glyphs array */
130 NSUInteger coveredGlyphIdx = [coveredGlyphIndexes firstIndex];
131 for (NSUInteger i=0; i < numGlyphs; i++) {
132 outGlyphs[coveredGlyphIdx] = (struct HFGlyph_t){.fontIndex = 0, .glyph = cgglyphs[i]};
133 coveredGlyphIdx = [coveredGlyphIndexes indexGreaterThanIndex:coveredGlyphIdx];
134
135 /* Record the advancement. Note that this may be more efficient to do in bulk. */
136 if (outMaxAdvance) *outMaxAdvance = HFMax(*outMaxAdvance, [baseFont advancementForGlyph:cgglyphs[i]].width);
137
138 }
139 HFASSERT(coveredGlyphIdx == NSNotFound); //we must have exhausted the table
140 FREE_ARRAY(cgglyphs);
141 }
142
143 /* Now do substitution glyphs. */
144 {
145 NSUInteger substitutionGlyphIndex = [substitutionGlyphIndexes firstIndex], numSubstitutionChars = [substitutionFontsGlyphFetchingString length];
146 for (NSUInteger i=0; i < numSubstitutionChars; i++) {
147 CTFontRef substitutionFont = CTFontCreateForString((CTFontRef)baseFont, (CFStringRef)substitutionFontsGlyphFetchingString, CFRangeMake(i, 1));
148 if (substitutionFont) {
149 /* We have a font for this string */
150 CGGlyph glyph;
151 unichar c = [substitutionFontsGlyphFetchingString characterAtIndex:i];
152 NSString *substring = [[NSString alloc] initWithCharacters:&c length:1];
153 BOOL success = getGlyphs(&glyph, substring, (NSFont *)substitutionFont);
154 [substring release];
155
156 if (! success) {
157 /* Turns out there wasn't a glyph like we thought there would be, so set an invalid glyph marker */
158 outGlyphs[substitutionGlyphIndex] = invalidGlyph;
159 } else {
160 /* Find the index in fonts. If none, add to it. */
161 HFASSERT(fonts != nil);
162 NSUInteger fontIndex = [fonts indexOfObject:(id)substitutionFont];
163 if (fontIndex == NSNotFound) {
164 [fonts addObject:(id)substitutionFont];
165 fontIndex = [fonts count] - 1;
166 }
167
168 /* Now make the glyph */
169 HFASSERT(fontIndex < UINT16_MAX);
170 outGlyphs[substitutionGlyphIndex] = (struct HFGlyph_t){.fontIndex = (uint16_t)fontIndex, .glyph = glyph};
171 }
172
173 /* We're done with this */
174 CFRelease(substitutionFont);
175
176 }
177 substitutionGlyphIndex = [substitutionGlyphIndexes indexGreaterThanIndex:substitutionGlyphIndex];
178 }
179 }
180
181 [coveredGlyphFetchingString release];
182 [coveredGlyphIndexes release];
183 [substitutionFontsGlyphFetchingString release];
184 [substitutionGlyphIndexes release];
185 }
186
187 static int compareGlyphFontIndexes(const void *p1, const void *p2) {
188 const struct HFGlyph_t *g1 = p1, *g2 = p2;
189 if (g1->fontIndex != g2->fontIndex) {
190 /* Prefer to sort by font index */
191 return (g1->fontIndex > g2->fontIndex) - (g2->fontIndex > g1->fontIndex);
192 } else {
193 /* If they have equal font indexes, sort by glyph value */
194 return (g1->glyph > g2->glyph) - (g2->glyph > g1->glyph);
195 }
196 }
197
198 - (void)threadedPrecacheGlyphs:(const struct HFGlyph_t *)glyphs withFonts:(NSArray *)localFonts count:(NSUInteger)count {
199 /* This method draws glyphs anywhere, so that they get cached by CG and drawing them a second time can be fast. */
200 NSUInteger i, validGlyphCount;
201
202 /* We can use 0 advances */
203 NEW_ARRAY(CGSize, advances, count);
204 bzero(advances, count * sizeof *advances);
205
206 /* Make a local copy of the glyphs, and sort them according to their font index so that we can draw them with the fewest runs. */
207 NEW_ARRAY(struct HFGlyph_t, validGlyphs, count);
208
209 validGlyphCount = 0;
210 for (i=0; i < count; i++) {
211 if (glyphs[i].glyph <= kCGGlyphMax && glyphs[i].fontIndex != kHFGlyphFontIndexInvalid) {
212 validGlyphs[validGlyphCount++] = glyphs[i];
213 }
214 }
215 qsort(validGlyphs, validGlyphCount, sizeof *validGlyphs, compareGlyphFontIndexes);
216
217 /* Remove duplicate glyphs */
218 NSUInteger trailing = 0;
219 struct HFGlyph_t lastGlyph = {.glyph = kCGFontIndexInvalid, .fontIndex = kHFGlyphFontIndexInvalid};
220 for (i=0; i < validGlyphCount; i++) {
221 if (! HFGlyphEqualsGlyph(lastGlyph, validGlyphs[i])) {
222 lastGlyph = validGlyphs[i];
223 validGlyphs[trailing++] = lastGlyph;
224 }
225 }
226 validGlyphCount = trailing;
227
228 /* Draw the glyphs in runs */
229 NEW_ARRAY(CGGlyph, cgglyphs, count);
230 NSImage *glyphDrawingImage = [[NSImage alloc] initWithSize:NSMakeSize(100, 100)];
231 [glyphDrawingImage lockFocus];
232 CGContextRef ctx = [[NSGraphicsContext currentContext] graphicsPort];
233 HFGlyphFontIndex runFontIndex = -1;
234 NSUInteger runLength = 0;
235 for (i=0; i <= validGlyphCount; i++) {
236 if (i == validGlyphCount || validGlyphs[i].fontIndex != runFontIndex) {
237 /* End the current run */
238 if (runLength > 0) {
239 NSLog(@"Drawing with %@", [localFonts[runFontIndex] screenFont]);
240 [[localFonts[runFontIndex] screenFont] set];
241 CGContextSetTextPosition(ctx, 0, 50);
242 CGContextShowGlyphsWithAdvances(ctx, cgglyphs, advances, runLength);
243 }
244 NSLog(@"Drew a run of length %lu", (unsigned long)runLength);
245 runLength = 0;
246 if (i < validGlyphCount) runFontIndex = validGlyphs[i].fontIndex;
247 }
248 if (i < validGlyphCount) {
249 /* Append to the current run */
250 cgglyphs[runLength++] = validGlyphs[i].glyph;
251 }
252 }
253
254 /* All done */
255 [glyphDrawingImage unlockFocus];
256 [glyphDrawingImage release];
257 FREE_ARRAY(advances);
258 FREE_ARRAY(validGlyphs);
259 FREE_ARRAY(cgglyphs);
260 }
261
262 - (void)threadedLoadGlyphs:(id)unused {
263 /* Note that this is running on a background thread */
264 USE(unused);
265
266 /* Do some things under the lock. Someone else may wish to read fonts, and we're going to write to it, so make a local copy. Also figure out what characters to load. */
267 NSMutableArray *localFonts;
268 NSIndexSet *charactersToLoad;
269 OSSpinLockLock(&glyphLoadLock);
270 localFonts = [fonts mutableCopy];
271 charactersToLoad = requestedCharacters;
272 /* Set requestedCharacters to nil so that the caller knows we aren't going to check again, and will have to re-invoke us. */
273 requestedCharacters = nil;
274 OSSpinLockUnlock(&glyphLoadLock);
275
276 /* The base font is the first font */
277 NSFont *font = localFonts[0];
278
279 NSUInteger charVal, glyphIdx, charCount = [charactersToLoad count];
280 NEW_ARRAY(struct HFGlyph_t, glyphs, charCount);
281
282 /* Now generate our glyphs */
283 NEW_ARRAY(NSUInteger, characters, charCount);
284 [charactersToLoad getIndexes:characters maxCount:charCount inIndexRange:NULL];
285 generateGlyphs(font, localFonts, glyphs, bytesPerChar, encoding, characters, charCount, NULL);
286 FREE_ARRAY(characters);
287
288 /* The first time we draw glyphs, it's slow, so pre-cache them by drawing them now. */
289 // This was disabled because it blows up the CG glyph cache
290 // [self threadedPrecacheGlyphs:glyphs withFonts:localFonts count:charCount];
291
292 /* Replace fonts. Do this before we insert into the glyph trie, because the glyph trie references fonts that we're just now putting in the fonts array. */
293 id oldFonts;
294 OSSpinLockLock(&glyphLoadLock);
295 oldFonts = fonts;
296 fonts = localFonts;
297 OSSpinLockUnlock(&glyphLoadLock);
298 [oldFonts release];
299
300 /* Now insert all of the glyphs into the glyph trie */
301 glyphIdx = 0;
302 for (charVal = [charactersToLoad firstIndex]; charVal != NSNotFound; charVal = [charactersToLoad indexGreaterThanIndex:charVal]) {
303 HFGlyphTrieInsert(&glyphTable, charVal, glyphs[glyphIdx++]);
304 }
305 FREE_ARRAY(glyphs);
306
307 /* Trigger a redisplay */
308 [self performSelectorOnMainThread:@selector(triggerRedisplay:) withObject:nil waitUntilDone:NO];
309
310 /* All done. We inherited the retain on requestedCharacters, so release it. */
311 [charactersToLoad release];
312 }
313
314 - (void)triggerRedisplay:unused {
315 USE(unused);
316 [self setNeedsDisplay:YES];
317 }
318
319 - (void)beginLoadGlyphsForCharacters:(NSIndexSet *)charactersToLoad {
320 /* Create the operation (and maybe the operation queue itself) */
321 if (! glyphLoader) {
322 glyphLoader = [[NSOperationQueue alloc] init];
323 [glyphLoader setMaxConcurrentOperationCount:1];
324 }
325 if (! fonts) {
326 NSFont *font = [self font];
327 fonts = [[NSMutableArray alloc] initWithObjects:&font count:1];
328 }
329
330 BOOL needToStartOperation;
331 OSSpinLockLock(&glyphLoadLock);
332 if (requestedCharacters) {
333 /* There's a pending request, so just add to it */
334 [requestedCharacters addIndexes:charactersToLoad];
335 needToStartOperation = NO;
336 } else {
337 /* There's no pending request, so we will create one */
338 requestedCharacters = [charactersToLoad mutableCopy];
339 needToStartOperation = YES;
340 }
341 OSSpinLockUnlock(&glyphLoadLock);
342
343 if (needToStartOperation) {
344 NSInvocationOperation *op = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(threadedLoadGlyphs:) object:charactersToLoad];
345 [glyphLoader addOperation:op];
346 [op release];
347 }
348 }
349
350 - (void)dealloc {
351 HFGlyphTreeFree(&glyphTable);
352 [fonts release];
353 [super dealloc];
354 }
355
356 - (void)staleTieredProperties {
357 tier1DataIsStale = YES;
358 /* We have to free the glyph table */
359 requestedCancel = YES;
360 [glyphLoader waitUntilAllOperationsAreFinished];
361 requestedCancel = NO;
362 HFGlyphTreeFree(&glyphTable);
363 HFGlyphTrieInitialize(&glyphTable, bytesPerChar);
364 [fonts release];
365 fonts = nil;
366 [fontCache release];
367 fontCache = nil;
368 }
369
370 - (void)setFont:(NSFont *)font {
371 [self staleTieredProperties];
372 /* fonts is preloaded with our one font */
373 if (! fonts) fonts = [[NSMutableArray alloc] init];
374 [fonts addObject:font];
375 [super setFont:font];
376 }
377
378 - (instancetype)initWithCoder:(NSCoder *)coder {
379 HFASSERT([coder allowsKeyedCoding]);
380 self = [super initWithCoder:coder];
381 encoding = (NSStringEncoding)[coder decodeInt64ForKey:@"HFStringEncoding"];
382 bytesPerChar = HFStringEncodingCharacterLength(encoding);
383 [self staleTieredProperties];
384 return self;
385 }
386
387 - (instancetype)initWithFrame:(NSRect)frameRect {
388 self = [super initWithFrame:frameRect];
389 encoding = NSMacOSRomanStringEncoding;
390 bytesPerChar = HFStringEncodingCharacterLength(encoding);
391 [self staleTieredProperties];
392 return self;
393 }
394
395 - (void)encodeWithCoder:(NSCoder *)coder {
396 HFASSERT([coder allowsKeyedCoding]);
397 [super encodeWithCoder:coder];
398 [coder encodeInt64:encoding forKey:@"HFStringEncoding"];
399 }
400
401 - (NSStringEncoding)encoding {
402 return encoding;
403 }
404
405 - (void)setEncoding:(NSStringEncoding)val {
406 if (encoding != val) {
407 /* Our glyph table is now stale. Call this first to ensure our background operation is complete. */
408 [self staleTieredProperties];
409
410 /* Store the new encoding. */
411 encoding = val;
412
413 /* Compute bytes per character */
414 bytesPerChar = HFStringEncodingCharacterLength(encoding);
415 HFASSERT(bytesPerChar > 0);
416
417 /* Ensure the tree knows about the new bytes per character */
418 HFGlyphTrieInitialize(&glyphTable, bytesPerChar);
419
420 /* Redraw ourselves with our new glyphs */
421 [self setNeedsDisplay:YES];
422 }
423 }
424
425 - (void)loadTier1Data {
426 NSFont *font = [self font];
427
428 /* Use the max advance as the glyph advance */
429 glyphAdvancement = HFCeil([font maximumAdvancement].width);
430
431 /* Generate replacementGlyph */
432 CGGlyph glyph[1];
433 BOOL foundReplacement = NO;
434 if (! foundReplacement) foundReplacement = getGlyphs(glyph, @".", font);
435 if (! foundReplacement) foundReplacement = getGlyphs(glyph, @"*", font);
436 if (! foundReplacement) foundReplacement = getGlyphs(glyph, @"!", font);
437 if (! foundReplacement) {
438 /* Really we should just fall back to another font in this case */
439 [NSException raise:NSInternalInconsistencyException format:@"Unable to find replacement glyph for font %@", font];
440 }
441 replacementGlyph.fontIndex = 0;
442 replacementGlyph.glyph = glyph[0];
443
444 /* We're no longer stale */
445 tier1DataIsStale = NO;
446 }
447
448 /* Override of base class method for font substitution */
449 - (NSFont *)fontAtSubstitutionIndex:(uint16_t)idx {
450 HFASSERT(idx != kHFGlyphFontIndexInvalid);
451 if (idx >= [fontCache count]) {
452 /* Our font cache is out of date. Take the lock and update the cache. */
453 NSArray *newFonts = nil;
454 OSSpinLockLock(&glyphLoadLock);
455 HFASSERT(idx < [fonts count]);
456 newFonts = [fonts copy];
457 OSSpinLockUnlock(&glyphLoadLock);
458
459 /* Store the new cache */
460 [fontCache release];
461 fontCache = newFonts;
462
463 /* Now our cache should be up to date */
464 HFASSERT(idx < [fontCache count]);
465 }
466 return fontCache[idx];
467 }
468
469 /* Override of base class method in case we are 16 bit */
470 - (NSUInteger)bytesPerCharacter {
471 return bytesPerChar;
472 }
473
474 - (void)extractGlyphsForBytes:(const unsigned char *)bytes count:(NSUInteger)numBytes offsetIntoLine:(NSUInteger)offsetIntoLine intoArray:(struct HFGlyph_t *)glyphs advances:(CGSize *)advances resultingGlyphCount:(NSUInteger *)resultGlyphCount {
475 HFASSERT(bytes != NULL);
476 HFASSERT(glyphs != NULL);
477 HFASSERT(resultGlyphCount != NULL);
478 HFASSERT(advances != NULL);
479 USE(offsetIntoLine);
480
481 /* Ensure we have advance, etc. before trying to use it */
482 if (tier1DataIsStale) [self loadTier1Data];
483
484 CGSize advance = CGSizeMake(glyphAdvancement, 0);
485 NSMutableIndexSet *charactersToLoad = nil; //note: in UTF-32 this may have to move to an NSSet
486
487 const uint8_t localBytesPerChar = bytesPerChar;
488 NSUInteger charIndex, numChars = numBytes / localBytesPerChar, byteIndex = 0;
489 for (charIndex = 0; charIndex < numChars; charIndex++) {
490 NSUInteger character = -1;
491 if (localBytesPerChar == 1) {
492 character = *(const uint8_t *)(bytes + byteIndex);
493 } else if (localBytesPerChar == 2) {
494 character = *(const uint16_t *)(bytes + byteIndex);
495 } else if (localBytesPerChar == 4) {
496 character = *(const uint32_t *)(bytes + byteIndex);
497 }
498
499 struct HFGlyph_t glyph = HFGlyphTrieGet(&glyphTable, character);
500 if (glyph.glyph == 0 && glyph.fontIndex == 0) {
501 /* Unloaded glyph, so load it */
502 if (! charactersToLoad) charactersToLoad = [[NSMutableIndexSet alloc] init];
503 [charactersToLoad addIndex:character];
504 glyph = replacementGlyph;
505 } else if (glyph.glyph == (uint16_t)-1 && glyph.fontIndex == kHFGlyphFontIndexInvalid) {
506 /* Missing glyph, so ignore it */
507 glyph = replacementGlyph;
508 } else {
509 /* Valid glyph */
510 }
511
512 HFASSERT(glyph.fontIndex != kHFGlyphFontIndexInvalid);
513
514 advances[charIndex] = advance;
515 glyphs[charIndex] = glyph;
516 byteIndex += localBytesPerChar;
517 }
518 *resultGlyphCount = numChars;
519
520 if (charactersToLoad) {
521 [self beginLoadGlyphsForCharacters:charactersToLoad];
522 [charactersToLoad release];
523 }
524 }
525
526 - (CGFloat)advancePerCharacter {
527 /* The glyph advancement is determined by our glyph table */
528 if (tier1DataIsStale) [self loadTier1Data];
529 return glyphAdvancement;
530 }
531
532 - (CGFloat)advanceBetweenColumns {
533 return 0; //don't have any space between columns
534 }
535
536 - (NSUInteger)maximumGlyphCountForByteCount:(NSUInteger)byteCount {
537 return byteCount / [self bytesPerCharacter];
538 }
539
540 @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.