3 Copyright 2004, Apple, Inc. All rights reserved.
6 #import "WebTextRenderer.h"
8 #import <ApplicationServices/ApplicationServices.h>
9 #import <Cocoa/Cocoa.h>
11 #import <AppKit/NSFont_Private.h>
12 #import <CoreGraphics/CoreGraphicsPrivate.h>
13 #import <QD/ATSUnicodePriv.h>
15 #import <WebCore/WebCoreUnicode.h>
17 #import <WebKit/WebGlyphBuffer.h>
18 #import <WebKit/WebGraphicsBridge.h>
19 #import <WebKit/WebKitLogging.h>
20 #import <WebKit/WebNSObjectExtras.h>
21 #import <WebKit/WebTextRendererFactory.h>
22 #import <WebKit/WebUnicode.h>
23 #import <WebKit/WebViewPrivate.h>
27 #import <unicode/uchar.h>
28 #import <unicode/unorm.h>
30 // FIXME: FATAL_ALWAYS seems like a bad idea; lets stop using it.
32 // SPI from other frameworks.
34 @interface NSLanguage : NSObject
35 + (NSLanguage *)defaultLanguage;
38 @interface NSFont (WebPrivate)
39 - (ATSUFontID)_atsFontID;
40 - (CGFontRef)_backingCGSFont;
41 // Private method to find a font for a character.
42 + (NSFont *) findFontLike:(NSFont *)aFont forCharacter:(UInt32)c inLanguage:(NSLanguage *) language;
43 + (NSFont *) findFontLike:(NSFont *)aFont forString:(NSString *)string withRange:(NSRange)range inLanguage:(NSLanguage *) language;
44 - (NSGlyph)_defaultGlyphForChar:(unichar)uu;
49 #define NO_BREAK_SPACE 0x00A0
50 #define ZERO_WIDTH_SPACE 0x200B
52 #define ROUND_TO_INT(x) (int)((x)+.5)
54 // Lose precision beyond 1000ths place. This is to work around an apparent
55 // bug in CoreGraphics where there seem to be small errors to some metrics.
56 #define CEIL_TO_INT(x) ((int)(x + 0.999)) /* ((int)(x + 1.0 - FLT_EPSILON)) */
58 // MAX_GLYPH_EXPANSION is the maximum numbers of glyphs that may be
59 // use to represent a single Unicode code point.
60 #define MAX_GLYPH_EXPANSION 4
61 #define LOCAL_BUFFER_SIZE 2048
64 #define INITIAL_BLOCK_SIZE 0x200
66 // Get additional blocks of glyphs and widths in bigger chunks.
67 // This will typically be for other character sets.
68 #define INCREMENTAL_BLOCK_SIZE 0x400
70 #define UNINITIALIZED_GLYPH_WIDTH 65535
72 #define ATSFontRefFromNSFont(font) (FMGetATSFontRefFromFont((FMFont)[font _atsFontID]))
74 #define SMALLCAPS_FONTSIZE_MULTIPLIER 0.7
75 #define INVALID_WIDTH -(__FLT_MAX__)
77 #if !defined(ScaleEmToUnits)
78 #define CONTEXT_DPI (72.0)
80 #define ScaleEmToUnits(X, U_PER_EM) (X * ((1.0 * CONTEXT_DPI) / (CONTEXT_DPI * U_PER_EM)))
84 typedef float WebGlyphWidth;
85 typedef UInt32 UnicodeChar;
92 ATSGlyphRef startRange;
111 struct UnicodeGlyphMap {
112 UnicodeChar startRange;
113 UnicodeChar endRange;
114 UnicodeGlyphMap *next;
118 struct SubstituteFontWidthMap {
123 struct CharacterWidthIterator
125 WebTextRenderer *renderer;
126 const WebCoreTextRun *run;
127 const WebCoreTextStyle *style;
128 unsigned currentCharacter;
136 @interface WebTextRenderer (WebInternal)
138 - (NSFont *)_substituteFontForCharacters: (const unichar *)characters length: (int)numCharacters families: (NSString **)families;
140 - (WidthMap *)_extendGlyphToWidthMapToInclude:(ATSGlyphRef)glyphID font:(NSFont *)font;
141 - (ATSGlyphRef)_extendCharacterToGlyphMapToInclude:(UniChar) c;
142 - (ATSGlyphRef)_extendUnicodeCharacterToGlyphMapToInclude: (UnicodeChar)c;
143 - (void)_updateGlyphEntryForCharacter: (UniChar)c glyphID: (ATSGlyphRef)glyphID font: (NSFont *)substituteFont;
145 - (float)_floatWidthForRun:(const WebCoreTextRun *)run style:(const WebCoreTextStyle *)style widths:(float *)widthBuffer fonts:(NSFont **)fontBuffer glyphs:(CGGlyph *)glyphBuffer startPosition:(float *)startPosition numGlyphs:(int *)_numGlyphs;
148 - (float)_CG_floatWidthForRun:(const WebCoreTextRun *)run style:(const WebCoreTextStyle *)style widths: (float *)widthBuffer fonts: (NSFont **)fontBuffer glyphs: (CGGlyph *)glyphBuffer startPosition:(float *)startPosition numGlyphs: (int *)_numGlyphs;
149 - (float)_ATSU_floatWidthForRun:(const WebCoreTextRun *)run style:(const WebCoreTextStyle *)style;
152 - (void)_CG_drawRun:(const WebCoreTextRun *)run style:(const WebCoreTextStyle *)style geometry:(const WebCoreTextGeometry *)geometry;
153 - (void)_ATSU_drawRun:(const WebCoreTextRun *)run style:(const WebCoreTextStyle *)style geometry:(const WebCoreTextGeometry *)geometry;
155 // Selection point detection in runs.
156 - (int)_CG_pointToOffset:(const WebCoreTextRun *)run style:(const WebCoreTextStyle *)style position:(int)x reversed:(BOOL)reversed includePartialGlyphs:(BOOL)includePartialGlyphs;
157 - (int)_ATSU_pointToOffset:(const WebCoreTextRun *)run style:(const WebCoreTextStyle *)style position:(int)x reversed:(BOOL)reversed includePartialGlyphs:(BOOL)includePartialGlyphs;
159 // Drawing highlight for runs.
160 - (void)_CG_drawHighlightForRun:(const WebCoreTextRun *)run style:(const WebCoreTextStyle *)style geometry:(const WebCoreTextGeometry *)geometry;
161 - (void)_ATSU_drawHighlightForRun:(const WebCoreTextRun *)run style:(const WebCoreTextStyle *)style geometry:(const WebCoreTextGeometry *)geometry;
166 - (void)_setIsSmallCapsRenderer:(BOOL)flag;
167 - (BOOL)_isSmallCapsRenderer;
168 - (WebTextRenderer *)_smallCapsRenderer;
169 - (NSFont *)_smallCapsFont;
174 // Character property functions.
176 static inline BOOL isSpace(UniChar c)
178 return c == SPACE || c == '\n' || c == NO_BREAK_SPACE;
181 static const uint8_t isRoundingHackCharacterTable[0x100] = {
182 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1 /*\n*/, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
183 1 /*space*/, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1 /*-*/, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1 /*?*/,
184 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
185 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
186 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
187 1 /*no-break space*/, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
188 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
189 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
192 static inline BOOL isRoundingHackCharacter(UniChar c)
194 return (c & ~0xFF) == 0 && isRoundingHackCharacterTable[c];
197 // Map utility functions
198 static void freeWidthMap(WidthMap *map);
199 static void freeGlyphMap(GlyphMap *map);
200 static void freeUnicodeGlyphMap(UnicodeGlyphMap *map);
201 static inline ATSGlyphRef glyphForUnicodeCharacter (UnicodeGlyphMap *map, UnicodeChar c, NSFont **font);
202 static inline SubstituteFontWidthMap *mapForSubstituteFont(WebTextRenderer *renderer, NSFont *font);
203 static inline ATSGlyphRef glyphForCharacter (GlyphMap *map, UniChar c, NSFont **font);
204 static inline SubstituteFontWidthMap *mapForSubstituteFont(WebTextRenderer *renderer, NSFont *font);
205 static inline WebGlyphWidth widthFromMap (WebTextRenderer *renderer, WidthMap *map, ATSGlyphRef glyph, NSFont *font);
206 static inline WebGlyphWidth widthForGlyph (WebTextRenderer *renderer, ATSGlyphRef glyph, NSFont *font);
208 #if BUILDING_ON_PANTHER
210 static WebGlyphWidth getUncachedWidth(WebTextRenderer *renderer, WidthMap *map, ATSGlyphRef glyph, NSFont *font)
215 font = renderer->font;
217 if (!CGFontGetGlyphScaledAdvances ([font _backingCGSFont], &glyph, 1, &width, [font pointSize])) {
218 ERROR ("Unable to cache glyph widths for %@ %f", [font displayName], [font pointSize]);
227 static inline CGFontRenderingMode _AppkitGetCGRenderingMode(NSFont *font) {
228 switch ([font renderingMode]) {
229 case NSFontIntegerAdvancementsRenderingMode: return kCGFontRenderingMode1BitPixelAligned;
230 case NSFontAntialiasedIntegerAdvancementsRenderingMode: return kCGFontRenderingModeAntialiasedPixelAligned;
231 default: return kCGFontRenderingModeAntialiased;
235 static WebGlyphWidth getUncachedWidth(WebTextRenderer *renderer, WidthMap *map, ATSGlyphRef glyph, NSFont *font)
242 font = renderer->font;
244 pointSize = [font pointSize];
245 m = CGAffineTransformMakeScale(pointSize, pointSize);
246 if (!CGFontGetGlyphTransformedAdvances([font _backingCGSFont], &m, _AppkitGetCGRenderingMode(font), &glyph, 1, &advance)) {
247 ERROR ("Unable to cache glyph widths for %@ %f", [font displayName], pointSize);
251 return advance.width;
256 static inline WebGlyphWidth widthFromMap (WebTextRenderer *renderer, WidthMap *map, ATSGlyphRef glyph, NSFont *font)
258 WebGlyphWidth width = UNINITIALIZED_GLYPH_WIDTH;
262 map = [renderer _extendGlyphToWidthMapToInclude: glyph font:font];
264 if (glyph >= map->startRange && glyph <= map->endRange){
265 width = map->widths[glyph - map->startRange].width;
266 if (width == UNINITIALIZED_GLYPH_WIDTH){
267 width = getUncachedWidth (renderer, map, glyph, font);
268 map->widths[glyph - map->startRange].width = width;
280 static inline WebGlyphWidth widthForGlyph (WebTextRenderer *renderer, ATSGlyphRef glyph, NSFont *font)
284 if (font && font != renderer->font)
285 map = mapForSubstituteFont(renderer, font)->map;
287 map = renderer->glyphToWidthMap;
289 return widthFromMap (renderer, map, glyph, font);
292 // Iterator functions
293 static void initializeCharacterWidthIterator (CharacterWidthIterator *iterator, WebTextRenderer *renderer, const WebCoreTextRun *run , const WebCoreTextStyle *style);
294 static float widthForNextCharacter (CharacterWidthIterator *iterator, ATSGlyphRef *glyphUsed, NSFont **fontUsed);
298 static BOOL fillStyleWithAttributes(ATSUStyle style, NSFont *theFont);
299 static BOOL shouldUseATSU(const WebCoreTextRun *run);
300 static NSString *pathFromFont(NSFont *font);
304 static CFCharacterSetRef nonBaseChars = NULL;
305 static BOOL bufferTextDrawing = NO;
306 static BOOL alwaysUseATSU = NO;
309 @implementation WebTextRenderer
311 + (NSString *)webFallbackFontFamily
313 static NSString *webFallbackFontFamily = nil;
314 if (!webFallbackFontFamily)
315 webFallbackFontFamily = [[[NSFont systemFontOfSize:16.0] familyName] retain];
316 return webFallbackFontFamily;
319 + (BOOL)shouldBufferTextDrawing
321 return bufferTextDrawing;
326 nonBaseChars = CFCharacterSetGetPredefined(kCFCharacterSetNonBase);
327 bufferTextDrawing = [[[NSUserDefaults standardUserDefaults] stringForKey:@"BufferTextDrawing"] isEqual: @"YES"];
330 - initWithFont:(NSFont *)f usingPrinterFont:(BOOL)p
334 // Quartz can only handle fonts with these glyph packings. Other packings have
336 if ([f glyphPacking] != NSNativeShortGlyphPacking &&
337 [f glyphPacking] != NSTwoByteGlyphPacking) {
338 // Apparantly there are many deprecated fonts out there with unsupported packing types.
339 // Log and use fallback font.
340 // This change fixes the many crashes reported in 3782533. Most likely, the
341 // problem is encountered when people upgrade from OS 9, or have OS 9
342 // fonts installed on OS X.
343 NSLog (@"%s:%d Unable to use deprecated font %@ %f, using system font instead", __FILE__, __LINE__, [f displayName], [f pointSize]);
344 f = [NSFont systemFontOfSize:[f pointSize]];
347 maxSubstituteFontWidthMaps = NUM_SUBSTITUTE_FONT_MAPS;
348 substituteFontWidthMaps = calloc (1, maxSubstituteFontWidthMaps * sizeof(SubstituteFontWidthMap));
349 font = [(p ? [f printerFont] : [f screenFont]) retain];
350 usingPrinterFont = p;
352 bool failedSetup = false;
353 if (![self _setupFont]){
354 // Ack! Something very bad happened, like a corrupt font. Try
355 // looking for an alternate 'base' font for this renderer.
357 // Special case hack to use "Times New Roman" in place of "Times". "Times RO" is a common font
358 // whose family name is "Times". It overrides the normal "Times" family font. It also
359 // appears to have a corrupt regular variant.
360 NSString *fallbackFontFamily;
362 if ([[font familyName] isEqual:@"Times"])
363 fallbackFontFamily = @"Times New Roman";
365 fallbackFontFamily = [WebTextRenderer webFallbackFontFamily];
368 // Try setting up the alternate font. This is a last ditch effort to use a
369 // substitute font when something has gone wrong.
370 NSFont *initialFont = font;
371 [initialFont autorelease];
372 NSFont *af = [[NSFontManager sharedFontManager] convertFont:font toFamily:fallbackFontFamily];
373 font = [(p ? [af printerFont] : [af screenFont]) retain];
374 NSString *filePath = pathFromFont(initialFont);
375 filePath = filePath ? filePath : @"not known";
376 if (![self _setupFont]){
377 if ([fallbackFontFamily isEqual:@"Times New Roman"]) {
378 // OK, couldn't setup Times New Roman as an alternate to Times, fallback
379 // on the system font. If this fails we have no alternative left.
380 af = [[NSFontManager sharedFontManager] convertFont:font toFamily:[WebTextRenderer webFallbackFontFamily]];
381 font = [(p ? [af printerFont] : [af screenFont]) retain];
382 if (![self _setupFont]){
383 // We tried, Times, Times New Roman, and the system font. No joy. We have to give up.
384 ERROR ("%@ unable to initialize with font %@ at %@", self, initialFont, filePath);
389 // We tried the requested font and the syste, font. No joy. We have to give up.
390 ERROR ("%@ unable to initialize with font %@ at %@", self, initialFont, filePath);
395 // Report the problem.
396 ERROR ("Corrupt font detected, using %@ in place of %@ located at \"%@\".",
398 [initialFont familyName],
402 // If all else fails try to setup using the system font. This is probably because
403 // Times and Times New Roman are both unavailable.
405 f = [NSFont systemFontOfSize:[f pointSize]];
406 ERROR ("%@ failed to setup font, using system font %s", self, f);
407 font = [(p ? [f printerFont] : [f screenFont]) retain];
411 // We emulate the appkit metrics by applying rounding as is done
413 CGFontRef cgFont = [font _backingCGSFont];
414 const CGFontHMetrics *metrics = CGFontGetHMetrics(cgFont);
415 unsigned unitsPerEm = CGFontGetUnitsPerEm(cgFont);
416 float pointSize = [font pointSize];
417 float asc = (ScaleEmToUnits(metrics->ascent, unitsPerEm)*pointSize);
418 float dsc = (-ScaleEmToUnits(metrics->descent, unitsPerEm)*pointSize);
419 float _lineGap = ScaleEmToUnits(metrics->lineGap, unitsPerEm)*pointSize;
422 // We need to adjust Times, Helvetica, and Courier to closely match the
423 // vertical metrics of their Microsoft counterparts that are the de facto
424 // web standard. The AppKit adjustment of 20% is too big and is
425 // incorrectly added to line spacing, so we use a 15% adjustment instead
426 // and add it to the ascent.
427 if ([[font familyName] isEqualToString:@"Times"] ||
428 [[font familyName] isEqualToString:@"Helvetica"] ||
429 [[font familyName] isEqualToString:@"Courier"]) {
430 adjustment = floor(((asc + dsc) * 0.15) + 0.5);
435 ascent = ROUND_TO_INT(asc + adjustment);
436 descent = ROUND_TO_INT(dsc);
438 _lineGap = (_lineGap > 0.0 ? floor(_lineGap + 0.5) : 0.0);
439 lineGap = (int)_lineGap;
440 lineSpacing = ascent + descent + lineGap;
442 #ifdef COMPARE_APPKIT_CG_METRICS
443 printf ("\nCG/Appkit metrics for font %s, %f, lineGap %f, adjustment %f\n", [[font displayName] cString], [font pointSize], lineGap, adjustment);
444 if (ROUND_TO_INT([font ascender]) != ascent ||
445 ROUND_TO_INT(-[font descender]) != descent ||
446 ROUND_TO_INT([font defaultLineHeightForFont]) != lineSpacing){
447 printf ("\nCG/Appkit mismatched metrics for font %s, %f (%s)\n", [[font displayName] cString], [font pointSize],
448 ([font screenFont] ? [[[font screenFont] displayName] cString] : "none"));
449 printf ("ascent(%s), descent(%s), lineSpacing(%s)\n",
450 (ROUND_TO_INT([font ascender]) != ascent) ? "different" : "same",
451 (ROUND_TO_INT(-[font descender]) != descent) ? "different" : "same",
452 (ROUND_TO_INT([font defaultLineHeightForFont]) != lineSpacing) ? "different" : "same");
453 printf ("CG: ascent %f, ", asc);
454 printf ("descent %f, ", dsc);
455 printf ("lineGap %f, ", lineGap);
456 printf ("lineSpacing %d\n", lineSpacing);
458 printf ("NSFont: ascent %f, ", [font ascender]);
459 printf ("descent %f, ", [font descender]);
460 printf ("lineSpacing %f\n", [font defaultLineHeightForFont]);
464 isSmallCapsRenderer = NO;
472 [smallCapsFont release];
473 [smallCapsRenderer release];
476 ATSUDisposeStyleGroup(styleGroup);
478 freeWidthMap(glyphToWidthMap);
479 freeGlyphMap(characterToGlyphMap);
480 freeUnicodeGlyphMap(unicodeCharacterToGlyphMap);
482 if (ATSUStyleInitialized)
483 ATSUDisposeStyle(_ATSUSstyle);
491 ATSUDisposeStyleGroup(styleGroup);
493 freeWidthMap(glyphToWidthMap);
494 freeGlyphMap(characterToGlyphMap);
495 freeUnicodeGlyphMap(unicodeCharacterToGlyphMap);
497 if (ATSUStyleInitialized)
498 ATSUDisposeStyle(_ATSUSstyle);
505 // This simple return obviously can't throw an exception.
511 // This simple return obviously can't throw an exception.
517 // This simple return obviously can't throw an exception.
523 // Measure the actual character "x", because AppKit synthesizes X height rather
524 // than getting it from the font. Unfortunately, NSFont will round this for us
525 // so we don't quite get the right value.
526 NSGlyph xGlyph = [font glyphWithName:@"x"];
528 NSRect xBox = [font boundingRectForGlyph:xGlyph];
529 // Use the maximum of either width or height because "x" is nearly square
530 // and web pages that foolishly use this metric for width will be laid out
531 // poorly if we return an accurate height. Classic case is Times 13 point,
532 // which has an "x" that is 7x6 pixels.
533 return MAX(NSMaxX(xBox), NSMaxY(xBox));
536 return [font xHeight];
539 - (void)drawRun:(const WebCoreTextRun *)run style:(const WebCoreTextStyle *)style geometry:(const WebCoreTextGeometry *)geometry
541 if (style->smallCaps && !isSmallCapsRenderer) {
542 [[self _smallCapsRenderer] drawRun:run style:style geometry:geometry];
545 if (shouldUseATSU(run))
546 [self _ATSU_drawRun:run style:style geometry:geometry];
548 [self _CG_drawRun:run style:style geometry:geometry];
552 - (float)floatWidthForRun:(const WebCoreTextRun *)run style:(const WebCoreTextStyle *)style widths:(float *)widthBuffer
554 if (style->smallCaps && !isSmallCapsRenderer) {
555 return [[self _smallCapsRenderer] _floatWidthForRun:run style:style widths:widthBuffer fonts:nil glyphs:nil startPosition:nil numGlyphs:nil];
557 return [self _floatWidthForRun:run style:style widths:widthBuffer fonts:nil glyphs:nil startPosition:nil numGlyphs:nil];
560 - (void)drawLineForCharacters:(NSPoint)point yOffset:(float)yOffset width: (int)width color:(NSColor *)color thickness:(float)thickness
562 NSGraphicsContext *graphicsContext = [NSGraphicsContext currentContext];
563 CGContextRef cgContext;
565 // This will draw the text from the top of the bounding box down.
566 // Qt expects to draw from the baseline.
567 // Remember that descender is negative.
568 point.y -= [self lineSpacing] - [self descent];
570 BOOL flag = [graphicsContext shouldAntialias];
572 [graphicsContext setShouldAntialias: NO];
574 // We don't want antialiased lines on screen, but we do when printing (else they are too thick)
575 if ([graphicsContext isDrawingToScreen]) {
576 [graphicsContext setShouldAntialias:NO];
581 cgContext = (CGContextRef)[graphicsContext graphicsPort];
583 // hack to make thickness 2 underlines for internation text input look right
584 if (thickness > 1.5 && thickness < 2.5) {
588 if (thickness == 0.0) {
589 CGSize size = CGSizeApplyAffineTransform(CGSizeMake(1.0, 1.0), CGAffineTransformInvert(CGContextGetCTM(cgContext)));
590 CGContextSetLineWidth(cgContext, size.width);
592 CGContextSetLineWidth(cgContext, thickness);
596 // With Q2DX turned on CGContextStrokeLineSegments sometimes fails to draw lines. See 3952084.
597 // So, it has been requested that we turn off use of the new API until 3952084 is fixed.
599 //#if BUILDING_ON_PANTHER
600 CGContextMoveToPoint(cgContext, point.x, point.y + [self lineSpacing] + 1.5 - [self descent] + yOffset);
601 // Subtract 1 to ensure that the line is always within bounds of element.
602 CGContextAddLineToPoint(cgContext, point.x + width - 1.0, point.y + [self lineSpacing] + 1.5 - [self descent] + yOffset);
603 CGContextStrokePath(cgContext);
605 // Use CGContextStrokeLineSegments on Tiger. J. Burkey says this will be a big performance win.
607 CGPoint linePoints[2];
608 linePoints[0].x = point.x;
609 linePoints[0].y = point.y + [self lineSpacing] + 1.5 - [self descent] + yOffset;
610 linePoints[1].x = point.x + width - 1.0;
611 linePoints[1].y = linePoints[0].y;
612 CGContextStrokeLineSegments (cgContext, linePoints, 2);
615 [graphicsContext setShouldAntialias: flag];
619 - (void)drawHighlightForRun:(const WebCoreTextRun *)run style:(const WebCoreTextStyle *)style geometry:(const WebCoreTextGeometry *)geometry
621 if (style->smallCaps && !isSmallCapsRenderer) {
622 [[self _smallCapsRenderer] drawHighlightForRun:run style:style geometry:geometry];
625 if (shouldUseATSU(run))
626 [self _ATSU_drawHighlightForRun:run style:style geometry:geometry];
628 [self _CG_drawHighlightForRun:run style:style geometry:geometry];
632 - (int)misspellingLineThickness
637 - (int)misspellingLinePatternWidth
642 - (void)drawLineForMisspelling:(NSPoint)point withWidth:(int)width
644 // Constants for pattern color
645 static NSColor *spellingPatternColor = nil;
646 static bool usingDot = false;
647 int patternHeight = [self misspellingLineThickness];
648 int patternWidth = [self misspellingLinePatternWidth];
650 // Initialize pattern color if needed
651 if (!spellingPatternColor) {
652 NSImage *image = [NSImage imageNamed:@"SpellingDot"];
653 ASSERT(image); // if image is not available, we want to know
654 NSColor *color = (image ? [NSColor colorWithPatternImage:image] : nil);
658 color = [NSColor redColor];
659 spellingPatternColor = [color retain];
662 // Make sure to draw only complete dots
663 // NOTE: Code here used to shift the underline to the left and increase the width
664 // to make sure everything gets underlined, but that results in drawing out of
665 // bounds, e.g. when at the edge of a view.
667 width -= (width % patternWidth);
669 // Compute the appropriate phase relative to the top level view in the window.
670 NSPoint originInWindow = [[NSView focusView] convertPoint:point toView:nil];
671 // WebCore may translate the focus, and thus need an extra phase correction
672 NSPoint extraPhase = [[WebGraphicsBridge sharedBridge] additionalPatternPhase];
673 originInWindow.x += extraPhase.x;
674 originInWindow.y += extraPhase.y;
675 CGSize phase = CGSizeMake(fmodf(originInWindow.x, patternWidth), fmodf(originInWindow.y, patternHeight));
678 NSGraphicsContext *currentContext = [NSGraphicsContext currentContext];
679 [currentContext saveGraphicsState];
680 [spellingPatternColor set];
681 CGContextSetPatternPhase((CGContextRef)[currentContext graphicsPort], phase);
682 NSRectFillUsingOperation(NSMakeRect(point.x, point.y, width, patternHeight), NSCompositeSourceOver);
683 [currentContext restoreGraphicsState];
686 - (int)pointToOffset:(const WebCoreTextRun *)run style:(const WebCoreTextStyle *)style position:(int)x reversed:(BOOL)reversed includePartialGlyphs:(BOOL)includePartialGlyphs
688 if (style->smallCaps && !isSmallCapsRenderer) {
689 return [[self _smallCapsRenderer] pointToOffset:run style:style position:x reversed:reversed includePartialGlyphs:includePartialGlyphs];
692 if (shouldUseATSU(run))
693 return [self _ATSU_pointToOffset:run style:style position:x reversed:reversed includePartialGlyphs:includePartialGlyphs];
694 return [self _CG_pointToOffset:run style:style position:x reversed:reversed includePartialGlyphs:includePartialGlyphs];
700 // ------------------- Private API -------------------
703 @implementation WebTextRenderer (WebInternal)
705 + (void)_setAlwaysUseATSU:(BOOL)f
710 - (void)_setIsSmallCapsRenderer:(BOOL)flag
712 isSmallCapsRenderer = flag;
715 - (BOOL)_isSmallCapsRenderer
717 return isSmallCapsRenderer;
720 - (WebTextRenderer *)_smallCapsRenderer
722 if (!smallCapsRenderer) {
724 smallCapsRenderer = [[WebTextRenderer alloc] initWithFont:font usingPrinterFont:usingPrinterFont];
726 if (ASSERT_DISABLED) {
727 NSLog(@"Uncaught exception - %@\n", localException);
729 ASSERT_WITH_MESSAGE(0, "Uncaught exception - %@", localException);
733 [smallCapsRenderer _setIsSmallCapsRenderer:YES];
735 return smallCapsRenderer;
738 - (NSFont *)_smallCapsFont
741 smallCapsFont = [[[NSFontManager sharedFontManager] convertFont:font toSize:([font pointSize] * SMALLCAPS_FONTSIZE_MULTIPLIER)] screenFont];
742 return usingPrinterFont ? [smallCapsFont printerFont] : smallCapsFont;
745 static inline BOOL fontContainsString(NSFont *font, NSString *string)
747 NSCharacterSet *set = [[font coveredCharacterSet] invertedSet];
748 return set && [string rangeOfCharacterFromSet:set].location == NSNotFound;
751 - (NSFont *)_substituteFontForString: (NSString *)string families: (NSString **)families
753 NSFont *substituteFont = nil;
755 // First search the CSS family fallback list. Start at 1 (2nd font)
756 // because we've already failed on the first lookup.
757 NSString *family = nil;
759 while (families && families[i] != 0) {
760 family = families[i++];
761 substituteFont = [[WebTextRendererFactory sharedFactory] cachedFontFromFamily: family traits:[[NSFontManager sharedFontManager] traitsOfFont:font] size:[font pointSize]];
762 if (substituteFont) {
763 if (fontContainsString(substituteFont, string))
765 substituteFont = nil;
769 // Now do string based lookup.
770 if (substituteFont == nil)
771 substituteFont = [NSFont findFontLike:font forString:string withRange:NSMakeRange (0,[string length]) inLanguage:[NSLanguage defaultLanguage]];
773 // Now do character based lookup.
774 if (substituteFont == nil && [string length] == 1)
775 substituteFont = [NSFont findFontLike:font forCharacter: [string characterAtIndex: 0] inLanguage:[NSLanguage defaultLanguage]];
777 // Get the screen or printer variation of the font.
778 substituteFont = usingPrinterFont ? [substituteFont printerFont] : [substituteFont screenFont];
780 if ([substituteFont isEqual: font])
781 substituteFont = nil;
783 // Now that we have a substitute font, attempt to match it to the best variation. If we have
784 // a good match return that, otherwise return the font the AppKit has found.
785 NSFontManager *manager = [NSFontManager sharedFontManager];
786 NSFont *substituteFont2 = [manager fontWithFamily:(NSString *)[substituteFont familyName] traits:[manager traitsOfFont:font] weight:[manager weightOfFont:font] size:[font pointSize]];
788 substituteFont = substituteFont2;
790 // Now, finally, get the printer or screen variation.
791 substituteFont = usingPrinterFont ? [substituteFont printerFont] : [substituteFont screenFont];
793 return substituteFont;
796 - (NSFont *)_substituteFontForCharacters: (const unichar *)characters length: (int)numCharacters families: (NSString **)families
798 NSString *string = [[NSString alloc] initWithCharactersNoCopy:(unichar *)characters length: numCharacters freeWhenDone: NO];
799 NSFont *substituteFont = [self _substituteFontForString: string families: families];
801 return substituteFont;
804 - (void)_convertCharacters: (const UniChar *)characters length: (unsigned)numCharacters toGlyphs: (ATSGlyphVector *)glyphs
806 OSStatus status = ATSUConvertCharToGlyphs(styleGroup, characters, 0, numCharacters, 0, glyphs);
807 if (status != noErr){
808 FATAL_ALWAYS ("unable to get glyphsfor %@ %f error = (%d)", self, [font displayName], [font pointSize], status);
813 ATSLayoutRecord *glyphRecord;
814 for (i = 0; i < numCharacters; i++) {
815 glyphRecord = (ATSLayoutRecord *)glyphs->firstRecord;
816 for (i = 0; i < numCharacters; i++) {
817 if (glyphRecord->glyphID != 0)
819 glyphRecord = (ATSLayoutRecord *)((char *)glyphRecord + glyphs->recordSize);
822 printf ("For %s found %d glyphs in range 0x%04x to 0x%04x\n", [[font displayName] cString], foundGlyphs, characters[0], characters[numCharacters-1]);
826 - (void)_convertUnicodeCharacters: (const UnicodeChar *)characters length: (unsigned)numCharacters toGlyphs: (ATSGlyphVector *)glyphs
828 UniChar localBuffer[LOCAL_BUFFER_SIZE];
829 UniChar *buffer = localBuffer;
830 unsigned i, bufPos = 0;
832 if (numCharacters*2 > LOCAL_BUFFER_SIZE) {
833 buffer = (UniChar *)malloc(sizeof(UniChar) * numCharacters * 2);
836 for (i = 0; i < numCharacters; i++) {
837 UnicodeChar c = characters[i];
838 UniChar h = HighSurrogatePair(c);
839 UniChar l = LowSurrogatePair(c);
840 buffer[bufPos++] = h;
841 buffer[bufPos++] = l;
844 OSStatus status = ATSUConvertCharToGlyphs(styleGroup, buffer, 0, numCharacters*2, 0, glyphs);
845 if (status != noErr){
846 FATAL_ALWAYS ("unable to get glyphsfor %@ %f error = (%d)", self, [font displayName], [font pointSize], status);
849 if (buffer != localBuffer) {
854 // Nasty hack to determine if we should round or ceil space widths.
855 // If the font is monospace or fake monospace we ceil to ensure that
856 // every character and the space are the same width. Otherwise we round.
857 - (BOOL)_computeWidthForSpace
859 spaceGlyph = [self _extendCharacterToGlyphMapToInclude:SPACE];
860 if (spaceGlyph == 0) {
864 float width = widthForGlyph(self, spaceGlyph, 0);
867 treatAsFixedPitch = [[WebTextRendererFactory sharedFactory] isFontFixedPitch:font];
868 adjustedSpaceWidth = treatAsFixedPitch ? CEIL_TO_INT(width) : ROUND_TO_INT(width);
876 if (ATSUCreateStyle(&fontStyle) != noErr)
879 if (!fillStyleWithAttributes(fontStyle, font)) {
880 ATSUDisposeStyle(fontStyle);
884 if (ATSUGetStyleGroup(fontStyle, &styleGroup) != noErr) {
885 ATSUDisposeStyle(fontStyle);
889 ATSUDisposeStyle(fontStyle);
891 if (![self _computeWidthForSpace]) {
892 freeGlyphMap(characterToGlyphMap);
893 characterToGlyphMap = NULL;
894 ATSUDisposeStyleGroup(styleGroup);
902 static NSString *pathFromFont (NSFont *font)
904 UInt8 _filePathBuffer[PATH_MAX];
905 NSString *filePath = nil;
907 OSStatus status = ATSFontGetFileSpecification(
908 ATSFontRefFromNSFont(font),
910 if (status == noErr){
913 err = FSpMakeFSRef(&oFile,&fileRef);
915 status = FSRefMakePath(&fileRef,_filePathBuffer, PATH_MAX);
916 if (status == noErr){
917 filePath = [NSString stringWithUTF8String:&_filePathBuffer[0]];
924 // Useful page for testing http://home.att.net/~jameskass
925 static void _drawGlyphs(NSFont *font, NSColor *color, CGGlyph *glyphs, CGSize *advances, float x, float y, int numGlyphs)
927 CGContextRef cgContext;
929 if ([WebTextRenderer shouldBufferTextDrawing] && [[WebTextRendererFactory sharedFactory] coalesceTextDrawing]){
930 // Add buffered glyphs and advances
931 // FIXME: If we ever use this again, need to add RTL.
932 WebGlyphBuffer *gBuffer = [[WebTextRendererFactory sharedFactory] glyphBufferForFont: font andColor: color];
933 [gBuffer addGlyphs: glyphs advances: advances count: numGlyphs at: x : y];
936 NSGraphicsContext *gContext = [NSGraphicsContext currentContext];
937 cgContext = (CGContextRef)[gContext graphicsPort];
938 // Setup the color and font.
940 bool originalShouldUseFontSmoothing;
942 originalShouldUseFontSmoothing = CGContextGetShouldSmoothFonts (cgContext);
943 CGContextSetShouldSmoothFonts (cgContext, [WebView _shouldUseFontSmoothing]);
945 #if BUILDING_ON_PANTHER
946 if ([gContext isDrawingToScreen]){
947 NSFont *screenFont = [font screenFont];
948 if (screenFont != font){
949 // We are getting this in too many places (3406411); use ERROR so it only prints on
950 // debug versions for now. (We should debug this also, eventually).
951 ERROR ("Attempting to set non-screen font (%@) when drawing to screen. Using screen font anyway, may result in incorrect metrics.", [[[font fontDescriptor] fontAttributes] objectForKey: NSFontNameAttribute]);
956 NSFont *printerFont = [font printerFont];
957 if (printerFont != font){
958 NSLog (@"Attempting to set non-printer font (%@) when printing. Using printer font anyway, may result in incorrect metrics.", [[[font fontDescriptor] fontAttributes] objectForKey: NSFontNameAttribute]);
965 if ([gContext isDrawingToScreen]){
966 drawFont = [font screenFont];
967 if (drawFont != font){
968 // We are getting this in too many places (3406411); use ERROR so it only prints on
969 // debug versions for now. (We should debug this also, eventually).
970 ERROR ("Attempting to set non-screen font (%@) when drawing to screen. Using screen font anyway, may result in incorrect metrics.", [[[font fontDescriptor] fontAttributes] objectForKey: NSFontNameAttribute]);
974 drawFont = [font printerFont];
975 if (drawFont != font){
976 NSLog (@"Attempting to set non-printer font (%@) when printing. Using printer font anyway, may result in incorrect metrics.", [[[font fontDescriptor] fontAttributes] objectForKey: NSFontNameAttribute]);
980 NSView *v = [NSView focusView];
982 CGContextSetFont (cgContext, [drawFont _backingCGSFont]);
984 // Deal will flipping flippyness.
985 const float *matrix = [drawFont matrix];
986 float flip = [v isFlipped] ? -1 : 1;
987 CGContextSetTextMatrix(cgContext, CGAffineTransformMake(matrix[0], matrix[1] * flip, matrix[2], matrix[3] * flip, matrix[4], matrix[5]));
988 CGContextSetFontRenderingMode (cgContext, _AppkitGetCGRenderingMode(drawFont));
989 CGContextSetFontSize(cgContext, 1.0);
994 CGContextSetTextPosition (cgContext, x, y);
995 CGContextShowGlyphsWithAdvances (cgContext, glyphs, advances, numGlyphs);
997 CGContextSetShouldSmoothFonts (cgContext, originalShouldUseFontSmoothing);
1002 - (void)_CG_drawHighlightForRun:(const WebCoreTextRun *)run style:(const WebCoreTextStyle *)style geometry:(const WebCoreTextGeometry *)geometry
1004 if (run->length == 0)
1007 CharacterWidthIterator widthIterator;
1008 WebCoreTextRun completeRun = *run;
1009 completeRun.from = 0;
1010 completeRun.to = run->length;
1011 initializeCharacterWidthIterator(&widthIterator, self, &completeRun, style);
1013 float startPosition = 0;
1015 // The starting point needs to be adjusted to account for the width of
1016 // the glyphs at the start of the run.
1017 while (widthIterator.currentCharacter < (unsigned)run->from) {
1018 startPosition += widthForNextCharacter(&widthIterator, 0, 0);
1020 float startX = startPosition + geometry->point.x;
1022 float backgroundWidth = 0.0;
1023 while (widthIterator.currentCharacter < (unsigned)run->to) {
1024 backgroundWidth += widthForNextCharacter(&widthIterator, 0, 0);
1027 if (style->backgroundColor != nil){
1028 // Calculate the width of the selection background by adding
1029 // up the advances of all the glyphs in the selection.
1031 [style->backgroundColor set];
1033 float yPos = geometry->useFontMetricsForSelectionYAndHeight ? geometry->point.y - [self ascent] - (lineGap/2) : geometry->selectionY;
1034 float height = geometry->useFontMetricsForSelectionYAndHeight ? [self lineSpacing] : geometry->selectionHeight;
1036 float completeRunWidth = startPosition + backgroundWidth;
1037 while (widthIterator.currentCharacter < run->length) {
1038 completeRunWidth += widthForNextCharacter(&widthIterator, 0, 0);
1041 [NSBezierPath fillRect:NSMakeRect(geometry->point.x + completeRunWidth - startPosition - backgroundWidth, yPos, backgroundWidth, height)];
1044 [NSBezierPath fillRect:NSMakeRect(startX, yPos, backgroundWidth, height)];
1050 - (void)_CG_drawRun:(const WebCoreTextRun *)run style:(const WebCoreTextStyle *)style geometry:(const WebCoreTextGeometry *)geometry
1052 float *widthBuffer, localWidthBuffer[LOCAL_BUFFER_SIZE];
1053 CGGlyph *glyphBuffer, localGlyphBuffer[LOCAL_BUFFER_SIZE];
1054 NSFont **fontBuffer, *localFontBuffer[LOCAL_BUFFER_SIZE];
1055 CGSize *advances, localAdvanceBuffer[LOCAL_BUFFER_SIZE];
1056 int numGlyphs = 0, i;
1058 unsigned length = run->length;
1060 if (run->length == 0)
1063 if (length*MAX_GLYPH_EXPANSION > LOCAL_BUFFER_SIZE) {
1064 advances = (CGSize *)calloc(length*MAX_GLYPH_EXPANSION, sizeof(CGSize));
1065 widthBuffer = (float *)calloc(length*MAX_GLYPH_EXPANSION, sizeof(float));
1066 glyphBuffer = (CGGlyph *)calloc(length*MAX_GLYPH_EXPANSION, sizeof(ATSGlyphRef));
1067 fontBuffer = (NSFont **)calloc(length*MAX_GLYPH_EXPANSION, sizeof(NSFont *));
1069 advances = localAdvanceBuffer;
1070 widthBuffer = localWidthBuffer;
1071 glyphBuffer = localGlyphBuffer;
1072 fontBuffer = localFontBuffer;
1075 [self _floatWidthForRun:run
1080 startPosition:&startX
1081 numGlyphs: &numGlyphs];
1083 // Eek. We couldn't generate ANY glyphs for the run.
1087 // Fill the advances array.
1088 for (i = 0; i < numGlyphs; i++){
1089 advances[i].width = widthBuffer[i];
1090 advances[i].height = 0;
1093 // Calculate the starting point of the glyphs to be displayed by adding
1094 // all the advances up to the first glyph.
1095 startX += geometry->point.x;
1097 if (style->backgroundColor != nil)
1098 [self _CG_drawHighlightForRun:run style:style geometry:geometry];
1100 // Finally, draw the glyphs.
1104 // Swap the order of the glyphs if right-to-left.
1105 if (style->rtl && numGlyphs > 1){
1107 int end = numGlyphs;
1108 CGGlyph gswap1, gswap2;
1109 CGSize aswap1, aswap2;
1110 NSFont *fswap1, *fswap2;
1112 for (i = pos, end = numGlyphs-1; i < (numGlyphs - pos)/2; i++){
1113 gswap1 = glyphBuffer[i];
1114 gswap2 = glyphBuffer[--end];
1115 glyphBuffer[i] = gswap2;
1116 glyphBuffer[end] = gswap1;
1118 for (i = pos, end = numGlyphs - 1; i < (numGlyphs - pos)/2; i++){
1119 aswap1 = advances[i];
1120 aswap2 = advances[--end];
1121 advances[i] = aswap2;
1122 advances[end] = aswap1;
1124 for (i = pos, end = numGlyphs - 1; i < (numGlyphs - pos)/2; i++){
1125 fswap1 = fontBuffer[i];
1126 fswap2 = fontBuffer[--end];
1127 fontBuffer[i] = fswap2;
1128 fontBuffer[end] = fswap1;
1132 // Draw each contiguous run of glyphs that are included in the same font.
1133 NSFont *currentFont = fontBuffer[pos];
1134 float nextX = startX;
1135 int nextGlyph = pos;
1137 while (nextGlyph < numGlyphs){
1138 if ((fontBuffer[nextGlyph] != 0 && fontBuffer[nextGlyph] != currentFont)){
1139 _drawGlyphs(currentFont, style->textColor, &glyphBuffer[lastFrom], &advances[lastFrom], startX, geometry->point.y, nextGlyph - lastFrom);
1140 lastFrom = nextGlyph;
1141 currentFont = fontBuffer[nextGlyph];
1144 nextX += advances[nextGlyph].width;
1147 _drawGlyphs(currentFont, style->textColor, &glyphBuffer[lastFrom], &advances[lastFrom], startX, geometry->point.y, nextGlyph - lastFrom);
1149 if (advances != localAdvanceBuffer) {
1157 #ifdef DEBUG_COMBINING
1158 static const char *directionNames[] = {
1159 "DirectionL", // Left Letter
1160 "DirectionR", // Right Letter
1161 "DirectionEN", // European Number
1162 "DirectionES", // European Separator
1163 "DirectionET", // European Terminator (post/prefix e.g. $ and %)
1164 "DirectionAN", // Arabic Number
1165 "DirectionCS", // Common Separator
1166 "DirectionB", // Paragraph Separator (aka as PS)
1167 "DirectionS", // Segment Separator (TAB)
1168 "DirectionWS", // White space
1169 "DirectionON", // Other Neutral
1171 // types for explicit controls
1175 "DirectionAL", // Arabic Letter (Right-to-left)
1181 "DirectionNSM", // Non-spacing Mark
1182 "DirectionBN" // Boundary neutral (type of RLE etc after explicit levels)
1185 static const char *joiningNames[] = {
1193 - (float)_floatWidthForRun:(const WebCoreTextRun *)run style:(const WebCoreTextStyle *)style widths:(float *)widthBuffer fonts:(NSFont **)fontBuffer glyphs:(CGGlyph *)glyphBuffer startPosition:(float *)startPosition numGlyphs:(int *)_numGlyphs
1195 if (shouldUseATSU(run))
1196 return [self _ATSU_floatWidthForRun:run style:style];
1198 return [self _CG_floatWidthForRun:run style:style widths:widthBuffer fonts:fontBuffer glyphs:glyphBuffer startPosition:startPosition numGlyphs:_numGlyphs];
1202 - (float)_CG_floatWidthForRun:(const WebCoreTextRun *)run style:(const WebCoreTextStyle *)style widths: (float *)widthBuffer fonts: (NSFont **)fontBuffer glyphs: (CGGlyph *)glyphBuffer startPosition:(float *)startPosition numGlyphs: (int *)_numGlyphs
1204 float _totalWidth = 0, _nextWidth;
1205 CharacterWidthIterator widthIterator;
1206 NSFont *fontUsed = 0;
1207 ATSGlyphRef glyphUsed;
1210 initializeCharacterWidthIterator(&widthIterator, self, run, style);
1212 *startPosition = widthIterator.widthToStart;
1213 while ((_nextWidth = widthForNextCharacter(&widthIterator, &glyphUsed, &fontUsed)) != INVALID_WIDTH){
1215 fontBuffer[numGlyphs] = fontUsed;
1217 glyphBuffer[numGlyphs] = glyphUsed;
1219 widthBuffer[numGlyphs] = _nextWidth;
1221 _totalWidth += _nextWidth;
1225 *_numGlyphs = numGlyphs;
1230 - (ATSGlyphRef)_extendUnicodeCharacterToGlyphMapToInclude:(UnicodeChar)c
1232 UnicodeGlyphMap *map = (UnicodeGlyphMap *)calloc (1, sizeof(UnicodeGlyphMap));
1233 ATSLayoutRecord *glyphRecord;
1234 ATSGlyphVector glyphVector;
1235 UnicodeChar end, start;
1237 ATSGlyphRef glyphID;
1239 if (unicodeCharacterToGlyphMap == 0)
1240 blockSize = INITIAL_BLOCK_SIZE;
1242 blockSize = INCREMENTAL_BLOCK_SIZE;
1243 start = (c / blockSize) * blockSize;
1244 end = start + (blockSize - 1);
1246 LOG(FontCache, "%@ (0x%04x) adding glyphs for 0x%04x to 0x%04x", font, c, start, end);
1248 map->startRange = start;
1249 map->endRange = end;
1251 unsigned i, count = end - start + 1;
1252 UnicodeChar buffer[INCREMENTAL_BLOCK_SIZE+2];
1254 for (i = 0; i < count; i++){
1255 buffer[i] = i+start;
1259 status = ATSInitializeGlyphVector(count*2, 0, &glyphVector);
1260 if (status != noErr){
1261 // This should never happen, indicates a bad font! If it does the
1262 // font substitution code will find an alternate font.
1267 [self _convertUnicodeCharacters: &buffer[0] length: count toGlyphs: &glyphVector];
1268 unsigned numGlyphs = glyphVector.numGlyphs;
1269 if (numGlyphs != count){
1270 // This should never happen, indicates a bad font! If it does the
1271 // font substitution code will find an alternate font.
1276 map->glyphs = (GlyphEntry *)malloc (count * sizeof(GlyphEntry));
1277 glyphRecord = (ATSLayoutRecord *)glyphVector.firstRecord;
1278 for (i = 0; i < count; i++) {
1279 map->glyphs[i].glyph = glyphRecord->glyphID;
1280 map->glyphs[i].font = 0;
1281 glyphRecord = (ATSLayoutRecord *)((char *)glyphRecord + glyphVector.recordSize);
1283 ATSClearGlyphVector(&glyphVector);
1285 if (unicodeCharacterToGlyphMap == 0)
1286 unicodeCharacterToGlyphMap = map;
1288 UnicodeGlyphMap *lastMap = unicodeCharacterToGlyphMap;
1289 while (lastMap->next != 0)
1290 lastMap = lastMap->next;
1291 lastMap->next = map;
1294 glyphID = map->glyphs[c - start].glyph;
1299 - (void)_updateGlyphEntryForCharacter:(UniChar)c glyphID:(ATSGlyphRef)glyphID font:(NSFont *)substituteFont
1301 GlyphMap *lastMap = characterToGlyphMap;
1302 while (lastMap != 0){
1303 if (c >= lastMap->startRange && c <= lastMap->endRange){
1304 lastMap->glyphs[c - lastMap->startRange].glyph = glyphID;
1305 // This font will leak. No problem though, it has to stick around
1306 // forever. Max theoretical retain counts applied here will be
1307 // num_fonts_on_system * num_glyphs_in_font.
1308 lastMap->glyphs[c - lastMap->startRange].font = [substituteFont retain];
1311 lastMap = lastMap->next;
1315 - (ATSGlyphRef)_extendCharacterToGlyphMapToInclude:(UniChar) c
1317 GlyphMap *map = (GlyphMap *)calloc (1, sizeof(GlyphMap));
1318 ATSLayoutRecord *glyphRecord;
1319 ATSGlyphVector glyphVector;
1322 ATSGlyphRef glyphID;
1324 if (characterToGlyphMap == 0)
1325 blockSize = INITIAL_BLOCK_SIZE;
1327 blockSize = INCREMENTAL_BLOCK_SIZE;
1328 start = (c / blockSize) * blockSize;
1329 end = start + (blockSize - 1);
1331 LOG(FontCache, "%@ (0x%04x) adding glyphs for 0x%04x to 0x%04x", font, c, start, end);
1333 map->startRange = start;
1334 map->endRange = end;
1336 unsigned i, count = end - start + 1;
1337 short unsigned buffer[INCREMENTAL_BLOCK_SIZE+2];
1339 for (i = 0; i < count; i++) {
1340 buffer[i] = i+start;
1344 // Control characters must not render at all.
1345 for (i = 0; i < 0x20; ++i)
1346 buffer[i] = ZERO_WIDTH_SPACE;
1347 buffer[0x7F] = ZERO_WIDTH_SPACE;
1349 // But both \n and nonbreaking space must render as a space.
1351 buffer[NO_BREAK_SPACE] = ' ';
1354 OSStatus status = ATSInitializeGlyphVector(count, 0, &glyphVector);
1355 if (status != noErr) {
1356 // This should never happen, perhaps indicates a bad font! If it does the
1357 // font substitution code will find an alternate font.
1362 [self _convertCharacters: &buffer[0] length: count toGlyphs: &glyphVector];
1363 unsigned numGlyphs = glyphVector.numGlyphs;
1364 if (numGlyphs != count){
1365 // This should never happen, perhaps indicates a bad font! If it does the
1366 // font substitution code will find an alternate font.
1371 map->glyphs = (GlyphEntry *)malloc (count * sizeof(GlyphEntry));
1372 glyphRecord = (ATSLayoutRecord *)glyphVector.firstRecord;
1373 for (i = 0; i < count; i++) {
1374 map->glyphs[i].glyph = glyphRecord->glyphID;
1375 map->glyphs[i].font = 0;
1376 glyphRecord = (ATSLayoutRecord *)((char *)glyphRecord + glyphVector.recordSize);
1378 ATSClearGlyphVector(&glyphVector);
1380 if (characterToGlyphMap == 0)
1381 characterToGlyphMap = map;
1383 GlyphMap *lastMap = characterToGlyphMap;
1384 while (lastMap->next != 0)
1385 lastMap = lastMap->next;
1386 lastMap->next = map;
1389 glyphID = map->glyphs[c - start].glyph;
1391 // Special case for characters 007F-00A0.
1392 if (glyphID == 0 && c >= 0x7F && c <= 0xA0){
1393 glyphID = [font _defaultGlyphForChar: c];
1394 map->glyphs[c - start].glyph = glyphID;
1395 map->glyphs[c - start].font = 0;
1402 - (WidthMap *)_extendGlyphToWidthMapToInclude:(ATSGlyphRef)glyphID font:(NSFont *)subFont
1404 WidthMap *map = (WidthMap *)calloc (1, sizeof(WidthMap)), **rootMap;
1410 if (subFont && subFont != font)
1411 rootMap = &mapForSubstituteFont(self,subFont)->map;
1413 rootMap = &glyphToWidthMap;
1416 if ([(subFont ? subFont : font) numberOfGlyphs] < INITIAL_BLOCK_SIZE)
1417 blockSize = [font numberOfGlyphs];
1419 blockSize = INITIAL_BLOCK_SIZE;
1422 blockSize = INCREMENTAL_BLOCK_SIZE;
1423 start = (glyphID / blockSize) * blockSize;
1424 end = ((unsigned)start) + blockSize;
1428 LOG(FontCache, "%@ (0x%04x) adding widths for range 0x%04x to 0x%04x", font, glyphID, start, end);
1430 map->startRange = start;
1431 map->endRange = end;
1432 count = end - start + 1;
1434 map->widths = (WidthEntry *)malloc (count * sizeof(WidthEntry));
1436 for (i = 0; i < count; i++){
1437 map->widths[i].width = UNINITIALIZED_GLYPH_WIDTH;
1443 WidthMap *lastMap = *rootMap;
1444 while (lastMap->next != 0)
1445 lastMap = lastMap->next;
1446 lastMap->next = map;
1450 LOG(FontCache, "%@ total time to advances lookup %f seconds", font, totalCGGetAdvancesTime);
1456 - (void)_initializeATSUStyle
1458 // The two NSFont calls in this method (pointSize and _atsFontID)
1459 // are both exception-safe.
1461 if (!ATSUStyleInitialized){
1464 status = ATSUCreateStyle(&_ATSUSstyle);
1466 FATAL_ALWAYS ("ATSUCreateStyle failed (%d)", status);
1468 ATSUFontID fontID = [font _atsFontID];
1470 ATSUDisposeStyle(_ATSUSstyle);
1471 ERROR ("unable to get ATSUFontID for %@", font);
1475 CGAffineTransform transform = CGAffineTransformMakeScale (1,-1);
1476 Fixed fontSize = FloatToFixed([font pointSize]);
1477 ATSUAttributeTag styleTags[] = { kATSUSizeTag, kATSUFontTag, kATSUFontMatrixTag};
1478 ByteCount styleSizes[] = { sizeof(Fixed), sizeof(ATSUFontID), sizeof(CGAffineTransform) };
1479 ATSUAttributeValuePtr styleValues[] = { &fontSize, &fontID, &transform };
1480 status = ATSUSetAttributes (_ATSUSstyle, 3, styleTags, styleSizes, styleValues);
1482 FATAL_ALWAYS ("ATSUSetAttributes failed (%d)", status);
1484 ATSUStyleInitialized = YES;
1488 - (ATSUTextLayout)_createATSUTextLayoutForRun:(const WebCoreTextRun *)run style:(const WebCoreTextStyle *)style
1490 // The only Cocoa calls here are to NSGraphicsContext and the self
1491 // call to _initializeATSUStyle, which are all exception-safe.
1493 ATSUTextLayout layout;
1494 UniCharCount runLength;
1497 [self _initializeATSUStyle];
1499 // FIXME: This is missing the following features that the CoreGraphics code path has:
1500 // - Both \n and nonbreaking space render as a space.
1501 // - All other control characters must not render at all (other code path uses zero-width spaces).
1503 runLength = run->to - run->from;
1504 status = ATSUCreateTextLayoutWithTextPtr(
1506 run->from, // offset
1507 runLength, // length
1508 run->length, // total length
1510 &runLength, // length of style run
1514 FATAL_ALWAYS ("ATSUCreateTextLayoutWithTextPtr failed(%d)", status);
1516 CGContextRef cgContext = (CGContextRef)[[NSGraphicsContext currentContext] graphicsPort];
1517 ATSLineLayoutOptions lineLayoutOptions = (kATSLineFractDisable | kATSLineDisableAutoAdjustDisplayPos | kATSLineUseDeviceMetrics |
1518 kATSLineKeepSpacesOutOfMargin | kATSLineHasNoHangers);
1519 Boolean rtl = style->rtl;
1520 ATSUAttributeTag tags[] = { kATSUCGContextTag, kATSULineLayoutOptionsTag, kATSULineDirectionTag };
1521 ByteCount sizes[] = { sizeof(CGContextRef), sizeof(ATSLineLayoutOptions), sizeof(Boolean) };
1522 ATSUAttributeValuePtr values[] = { &cgContext, &lineLayoutOptions, &rtl };
1524 status = ATSUSetLayoutControls(layout, 3, tags, sizes, values);
1526 FATAL_ALWAYS ("ATSUSetLayoutControls failed(%d)", status);
1528 status = ATSUSetTransientFontMatching (layout, YES);
1530 FATAL_ALWAYS ("ATSUSetTransientFontMatching failed(%d)", status);
1536 - (ATSTrapezoid)_trapezoidForRun:(const WebCoreTextRun *)run style:(const WebCoreTextStyle *)style atPoint:(NSPoint )p
1538 // The only Cocoa call here is the self call to
1539 // _createATSUTextLayoutForRun:, which is exception-safe.
1543 if (run->to - run->from <= 0){
1544 ATSTrapezoid nilTrapezoid = { {0,0} , {0,0}, {0,0}, {0,0} };
1545 return nilTrapezoid;
1548 ATSUTextLayout layout = [self _createATSUTextLayoutForRun:run style:style];
1550 ATSTrapezoid firstGlyphBounds;
1551 ItemCount actualNumBounds;
1552 status = ATSUGetGlyphBounds (layout, FloatToFixed(p.x), FloatToFixed(p.y), run->from, run->to - run->from, kATSUseDeviceOrigins, 1, &firstGlyphBounds, &actualNumBounds);
1554 FATAL_ALWAYS ("ATSUGetGlyphBounds() failed(%d)", status);
1556 if (actualNumBounds != 1)
1557 FATAL_ALWAYS ("unexpected result from ATSUGetGlyphBounds(): actualNumBounds(%d) != 1", actualNumBounds);
1559 ATSUDisposeTextLayout (layout); // Ignore the error. Nothing we can do anyway.
1561 return firstGlyphBounds;
1565 - (float)_ATSU_floatWidthForRun:(const WebCoreTextRun *)run style:(const WebCoreTextStyle *)style
1567 ATSTrapezoid oGlyphBounds;
1569 oGlyphBounds = [self _trapezoidForRun:run style:style atPoint:NSMakePoint (0,0)];
1572 MAX(FixedToFloat(oGlyphBounds.upperRight.x), FixedToFloat(oGlyphBounds.lowerRight.x)) -
1573 MIN(FixedToFloat(oGlyphBounds.upperLeft.x), FixedToFloat(oGlyphBounds.lowerLeft.x));
1578 // Be sure to free the run.characters allocated by this function.
1579 static WebCoreTextRun reverseCharactersInRun(const WebCoreTextRun *run)
1581 WebCoreTextRun swappedRun;
1584 UniChar *swappedCharacters = (UniChar *)malloc(sizeof(UniChar)*run->length);
1585 for (i = 0; i < run->length; i++) {
1586 swappedCharacters[i] = run->characters[run->length-i-1];
1588 swappedRun.characters = swappedCharacters;
1589 swappedRun.from = run->length - (run->to == -1 ? (int)run->length : run->to);
1590 swappedRun.to = run->length - (run->from == -1 ? 0 : run->from);
1591 swappedRun.length = run->length;
1596 - (void)_ATSU_drawHighlightForRun:(const WebCoreTextRun *)run style:(const WebCoreTextStyle *)style geometry:(const WebCoreTextGeometry *)geometry
1598 // The only Cocoa calls made here are to NSColor and NSBezierPath,
1599 // plus the self calls to _createATSUTextLayoutForRun: and
1600 // _trapezoidForRun:. These are all exception-safe.
1602 ATSUTextLayout layout;
1604 float selectedLeftX;
1605 const WebCoreTextRun *aRun = run;
1606 WebCoreTextRun swappedRun;
1608 if (style->backgroundColor == nil)
1611 if (style->visuallyOrdered) {
1612 swappedRun = reverseCharactersInRun(run);
1623 int runLength = to - from;
1624 if (runLength <= 0){
1628 layout = [self _createATSUTextLayoutForRun:aRun style:style];
1630 WebCoreTextRun leadingRun = *aRun;
1631 leadingRun.from = 0;
1632 leadingRun.to = run->from;
1634 // ATSU provides the bounds of the glyphs for the run with an origin of
1635 // (0,0), so we need to find the width of the glyphs immediately before
1636 // the actually selected glyphs.
1637 ATSTrapezoid leadingTrapezoid = [self _trapezoidForRun:&leadingRun style:style atPoint:geometry->point];
1638 ATSTrapezoid selectedTrapezoid = [self _trapezoidForRun:run style:style atPoint:geometry->point];
1640 float backgroundWidth =
1641 MAX(FixedToFloat(selectedTrapezoid.upperRight.x), FixedToFloat(selectedTrapezoid.lowerRight.x)) -
1642 MIN(FixedToFloat(selectedTrapezoid.upperLeft.x), FixedToFloat(selectedTrapezoid.lowerLeft.x));
1645 selectedLeftX = geometry->point.x;
1647 selectedLeftX = MIN(FixedToFloat(leadingTrapezoid.upperRight.x), FixedToFloat(leadingTrapezoid.lowerRight.x));
1649 [style->backgroundColor set];
1651 float yPos = geometry->useFontMetricsForSelectionYAndHeight ? geometry->point.y - [self ascent] : geometry->selectionY;
1652 float height = geometry->useFontMetricsForSelectionYAndHeight ? [self lineSpacing] : geometry->selectionHeight;
1653 if (style->rtl || style->visuallyOrdered){
1654 WebCoreTextRun completeRun = *aRun;
1655 completeRun.from = 0;
1656 completeRun.to = aRun->length;
1657 float completeRunWidth = [self floatWidthForRun:&completeRun style:style widths:0];
1658 [NSBezierPath fillRect:NSMakeRect(geometry->point.x + completeRunWidth - (selectedLeftX-geometry->point.x) - backgroundWidth, yPos, backgroundWidth, height)];
1661 [NSBezierPath fillRect:NSMakeRect(selectedLeftX, yPos, backgroundWidth, height)];
1664 ATSUDisposeTextLayout (layout); // Ignore the error. Nothing we can do anyway.
1666 if (style->visuallyOrdered)
1667 free ((void *)swappedRun.characters);
1671 - (void)_ATSU_drawRun:(const WebCoreTextRun *)run style:(const WebCoreTextStyle *)style geometry:(const WebCoreTextGeometry *)geometry
1673 // The only Cocoa calls made here are to NSColor, plus the self
1674 // calls to _createATSUTextLayoutForRun: and
1675 // _ATSU_drawHighlightForRun:. These are all exception-safe.
1677 ATSUTextLayout layout;
1680 const WebCoreTextRun *aRun = run;
1681 WebCoreTextRun swappedRun;
1683 if (style->visuallyOrdered) {
1684 swappedRun = reverseCharactersInRun(run);
1695 int runLength = to - from;
1699 layout = [self _createATSUTextLayoutForRun:aRun style:style];
1701 if (style->backgroundColor != nil)
1702 [self _ATSU_drawHighlightForRun:run style:style geometry:geometry];
1704 [style->textColor set];
1706 status = ATSUDrawText(layout,
1709 FloatToFixed(geometry->point.x), // these values are
1710 FloatToFixed(geometry->point.y)); // also of type Fixed
1711 if (status != noErr){
1712 // Nothing to do but report the error (dev build only).
1713 ERROR ("ATSUDrawText() failed(%d)", status);
1716 ATSUDisposeTextLayout (layout); // Ignore the error. Nothing we can do anyway.
1718 if (style->visuallyOrdered)
1719 free ((void *)swappedRun.characters);
1722 - (int)_ATSU_pointToOffset:(const WebCoreTextRun *)run style:(const WebCoreTextStyle *)style position:(int)x reversed:(BOOL)reversed includePartialGlyphs:(BOOL)includePartialGlyphs
1724 // The only Cocoa calls made here is to the self call to
1725 // _createATSUTextLayoutForRun:. This is exception-safe.
1727 unsigned offset = 0;
1728 ATSUTextLayout layout;
1729 UniCharArrayOffset primaryOffset = 0;
1730 UniCharArrayOffset secondaryOffset = 0;
1733 const WebCoreTextRun *aRun = run;
1734 WebCoreTextRun swappedRun;
1736 // Reverse the visually ordered characters. ATSU will re-reverse. Ick!
1737 if (style->visuallyOrdered) {
1738 swappedRun = reverseCharactersInRun(run);
1742 layout = [self _createATSUTextLayoutForRun:aRun style:style];
1744 primaryOffset = aRun->from;
1746 // FIXME: No idea how to avoid including partial glyphs. Not even sure if that's the behavior
1748 status = ATSUPositionToOffset(layout, FloatToFixed(x), FloatToFixed(-1), &primaryOffset, &isLeading, &secondaryOffset);
1749 if (status == noErr){
1750 offset = (unsigned)primaryOffset;
1753 // Failed to find offset! Return 0 offset.
1756 if (style->visuallyOrdered) {
1757 free ((void *)swappedRun.characters);
1760 return offset - aRun->from;
1763 - (int)_CG_pointToOffset:(const WebCoreTextRun *)run style:(const WebCoreTextStyle *)style position:(int)x reversed:(BOOL)reversed includePartialGlyphs:(BOOL)includePartialGlyphs
1765 float delta = (float)x;
1767 unsigned offset = run->from;
1768 CharacterWidthIterator widthIterator;
1770 initializeCharacterWidthIterator(&widthIterator, self, run, style);
1773 width = [self floatWidthForRun:run style:style widths:nil];
1775 while (offset < run->length) {
1776 float w = widthForNextCharacter(&widthIterator, 0, 0);
1777 if (w == INVALID_WIDTH) {
1778 // Something very bad happened, like we only have half of a surrogate pair.
1783 if (includePartialGlyphs)
1788 if (includePartialGlyphs)
1791 offset = widthIterator.currentCharacter;
1795 while (offset < run->length) {
1796 float w = widthForNextCharacter(&widthIterator, 0, 0);
1797 if (w == INVALID_WIDTH) {
1798 // Something very bad happened, like we only have half of a surrogate pair.
1803 if (includePartialGlyphs)
1808 if (includePartialGlyphs)
1811 offset = widthIterator.currentCharacter;
1816 return offset - run->from;
1821 // ------------------- Private functions -------------------
1823 static void freeWidthMap(WidthMap *map)
1826 WidthMap *next = map->next;
1834 static void freeGlyphMap(GlyphMap *map)
1837 GlyphMap *next = map->next;
1845 static void freeUnicodeGlyphMap(UnicodeGlyphMap *map)
1848 UnicodeGlyphMap *next = map->next;
1856 static inline ATSGlyphRef glyphForCharacter (GlyphMap *map, UniChar c, NSFont **font)
1862 if (c >= map->startRange && c <= map->endRange){
1863 *font = map->glyphs[c-map->startRange].font;
1864 return map->glyphs[c-map->startRange].glyph;
1872 static inline ATSGlyphRef glyphForUnicodeCharacter (UnicodeGlyphMap *map, UnicodeChar c, NSFont **font)
1878 if (c >= map->startRange && c <= map->endRange){
1879 *font = map->glyphs[c-map->startRange].font;
1880 return map->glyphs[c-map->startRange].glyph;
1889 static double totalCGGetAdvancesTime = 0;
1892 static inline SubstituteFontWidthMap *mapForSubstituteFont(WebTextRenderer *renderer, NSFont *font)
1896 for (i = 0; i < renderer->numSubstituteFontWidthMaps; i++){
1897 if (font == renderer->substituteFontWidthMaps[i].font)
1898 return &renderer->substituteFontWidthMaps[i];
1901 if (renderer->numSubstituteFontWidthMaps == renderer->maxSubstituteFontWidthMaps){
1902 renderer->maxSubstituteFontWidthMaps = renderer->maxSubstituteFontWidthMaps * 2;
1903 renderer->substituteFontWidthMaps = realloc (renderer->substituteFontWidthMaps, renderer->maxSubstituteFontWidthMaps * sizeof(SubstituteFontWidthMap));
1904 for (i = renderer->numSubstituteFontWidthMaps; i < renderer->maxSubstituteFontWidthMaps; i++){
1905 renderer->substituteFontWidthMaps[i].font = 0;
1906 renderer->substituteFontWidthMaps[i].map = 0;
1910 renderer->substituteFontWidthMaps[renderer->numSubstituteFontWidthMaps].font = font;
1911 return &renderer->substituteFontWidthMaps[renderer->numSubstituteFontWidthMaps++];
1914 static void initializeCharacterWidthIterator (CharacterWidthIterator *iterator, WebTextRenderer *renderer, const WebCoreTextRun *run , const WebCoreTextStyle *style)
1916 iterator->renderer = renderer;
1917 iterator->run = run;
1918 iterator->style = style;
1919 iterator->currentCharacter = run->from;
1920 iterator->runWidthSoFar = 0;
1922 // If the padding is non-zero, count the number of spaces in the run
1923 // and divide that by the padding for per space addition.
1924 iterator->padding = style->padding;
1925 if (iterator->padding > 0){
1927 int from = run->from;
1928 int len = run->to - from;
1930 for (k = from; k < from + len; k++) {
1931 if (isSpace(run->characters[k])) {
1935 iterator->padPerSpace = CEIL_TO_INT ((((float)style->padding) / ((float)numSpaces)));
1938 iterator->padPerSpace = 0;
1941 // Calculate width up to starting position of the run. This is
1942 // necessary to ensure that our rounding hacks are always consistently
1944 if (run->from != 0){
1945 WebCoreTextRun startPositionRun = *run;
1946 startPositionRun.from = 0;
1947 startPositionRun.to = run->from;
1948 CharacterWidthIterator startPositionIterator;
1949 initializeCharacterWidthIterator (&startPositionIterator, renderer, &startPositionRun, style);
1951 while (startPositionIterator.currentCharacter < (unsigned)startPositionRun.to){
1952 widthForNextCharacter(&startPositionIterator, 0, 0);
1954 iterator->widthToStart = startPositionIterator.runWidthSoFar;
1957 iterator->widthToStart = 0;
1960 static inline float ceilCurrentWidth (CharacterWidthIterator *iterator)
1962 float delta = CEIL_TO_INT(iterator->widthToStart + iterator->runWidthSoFar) - (iterator->widthToStart + iterator->runWidthSoFar);
1963 iterator->runWidthSoFar += delta;
1967 // According to http://www.unicode.org/Public/UNIDATA/UCD.html#Canonical_Combining_Class_Values
1968 #define HIRAGANA_KATAKANA_VOICING_MARKS 8
1970 // Return INVALID_WIDTH if an error is encountered or we're at the end of the range in the run.
1971 static float widthForNextCharacter(CharacterWidthIterator *iterator, ATSGlyphRef *glyphUsed, NSFont **fontUsed)
1973 WebTextRenderer *renderer = iterator->renderer;
1974 const WebCoreTextRun *run = iterator->run;
1975 const WebCoreTextStyle *style = iterator->style;
1976 unsigned currentCharacter = iterator->currentCharacter;
1978 NSFont *_fontUsed = nil;
1979 ATSGlyphRef _glyphUsed;
1982 fontUsed = &_fontUsed;
1984 glyphUsed = &_glyphUsed;
1986 if (currentCharacter >= (unsigned)run->to)
1987 // Error! Offset specified beyond end of run.
1988 return INVALID_WIDTH;
1990 const UniChar *cp = &run->characters[currentCharacter];
1992 UnicodeChar c = *cp;
1994 if (IsLowSurrogatePair(c))
1995 return INVALID_WIDTH;
1997 // Do we have a surrogate pair? If so, determine the full Unicode (32 bit)
1998 // code point before glyph lookup.
1999 unsigned clusterLength = 1;
2000 if (IsHighSurrogatePair(c)) {
2001 // Make sure we have another character and it's a low surrogate.
2003 if (currentCharacter + 1 >= run->length || !IsLowSurrogatePair((low = cp[1]))) {
2004 // Error! The second component of the surrogate pair is missing.
2005 return INVALID_WIDTH;
2008 c = UnicodeValueForSurrogatePair(c, low);
2012 // If small-caps convert lowercase to upper.
2013 BOOL useSmallCapsFont = NO;
2014 if (renderer->isSmallCapsRenderer) {
2015 if (!u_isUUppercase(c)) {
2016 // Only use small cap font if the the uppercase version of the character
2017 // is different than the lowercase.
2018 UnicodeChar newC = u_toupper(c);
2020 useSmallCapsFont = YES;
2026 // Deal with Hiragana and Katakana voiced and semi-voiced syllables. Normalize into
2027 // composed form, and then look for glyph with base + combined mark.
2028 if (c >= 0x3041 && c <= 0x30FE) { // Early out to minimize performance impact. Do we have a Hiragana/Katakana character?
2029 if (currentCharacter < (unsigned)run->to) {
2030 UnicodeChar nextCharacter = run->characters[currentCharacter+1];
2031 if (u_getCombiningClass(nextCharacter) == HIRAGANA_KATAKANA_VOICING_MARKS) {
2032 UChar normalizedCharacters[2] = { 0, 0 };
2033 UErrorCode uStatus = 0;
2034 int32_t resultLength;
2036 // Normalize into composed form using 3.2 rules.
2037 resultLength = unorm_normalize(&run->characters[currentCharacter], 2,
2038 UNORM_NFC, UNORM_UNICODE_3_2,
2039 &normalizedCharacters[0], 2,
2041 if (resultLength == 1 && uStatus == 0){
2042 c = normalizedCharacters[0];
2050 c = u_charMirror(c);
2054 *glyphUsed = glyphForCharacter(renderer->characterToGlyphMap, c, fontUsed);
2055 if (*glyphUsed == nonGlyphID) {
2056 *glyphUsed = [renderer _extendCharacterToGlyphMapToInclude:c];
2059 *glyphUsed = glyphForUnicodeCharacter(renderer->unicodeCharacterToGlyphMap, c, fontUsed);
2060 if (*glyphUsed == nonGlyphID) {
2061 *glyphUsed = [renderer _extendUnicodeCharacterToGlyphMapToInclude:c];
2065 // Check to see if we're rendering in 'small-caps' mode.
2066 // ASSUMPTION: We assume the same font in a smaller size has
2067 // the same glyphs as the large font.
2068 if (useSmallCapsFont) {
2069 if (*fontUsed == nil)
2070 *fontUsed = [renderer _smallCapsFont];
2072 // Potential for optimization. This path should only be taken if we're
2073 // using a cached substituted font.
2074 *fontUsed = [[NSFontManager sharedFontManager] convertFont:*fontUsed toSize:[*fontUsed pointSize] * SMALLCAPS_FONTSIZE_MULTIPLIER];
2078 // Now that we have glyph and font, get its width.
2079 WebGlyphWidth width = widthForGlyph(renderer, *glyphUsed, *fontUsed);
2081 // We special case spaces in two ways when applying word rounding.
2082 // First, we round spaces to an adjusted width in all fonts.
2083 // Second, in fixed-pitch fonts we ensure that all characters that
2084 // match the width of the space character have the same width as the space character.
2085 if ((renderer->treatAsFixedPitch ? width == renderer->spaceWidth : *glyphUsed == renderer->spaceGlyph) && iterator->style->applyWordRounding)
2086 width = renderer->adjustedSpaceWidth;
2088 // Try to find a substitute font if this font didn't have a glyph for a character in the
2089 // string. If one isn't found we end up drawing and measuring the 0 glyph, usually a box.
2090 if (*glyphUsed == 0 && iterator->style->attemptFontSubstitution) {
2091 UniChar characterArray[2];
2092 unsigned characterArrayLength;
2095 characterArray[0] = c;
2096 characterArrayLength = 1;
2098 characterArray[0] = HighSurrogatePair(c);
2099 characterArray[1] = LowSurrogatePair(c);
2100 characterArrayLength = 2;
2103 NSFont *substituteFont = [renderer _substituteFontForCharacters:characterArray length:characterArrayLength
2104 families:iterator->style->families];
2105 if (substituteFont) {
2107 ATSGlyphRef localGlyphBuffer[MAX_GLYPH_EXPANSION];
2109 WebCoreTextRun clusterRun;
2110 WebCoreInitializeTextRun(&clusterRun, characterArray, characterArrayLength, 0, characterArrayLength);
2111 WebCoreTextStyle clusterStyle = *iterator->style;
2112 clusterStyle.padding = 0;
2113 clusterStyle.applyRunRounding = false;
2114 clusterStyle.attemptFontSubstitution = false;
2116 WebTextRenderer *substituteRenderer;
2117 substituteRenderer = [[WebTextRendererFactory sharedFactory] rendererWithFont:substituteFont usingPrinterFont:renderer->usingPrinterFont];
2118 width = [substituteRenderer
2119 _floatWidthForRun:&clusterRun
2123 glyphs: &localGlyphBuffer[0]
2125 numGlyphs:&cNumGlyphs];
2127 *fontUsed = substituteFont;
2128 *glyphUsed = localGlyphBuffer[0];
2130 if (c <= 0xFFFF && cNumGlyphs == 1 && localGlyphBuffer[0] != 0){
2131 [renderer _updateGlyphEntryForCharacter:c glyphID:localGlyphBuffer[0] font:substituteFont];
2137 *fontUsed = renderer->font;
2139 // Force characters that are used to determine word boundaries for the rounding hack
2140 // to be integer width, so following words will start on an integer boundary.
2141 if (isRoundingHackCharacter(c) && iterator->style->applyWordRounding) {
2142 width = CEIL_TO_INT(width);
2145 // Account for letter-spacing
2146 if (iterator->style->letterSpacing && width > 0)
2147 width += iterator->style->letterSpacing;
2149 // Account for padding. khtml uses space padding to justify text. We
2150 // distribute the specified padding over the available spaces in the run.
2152 if (iterator->padding > 0) {
2153 // Only use left over padding if note evenly divisible by
2154 // number of spaces.
2155 if (iterator->padding < iterator->padPerSpace){
2156 width += iterator->padding;
2157 iterator->padding = 0;
2160 width += iterator->padPerSpace;
2161 iterator->padding -= iterator->padPerSpace;
2165 // Account for word-spacing. We apply additional space between "words" by
2166 // adding width to the space character.
2167 if (currentCharacter > 0 && !isSpace(cp[-1]))
2168 width += iterator->style->wordSpacing;
2171 iterator->runWidthSoFar += width;
2173 // Advance past the character we just dealt with.
2174 currentCharacter += clusterLength;
2175 iterator->currentCharacter = currentCharacter;
2177 int len = run->to - run->from;
2179 // Account for float/integer impedance mismatch between CG and khtml. "Words" (characters
2180 // followed by a character defined by isSpace()) are always an integer width. We adjust the
2181 // width of the last character of a "word" to ensure an integer width. When we move khtml to
2182 // floats we can remove this (and related) hacks.
2184 // Check to see if the next character is a "RoundingHackCharacter", if so, adjust.
2185 if (currentCharacter < run->length && isRoundingHackCharacter(cp[clusterLength]) && iterator->style->applyWordRounding) {
2186 width += ceilCurrentWidth(iterator);
2188 else if (currentCharacter >= (unsigned)run->to && (len > 1 || run->length == 1) && iterator->style->applyRunRounding) {
2189 width += ceilCurrentWidth(iterator);
2196 static BOOL fillStyleWithAttributes(ATSUStyle style, NSFont *theFont)
2199 ATSUFontID fontId = [theFont _atsFontID];
2200 LOG (FontCache, "fillStyleWithAttributes: font = %p,%@, _atsFontID = %x\n", theFont, theFont, (unsigned)fontId);
2201 ATSUAttributeTag tag = kATSUFontTag;
2202 ByteCount size = sizeof(ATSUFontID);
2203 ATSUFontID *valueArray[1] = {&fontId};
2207 status = ATSUSetAttributes(style, 1, &tag, &size, (void *)valueArray);
2208 if (status != noErr){
2209 LOG (FontCache, "fillStyleWithAttributes failed(%d): font = %p,%@, _atsFontID = %x\n", (int)status, theFont, theFont, (unsigned)fontId);
2221 static BOOL shouldUseATSU(const WebCoreTextRun *run)
2224 const UniChar *characters = run->characters;
2225 int i, from = run->from, to = run->to;
2230 for (i = from; i < to; i++){
2232 if (c < 0x300) // Early continue to avoid other checks for the common case.
2235 if (c >= 0x300 && c <= 0x36F) // U+0300 through U+036F Combining diacritical marks
2237 if (c >= 0x20D0 && c <= 0x20FF) // U+20D0 through U+20FF Combining marks for symbols
2239 if (c >= 0xFE20 && c <= 0xFE2f) // U+FE20 through U+FE2F Combining half marks
2241 if (c >= 0x591 && c <= 0x1059) // U+0591 through U+1059 Arabic, Hebrew, Syriac, Thaana, Devanagari, Bengali, Gurmukhi, Gujarati, Oriya, Tamil, Telugu, Kannada, Malayalam, Sinhala, Thai, Lao, Tibetan, Myanmar
2243 if (c >= 0x1100 && c <= 0x11FF) // U+1100 through U+11FF Hangul Jamo (only Ancient Korean should be left here if you precompose; Modern Korean will be precomposed as a result of step A)
2245 if (c >= 0x1780 && c <= 0x18AF) // U+1780 through U+18AF Khmer, Mongolian
2247 if (c >= 0x1900 && c <= 0x194F) // U+1900 through U+194F Limbu (Unicode 4.0)