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 // the number of transparent pixels after the dot
643 - (int)misspellingLinePatternGapWidth
648 - (void)drawLineForMisspelling:(NSPoint)point withWidth:(int)width
650 // Constants for pattern color
651 static NSColor *spellingPatternColor = nil;
652 static bool usingDot = false;
653 int patternHeight = [self misspellingLineThickness];
654 int patternWidth = [self misspellingLinePatternWidth];
656 // Initialize pattern color if needed
657 if (!spellingPatternColor) {
658 NSImage *image = [NSImage imageNamed:@"SpellingDot"];
659 ASSERT(image); // if image is not available, we want to know
660 NSColor *color = (image ? [NSColor colorWithPatternImage:image] : nil);
664 color = [NSColor redColor];
665 spellingPatternColor = [color retain];
668 // Make sure to draw only complete dots.
669 // NOTE: Code here used to shift the underline to the left and increase the width
670 // to make sure everything gets underlined, but that results in drawing out of
671 // bounds (e.g. when at the edge of a view) and could make it appear that the
672 // space between adjacent misspelled words was underlined.
674 // allow slightly more considering that the pattern ends with a transparent pixel
675 int widthMod = width % patternWidth;
676 if (patternWidth - widthMod > [self misspellingLinePatternGapWidth])
680 // Compute the appropriate phase relative to the top level view in the window.
681 NSPoint originInWindow = [[NSView focusView] convertPoint:point toView:nil];
682 // WebCore may translate the focus, and thus need an extra phase correction
683 NSPoint extraPhase = [[WebGraphicsBridge sharedBridge] additionalPatternPhase];
684 originInWindow.x += extraPhase.x;
685 originInWindow.y += extraPhase.y;
686 CGSize phase = CGSizeMake(fmodf(originInWindow.x, patternWidth), fmodf(originInWindow.y, patternHeight));
689 NSGraphicsContext *currentContext = [NSGraphicsContext currentContext];
690 [currentContext saveGraphicsState];
691 [spellingPatternColor set];
692 CGContextSetPatternPhase((CGContextRef)[currentContext graphicsPort], phase);
693 NSRectFillUsingOperation(NSMakeRect(point.x, point.y, width, patternHeight), NSCompositeSourceOver);
694 [currentContext restoreGraphicsState];
697 - (int)pointToOffset:(const WebCoreTextRun *)run style:(const WebCoreTextStyle *)style position:(int)x reversed:(BOOL)reversed includePartialGlyphs:(BOOL)includePartialGlyphs
699 if (style->smallCaps && !isSmallCapsRenderer) {
700 return [[self _smallCapsRenderer] pointToOffset:run style:style position:x reversed:reversed includePartialGlyphs:includePartialGlyphs];
703 if (shouldUseATSU(run))
704 return [self _ATSU_pointToOffset:run style:style position:x reversed:reversed includePartialGlyphs:includePartialGlyphs];
705 return [self _CG_pointToOffset:run style:style position:x reversed:reversed includePartialGlyphs:includePartialGlyphs];
711 // ------------------- Private API -------------------
714 @implementation WebTextRenderer (WebInternal)
716 + (void)_setAlwaysUseATSU:(BOOL)f
721 - (void)_setIsSmallCapsRenderer:(BOOL)flag
723 isSmallCapsRenderer = flag;
726 - (BOOL)_isSmallCapsRenderer
728 return isSmallCapsRenderer;
731 - (WebTextRenderer *)_smallCapsRenderer
733 if (!smallCapsRenderer) {
735 smallCapsRenderer = [[WebTextRenderer alloc] initWithFont:font usingPrinterFont:usingPrinterFont];
737 if (ASSERT_DISABLED) {
738 NSLog(@"Uncaught exception - %@\n", localException);
740 ASSERT_WITH_MESSAGE(0, "Uncaught exception - %@", localException);
744 [smallCapsRenderer _setIsSmallCapsRenderer:YES];
746 return smallCapsRenderer;
749 - (NSFont *)_smallCapsFont
752 smallCapsFont = [[[NSFontManager sharedFontManager] convertFont:font toSize:([font pointSize] * SMALLCAPS_FONTSIZE_MULTIPLIER)] screenFont];
753 return usingPrinterFont ? [smallCapsFont printerFont] : smallCapsFont;
756 static inline BOOL fontContainsString(NSFont *font, NSString *string)
758 NSCharacterSet *set = [[font coveredCharacterSet] invertedSet];
759 return set && [string rangeOfCharacterFromSet:set].location == NSNotFound;
762 - (NSFont *)_substituteFontForString: (NSString *)string families: (NSString **)families
764 NSFont *substituteFont = nil;
766 // First search the CSS family fallback list. Start at 1 (2nd font)
767 // because we've already failed on the first lookup.
768 NSString *family = nil;
770 while (families && families[i] != 0) {
771 family = families[i++];
772 substituteFont = [[WebTextRendererFactory sharedFactory] cachedFontFromFamily: family traits:[[NSFontManager sharedFontManager] traitsOfFont:font] size:[font pointSize]];
773 if (substituteFont) {
774 if (fontContainsString(substituteFont, string))
776 substituteFont = nil;
780 // Now do string based lookup.
781 if (substituteFont == nil)
782 substituteFont = [NSFont findFontLike:font forString:string withRange:NSMakeRange (0,[string length]) inLanguage:[NSLanguage defaultLanguage]];
784 // Now do character based lookup.
785 if (substituteFont == nil && [string length] == 1)
786 substituteFont = [NSFont findFontLike:font forCharacter: [string characterAtIndex: 0] inLanguage:[NSLanguage defaultLanguage]];
788 // Get the screen or printer variation of the font.
789 substituteFont = usingPrinterFont ? [substituteFont printerFont] : [substituteFont screenFont];
791 if ([substituteFont isEqual: font])
792 substituteFont = nil;
794 // Now that we have a substitute font, attempt to match it to the best variation. If we have
795 // a good match return that, otherwise return the font the AppKit has found.
796 NSFontManager *manager = [NSFontManager sharedFontManager];
797 NSFont *substituteFont2 = [manager fontWithFamily:(NSString *)[substituteFont familyName] traits:[manager traitsOfFont:font] weight:[manager weightOfFont:font] size:[font pointSize]];
799 substituteFont = substituteFont2;
801 // Now, finally, get the printer or screen variation.
802 substituteFont = usingPrinterFont ? [substituteFont printerFont] : [substituteFont screenFont];
804 return substituteFont;
807 - (NSFont *)_substituteFontForCharacters: (const unichar *)characters length: (int)numCharacters families: (NSString **)families
809 NSString *string = [[NSString alloc] initWithCharactersNoCopy:(unichar *)characters length: numCharacters freeWhenDone: NO];
810 NSFont *substituteFont = [self _substituteFontForString: string families: families];
812 return substituteFont;
815 - (void)_convertCharacters: (const UniChar *)characters length: (unsigned)numCharacters toGlyphs: (ATSGlyphVector *)glyphs
817 OSStatus status = ATSUConvertCharToGlyphs(styleGroup, characters, 0, numCharacters, 0, glyphs);
818 if (status != noErr){
819 FATAL_ALWAYS ("unable to get glyphsfor %@ %f error = (%d)", self, [font displayName], [font pointSize], status);
824 ATSLayoutRecord *glyphRecord;
825 for (i = 0; i < numCharacters; i++) {
826 glyphRecord = (ATSLayoutRecord *)glyphs->firstRecord;
827 for (i = 0; i < numCharacters; i++) {
828 if (glyphRecord->glyphID != 0)
830 glyphRecord = (ATSLayoutRecord *)((char *)glyphRecord + glyphs->recordSize);
833 printf ("For %s found %d glyphs in range 0x%04x to 0x%04x\n", [[font displayName] cString], foundGlyphs, characters[0], characters[numCharacters-1]);
837 - (void)_convertUnicodeCharacters: (const UnicodeChar *)characters length: (unsigned)numCharacters toGlyphs: (ATSGlyphVector *)glyphs
839 UniChar localBuffer[LOCAL_BUFFER_SIZE];
840 UniChar *buffer = localBuffer;
841 unsigned i, bufPos = 0;
843 if (numCharacters*2 > LOCAL_BUFFER_SIZE) {
844 buffer = (UniChar *)malloc(sizeof(UniChar) * numCharacters * 2);
847 for (i = 0; i < numCharacters; i++) {
848 UnicodeChar c = characters[i];
849 UniChar h = HighSurrogatePair(c);
850 UniChar l = LowSurrogatePair(c);
851 buffer[bufPos++] = h;
852 buffer[bufPos++] = l;
855 OSStatus status = ATSUConvertCharToGlyphs(styleGroup, buffer, 0, numCharacters*2, 0, glyphs);
856 if (status != noErr){
857 FATAL_ALWAYS ("unable to get glyphsfor %@ %f error = (%d)", self, [font displayName], [font pointSize], status);
860 if (buffer != localBuffer) {
865 // Nasty hack to determine if we should round or ceil space widths.
866 // If the font is monospace or fake monospace we ceil to ensure that
867 // every character and the space are the same width. Otherwise we round.
868 - (BOOL)_computeWidthForSpace
870 spaceGlyph = [self _extendCharacterToGlyphMapToInclude:SPACE];
871 if (spaceGlyph == 0) {
875 float width = widthForGlyph(self, spaceGlyph, 0);
878 treatAsFixedPitch = [[WebTextRendererFactory sharedFactory] isFontFixedPitch:font];
879 adjustedSpaceWidth = treatAsFixedPitch ? CEIL_TO_INT(width) : ROUND_TO_INT(width);
887 if (ATSUCreateStyle(&fontStyle) != noErr)
890 if (!fillStyleWithAttributes(fontStyle, font)) {
891 ATSUDisposeStyle(fontStyle);
895 if (ATSUGetStyleGroup(fontStyle, &styleGroup) != noErr) {
896 ATSUDisposeStyle(fontStyle);
900 ATSUDisposeStyle(fontStyle);
902 if (![self _computeWidthForSpace]) {
903 freeGlyphMap(characterToGlyphMap);
904 characterToGlyphMap = NULL;
905 ATSUDisposeStyleGroup(styleGroup);
913 static NSString *pathFromFont (NSFont *font)
915 UInt8 _filePathBuffer[PATH_MAX];
916 NSString *filePath = nil;
918 OSStatus status = ATSFontGetFileSpecification(
919 ATSFontRefFromNSFont(font),
921 if (status == noErr){
924 err = FSpMakeFSRef(&oFile,&fileRef);
926 status = FSRefMakePath(&fileRef,_filePathBuffer, PATH_MAX);
927 if (status == noErr){
928 filePath = [NSString stringWithUTF8String:&_filePathBuffer[0]];
935 // Useful page for testing http://home.att.net/~jameskass
936 static void _drawGlyphs(NSFont *font, NSColor *color, CGGlyph *glyphs, CGSize *advances, float x, float y, int numGlyphs)
938 CGContextRef cgContext;
940 if ([WebTextRenderer shouldBufferTextDrawing] && [[WebTextRendererFactory sharedFactory] coalesceTextDrawing]){
941 // Add buffered glyphs and advances
942 // FIXME: If we ever use this again, need to add RTL.
943 WebGlyphBuffer *gBuffer = [[WebTextRendererFactory sharedFactory] glyphBufferForFont: font andColor: color];
944 [gBuffer addGlyphs: glyphs advances: advances count: numGlyphs at: x : y];
947 NSGraphicsContext *gContext = [NSGraphicsContext currentContext];
948 cgContext = (CGContextRef)[gContext graphicsPort];
949 // Setup the color and font.
951 bool originalShouldUseFontSmoothing;
953 originalShouldUseFontSmoothing = CGContextGetShouldSmoothFonts (cgContext);
954 CGContextSetShouldSmoothFonts (cgContext, [WebView _shouldUseFontSmoothing]);
956 #if BUILDING_ON_PANTHER
957 if ([gContext isDrawingToScreen]){
958 NSFont *screenFont = [font screenFont];
959 if (screenFont != font){
960 // We are getting this in too many places (3406411); use ERROR so it only prints on
961 // debug versions for now. (We should debug this also, eventually).
962 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]);
967 NSFont *printerFont = [font printerFont];
968 if (printerFont != font){
969 NSLog (@"Attempting to set non-printer font (%@) when printing. Using printer font anyway, may result in incorrect metrics.", [[[font fontDescriptor] fontAttributes] objectForKey: NSFontNameAttribute]);
976 if ([gContext isDrawingToScreen]){
977 drawFont = [font screenFont];
978 if (drawFont != font){
979 // We are getting this in too many places (3406411); use ERROR so it only prints on
980 // debug versions for now. (We should debug this also, eventually).
981 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]);
985 drawFont = [font printerFont];
986 if (drawFont != font){
987 NSLog (@"Attempting to set non-printer font (%@) when printing. Using printer font anyway, may result in incorrect metrics.", [[[font fontDescriptor] fontAttributes] objectForKey: NSFontNameAttribute]);
991 NSView *v = [NSView focusView];
993 CGContextSetFont (cgContext, [drawFont _backingCGSFont]);
995 // Deal will flipping flippyness.
996 const float *matrix = [drawFont matrix];
997 float flip = [v isFlipped] ? -1 : 1;
998 CGContextSetTextMatrix(cgContext, CGAffineTransformMake(matrix[0], matrix[1] * flip, matrix[2], matrix[3] * flip, matrix[4], matrix[5]));
999 CGContextSetFontRenderingMode (cgContext, _AppkitGetCGRenderingMode(drawFont));
1000 CGContextSetFontSize(cgContext, 1.0);
1005 CGContextSetTextPosition (cgContext, x, y);
1006 CGContextShowGlyphsWithAdvances (cgContext, glyphs, advances, numGlyphs);
1008 CGContextSetShouldSmoothFonts (cgContext, originalShouldUseFontSmoothing);
1013 - (void)_CG_drawHighlightForRun:(const WebCoreTextRun *)run style:(const WebCoreTextStyle *)style geometry:(const WebCoreTextGeometry *)geometry
1015 if (run->length == 0)
1018 CharacterWidthIterator widthIterator;
1019 WebCoreTextRun completeRun = *run;
1020 completeRun.from = 0;
1021 completeRun.to = run->length;
1022 initializeCharacterWidthIterator(&widthIterator, self, &completeRun, style);
1024 float startPosition = 0;
1026 // The starting point needs to be adjusted to account for the width of
1027 // the glyphs at the start of the run.
1028 while (widthIterator.currentCharacter < (unsigned)run->from) {
1029 startPosition += widthForNextCharacter(&widthIterator, 0, 0);
1031 float startX = startPosition + geometry->point.x;
1033 float backgroundWidth = 0.0;
1034 while (widthIterator.currentCharacter < (unsigned)run->to) {
1035 backgroundWidth += widthForNextCharacter(&widthIterator, 0, 0);
1038 if (style->backgroundColor != nil){
1039 // Calculate the width of the selection background by adding
1040 // up the advances of all the glyphs in the selection.
1042 [style->backgroundColor set];
1044 float yPos = geometry->useFontMetricsForSelectionYAndHeight ? geometry->point.y - [self ascent] - (lineGap/2) : geometry->selectionY;
1045 float height = geometry->useFontMetricsForSelectionYAndHeight ? [self lineSpacing] : geometry->selectionHeight;
1047 float completeRunWidth = startPosition + backgroundWidth;
1048 while (widthIterator.currentCharacter < run->length) {
1049 completeRunWidth += widthForNextCharacter(&widthIterator, 0, 0);
1052 [NSBezierPath fillRect:NSMakeRect(geometry->point.x + completeRunWidth - startPosition - backgroundWidth, yPos, backgroundWidth, height)];
1055 [NSBezierPath fillRect:NSMakeRect(startX, yPos, backgroundWidth, height)];
1061 - (void)_CG_drawRun:(const WebCoreTextRun *)run style:(const WebCoreTextStyle *)style geometry:(const WebCoreTextGeometry *)geometry
1063 float *widthBuffer, localWidthBuffer[LOCAL_BUFFER_SIZE];
1064 CGGlyph *glyphBuffer, localGlyphBuffer[LOCAL_BUFFER_SIZE];
1065 NSFont **fontBuffer, *localFontBuffer[LOCAL_BUFFER_SIZE];
1066 CGSize *advances, localAdvanceBuffer[LOCAL_BUFFER_SIZE];
1067 int numGlyphs = 0, i;
1069 unsigned length = run->length;
1071 if (run->length == 0)
1074 if (length*MAX_GLYPH_EXPANSION > LOCAL_BUFFER_SIZE) {
1075 advances = (CGSize *)calloc(length*MAX_GLYPH_EXPANSION, sizeof(CGSize));
1076 widthBuffer = (float *)calloc(length*MAX_GLYPH_EXPANSION, sizeof(float));
1077 glyphBuffer = (CGGlyph *)calloc(length*MAX_GLYPH_EXPANSION, sizeof(ATSGlyphRef));
1078 fontBuffer = (NSFont **)calloc(length*MAX_GLYPH_EXPANSION, sizeof(NSFont *));
1080 advances = localAdvanceBuffer;
1081 widthBuffer = localWidthBuffer;
1082 glyphBuffer = localGlyphBuffer;
1083 fontBuffer = localFontBuffer;
1086 [self _floatWidthForRun:run
1091 startPosition:&startX
1092 numGlyphs: &numGlyphs];
1094 // Eek. We couldn't generate ANY glyphs for the run.
1098 // Fill the advances array.
1099 for (i = 0; i < numGlyphs; i++){
1100 advances[i].width = widthBuffer[i];
1101 advances[i].height = 0;
1104 // Calculate the starting point of the glyphs to be displayed by adding
1105 // all the advances up to the first glyph.
1106 startX += geometry->point.x;
1108 if (style->backgroundColor != nil)
1109 [self _CG_drawHighlightForRun:run style:style geometry:geometry];
1111 // Finally, draw the glyphs.
1115 // Swap the order of the glyphs if right-to-left.
1116 if (style->rtl && numGlyphs > 1){
1118 int end = numGlyphs;
1119 CGGlyph gswap1, gswap2;
1120 CGSize aswap1, aswap2;
1121 NSFont *fswap1, *fswap2;
1123 for (i = pos, end = numGlyphs-1; i < (numGlyphs - pos)/2; i++){
1124 gswap1 = glyphBuffer[i];
1125 gswap2 = glyphBuffer[--end];
1126 glyphBuffer[i] = gswap2;
1127 glyphBuffer[end] = gswap1;
1129 for (i = pos, end = numGlyphs - 1; i < (numGlyphs - pos)/2; i++){
1130 aswap1 = advances[i];
1131 aswap2 = advances[--end];
1132 advances[i] = aswap2;
1133 advances[end] = aswap1;
1135 for (i = pos, end = numGlyphs - 1; i < (numGlyphs - pos)/2; i++){
1136 fswap1 = fontBuffer[i];
1137 fswap2 = fontBuffer[--end];
1138 fontBuffer[i] = fswap2;
1139 fontBuffer[end] = fswap1;
1143 // Draw each contiguous run of glyphs that are included in the same font.
1144 NSFont *currentFont = fontBuffer[pos];
1145 float nextX = startX;
1146 int nextGlyph = pos;
1148 while (nextGlyph < numGlyphs){
1149 if ((fontBuffer[nextGlyph] != 0 && fontBuffer[nextGlyph] != currentFont)){
1150 _drawGlyphs(currentFont, style->textColor, &glyphBuffer[lastFrom], &advances[lastFrom], startX, geometry->point.y, nextGlyph - lastFrom);
1151 lastFrom = nextGlyph;
1152 currentFont = fontBuffer[nextGlyph];
1155 nextX += advances[nextGlyph].width;
1158 _drawGlyphs(currentFont, style->textColor, &glyphBuffer[lastFrom], &advances[lastFrom], startX, geometry->point.y, nextGlyph - lastFrom);
1160 if (advances != localAdvanceBuffer) {
1168 #ifdef DEBUG_COMBINING
1169 static const char *directionNames[] = {
1170 "DirectionL", // Left Letter
1171 "DirectionR", // Right Letter
1172 "DirectionEN", // European Number
1173 "DirectionES", // European Separator
1174 "DirectionET", // European Terminator (post/prefix e.g. $ and %)
1175 "DirectionAN", // Arabic Number
1176 "DirectionCS", // Common Separator
1177 "DirectionB", // Paragraph Separator (aka as PS)
1178 "DirectionS", // Segment Separator (TAB)
1179 "DirectionWS", // White space
1180 "DirectionON", // Other Neutral
1182 // types for explicit controls
1186 "DirectionAL", // Arabic Letter (Right-to-left)
1192 "DirectionNSM", // Non-spacing Mark
1193 "DirectionBN" // Boundary neutral (type of RLE etc after explicit levels)
1196 static const char *joiningNames[] = {
1204 - (float)_floatWidthForRun:(const WebCoreTextRun *)run style:(const WebCoreTextStyle *)style widths:(float *)widthBuffer fonts:(NSFont **)fontBuffer glyphs:(CGGlyph *)glyphBuffer startPosition:(float *)startPosition numGlyphs:(int *)_numGlyphs
1206 if (shouldUseATSU(run))
1207 return [self _ATSU_floatWidthForRun:run style:style];
1209 return [self _CG_floatWidthForRun:run style:style widths:widthBuffer fonts:fontBuffer glyphs:glyphBuffer startPosition:startPosition numGlyphs:_numGlyphs];
1213 - (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
1215 float _totalWidth = 0, _nextWidth;
1216 CharacterWidthIterator widthIterator;
1217 NSFont *fontUsed = 0;
1218 ATSGlyphRef glyphUsed;
1221 initializeCharacterWidthIterator(&widthIterator, self, run, style);
1223 *startPosition = widthIterator.widthToStart;
1224 while ((_nextWidth = widthForNextCharacter(&widthIterator, &glyphUsed, &fontUsed)) != INVALID_WIDTH){
1226 fontBuffer[numGlyphs] = fontUsed;
1228 glyphBuffer[numGlyphs] = glyphUsed;
1230 widthBuffer[numGlyphs] = _nextWidth;
1232 _totalWidth += _nextWidth;
1236 *_numGlyphs = numGlyphs;
1241 - (ATSGlyphRef)_extendUnicodeCharacterToGlyphMapToInclude:(UnicodeChar)c
1243 UnicodeGlyphMap *map = (UnicodeGlyphMap *)calloc (1, sizeof(UnicodeGlyphMap));
1244 ATSLayoutRecord *glyphRecord;
1245 ATSGlyphVector glyphVector;
1246 UnicodeChar end, start;
1248 ATSGlyphRef glyphID;
1250 if (unicodeCharacterToGlyphMap == 0)
1251 blockSize = INITIAL_BLOCK_SIZE;
1253 blockSize = INCREMENTAL_BLOCK_SIZE;
1254 start = (c / blockSize) * blockSize;
1255 end = start + (blockSize - 1);
1257 LOG(FontCache, "%@ (0x%04x) adding glyphs for 0x%04x to 0x%04x", font, c, start, end);
1259 map->startRange = start;
1260 map->endRange = end;
1262 unsigned i, count = end - start + 1;
1263 UnicodeChar buffer[INCREMENTAL_BLOCK_SIZE+2];
1265 for (i = 0; i < count; i++){
1266 buffer[i] = i+start;
1270 status = ATSInitializeGlyphVector(count*2, 0, &glyphVector);
1271 if (status != noErr){
1272 // This should never happen, indicates a bad font! If it does the
1273 // font substitution code will find an alternate font.
1278 [self _convertUnicodeCharacters: &buffer[0] length: count toGlyphs: &glyphVector];
1279 unsigned numGlyphs = glyphVector.numGlyphs;
1280 if (numGlyphs != count){
1281 // This should never happen, indicates a bad font! If it does the
1282 // font substitution code will find an alternate font.
1287 map->glyphs = (GlyphEntry *)malloc (count * sizeof(GlyphEntry));
1288 glyphRecord = (ATSLayoutRecord *)glyphVector.firstRecord;
1289 for (i = 0; i < count; i++) {
1290 map->glyphs[i].glyph = glyphRecord->glyphID;
1291 map->glyphs[i].font = 0;
1292 glyphRecord = (ATSLayoutRecord *)((char *)glyphRecord + glyphVector.recordSize);
1294 ATSClearGlyphVector(&glyphVector);
1296 if (unicodeCharacterToGlyphMap == 0)
1297 unicodeCharacterToGlyphMap = map;
1299 UnicodeGlyphMap *lastMap = unicodeCharacterToGlyphMap;
1300 while (lastMap->next != 0)
1301 lastMap = lastMap->next;
1302 lastMap->next = map;
1305 glyphID = map->glyphs[c - start].glyph;
1310 - (void)_updateGlyphEntryForCharacter:(UniChar)c glyphID:(ATSGlyphRef)glyphID font:(NSFont *)substituteFont
1312 GlyphMap *lastMap = characterToGlyphMap;
1313 while (lastMap != 0){
1314 if (c >= lastMap->startRange && c <= lastMap->endRange){
1315 lastMap->glyphs[c - lastMap->startRange].glyph = glyphID;
1316 // This font will leak. No problem though, it has to stick around
1317 // forever. Max theoretical retain counts applied here will be
1318 // num_fonts_on_system * num_glyphs_in_font.
1319 lastMap->glyphs[c - lastMap->startRange].font = [substituteFont retain];
1322 lastMap = lastMap->next;
1326 - (ATSGlyphRef)_extendCharacterToGlyphMapToInclude:(UniChar) c
1328 GlyphMap *map = (GlyphMap *)calloc (1, sizeof(GlyphMap));
1329 ATSLayoutRecord *glyphRecord;
1330 ATSGlyphVector glyphVector;
1333 ATSGlyphRef glyphID;
1335 if (characterToGlyphMap == 0)
1336 blockSize = INITIAL_BLOCK_SIZE;
1338 blockSize = INCREMENTAL_BLOCK_SIZE;
1339 start = (c / blockSize) * blockSize;
1340 end = start + (blockSize - 1);
1342 LOG(FontCache, "%@ (0x%04x) adding glyphs for 0x%04x to 0x%04x", font, c, start, end);
1344 map->startRange = start;
1345 map->endRange = end;
1347 unsigned i, count = end - start + 1;
1348 short unsigned buffer[INCREMENTAL_BLOCK_SIZE+2];
1350 for (i = 0; i < count; i++) {
1351 buffer[i] = i+start;
1355 // Control characters must not render at all.
1356 for (i = 0; i < 0x20; ++i)
1357 buffer[i] = ZERO_WIDTH_SPACE;
1358 buffer[0x7F] = ZERO_WIDTH_SPACE;
1360 // But both \n and nonbreaking space must render as a space.
1362 buffer[NO_BREAK_SPACE] = ' ';
1365 OSStatus status = ATSInitializeGlyphVector(count, 0, &glyphVector);
1366 if (status != noErr) {
1367 // This should never happen, perhaps indicates a bad font! If it does the
1368 // font substitution code will find an alternate font.
1373 [self _convertCharacters: &buffer[0] length: count toGlyphs: &glyphVector];
1374 unsigned numGlyphs = glyphVector.numGlyphs;
1375 if (numGlyphs != count){
1376 // This should never happen, perhaps indicates a bad font! If it does the
1377 // font substitution code will find an alternate font.
1382 map->glyphs = (GlyphEntry *)malloc (count * sizeof(GlyphEntry));
1383 glyphRecord = (ATSLayoutRecord *)glyphVector.firstRecord;
1384 for (i = 0; i < count; i++) {
1385 map->glyphs[i].glyph = glyphRecord->glyphID;
1386 map->glyphs[i].font = 0;
1387 glyphRecord = (ATSLayoutRecord *)((char *)glyphRecord + glyphVector.recordSize);
1389 ATSClearGlyphVector(&glyphVector);
1391 if (characterToGlyphMap == 0)
1392 characterToGlyphMap = map;
1394 GlyphMap *lastMap = characterToGlyphMap;
1395 while (lastMap->next != 0)
1396 lastMap = lastMap->next;
1397 lastMap->next = map;
1400 glyphID = map->glyphs[c - start].glyph;
1402 // Special case for characters 007F-00A0.
1403 if (glyphID == 0 && c >= 0x7F && c <= 0xA0){
1404 glyphID = [font _defaultGlyphForChar: c];
1405 map->glyphs[c - start].glyph = glyphID;
1406 map->glyphs[c - start].font = 0;
1413 - (WidthMap *)_extendGlyphToWidthMapToInclude:(ATSGlyphRef)glyphID font:(NSFont *)subFont
1415 WidthMap *map = (WidthMap *)calloc (1, sizeof(WidthMap)), **rootMap;
1421 if (subFont && subFont != font)
1422 rootMap = &mapForSubstituteFont(self,subFont)->map;
1424 rootMap = &glyphToWidthMap;
1427 if ([(subFont ? subFont : font) numberOfGlyphs] < INITIAL_BLOCK_SIZE)
1428 blockSize = [font numberOfGlyphs];
1430 blockSize = INITIAL_BLOCK_SIZE;
1433 blockSize = INCREMENTAL_BLOCK_SIZE;
1434 start = (glyphID / blockSize) * blockSize;
1435 end = ((unsigned)start) + blockSize;
1439 LOG(FontCache, "%@ (0x%04x) adding widths for range 0x%04x to 0x%04x", font, glyphID, start, end);
1441 map->startRange = start;
1442 map->endRange = end;
1443 count = end - start + 1;
1445 map->widths = (WidthEntry *)malloc (count * sizeof(WidthEntry));
1447 for (i = 0; i < count; i++){
1448 map->widths[i].width = UNINITIALIZED_GLYPH_WIDTH;
1454 WidthMap *lastMap = *rootMap;
1455 while (lastMap->next != 0)
1456 lastMap = lastMap->next;
1457 lastMap->next = map;
1461 LOG(FontCache, "%@ total time to advances lookup %f seconds", font, totalCGGetAdvancesTime);
1467 - (void)_initializeATSUStyle
1469 // The two NSFont calls in this method (pointSize and _atsFontID)
1470 // are both exception-safe.
1472 if (!ATSUStyleInitialized){
1475 status = ATSUCreateStyle(&_ATSUSstyle);
1477 FATAL_ALWAYS ("ATSUCreateStyle failed (%d)", status);
1479 ATSUFontID fontID = [font _atsFontID];
1481 ATSUDisposeStyle(_ATSUSstyle);
1482 ERROR ("unable to get ATSUFontID for %@", font);
1486 CGAffineTransform transform = CGAffineTransformMakeScale (1,-1);
1487 Fixed fontSize = FloatToFixed([font pointSize]);
1488 ATSUAttributeTag styleTags[] = { kATSUSizeTag, kATSUFontTag, kATSUFontMatrixTag};
1489 ByteCount styleSizes[] = { sizeof(Fixed), sizeof(ATSUFontID), sizeof(CGAffineTransform) };
1490 ATSUAttributeValuePtr styleValues[] = { &fontSize, &fontID, &transform };
1491 status = ATSUSetAttributes (_ATSUSstyle, 3, styleTags, styleSizes, styleValues);
1493 FATAL_ALWAYS ("ATSUSetAttributes failed (%d)", status);
1495 ATSUStyleInitialized = YES;
1499 - (ATSUTextLayout)_createATSUTextLayoutForRun:(const WebCoreTextRun *)run style:(const WebCoreTextStyle *)style
1501 // The only Cocoa calls here are to NSGraphicsContext and the self
1502 // call to _initializeATSUStyle, which are all exception-safe.
1504 ATSUTextLayout layout;
1505 UniCharCount runLength;
1508 [self _initializeATSUStyle];
1510 // FIXME: This is missing the following features that the CoreGraphics code path has:
1511 // - Both \n and nonbreaking space render as a space.
1512 // - All other control characters must not render at all (other code path uses zero-width spaces).
1514 runLength = run->to - run->from;
1515 status = ATSUCreateTextLayoutWithTextPtr(
1517 run->from, // offset
1518 runLength, // length
1519 run->length, // total length
1521 &runLength, // length of style run
1525 FATAL_ALWAYS ("ATSUCreateTextLayoutWithTextPtr failed(%d)", status);
1527 CGContextRef cgContext = (CGContextRef)[[NSGraphicsContext currentContext] graphicsPort];
1528 ATSLineLayoutOptions lineLayoutOptions = (kATSLineFractDisable | kATSLineDisableAutoAdjustDisplayPos | kATSLineUseDeviceMetrics |
1529 kATSLineKeepSpacesOutOfMargin | kATSLineHasNoHangers);
1530 Boolean rtl = style->rtl;
1531 ATSUAttributeTag tags[] = { kATSUCGContextTag, kATSULineLayoutOptionsTag, kATSULineDirectionTag };
1532 ByteCount sizes[] = { sizeof(CGContextRef), sizeof(ATSLineLayoutOptions), sizeof(Boolean) };
1533 ATSUAttributeValuePtr values[] = { &cgContext, &lineLayoutOptions, &rtl };
1535 status = ATSUSetLayoutControls(layout, 3, tags, sizes, values);
1537 FATAL_ALWAYS ("ATSUSetLayoutControls failed(%d)", status);
1539 status = ATSUSetTransientFontMatching (layout, YES);
1541 FATAL_ALWAYS ("ATSUSetTransientFontMatching failed(%d)", status);
1547 - (ATSTrapezoid)_trapezoidForRun:(const WebCoreTextRun *)run style:(const WebCoreTextStyle *)style atPoint:(NSPoint )p
1549 // The only Cocoa call here is the self call to
1550 // _createATSUTextLayoutForRun:, which is exception-safe.
1554 if (run->to - run->from <= 0){
1555 ATSTrapezoid nilTrapezoid = { {0,0} , {0,0}, {0,0}, {0,0} };
1556 return nilTrapezoid;
1559 ATSUTextLayout layout = [self _createATSUTextLayoutForRun:run style:style];
1561 ATSTrapezoid firstGlyphBounds;
1562 ItemCount actualNumBounds;
1563 status = ATSUGetGlyphBounds (layout, FloatToFixed(p.x), FloatToFixed(p.y), run->from, run->to - run->from, kATSUseDeviceOrigins, 1, &firstGlyphBounds, &actualNumBounds);
1565 FATAL_ALWAYS ("ATSUGetGlyphBounds() failed(%d)", status);
1567 if (actualNumBounds != 1)
1568 FATAL_ALWAYS ("unexpected result from ATSUGetGlyphBounds(): actualNumBounds(%d) != 1", actualNumBounds);
1570 ATSUDisposeTextLayout (layout); // Ignore the error. Nothing we can do anyway.
1572 return firstGlyphBounds;
1576 - (float)_ATSU_floatWidthForRun:(const WebCoreTextRun *)run style:(const WebCoreTextStyle *)style
1578 ATSTrapezoid oGlyphBounds;
1580 oGlyphBounds = [self _trapezoidForRun:run style:style atPoint:NSMakePoint (0,0)];
1583 MAX(FixedToFloat(oGlyphBounds.upperRight.x), FixedToFloat(oGlyphBounds.lowerRight.x)) -
1584 MIN(FixedToFloat(oGlyphBounds.upperLeft.x), FixedToFloat(oGlyphBounds.lowerLeft.x));
1589 // Be sure to free the run.characters allocated by this function.
1590 static WebCoreTextRun reverseCharactersInRun(const WebCoreTextRun *run)
1592 WebCoreTextRun swappedRun;
1595 UniChar *swappedCharacters = (UniChar *)malloc(sizeof(UniChar)*run->length);
1596 for (i = 0; i < run->length; i++) {
1597 swappedCharacters[i] = run->characters[run->length-i-1];
1599 swappedRun.characters = swappedCharacters;
1600 swappedRun.from = run->length - (run->to == -1 ? (int)run->length : run->to);
1601 swappedRun.to = run->length - (run->from == -1 ? 0 : run->from);
1602 swappedRun.length = run->length;
1607 - (void)_ATSU_drawHighlightForRun:(const WebCoreTextRun *)run style:(const WebCoreTextStyle *)style geometry:(const WebCoreTextGeometry *)geometry
1609 // The only Cocoa calls made here are to NSColor and NSBezierPath,
1610 // plus the self calls to _createATSUTextLayoutForRun: and
1611 // _trapezoidForRun:. These are all exception-safe.
1613 ATSUTextLayout layout;
1615 float selectedLeftX;
1616 const WebCoreTextRun *aRun = run;
1617 WebCoreTextRun swappedRun;
1619 if (style->backgroundColor == nil)
1622 if (style->visuallyOrdered) {
1623 swappedRun = reverseCharactersInRun(run);
1634 int runLength = to - from;
1635 if (runLength <= 0){
1639 layout = [self _createATSUTextLayoutForRun:aRun style:style];
1641 WebCoreTextRun leadingRun = *aRun;
1642 leadingRun.from = 0;
1643 leadingRun.to = run->from;
1645 // ATSU provides the bounds of the glyphs for the run with an origin of
1646 // (0,0), so we need to find the width of the glyphs immediately before
1647 // the actually selected glyphs.
1648 ATSTrapezoid leadingTrapezoid = [self _trapezoidForRun:&leadingRun style:style atPoint:geometry->point];
1649 ATSTrapezoid selectedTrapezoid = [self _trapezoidForRun:run style:style atPoint:geometry->point];
1651 float backgroundWidth =
1652 MAX(FixedToFloat(selectedTrapezoid.upperRight.x), FixedToFloat(selectedTrapezoid.lowerRight.x)) -
1653 MIN(FixedToFloat(selectedTrapezoid.upperLeft.x), FixedToFloat(selectedTrapezoid.lowerLeft.x));
1656 selectedLeftX = geometry->point.x;
1658 selectedLeftX = MIN(FixedToFloat(leadingTrapezoid.upperRight.x), FixedToFloat(leadingTrapezoid.lowerRight.x));
1660 [style->backgroundColor set];
1662 float yPos = geometry->useFontMetricsForSelectionYAndHeight ? geometry->point.y - [self ascent] : geometry->selectionY;
1663 float height = geometry->useFontMetricsForSelectionYAndHeight ? [self lineSpacing] : geometry->selectionHeight;
1664 if (style->rtl || style->visuallyOrdered){
1665 WebCoreTextRun completeRun = *aRun;
1666 completeRun.from = 0;
1667 completeRun.to = aRun->length;
1668 float completeRunWidth = [self floatWidthForRun:&completeRun style:style widths:0];
1669 [NSBezierPath fillRect:NSMakeRect(geometry->point.x + completeRunWidth - (selectedLeftX-geometry->point.x) - backgroundWidth, yPos, backgroundWidth, height)];
1672 [NSBezierPath fillRect:NSMakeRect(selectedLeftX, yPos, backgroundWidth, height)];
1675 ATSUDisposeTextLayout (layout); // Ignore the error. Nothing we can do anyway.
1677 if (style->visuallyOrdered)
1678 free ((void *)swappedRun.characters);
1682 - (void)_ATSU_drawRun:(const WebCoreTextRun *)run style:(const WebCoreTextStyle *)style geometry:(const WebCoreTextGeometry *)geometry
1684 // The only Cocoa calls made here are to NSColor, plus the self
1685 // calls to _createATSUTextLayoutForRun: and
1686 // _ATSU_drawHighlightForRun:. These are all exception-safe.
1688 ATSUTextLayout layout;
1691 const WebCoreTextRun *aRun = run;
1692 WebCoreTextRun swappedRun;
1694 if (style->visuallyOrdered) {
1695 swappedRun = reverseCharactersInRun(run);
1706 int runLength = to - from;
1710 layout = [self _createATSUTextLayoutForRun:aRun style:style];
1712 if (style->backgroundColor != nil)
1713 [self _ATSU_drawHighlightForRun:run style:style geometry:geometry];
1715 [style->textColor set];
1717 status = ATSUDrawText(layout,
1720 FloatToFixed(geometry->point.x), // these values are
1721 FloatToFixed(geometry->point.y)); // also of type Fixed
1722 if (status != noErr){
1723 // Nothing to do but report the error (dev build only).
1724 ERROR ("ATSUDrawText() failed(%d)", status);
1727 ATSUDisposeTextLayout (layout); // Ignore the error. Nothing we can do anyway.
1729 if (style->visuallyOrdered)
1730 free ((void *)swappedRun.characters);
1733 - (int)_ATSU_pointToOffset:(const WebCoreTextRun *)run style:(const WebCoreTextStyle *)style position:(int)x reversed:(BOOL)reversed includePartialGlyphs:(BOOL)includePartialGlyphs
1735 // The only Cocoa calls made here is to the self call to
1736 // _createATSUTextLayoutForRun:. This is exception-safe.
1738 unsigned offset = 0;
1739 ATSUTextLayout layout;
1740 UniCharArrayOffset primaryOffset = 0;
1741 UniCharArrayOffset secondaryOffset = 0;
1744 const WebCoreTextRun *aRun = run;
1745 WebCoreTextRun swappedRun;
1747 // Reverse the visually ordered characters. ATSU will re-reverse. Ick!
1748 if (style->visuallyOrdered) {
1749 swappedRun = reverseCharactersInRun(run);
1753 layout = [self _createATSUTextLayoutForRun:aRun style:style];
1755 primaryOffset = aRun->from;
1757 // FIXME: No idea how to avoid including partial glyphs. Not even sure if that's the behavior
1759 status = ATSUPositionToOffset(layout, FloatToFixed(x), FloatToFixed(-1), &primaryOffset, &isLeading, &secondaryOffset);
1760 if (status == noErr){
1761 offset = (unsigned)primaryOffset;
1764 // Failed to find offset! Return 0 offset.
1767 if (style->visuallyOrdered) {
1768 free ((void *)swappedRun.characters);
1771 return offset - aRun->from;
1774 - (int)_CG_pointToOffset:(const WebCoreTextRun *)run style:(const WebCoreTextStyle *)style position:(int)x reversed:(BOOL)reversed includePartialGlyphs:(BOOL)includePartialGlyphs
1776 float delta = (float)x;
1778 unsigned offset = run->from;
1779 CharacterWidthIterator widthIterator;
1781 initializeCharacterWidthIterator(&widthIterator, self, run, style);
1784 width = [self floatWidthForRun:run style:style widths:nil];
1786 while (offset < run->length) {
1787 float w = widthForNextCharacter(&widthIterator, 0, 0);
1788 if (w == INVALID_WIDTH) {
1789 // Something very bad happened, like we only have half of a surrogate pair.
1794 if (includePartialGlyphs)
1799 if (includePartialGlyphs)
1802 offset = widthIterator.currentCharacter;
1806 while (offset < run->length) {
1807 float w = widthForNextCharacter(&widthIterator, 0, 0);
1808 if (w == INVALID_WIDTH) {
1809 // Something very bad happened, like we only have half of a surrogate pair.
1814 if (includePartialGlyphs)
1819 if (includePartialGlyphs)
1822 offset = widthIterator.currentCharacter;
1827 return offset - run->from;
1832 // ------------------- Private functions -------------------
1834 static void freeWidthMap(WidthMap *map)
1837 WidthMap *next = map->next;
1845 static void freeGlyphMap(GlyphMap *map)
1848 GlyphMap *next = map->next;
1856 static void freeUnicodeGlyphMap(UnicodeGlyphMap *map)
1859 UnicodeGlyphMap *next = map->next;
1867 static inline ATSGlyphRef glyphForCharacter (GlyphMap *map, UniChar c, NSFont **font)
1873 if (c >= map->startRange && c <= map->endRange){
1874 *font = map->glyphs[c-map->startRange].font;
1875 return map->glyphs[c-map->startRange].glyph;
1883 static inline ATSGlyphRef glyphForUnicodeCharacter (UnicodeGlyphMap *map, UnicodeChar c, NSFont **font)
1889 if (c >= map->startRange && c <= map->endRange){
1890 *font = map->glyphs[c-map->startRange].font;
1891 return map->glyphs[c-map->startRange].glyph;
1900 static double totalCGGetAdvancesTime = 0;
1903 static inline SubstituteFontWidthMap *mapForSubstituteFont(WebTextRenderer *renderer, NSFont *font)
1907 for (i = 0; i < renderer->numSubstituteFontWidthMaps; i++){
1908 if (font == renderer->substituteFontWidthMaps[i].font)
1909 return &renderer->substituteFontWidthMaps[i];
1912 if (renderer->numSubstituteFontWidthMaps == renderer->maxSubstituteFontWidthMaps){
1913 renderer->maxSubstituteFontWidthMaps = renderer->maxSubstituteFontWidthMaps * 2;
1914 renderer->substituteFontWidthMaps = realloc (renderer->substituteFontWidthMaps, renderer->maxSubstituteFontWidthMaps * sizeof(SubstituteFontWidthMap));
1915 for (i = renderer->numSubstituteFontWidthMaps; i < renderer->maxSubstituteFontWidthMaps; i++){
1916 renderer->substituteFontWidthMaps[i].font = 0;
1917 renderer->substituteFontWidthMaps[i].map = 0;
1921 renderer->substituteFontWidthMaps[renderer->numSubstituteFontWidthMaps].font = font;
1922 return &renderer->substituteFontWidthMaps[renderer->numSubstituteFontWidthMaps++];
1925 static void initializeCharacterWidthIterator (CharacterWidthIterator *iterator, WebTextRenderer *renderer, const WebCoreTextRun *run , const WebCoreTextStyle *style)
1927 iterator->renderer = renderer;
1928 iterator->run = run;
1929 iterator->style = style;
1930 iterator->currentCharacter = run->from;
1931 iterator->runWidthSoFar = 0;
1933 // If the padding is non-zero, count the number of spaces in the run
1934 // and divide that by the padding for per space addition.
1935 iterator->padding = style->padding;
1936 if (iterator->padding > 0){
1938 int from = run->from;
1939 int len = run->to - from;
1941 for (k = from; k < from + len; k++) {
1942 if (isSpace(run->characters[k])) {
1946 iterator->padPerSpace = CEIL_TO_INT ((((float)style->padding) / ((float)numSpaces)));
1949 iterator->padPerSpace = 0;
1952 // Calculate width up to starting position of the run. This is
1953 // necessary to ensure that our rounding hacks are always consistently
1955 if (run->from != 0){
1956 WebCoreTextRun startPositionRun = *run;
1957 startPositionRun.from = 0;
1958 startPositionRun.to = run->from;
1959 CharacterWidthIterator startPositionIterator;
1960 initializeCharacterWidthIterator (&startPositionIterator, renderer, &startPositionRun, style);
1962 while (startPositionIterator.currentCharacter < (unsigned)startPositionRun.to){
1963 widthForNextCharacter(&startPositionIterator, 0, 0);
1965 iterator->widthToStart = startPositionIterator.runWidthSoFar;
1968 iterator->widthToStart = 0;
1971 static inline float ceilCurrentWidth (CharacterWidthIterator *iterator)
1973 float delta = CEIL_TO_INT(iterator->widthToStart + iterator->runWidthSoFar) - (iterator->widthToStart + iterator->runWidthSoFar);
1974 iterator->runWidthSoFar += delta;
1978 // According to http://www.unicode.org/Public/UNIDATA/UCD.html#Canonical_Combining_Class_Values
1979 #define HIRAGANA_KATAKANA_VOICING_MARKS 8
1981 // Return INVALID_WIDTH if an error is encountered or we're at the end of the range in the run.
1982 static float widthForNextCharacter(CharacterWidthIterator *iterator, ATSGlyphRef *glyphUsed, NSFont **fontUsed)
1984 WebTextRenderer *renderer = iterator->renderer;
1985 const WebCoreTextRun *run = iterator->run;
1986 const WebCoreTextStyle *style = iterator->style;
1987 unsigned currentCharacter = iterator->currentCharacter;
1989 NSFont *_fontUsed = nil;
1990 ATSGlyphRef _glyphUsed;
1993 fontUsed = &_fontUsed;
1995 glyphUsed = &_glyphUsed;
1997 if (currentCharacter >= (unsigned)run->to)
1998 // Error! Offset specified beyond end of run.
1999 return INVALID_WIDTH;
2001 const UniChar *cp = &run->characters[currentCharacter];
2003 UnicodeChar c = *cp;
2005 if (IsLowSurrogatePair(c))
2006 return INVALID_WIDTH;
2008 // Do we have a surrogate pair? If so, determine the full Unicode (32 bit)
2009 // code point before glyph lookup.
2010 unsigned clusterLength = 1;
2011 if (IsHighSurrogatePair(c)) {
2012 // Make sure we have another character and it's a low surrogate.
2014 if (currentCharacter + 1 >= run->length || !IsLowSurrogatePair((low = cp[1]))) {
2015 // Error! The second component of the surrogate pair is missing.
2016 return INVALID_WIDTH;
2019 c = UnicodeValueForSurrogatePair(c, low);
2023 // If small-caps convert lowercase to upper.
2024 BOOL useSmallCapsFont = NO;
2025 if (renderer->isSmallCapsRenderer) {
2026 if (!u_isUUppercase(c)) {
2027 // Only use small cap font if the the uppercase version of the character
2028 // is different than the lowercase.
2029 UnicodeChar newC = u_toupper(c);
2031 useSmallCapsFont = YES;
2037 // Deal with Hiragana and Katakana voiced and semi-voiced syllables. Normalize into
2038 // composed form, and then look for glyph with base + combined mark.
2039 if (c >= 0x3041 && c <= 0x30FE) { // Early out to minimize performance impact. Do we have a Hiragana/Katakana character?
2040 if (currentCharacter < (unsigned)run->to) {
2041 UnicodeChar nextCharacter = run->characters[currentCharacter+1];
2042 if (u_getCombiningClass(nextCharacter) == HIRAGANA_KATAKANA_VOICING_MARKS) {
2043 UChar normalizedCharacters[2] = { 0, 0 };
2044 UErrorCode uStatus = 0;
2045 int32_t resultLength;
2047 // Normalize into composed form using 3.2 rules.
2048 resultLength = unorm_normalize(&run->characters[currentCharacter], 2,
2049 UNORM_NFC, UNORM_UNICODE_3_2,
2050 &normalizedCharacters[0], 2,
2052 if (resultLength == 1 && uStatus == 0){
2053 c = normalizedCharacters[0];
2061 c = u_charMirror(c);
2065 *glyphUsed = glyphForCharacter(renderer->characterToGlyphMap, c, fontUsed);
2066 if (*glyphUsed == nonGlyphID) {
2067 *glyphUsed = [renderer _extendCharacterToGlyphMapToInclude:c];
2070 *glyphUsed = glyphForUnicodeCharacter(renderer->unicodeCharacterToGlyphMap, c, fontUsed);
2071 if (*glyphUsed == nonGlyphID) {
2072 *glyphUsed = [renderer _extendUnicodeCharacterToGlyphMapToInclude:c];
2076 // Check to see if we're rendering in 'small-caps' mode.
2077 // ASSUMPTION: We assume the same font in a smaller size has
2078 // the same glyphs as the large font.
2079 if (useSmallCapsFont) {
2080 if (*fontUsed == nil)
2081 *fontUsed = [renderer _smallCapsFont];
2083 // Potential for optimization. This path should only be taken if we're
2084 // using a cached substituted font.
2085 *fontUsed = [[NSFontManager sharedFontManager] convertFont:*fontUsed toSize:[*fontUsed pointSize] * SMALLCAPS_FONTSIZE_MULTIPLIER];
2089 // Now that we have glyph and font, get its width.
2090 WebGlyphWidth width = widthForGlyph(renderer, *glyphUsed, *fontUsed);
2092 // We special case spaces in two ways when applying word rounding.
2093 // First, we round spaces to an adjusted width in all fonts.
2094 // Second, in fixed-pitch fonts we ensure that all characters that
2095 // match the width of the space character have the same width as the space character.
2096 if ((renderer->treatAsFixedPitch ? width == renderer->spaceWidth : *glyphUsed == renderer->spaceGlyph) && iterator->style->applyWordRounding)
2097 width = renderer->adjustedSpaceWidth;
2099 // Try to find a substitute font if this font didn't have a glyph for a character in the
2100 // string. If one isn't found we end up drawing and measuring the 0 glyph, usually a box.
2101 if (*glyphUsed == 0 && iterator->style->attemptFontSubstitution) {
2102 UniChar characterArray[2];
2103 unsigned characterArrayLength;
2106 characterArray[0] = c;
2107 characterArrayLength = 1;
2109 characterArray[0] = HighSurrogatePair(c);
2110 characterArray[1] = LowSurrogatePair(c);
2111 characterArrayLength = 2;
2114 NSFont *substituteFont = [renderer _substituteFontForCharacters:characterArray length:characterArrayLength
2115 families:iterator->style->families];
2116 if (substituteFont) {
2118 ATSGlyphRef localGlyphBuffer[MAX_GLYPH_EXPANSION];
2120 WebCoreTextRun clusterRun;
2121 WebCoreInitializeTextRun(&clusterRun, characterArray, characterArrayLength, 0, characterArrayLength);
2122 WebCoreTextStyle clusterStyle = *iterator->style;
2123 clusterStyle.padding = 0;
2124 clusterStyle.applyRunRounding = false;
2125 clusterStyle.attemptFontSubstitution = false;
2127 WebTextRenderer *substituteRenderer;
2128 substituteRenderer = [[WebTextRendererFactory sharedFactory] rendererWithFont:substituteFont usingPrinterFont:renderer->usingPrinterFont];
2129 width = [substituteRenderer
2130 _floatWidthForRun:&clusterRun
2134 glyphs: &localGlyphBuffer[0]
2136 numGlyphs:&cNumGlyphs];
2138 *fontUsed = substituteFont;
2139 *glyphUsed = localGlyphBuffer[0];
2141 if (c <= 0xFFFF && cNumGlyphs == 1 && localGlyphBuffer[0] != 0){
2142 [renderer _updateGlyphEntryForCharacter:c glyphID:localGlyphBuffer[0] font:substituteFont];
2148 *fontUsed = renderer->font;
2150 // Force characters that are used to determine word boundaries for the rounding hack
2151 // to be integer width, so following words will start on an integer boundary.
2152 if (isRoundingHackCharacter(c) && iterator->style->applyWordRounding) {
2153 width = CEIL_TO_INT(width);
2156 // Account for letter-spacing
2157 if (iterator->style->letterSpacing && width > 0)
2158 width += iterator->style->letterSpacing;
2160 // Account for padding. khtml uses space padding to justify text. We
2161 // distribute the specified padding over the available spaces in the run.
2163 if (iterator->padding > 0) {
2164 // Only use left over padding if note evenly divisible by
2165 // number of spaces.
2166 if (iterator->padding < iterator->padPerSpace){
2167 width += iterator->padding;
2168 iterator->padding = 0;
2171 width += iterator->padPerSpace;
2172 iterator->padding -= iterator->padPerSpace;
2176 // Account for word-spacing. We apply additional space between "words" by
2177 // adding width to the space character.
2178 if (currentCharacter > 0 && !isSpace(cp[-1]))
2179 width += iterator->style->wordSpacing;
2182 iterator->runWidthSoFar += width;
2184 // Advance past the character we just dealt with.
2185 currentCharacter += clusterLength;
2186 iterator->currentCharacter = currentCharacter;
2188 int len = run->to - run->from;
2190 // Account for float/integer impedance mismatch between CG and khtml. "Words" (characters
2191 // followed by a character defined by isSpace()) are always an integer width. We adjust the
2192 // width of the last character of a "word" to ensure an integer width. When we move khtml to
2193 // floats we can remove this (and related) hacks.
2195 // Check to see if the next character is a "RoundingHackCharacter", if so, adjust.
2196 if (currentCharacter < run->length && isRoundingHackCharacter(cp[clusterLength]) && iterator->style->applyWordRounding) {
2197 width += ceilCurrentWidth(iterator);
2199 else if (currentCharacter >= (unsigned)run->to && (len > 1 || run->length == 1) && iterator->style->applyRunRounding) {
2200 width += ceilCurrentWidth(iterator);
2207 static BOOL fillStyleWithAttributes(ATSUStyle style, NSFont *theFont)
2210 ATSUFontID fontId = [theFont _atsFontID];
2211 LOG (FontCache, "fillStyleWithAttributes: font = %p,%@, _atsFontID = %x\n", theFont, theFont, (unsigned)fontId);
2212 ATSUAttributeTag tag = kATSUFontTag;
2213 ByteCount size = sizeof(ATSUFontID);
2214 ATSUFontID *valueArray[1] = {&fontId};
2218 status = ATSUSetAttributes(style, 1, &tag, &size, (void *)valueArray);
2219 if (status != noErr){
2220 LOG (FontCache, "fillStyleWithAttributes failed(%d): font = %p,%@, _atsFontID = %x\n", (int)status, theFont, theFont, (unsigned)fontId);
2232 static BOOL shouldUseATSU(const WebCoreTextRun *run)
2235 const UniChar *characters = run->characters;
2236 int i, from = run->from, to = run->to;
2241 for (i = from; i < to; i++){
2243 if (c < 0x300) // Early continue to avoid other checks for the common case.
2246 if (c >= 0x300 && c <= 0x36F) // U+0300 through U+036F Combining diacritical marks
2248 if (c >= 0x20D0 && c <= 0x20FF) // U+20D0 through U+20FF Combining marks for symbols
2250 if (c >= 0xFE20 && c <= 0xFE2f) // U+FE20 through U+FE2F Combining half marks
2252 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
2254 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)
2256 if (c >= 0x1780 && c <= 0x18AF) // U+1780 through U+18AF Khmer, Mongolian
2258 if (c >= 0x1900 && c <= 0x194F) // U+1900 through U+194F Limbu (Unicode 4.0)