2 * Copyright (C) 2005 Apple Computer, Inc. All rights reserved.
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
8 * 1. Redistributions of source code must retain the above copyright
9 * notice, this list of conditions and the following disclaimer.
10 * 2. Redistributions in binary form must reproduce the above copyright
11 * notice, this list of conditions and the following disclaimer in the
12 * documentation and/or other materials provided with the distribution.
13 * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of
14 * its contributors may be used to endorse or promote products derived
15 * from this software without specific prior written permission.
17 * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
18 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
19 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
20 * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
21 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
22 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
23 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
24 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
26 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29 #import "WebTextRenderer.h"
31 #import <ApplicationServices/ApplicationServices.h>
32 #import <Cocoa/Cocoa.h>
34 #import <WebKit/WebGraphicsBridge.h>
35 #import <WebKit/WebKitLogging.h>
36 #import <WebKit/WebNSObjectExtras.h>
37 #import <WebKit/WebTextRendererFactory.h>
38 #import <WebKit/WebViewPrivate.h>
39 #import <WebKitSystemInterface.h>
43 #import <unicode/uchar.h>
44 #import <unicode/unorm.h>
46 // FIXME: FATAL_ALWAYS seems like a bad idea; lets stop using it.
48 #define SMALLCAPS_FONTSIZE_MULTIPLIER 0.7f
50 // Should be more than enough for normal usage.
51 #define NUM_SUBSTITUTE_FONT_MAPS 10
53 // According to http://www.unicode.org/Public/UNIDATA/UCD.html#Canonical_Combining_Class_Values
54 #define HIRAGANA_KATAKANA_VOICING_MARKS 8
57 #define NO_BREAK_SPACE 0x00A0
58 #define ZERO_WIDTH_SPACE 0x200B
59 #define POP_DIRECTIONAL_FORMATTING 0x202C
60 #define LEFT_TO_RIGHT_OVERRIDE 0x202D
61 #define RIGHT_TO_LEFT_OVERRIDE 0x202E
63 // MAX_GLYPH_EXPANSION is the maximum numbers of glyphs that may be
64 // use to represent a single Unicode code point.
65 #define MAX_GLYPH_EXPANSION 4
66 #define LOCAL_BUFFER_SIZE 2048
69 #define INITIAL_BLOCK_SIZE 0x200
71 // Get additional blocks of glyphs and widths in bigger chunks.
72 // This will typically be for other character sets.
73 #define INCREMENTAL_BLOCK_SIZE 0x400
75 #define CONTEXT_DPI (72.0)
76 #define SCALE_EM_TO_UNITS(X, U_PER_EM) (X * ((1.0 * CONTEXT_DPI) / (CONTEXT_DPI * U_PER_EM)))
78 typedef float WebGlyphWidth;
81 ATSGlyphRef startRange;
84 WebGlyphWidth *widths;
87 typedef struct GlyphEntry {
89 WebTextRenderer *renderer;
99 typedef struct WidthIterator {
100 WebTextRenderer *renderer;
101 const WebCoreTextRun *run;
102 const WebCoreTextStyle *style;
103 unsigned currentCharacter;
110 typedef struct ATSULayoutParameters
112 WebTextRenderer *renderer;
113 const WebCoreTextRun *run;
114 const WebCoreTextStyle *style;
115 ATSUTextLayout layout;
116 } ATSULayoutParameters;
118 static WebTextRenderer *rendererForAlternateFont(WebTextRenderer *, WebCoreFont);
120 static WidthMap *extendWidthMap(WebTextRenderer *, ATSGlyphRef);
121 static ATSGlyphRef extendGlyphMap(WebTextRenderer *, UChar32);
122 static void updateGlyphMapEntry(WebTextRenderer *, UChar32, ATSGlyphRef, WebTextRenderer *substituteRenderer);
124 static void freeWidthMap(WidthMap *);
125 static void freeGlyphMap(GlyphMap *);
128 static float CG_floatWidthForRun(WebTextRenderer *, const WebCoreTextRun *, const WebCoreTextStyle *,
129 float *widthBuffer, WebTextRenderer **rendererBuffer, CGGlyph *glyphBuffer, float *startPosition, int *numGlyphsResult);
130 static float ATSU_floatWidthForRun(WebTextRenderer *, const WebCoreTextRun *, const WebCoreTextStyle *);
133 static void CG_draw(WebTextRenderer *, const WebCoreTextRun *, const WebCoreTextStyle *, const WebCoreTextGeometry *);
134 static void ATSU_draw(WebTextRenderer *, const WebCoreTextRun *, const WebCoreTextStyle *, const WebCoreTextGeometry *);
136 // Selection point detection in runs.
137 static int CG_pointToOffset(WebTextRenderer *, const WebCoreTextRun *, const WebCoreTextStyle *,
138 int x, bool includePartialGlyphs);
139 static int ATSU_pointToOffset(WebTextRenderer *, const WebCoreTextRun *, const WebCoreTextStyle *,
140 int x, bool includePartialGlyphs);
142 // Drawing highlight.
143 static void CG_drawHighlight(WebTextRenderer *, const WebCoreTextRun *, const WebCoreTextStyle *, const WebCoreTextGeometry *);
144 static void ATSU_drawHighlight(WebTextRenderer *, const WebCoreTextRun *, const WebCoreTextStyle *, const WebCoreTextGeometry *);
146 static bool setUpFont(WebTextRenderer *);
148 // Iterator functions
149 static void initializeWidthIterator(WidthIterator *iterator, WebTextRenderer *renderer, const WebCoreTextRun *run, const WebCoreTextStyle *style);
150 static unsigned advanceWidthIterator(WidthIterator *iterator, unsigned offset, float *widths, WebTextRenderer **renderersUsed, ATSGlyphRef *glyphsUsed);
152 static bool fillStyleWithAttributes(ATSUStyle style, NSFont *theFont);
153 static bool shouldUseATSU(const WebCoreTextRun *run);
156 static NSString *pathFromFont(NSFont *font);
159 static void createATSULayoutParameters(ATSULayoutParameters *params, WebTextRenderer *renderer, const WebCoreTextRun *run, const WebCoreTextStyle *style);
160 static void disposeATSULayoutParameters(ATSULayoutParameters *params);
163 static bool alwaysUseATSU = NO;
165 // Character property functions.
167 static inline bool isSpace(UChar32 c)
169 return c == SPACE || c == '\t' || c == '\n' || c == NO_BREAK_SPACE;
172 static const uint8_t isRoundingHackCharacterTable[0x100] = {
173 0, 0, 0, 0, 0, 0, 0, 0, 0, 1 /*\t*/, 1 /*\n*/, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
174 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 /*?*/,
175 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,
176 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,
177 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,
178 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,
179 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,
180 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
183 static inline bool isRoundingHackCharacter(UChar32 c)
185 return (((c & ~0xFF) == 0 && isRoundingHackCharacterTable[c]));
188 // Map utility functions
190 static inline WebGlyphWidth widthForGlyph(WebTextRenderer *renderer, ATSGlyphRef glyph)
193 for (map = renderer->glyphToWidthMap; 1; map = map->next) {
195 map = extendWidthMap(renderer, glyph);
196 if (glyph >= map->startRange && glyph <= map->endRange)
199 WebGlyphWidth width = map->widths[glyph - map->startRange];
202 NSFont *font = renderer->font.font;
203 float pointSize = [font pointSize];
204 CGAffineTransform m = CGAffineTransformMakeScale(pointSize, pointSize);
206 if (!WKGetGlyphTransformedAdvances(font, &m, &glyph, &advance)) {
207 ERROR("Unable to cache glyph widths for %@ %f", [font displayName], pointSize);
210 width = advance.width + renderer->syntheticBoldOffset;
211 map->widths[glyph - map->startRange] = width;
215 static OSStatus overrideLayoutOperation(ATSULayoutOperationSelector iCurrentOperation, ATSULineRef iLineRef, UInt32 iRefCon, void *iOperationCallbackParameterPtr, ATSULayoutOperationCallbackStatus *oCallbackStatus)
217 ATSULayoutParameters *params = (ATSULayoutParameters *)iRefCon;
220 ATSLayoutRecord *layoutRecords;
221 const WebCoreTextStyle *style = params->style;
223 if (style->applyWordRounding) {
224 status = ATSUDirectGetLayoutDataArrayPtrFromLineRef(iLineRef, kATSUDirectDataLayoutRecordATSLayoutRecordCurrent, true, (void **)&layoutRecords, &count);
225 if (status != noErr) {
226 *oCallbackStatus = kATSULayoutOperationCallbackStatusContinue;
230 // The CoreGraphics interpretation of NSFontAntialiasedIntegerAdvancementsRenderingMode seems
231 // to be "round each glyph's width to the nearest integer". This is not the same as ATSUI
232 // does in any of its device-metrics modes.
233 bool roundEachGlyph = [params->renderer->font.font renderingMode] == NSFontAntialiasedIntegerAdvancementsRenderingMode;
234 Fixed lastNativePos = 0;
235 float lastAdjustedPos = 0;
236 const WebCoreTextRun *run = params->run;
237 const UniChar *characters = run->characters + run->from;
238 WebTextRenderer *renderer = params->renderer;
240 nextCh = *(UniChar *)(((char *)characters)+layoutRecords[0].originalOffset);
241 // In the CoreGraphics code path, the rounding hack is applied in logical order.
242 // Here it is applied in visual left-to-right order, which may be better.
244 for (i = 1; i < count; i++) {
245 BOOL isLastChar = i == count - 1;
248 // Use space for nextCh at the end of the loop so that we get inside the rounding hack code.
249 // We won't actually round unless the other conditions are satisfied.
250 nextCh = isLastChar ? ' ' : *(UniChar *)(((char *)characters)+layoutRecords[i].originalOffset);
252 float width = FixedToFloat(layoutRecords[i].realPos - lastNativePos);
253 lastNativePos = layoutRecords[i].realPos;
255 width = roundf(width);
256 if (renderer->treatAsFixedPitch ? width == renderer->spaceWidth : (layoutRecords[i-1].flags & kATSGlyphInfoIsWhiteSpace))
257 width = renderer->adjustedSpaceWidth;
258 if (isRoundingHackCharacter(ch))
259 width = ceilf(width);
260 lastAdjustedPos = lastAdjustedPos + width;
261 if (isRoundingHackCharacter(nextCh))
263 || style->applyRunRounding
264 || (run->to < (int)run->length && isRoundingHackCharacter(characters[run->to - run->from])))
265 lastAdjustedPos = ceilf(lastAdjustedPos);
266 layoutRecords[i].realPos = FloatToFixed(lastAdjustedPos);
269 status = ATSUDirectReleaseLayoutDataArrayPtr(iLineRef, kATSUDirectDataLayoutRecordATSLayoutRecordCurrent, (void **)&layoutRecords);
271 *oCallbackStatus = kATSULayoutOperationCallbackStatusHandled;
275 @implementation WebTextRenderer
277 static NSString *webFallbackFontFamily(void)
279 static NSString *webFallbackFontFamily = nil;
280 if (!webFallbackFontFamily)
281 webFallbackFontFamily = [[[NSFont systemFontOfSize:16.0] familyName] retain];
282 return webFallbackFontFamily;
285 - initWithFont:(WebCoreFont)f
289 // Quartz can only handle fonts with these glyph packings. Other packings have
291 if ([f.font glyphPacking] != NSNativeShortGlyphPacking && [f.font glyphPacking] != NSTwoByteGlyphPacking) {
292 // Apparently there are many deprecated fonts out there with unsupported packing types.
293 // Log and use fallback font.
294 // This change fixes the many crashes reported in 3782533.
295 // Most likely, the problem is encountered when people upgrade from OS 9, or have OS 9 fonts installed on OS X.
296 NSLog(@"%s:%d Unable to use deprecated font %@ %f, using system font instead", __FILE__, __LINE__, [f.font displayName], [f.font pointSize]);
297 f.font = [NSFont systemFontOfSize:[f.font pointSize]];
302 syntheticBoldOffset = f.syntheticBold ? ceilf([f.font pointSize] / 24.0f) : 0.f;
304 bool failedSetup = false;
305 if (!setUpFont(self)) {
306 // Ack! Something very bad happened, like a corrupt font.
307 // Try looking for an alternate 'base' font for this renderer.
309 // Special case hack to use "Times New Roman" in place of "Times".
310 // "Times RO" is a common font whose family name is "Times".
311 // It overrides the normal "Times" family font.
312 // It also appears to have a corrupt regular variant.
313 NSString *fallbackFontFamily;
314 if ([[font.font familyName] isEqual:@"Times"])
315 fallbackFontFamily = @"Times New Roman";
317 fallbackFontFamily = webFallbackFontFamily();
319 // Try setting up the alternate font.
320 // This is a last ditch effort to use a substitute font when something has gone wrong.
322 NSFont *initialFont = font.font;
324 font.font = [[NSFontManager sharedFontManager] convertFont:font.font toFamily:fallbackFontFamily];
326 NSString *filePath = pathFromFont(initialFont);
328 filePath = @"not known";
330 if (!setUpFont(self)) {
331 if ([fallbackFontFamily isEqual:@"Times New Roman"]) {
332 // OK, couldn't setup Times New Roman as an alternate to Times, fallback
333 // on the system font. If this fails we have no alternative left.
334 font.font = [[NSFontManager sharedFontManager] convertFont:font.font toFamily:webFallbackFontFamily()];
335 if (!setUpFont(self)) {
336 // We tried, Times, Times New Roman, and the system font. No joy. We have to give up.
337 ERROR("%@ unable to initialize with font %@ at %@", self, initialFont, filePath);
341 // We tried the requested font and the system font. No joy. We have to give up.
342 ERROR("%@ unable to initialize with font %@ at %@", self, initialFont, filePath);
347 // Report the problem.
348 ERROR("Corrupt font detected, using %@ in place of %@ located at \"%@\".",
349 [font.font familyName], [initialFont familyName], filePath);
352 // If all else fails, try to set up using the system font.
353 // This is probably because Times and Times New Roman are both unavailable.
355 font.font = [NSFont systemFontOfSize:[font.font pointSize]];
356 ERROR("%@ failed to set up font, using system font %s", self, font.font);
364 WKGetFontMetrics(font.font, &iAscent, &iDescent, &iLineGap, &unitsPerEm);
365 float pointSize = [font.font pointSize];
366 float fAscent = SCALE_EM_TO_UNITS(iAscent, unitsPerEm) * pointSize;
367 float fDescent = -SCALE_EM_TO_UNITS(iDescent, unitsPerEm) * pointSize;
368 float fLineGap = SCALE_EM_TO_UNITS(iLineGap, unitsPerEm) * pointSize;
370 // We need to adjust Times, Helvetica, and Courier to closely match the
371 // vertical metrics of their Microsoft counterparts that are the de facto
372 // web standard. The AppKit adjustment of 20% is too big and is
373 // incorrectly added to line spacing, so we use a 15% adjustment instead
374 // and add it to the ascent.
375 NSString *familyName = [font.font familyName];
376 if ([familyName isEqualToString:@"Times"] || [familyName isEqualToString:@"Helvetica"] || [familyName isEqualToString:@"Courier"])
377 fAscent += floorf(((fAscent + fDescent) * 0.15f) + 0.5f);
379 ascent = lroundf(fAscent);
380 descent = lroundf(fDescent);
381 lineGap = lroundf(fLineGap);
383 lineSpacing = ascent + descent + lineGap;
390 static void destroy(WebTextRenderer *renderer)
392 if (renderer->styleGroup)
393 WKReleaseStyleGroup(renderer->styleGroup);
395 freeWidthMap(renderer->glyphToWidthMap);
396 freeGlyphMap(renderer->characterToGlyphMap);
398 if (renderer->ATSUStyleInitialized)
399 ATSUDisposeStyle(renderer->_ATSUStyle);
407 [smallCapsRenderer release];
436 // Measure the actual character "x", because AppKit synthesizes X height rather than getting it from the font.
437 // Unfortunately, NSFont will round this for us so we don't quite get the right value.
438 NSGlyph xGlyph = [font.font glyphWithName:@"x"];
440 NSRect xBox = [font.font boundingRectForGlyph:xGlyph];
441 // Use the maximum of either width or height because "x" is nearly square
442 // and web pages that foolishly use this metric for width will be laid out
443 // poorly if we return an accurate height. Classic case is Times 13 point,
444 // which has an "x" that is 7x6 pixels.
445 return MAX(NSMaxX(xBox), NSMaxY(xBox));
448 return [font.font xHeight];
451 - (void)drawRun:(const WebCoreTextRun *)run style:(const WebCoreTextStyle *)style geometry:(const WebCoreTextGeometry *)geometry
453 if (shouldUseATSU(run))
454 ATSU_draw(self, run, style, geometry);
456 CG_draw(self, run, style, geometry);
459 - (float)floatWidthForRun:(const WebCoreTextRun *)run style:(const WebCoreTextStyle *)style
461 if (shouldUseATSU(run))
462 return ATSU_floatWidthForRun(self, run, style);
463 return CG_floatWidthForRun(self, run, style, 0, 0, 0, 0, 0);
466 - (void)drawLineForCharacters:(NSPoint)point yOffset:(float)yOffset width:(int)width color:(NSColor *)color thickness:(float)thickness
468 NSGraphicsContext *graphicsContext = [NSGraphicsContext currentContext];
470 bool flag = [graphicsContext shouldAntialias];
472 // We don't want antialiased lines on screen, but we do when printing (else they are too thick).
473 if ([graphicsContext isDrawingToScreen]) {
474 [graphicsContext setShouldAntialias:NO];
479 CGContextRef cgContext = (CGContextRef)[graphicsContext graphicsPort];
481 // Hack to make thickness 2 underlines for international text input look right
482 if (thickness > 1.5F && thickness < 2.5F) {
486 if (thickness == 0.0F) {
487 if ([graphicsContext isDrawingToScreen]) {
488 CGSize size = CGSizeApplyAffineTransform(CGSizeMake(1.0F, 1.0F), CGAffineTransformInvert(CGContextGetCTM(cgContext)));
489 CGContextSetLineWidth(cgContext, size.width);
491 // See bugzilla bug 4255 for details of why we do this when printing
492 CGContextSetLineWidth(cgContext, 0.5F);
495 CGContextSetLineWidth(cgContext, thickness);
498 // Use CGContextStrokeLineSegments.
499 // With Q2DX turned on CGContextStrokeLineSegments sometimes fails to draw lines. See 3952084.
500 // Tiger shipped with Q2DX disabled, tho, so we can use CGContextStrokeLineSegments.
501 CGPoint linePoints[2];
502 linePoints[0].x = point.x;
503 linePoints[0].y = point.y + 1.5F + yOffset;
504 linePoints[1].x = point.x - 1.0F + width;
505 linePoints[1].y = linePoints[0].y;
506 CGContextStrokeLineSegments(cgContext, linePoints, 2);
508 [graphicsContext setShouldAntialias: flag];
511 - (void)drawHighlightForRun:(const WebCoreTextRun *)run style:(const WebCoreTextStyle *)style geometry:(const WebCoreTextGeometry *)geometry
513 if (shouldUseATSU(run))
514 ATSU_drawHighlight(self, run, style, geometry);
516 CG_drawHighlight(self, run, style, geometry);
519 - (int)misspellingLineThickness
524 - (int)misspellingLinePatternWidth
529 // the number of transparent pixels after the dot
530 - (int)misspellingLinePatternGapWidth
535 - (void)drawLineForMisspelling:(NSPoint)point withWidth:(int)width
537 // Constants for pattern color
538 static NSColor *spellingPatternColor = nil;
539 static bool usingDot = false;
540 int patternHeight = [self misspellingLineThickness];
541 int patternWidth = [self misspellingLinePatternWidth];
543 // Initialize pattern color if needed
544 if (!spellingPatternColor) {
545 NSImage *image = [NSImage imageNamed:@"SpellingDot"];
546 ASSERT(image); // if image is not available, we want to know
547 NSColor *color = (image ? [NSColor colorWithPatternImage:image] : nil);
551 color = [NSColor redColor];
552 spellingPatternColor = [color retain];
555 // Make sure to draw only complete dots.
556 // NOTE: Code here used to shift the underline to the left and increase the width
557 // to make sure everything gets underlined, but that results in drawing out of
558 // bounds (e.g. when at the edge of a view) and could make it appear that the
559 // space between adjacent misspelled words was underlined.
561 // allow slightly more considering that the pattern ends with a transparent pixel
562 int widthMod = width % patternWidth;
563 if (patternWidth - widthMod > [self misspellingLinePatternGapWidth])
568 NSGraphicsContext *currentContext = [NSGraphicsContext currentContext];
569 CGContextRef context = (CGContextRef)[currentContext graphicsPort];
570 CGContextSaveGState(context);
572 [spellingPatternColor set];
574 CGPoint transformedOrigin = CGPointApplyAffineTransform(CGPointMake(point.x, point.y), CGContextGetCTM(context));
575 CGContextSetPatternPhase(context, CGSizeMake(transformedOrigin.x, transformedOrigin.y));
577 NSRectFillUsingOperation(NSMakeRect(point.x, point.y, width, patternHeight), NSCompositeSourceOver);
579 CGContextRestoreGState(context);
582 - (int)pointToOffset:(const WebCoreTextRun *)run style:(const WebCoreTextStyle *)style position:(int)x includePartialGlyphs:(BOOL)includePartialGlyphs
584 if (shouldUseATSU(run))
585 return ATSU_pointToOffset(self, run, style, x, includePartialGlyphs);
586 return CG_pointToOffset(self, run, style, x, includePartialGlyphs);
589 + (void)setAlwaysUseATSU:(bool)f
596 static WebTextRenderer *getSmallCapsRenderer(WebTextRenderer *renderer)
598 if (!renderer->smallCapsRenderer) {
600 float size = [renderer->font.font pointSize] * SMALLCAPS_FONTSIZE_MULTIPLIER;
601 WebCoreFont smallCapsFont;
602 WebCoreInitializeFont(&smallCapsFont);
603 smallCapsFont.font = [[NSFontManager sharedFontManager] convertFont:renderer->font.font toSize:size];
604 renderer->smallCapsRenderer = [rendererForAlternateFont(renderer, smallCapsFont) retain];
606 NSLog(@"uncaught exception selecting font for small caps: %@", localException);
609 return renderer->smallCapsRenderer;
612 static inline bool fontContainsString(NSFont *font, NSString *string)
614 NSCharacterSet *set = [[font coveredCharacterSet] invertedSet];
615 return set && [string rangeOfCharacterFromSet:set].location == NSNotFound;
618 static NSFont *findSubstituteFont(WebTextRenderer *renderer, NSString *string, NSString **families)
620 NSFont *substituteFont = nil;
622 // First search the CSS family fallback list.
623 // Start at 1 (2nd font) because we've already failed on the first lookup.
624 NSString *family = nil;
626 while (families && families[i]) {
627 family = families[i++];
628 NSFont *f = [[WebTextRendererFactory sharedFactory] cachedFontFromFamily:family
629 traits:[[NSFontManager sharedFontManager] traitsOfFont:renderer->font.font]
630 size:[renderer->font.font pointSize]];
631 if (f && fontContainsString(f, string)) {
637 // Now do string based lookup.
638 if (substituteFont == nil)
639 substituteFont = WKGetFontInLanguageForRange(renderer->font.font, string, NSMakeRange(0, [string length]));
641 // Now do character based lookup.
642 if (substituteFont == nil && [string length] == 1)
643 substituteFont = WKGetFontInLanguageForCharacter(renderer->font.font, [string characterAtIndex:0]);
645 // Check to make sure this is a distinct font.
646 if (substituteFont && [[substituteFont screenFont] isEqual:[renderer->font.font screenFont]])
647 substituteFont = nil;
649 // Now that we have a substitute font, attempt to match it to the best variation.
650 // If we have a good match return that, otherwise return the font the AppKit has found.
651 if (substituteFont) {
652 NSFontManager *manager = [NSFontManager sharedFontManager];
653 NSFont *bestVariation = [manager fontWithFamily:[substituteFont familyName]
654 traits:[manager traitsOfFont:renderer->font.font]
655 weight:[manager weightOfFont:renderer->font.font]
656 size:[renderer->font.font pointSize]];
658 substituteFont = bestVariation;
661 return substituteFont;
664 static WebTextRenderer *rendererForAlternateFont(WebTextRenderer *renderer, WebCoreFont alternateFont)
666 if (!alternateFont.font)
669 NSFontManager *fontManager = [NSFontManager sharedFontManager];
670 NSFontTraitMask fontTraits = [fontManager traitsOfFont:renderer->font.font];
671 if (renderer->font.syntheticBold)
672 fontTraits |= NSBoldFontMask;
673 if (renderer->font.syntheticOblique)
674 fontTraits |= NSItalicFontMask;
675 NSFontTraitMask alternateFontTraits = [fontManager traitsOfFont:alternateFont.font];
677 alternateFont.syntheticBold = (fontTraits & NSBoldFontMask) && !(alternateFontTraits & NSBoldFontMask);
678 alternateFont.syntheticOblique = (fontTraits & NSItalicFontMask) && !(alternateFontTraits & NSItalicFontMask);
679 alternateFont.forPrinter = renderer->font.forPrinter;
681 return [[WebTextRendererFactory sharedFactory] rendererWithFont:alternateFont];
684 static WebTextRenderer *findSubstituteRenderer(WebTextRenderer *renderer, const unichar *characters, int numCharacters, NSString **families)
686 WebCoreFont substituteFont;
687 WebCoreInitializeFont(&substituteFont);
688 NSString *string = [[NSString alloc] initWithCharactersNoCopy:(unichar *)characters length: numCharacters freeWhenDone: NO];
689 substituteFont.font = findSubstituteFont(renderer, string, families);
691 return rendererForAlternateFont(renderer, substituteFont);
694 // Nasty hack to determine if we should round or ceil space widths.
695 // If the font is monospace or fake monospace we ceil to ensure that
696 // every character and the space are the same width. Otherwise we round.
697 static bool computeWidthForSpace(WebTextRenderer *renderer)
699 renderer->spaceGlyph = extendGlyphMap(renderer, SPACE);
700 if (renderer->spaceGlyph == 0)
703 float width = widthForGlyph(renderer, renderer->spaceGlyph);
705 renderer->spaceWidth = width;
707 renderer->treatAsFixedPitch = [[WebTextRendererFactory sharedFactory] isFontFixedPitch:renderer->font];
708 renderer->adjustedSpaceWidth = renderer->treatAsFixedPitch ? ceilf(width) : roundf(width);
713 static bool setUpFont(WebTextRenderer *renderer)
715 renderer->font.font = renderer->font.forPrinter ? [renderer->font.font printerFont] : [renderer->font.font screenFont];
718 if (ATSUCreateStyle(&fontStyle) != noErr)
721 if (!fillStyleWithAttributes(fontStyle, renderer->font.font)) {
722 ATSUDisposeStyle(fontStyle);
726 if (WKGetATSStyleGroup(fontStyle, &renderer->styleGroup) != noErr) {
727 ATSUDisposeStyle(fontStyle);
731 ATSUDisposeStyle(fontStyle);
733 if (!computeWidthForSpace(renderer)) {
734 freeGlyphMap(renderer->characterToGlyphMap);
735 renderer->characterToGlyphMap = 0;
736 WKReleaseStyleGroup(renderer->styleGroup);
737 renderer->styleGroup = 0;
746 static NSString *pathFromFont(NSFont *font)
749 OSStatus status = ATSFontGetFileSpecification(FMGetATSFontRefFromFont((FMFont)WKGetNSFontATSUFontId(font)), &oFile);
750 if (status == noErr) {
753 err = FSpMakeFSRef(&oFile, &fileRef);
755 UInt8 filePathBuffer[PATH_MAX];
756 status = FSRefMakePath(&fileRef, filePathBuffer, PATH_MAX);
758 return [NSString stringWithUTF8String:(const char *)filePathBuffer];
766 // Useful page for testing http://home.att.net/~jameskass
767 static void drawGlyphs(NSFont *font, NSColor *color, CGGlyph *glyphs, CGSize *advances, float x, float y, int numGlyphs,
768 float syntheticBoldOffset, bool syntheticOblique)
770 NSGraphicsContext *gContext = [NSGraphicsContext currentContext];
771 CGContextRef cgContext = (CGContextRef)[gContext graphicsPort];
773 bool originalShouldUseFontSmoothing = WKCGContextGetShouldSmoothFonts(cgContext);
774 CGContextSetShouldSmoothFonts(cgContext, [WebView _shouldUseFontSmoothing]);
777 if ([gContext isDrawingToScreen]) {
778 drawFont = [font screenFont];
779 if (drawFont != font)
780 // We are getting this in too many places (3406411); use ERROR so it only prints on debug versions for now. (We should debug this also, eventually).
781 ERROR("Attempting to set non-screen font (%@) when drawing to screen. Using screen font anyway, may result in incorrect metrics.",
782 [[[font fontDescriptor] fontAttributes] objectForKey:NSFontNameAttribute]);
784 drawFont = [font printerFont];
785 if (drawFont != font)
786 NSLog(@"Attempting to set non-printer font (%@) when printing. Using printer font anyway, may result in incorrect metrics.",
787 [[[font fontDescriptor] fontAttributes] objectForKey:NSFontNameAttribute]);
790 CGContextSetFont(cgContext, WKGetCGFontFromNSFont(drawFont));
792 CGAffineTransform matrix;
793 memcpy(&matrix, [drawFont matrix], sizeof(matrix));
794 if ([gContext isFlipped]) {
795 matrix.b = -matrix.b;
796 matrix.d = -matrix.d;
798 if (syntheticOblique)
799 matrix = CGAffineTransformConcat(matrix, CGAffineTransformMake(1, 0, -tanf(14 * acosf(0) / 90), 1, 0, 0));
800 CGContextSetTextMatrix(cgContext, matrix);
802 WKSetCGFontRenderingMode(cgContext, drawFont);
803 CGContextSetFontSize(cgContext, 1.0f);
807 CGContextSetTextPosition(cgContext, x, y);
808 CGContextShowGlyphsWithAdvances(cgContext, glyphs, advances, numGlyphs);
809 if (syntheticBoldOffset) {
810 CGContextSetTextPosition(cgContext, x + syntheticBoldOffset, y);
811 CGContextShowGlyphsWithAdvances(cgContext, glyphs, advances, numGlyphs);
814 CGContextSetShouldSmoothFonts(cgContext, originalShouldUseFontSmoothing);
817 static void CG_drawHighlight(WebTextRenderer *renderer, const WebCoreTextRun * run, const WebCoreTextStyle *style, const WebCoreTextGeometry *geometry)
819 if (run->length == 0)
822 if (style->backgroundColor == nil)
825 [style->backgroundColor set];
827 float yPos = geometry->useFontMetricsForSelectionYAndHeight
828 ? geometry->point.y - renderer->ascent - (renderer->lineGap / 2) : geometry->selectionY;
829 float height = geometry->useFontMetricsForSelectionYAndHeight
830 ? renderer->lineSpacing : geometry->selectionHeight;
832 WebCoreTextRun completeRun = *run;
833 completeRun.from = 0;
834 completeRun.to = run->length;
837 initializeWidthIterator(&it, renderer, &completeRun, style);
839 advanceWidthIterator(&it, run->from, 0, 0, 0);
840 float beforeWidth = it.runWidthSoFar;
841 // apply rounding as if this is the end of the run, since that's how RenderText::selectionRect() works
842 if ((style->applyWordRounding && isRoundingHackCharacter(run->characters[run->from]))
843 || style->applyRunRounding)
844 beforeWidth = ceilf(beforeWidth);
845 advanceWidthIterator(&it, run->to, 0, 0, 0);
846 float backgroundWidth = it.runWidthSoFar - beforeWidth;
848 advanceWidthIterator(&it, run->length, 0, 0, 0);
849 float totalWidth = it.runWidthSoFar;
850 if (style->applyRunRounding)
851 totalWidth = ceilf(totalWidth);
852 [NSBezierPath fillRect:NSMakeRect(geometry->point.x + roundf(totalWidth - backgroundWidth - beforeWidth), yPos, roundf(backgroundWidth), height)];
854 [NSBezierPath fillRect:NSMakeRect(geometry->point.x + roundf(beforeWidth), yPos, roundf(backgroundWidth), height)];
858 static void CG_draw(WebTextRenderer *renderer, const WebCoreTextRun *run, const WebCoreTextStyle *style, const WebCoreTextGeometry *geometry)
860 float *widthBuffer, localWidthBuffer[LOCAL_BUFFER_SIZE];
861 CGGlyph *glyphBuffer, localGlyphBuffer[LOCAL_BUFFER_SIZE];
862 WebTextRenderer **rendererBuffer, *localRendererBuffer[LOCAL_BUFFER_SIZE];
863 CGSize *advances, localAdvanceBuffer[LOCAL_BUFFER_SIZE];
864 int numGlyphs = 0, i;
866 unsigned length = run->length;
868 if (run->length == 0)
871 if (length * MAX_GLYPH_EXPANSION > LOCAL_BUFFER_SIZE) {
872 advances = malloc(length * MAX_GLYPH_EXPANSION * sizeof(CGSize));
873 widthBuffer = malloc(length * MAX_GLYPH_EXPANSION * sizeof(float));
874 glyphBuffer = malloc(length * MAX_GLYPH_EXPANSION * sizeof(ATSGlyphRef));
875 rendererBuffer = malloc(length * MAX_GLYPH_EXPANSION * sizeof(WebTextRenderer *));
877 advances = localAdvanceBuffer;
878 widthBuffer = localWidthBuffer;
879 glyphBuffer = localGlyphBuffer;
880 rendererBuffer = localRendererBuffer;
883 CG_floatWidthForRun(renderer, run, style, widthBuffer, rendererBuffer, glyphBuffer, &startX, &numGlyphs);
885 // Eek. We couldn't generate ANY glyphs for the run.
889 // Fill the advances array.
890 for (i = 0; i < numGlyphs; i++) {
891 advances[i].width = widthBuffer[i];
892 advances[i].height = 0;
895 // Calculate the starting point of the glyphs to be displayed by adding
896 // all the advances up to the first glyph.
897 startX += geometry->point.x;
899 if (style->backgroundColor != nil)
900 CG_drawHighlight(renderer, run, style, geometry);
902 // Swap the order of the glyphs if right-to-left.
905 int mid = numGlyphs / 2;
907 for (i = 0, end = numGlyphs - 1; i < mid; ++i, --end) {
908 CGGlyph gswap1 = glyphBuffer[i];
909 CGGlyph gswap2 = glyphBuffer[end];
910 glyphBuffer[i] = gswap2;
911 glyphBuffer[end] = gswap1;
913 CGSize aswap1 = advances[i];
914 CGSize aswap2 = advances[end];
915 advances[i] = aswap2;
916 advances[end] = aswap1;
918 WebTextRenderer *rswap1 = rendererBuffer[i];
919 WebTextRenderer *rswap2 = rendererBuffer[end];
920 rendererBuffer[i] = rswap2;
921 rendererBuffer[end] = rswap1;
925 // Draw each contiguous run of glyphs that use the same renderer.
926 WebTextRenderer *currentRenderer = rendererBuffer[0];
927 float nextX = startX;
930 while (nextGlyph < numGlyphs) {
931 WebTextRenderer *nextRenderer = rendererBuffer[nextGlyph];
932 if (nextRenderer != currentRenderer) {
933 drawGlyphs(currentRenderer->font.font, style->textColor, &glyphBuffer[lastFrom], &advances[lastFrom],
934 startX, geometry->point.y, nextGlyph - lastFrom,
935 currentRenderer->syntheticBoldOffset, currentRenderer->font.syntheticOblique);
936 lastFrom = nextGlyph;
937 currentRenderer = nextRenderer;
940 nextX += advances[nextGlyph].width;
943 drawGlyphs(currentRenderer->font.font, style->textColor, &glyphBuffer[lastFrom], &advances[lastFrom],
944 startX, geometry->point.y, nextGlyph - lastFrom,
945 currentRenderer->syntheticBoldOffset, currentRenderer->font.syntheticOblique);
947 if (advances != localAdvanceBuffer) {
951 free(rendererBuffer);
955 static float CG_floatWidthForRun(WebTextRenderer *renderer, const WebCoreTextRun *run, const WebCoreTextStyle *style, float *widthBuffer, WebTextRenderer **rendererBuffer, CGGlyph *glyphBuffer, float *startPosition, int *numGlyphsResult)
958 WebCoreTextRun completeRun;
959 const WebCoreTextRun *aRun;
964 completeRun.to = run->length;
967 initializeWidthIterator(&it, renderer, aRun, style);
968 int numGlyphs = advanceWidthIterator(&it, run->to, widthBuffer, rendererBuffer, glyphBuffer);
969 float runWidth = it.runWidthSoFar;
972 *startPosition = it.widthToStart;
974 advanceWidthIterator(&it, run->length, 0, 0, 0);
975 *startPosition = it.runWidthSoFar - runWidth;
979 *numGlyphsResult = numGlyphs;
983 static void updateGlyphMapEntry(WebTextRenderer *renderer, UChar32 c, ATSGlyphRef glyph, WebTextRenderer *substituteRenderer)
986 for (map = renderer->characterToGlyphMap; map; map = map->next) {
987 UChar32 start = map->startRange;
988 if (c >= start && c <= map->endRange) {
990 map->glyphs[i].glyph = glyph;
991 // This renderer will leak.
992 // No problem though; we want it to stick around forever.
993 // Max theoretical retain counts applied here will be num_fonts_on_system * num_glyphs_in_font.
994 map->glyphs[i].renderer = [substituteRenderer retain];
1000 static ATSGlyphRef extendGlyphMap(WebTextRenderer *renderer, UChar32 c)
1002 GlyphMap *map = malloc(sizeof(GlyphMap));
1003 ATSLayoutRecord *glyphRecord;
1004 char glyphVector[WKGlyphVectorSize];
1008 if (renderer->characterToGlyphMap == 0)
1009 blockSize = INITIAL_BLOCK_SIZE;
1011 blockSize = INCREMENTAL_BLOCK_SIZE;
1012 start = (c / blockSize) * blockSize;
1013 end = start + (blockSize - 1);
1015 LOG(FontCache, "%@ (0x%04x) adding glyphs for 0x%04x to 0x%04x", renderer->font, c, start, end);
1017 map->startRange = start;
1018 map->endRange = end;
1022 unsigned count = end - start + 1;
1023 unsigned short buffer[INCREMENTAL_BLOCK_SIZE * 2 + 2];
1024 unsigned bufferLength;
1026 if (start < 0x10000) {
1027 bufferLength = count;
1028 for (i = 0; i < count; i++)
1029 buffer[i] = i + start;
1032 // Control characters must not render at all.
1033 for (i = 0; i < 0x20; ++i)
1034 buffer[i] = ZERO_WIDTH_SPACE;
1035 buffer[0x7F] = ZERO_WIDTH_SPACE;
1037 // But \n, \t, and nonbreaking space must render as a space.
1040 buffer[NO_BREAK_SPACE] = ' ';
1043 bufferLength = count * 2;
1044 for (i = 0; i < count; i++) {
1046 buffer[i * 2] = U16_LEAD(c);
1047 buffer[i * 2 + 1] = U16_TRAIL(c);
1051 OSStatus status = WKInitializeGlyphVector(count, &glyphVector);
1052 if (status != noErr) {
1053 // This should never happen, perhaps indicates a bad font! If it does the
1054 // font substitution code will find an alternate font.
1059 WKConvertCharToGlyphs(renderer->styleGroup, &buffer[0], bufferLength, &glyphVector);
1060 unsigned numGlyphs = WKGetGlyphVectorNumGlyphs(&glyphVector);
1061 if (numGlyphs != count) {
1062 // This should never happen, perhaps indicates a bad font?
1063 // If it does happen, the font substitution code will find an alternate font.
1064 WKClearGlyphVector(&glyphVector);
1069 map->glyphs = malloc(count * sizeof(GlyphEntry));
1070 glyphRecord = (ATSLayoutRecord *)WKGetGlyphVectorFirstRecord(glyphVector);
1071 for (i = 0; i < count; i++) {
1072 map->glyphs[i].glyph = glyphRecord->glyphID;
1073 map->glyphs[i].renderer = renderer;
1074 glyphRecord = (ATSLayoutRecord *)((char *)glyphRecord + WKGetGlyphVectorRecordSize(glyphVector));
1076 WKClearGlyphVector(&glyphVector);
1078 if (renderer->characterToGlyphMap == 0)
1079 renderer->characterToGlyphMap = map;
1081 GlyphMap *lastMap = renderer->characterToGlyphMap;
1082 while (lastMap->next != 0)
1083 lastMap = lastMap->next;
1084 lastMap->next = map;
1087 ATSGlyphRef glyph = map->glyphs[c - start].glyph;
1089 // Special case for characters 007F-00A0.
1090 if (glyph == 0 && c >= 0x7F && c <= 0xA0) {
1091 glyph = WKGetDefaultGlyphForChar(renderer->font.font, c);
1092 map->glyphs[c - start].glyph = glyph;
1098 static WidthMap *extendWidthMap(WebTextRenderer *renderer, ATSGlyphRef glyph)
1100 WidthMap *map = (WidthMap *)calloc(1, sizeof(WidthMap));
1106 NSFont *f = renderer->font.font;
1107 if (renderer->glyphToWidthMap == 0) {
1108 if ([f numberOfGlyphs] < INITIAL_BLOCK_SIZE)
1109 blockSize = [f numberOfGlyphs];
1111 blockSize = INITIAL_BLOCK_SIZE;
1113 blockSize = INCREMENTAL_BLOCK_SIZE;
1115 if (blockSize == 0) {
1118 start = (glyph / blockSize) * blockSize;
1120 end = ((unsigned)start) + blockSize;
1122 LOG(FontCache, "%@ (0x%04x) adding widths for range 0x%04x to 0x%04x", renderer->font, glyph, start, end);
1124 map->startRange = start;
1125 map->endRange = end;
1126 count = end - start + 1;
1128 map->widths = malloc(count * sizeof(WebGlyphWidth));
1129 for (i = 0; i < count; i++)
1130 map->widths[i] = NAN;
1132 if (renderer->glyphToWidthMap == 0)
1133 renderer->glyphToWidthMap = map;
1135 WidthMap *lastMap = renderer->glyphToWidthMap;
1136 while (lastMap->next != 0)
1137 lastMap = lastMap->next;
1138 lastMap->next = map;
1144 static void initializeATSUStyle(WebTextRenderer *renderer)
1146 // The two NSFont calls in this method (pointSize and _atsFontID) do not raise exceptions.
1148 if (!renderer->ATSUStyleInitialized) {
1150 ByteCount propTableSize;
1152 status = ATSUCreateStyle(&renderer->_ATSUStyle);
1153 if (status != noErr)
1154 FATAL_ALWAYS("ATSUCreateStyle failed (%d)", status);
1156 ATSUFontID fontID = WKGetNSFontATSUFontId(renderer->font.font);
1158 ATSUDisposeStyle(renderer->_ATSUStyle);
1159 ERROR("unable to get ATSUFontID for %@", renderer->font.font);
1163 CGAffineTransform transform = CGAffineTransformMakeScale(1, -1);
1164 Fixed fontSize = FloatToFixed([renderer->font.font pointSize]);
1165 // Turn off automatic kerning until it is supported in the CG code path (6136 in bugzilla)
1166 Fract kerningInhibitFactor = FloatToFract(1.0);
1167 ATSUAttributeTag styleTags[4] = { kATSUSizeTag, kATSUFontTag, kATSUFontMatrixTag, kATSUKerningInhibitFactorTag };
1168 ByteCount styleSizes[4] = { sizeof(Fixed), sizeof(ATSUFontID), sizeof(CGAffineTransform), sizeof(Fract) };
1169 ATSUAttributeValuePtr styleValues[4] = { &fontSize, &fontID, &transform, &kerningInhibitFactor };
1170 status = ATSUSetAttributes(renderer->_ATSUStyle, 4, styleTags, styleSizes, styleValues);
1171 if (status != noErr)
1172 FATAL_ALWAYS("ATSUSetAttributes failed (%d)", status);
1173 status = ATSFontGetTable(fontID, 'prop', 0, 0, 0, &propTableSize);
1174 if (status == noErr) // naively assume that if a 'prop' table exists then it contains mirroring info
1175 renderer->ATSUMirrors = YES;
1176 else if (status == kATSInvalidFontTableAccess)
1177 renderer->ATSUMirrors = NO;
1179 FATAL_ALWAYS("ATSFontGetTable failed (%d)", status);
1181 // Turn off ligatures such as 'fi' to match the CG code path's behavior, until bugzilla 6135 is fixed.
1182 // Don't be too aggressive: if the font doesn't contain 'a', then assume that any ligatures it contains are
1183 // in characters that always go through ATSUI, and therefore allow them. Geeza Pro is an example.
1184 // See bugzilla 5166.
1185 if ([[renderer->font.font coveredCharacterSet] characterIsMember:'a']) {
1186 ATSUFontFeatureType featureTypes[] = { kLigaturesType };
1187 ATSUFontFeatureSelector featureSelectors[] = { kCommonLigaturesOffSelector };
1188 status = ATSUSetFontFeatures(renderer->_ATSUStyle, 1, featureTypes, featureSelectors);
1191 renderer->ATSUStyleInitialized = YES;
1195 static ATSUTextLayout createATSUTextLayout(WebTextRenderer *renderer, const WebCoreTextRun *run, const WebCoreTextStyle *style)
1197 // The only Cocoa calls here are to NSGraphicsContext, which does not raise exceptions.
1199 ATSUTextLayout layout;
1200 UniCharCount runLength;
1201 ATSUFontID ATSUSubstituteFont;
1202 UniCharArrayOffset substituteOffset;
1203 UniCharCount substituteLength;
1205 ATSULayoutOperationOverrideSpecifier overrideSpecifier;
1207 initializeATSUStyle(renderer);
1209 // FIXME: This is currently missing the following required features that the CoreGraphics code path has:
1210 // - \n, \t, and nonbreaking space render as a space.
1211 // - Other control characters do not render (other code path uses zero-width spaces).
1213 // - Synthesized bold.
1214 // - Synthesized oblique.
1216 runLength = run->to - run->from;
1217 status = ATSUCreateTextLayoutWithTextPtr(
1219 run->from, // offset
1220 runLength, // length
1221 run->length, // total length
1223 &runLength, // length of style run
1224 &renderer->_ATSUStyle,
1226 if (status != noErr)
1227 FATAL_ALWAYS("ATSUCreateTextLayoutWithTextPtr failed(%d)", status);
1229 CGContextRef cgContext = (CGContextRef)[[NSGraphicsContext currentContext] graphicsPort];
1230 ATSLineLayoutOptions lineLayoutOptions = kATSLineKeepSpacesOutOfMargin | kATSLineHasNoHangers;
1231 Boolean rtl = style->rtl;
1232 overrideSpecifier.operationSelector = kATSULayoutOperationPostLayoutAdjustment;
1233 overrideSpecifier.overrideUPP = overrideLayoutOperation;
1234 ATSUAttributeTag tags[] = { kATSUCGContextTag, kATSULineLayoutOptionsTag, kATSULineDirectionTag, kATSULayoutOperationOverrideTag };
1235 ByteCount sizes[] = { sizeof(CGContextRef), sizeof(ATSLineLayoutOptions), sizeof(Boolean), sizeof(ATSULayoutOperationOverrideSpecifier) };
1236 ATSUAttributeValuePtr values[] = { &cgContext, &lineLayoutOptions, &rtl, &overrideSpecifier };
1238 status = ATSUSetLayoutControls(layout, (style->applyWordRounding ? 4 : 3), tags, sizes, values);
1239 if (status != noErr)
1240 FATAL_ALWAYS("ATSUSetLayoutControls failed(%d)", status);
1242 status = ATSUSetTransientFontMatching(layout, YES);
1243 if (status != noErr)
1244 FATAL_ALWAYS("ATSUSetTransientFontMatching failed(%d)", status);
1246 substituteOffset = run->from;
1247 while ((status = ATSUMatchFontsToText(layout, substituteOffset, kATSUToTextEnd, &ATSUSubstituteFont, &substituteOffset, &substituteLength)) == kATSUFontsMatched || status == kATSUFontsNotMatched) {
1248 WebTextRenderer *substituteRenderer = findSubstituteRenderer(renderer, run->characters + substituteOffset, substituteLength, style->families);
1249 if (substituteRenderer) {
1250 initializeATSUStyle(substituteRenderer);
1251 if (substituteRenderer->_ATSUStyle)
1252 ATSUSetRunStyle(layout, substituteRenderer->_ATSUStyle, substituteOffset, substituteLength);
1255 substituteOffset += substituteLength;
1257 // ignoring errors in font substitution
1262 static ATSTrapezoid getTextBounds(WebTextRenderer *renderer, const WebCoreTextRun *run, const WebCoreTextStyle *style, NSPoint p)
1266 if (run->to - run->from <= 0) {
1267 ATSTrapezoid nilTrapezoid = { {0, 0}, {0, 0}, {0, 0}, {0, 0} };
1268 return nilTrapezoid;
1271 ATSULayoutParameters params;
1272 createATSULayoutParameters(¶ms, renderer, run, style);
1274 ATSTrapezoid firstGlyphBounds;
1275 ItemCount actualNumBounds;
1276 status = ATSUGetGlyphBounds(params.layout, FloatToFixed(p.x), FloatToFixed(p.y), run->from, run->to - run->from, kATSUseFractionalOrigins, 1, &firstGlyphBounds, &actualNumBounds);
1277 if (status != noErr)
1278 FATAL_ALWAYS("ATSUGetGlyphBounds() failed(%d)", status);
1279 if (actualNumBounds != 1)
1280 FATAL_ALWAYS("unexpected result from ATSUGetGlyphBounds(): actualNumBounds(%d) != 1", actualNumBounds);
1282 disposeATSULayoutParameters(¶ms);
1284 return firstGlyphBounds;
1287 static float ATSU_floatWidthForRun(WebTextRenderer *renderer, const WebCoreTextRun *run, const WebCoreTextStyle *style)
1289 ATSTrapezoid oGlyphBounds = getTextBounds(renderer, run, style, NSZeroPoint);
1290 return MAX(FixedToFloat(oGlyphBounds.upperRight.x), FixedToFloat(oGlyphBounds.lowerRight.x)) -
1291 MIN(FixedToFloat(oGlyphBounds.upperLeft.x), FixedToFloat(oGlyphBounds.lowerLeft.x));
1294 // Be sure to free the run.characters allocated by this function.
1295 static WebCoreTextRun addDirectionalOverride(const WebCoreTextRun *run, bool rtl)
1297 int from = run->from;
1304 UniChar *charactersWithOverride = malloc(sizeof(UniChar) * (run->length + 2));
1306 charactersWithOverride[0] = rtl ? RIGHT_TO_LEFT_OVERRIDE : LEFT_TO_RIGHT_OVERRIDE;
1307 memcpy(&charactersWithOverride[1], &run->characters[0], sizeof(UniChar) * run->length);
1308 charactersWithOverride[run->length + 1] = POP_DIRECTIONAL_FORMATTING;
1310 WebCoreTextRun runWithOverride;
1312 runWithOverride.from = from + 1;
1313 runWithOverride.to = to + 1;
1314 runWithOverride.length = run->length + 2;
1315 runWithOverride.characters = charactersWithOverride;
1317 return runWithOverride;
1320 // Be sure to free the run.characters allocated by this function.
1321 static WebCoreTextRun applyMirroringToRun(const WebCoreTextRun *run)
1323 UniChar *mirroredCharacters = malloc(sizeof(UniChar)*(run->length));
1325 for (i = 0; i < run->length; i++)
1326 mirroredCharacters[i] = u_charMirror(run->characters[i]);
1328 WebCoreTextRun mirroredRun;
1330 mirroredRun.from = run->from;
1331 mirroredRun.to = run->to;
1332 mirroredRun.length = run->length;
1333 mirroredRun.characters = mirroredCharacters;
1338 static void ATSU_drawHighlight(WebTextRenderer *renderer, const WebCoreTextRun *run, const WebCoreTextStyle *style, const WebCoreTextGeometry *geometry)
1340 // The only Cocoa calls made here are to NSColor and NSBezierPath, and they do not raise exceptions.
1342 if (style->backgroundColor == nil)
1345 int from = run->from;
1351 int runLength = to - from;
1355 WebCoreTextRun runWithLead = *run;
1356 runWithLead.from = 0;
1357 WebCoreTextRun *aRun = &runWithLead;
1358 WebCoreTextRun swappedRun;
1360 if (style->directionalOverride) {
1361 swappedRun = addDirectionalOverride(aRun, style->rtl);
1363 } else if (style->rtl && !renderer->ATSUMirrors) {
1364 swappedRun = applyMirroringToRun(aRun);
1369 float selectedLeftX;
1370 float widthWithLead = ATSU_floatWidthForRun(renderer, aRun, style);
1372 aRun->to -= runLength;
1373 float leadWidth = ATSU_floatWidthForRun(renderer, aRun, style);
1375 float backgroundWidth = roundf(widthWithLead - leadWidth);
1378 selectedLeftX = roundf(geometry->point.x + leadWidth);
1380 aRun->to += run->length - run->from;
1381 float totalWidth = ATSU_floatWidthForRun(renderer, aRun, style);
1382 selectedLeftX = roundf(geometry->point.x + totalWidth - widthWithLead);
1385 [style->backgroundColor set];
1387 float yPos = geometry->useFontMetricsForSelectionYAndHeight
1388 ? geometry->point.y - renderer->ascent : geometry->selectionY;
1389 float height = geometry->useFontMetricsForSelectionYAndHeight
1390 ? renderer->lineSpacing : geometry->selectionHeight;
1391 [NSBezierPath fillRect:NSMakeRect(selectedLeftX, yPos, backgroundWidth, height)];
1393 if (style->directionalOverride || (style->rtl && !renderer->ATSUMirrors))
1394 free((void *)swappedRun.characters);
1398 static void ATSU_draw(WebTextRenderer *renderer, const WebCoreTextRun *run, const WebCoreTextStyle *style, const WebCoreTextGeometry *geometry)
1400 // The only Cocoa calls made here are to NSColor and NSGraphicsContext, and they do not raise exceptions.
1404 const WebCoreTextRun *aRun = run;
1405 WebCoreTextRun swappedRun;
1407 if (style->directionalOverride) {
1408 swappedRun = addDirectionalOverride(run, style->rtl);
1410 } else if (style->rtl && !renderer->ATSUMirrors) {
1411 swappedRun = applyMirroringToRun(run);
1422 int runLength = to - from;
1426 WebCoreTextRun completeRun = *aRun;
1427 completeRun.from = 0;
1428 completeRun.to = aRun->length;
1429 ATSULayoutParameters params;
1430 createATSULayoutParameters(¶ms, renderer, &completeRun, style);
1432 if (style->backgroundColor != nil)
1433 ATSU_drawHighlight(renderer, run, style, geometry);
1435 [style->textColor set];
1437 // ATSUI can't draw beyond -32768 to +32767 so we translate the CTM and tell ATSUI to draw at (0, 0).
1438 NSGraphicsContext *gContext = [NSGraphicsContext currentContext];
1439 CGContextRef context = (CGContextRef)[gContext graphicsPort];
1440 CGContextTranslateCTM(context, geometry->point.x, geometry->point.y);
1441 bool flipped = [gContext isFlipped];
1443 CGContextScaleCTM(context, 1.0, -1.0);
1444 status = ATSUDrawText(params.layout, aRun->from, runLength, 0, 0);
1446 CGContextScaleCTM(context, 1.0, -1.0);
1447 CGContextTranslateCTM(context, -geometry->point.x, -geometry->point.y);
1449 if (status != noErr) {
1450 // Nothing to do but report the error (dev build only).
1451 ERROR("ATSUDrawText() failed(%d)", status);
1454 disposeATSULayoutParameters(¶ms);
1456 if (style->directionalOverride || (style->rtl && !renderer->ATSUMirrors))
1457 free((void *)swappedRun.characters);
1460 static int ATSU_pointToOffset(WebTextRenderer *renderer, const WebCoreTextRun *run, const WebCoreTextStyle *style,
1461 int x, bool includePartialGlyphs)
1463 const WebCoreTextRun *aRun = run;
1464 WebCoreTextRun swappedRun;
1466 // Enclose in LRO/RLO - PDF to force ATSU to render visually.
1467 if (style->directionalOverride) {
1468 swappedRun = addDirectionalOverride(aRun, style->rtl);
1470 } else if (style->rtl && !renderer->ATSUMirrors) {
1471 swappedRun = applyMirroringToRun(aRun);
1475 ATSULayoutParameters params;
1476 createATSULayoutParameters(¶ms, renderer, aRun, style);
1478 UniCharArrayOffset primaryOffset = aRun->from;
1480 // FIXME: No idea how to avoid including partial glyphs.
1481 // Not even sure if that's the behavior this yields now.
1483 UniCharArrayOffset secondaryOffset = 0;
1484 OSStatus status = ATSUPositionToOffset(params.layout, FloatToFixed(x), FloatToFixed(-1), &primaryOffset, &isLeading, &secondaryOffset);
1486 if (status == noErr) {
1487 offset = (unsigned)primaryOffset;
1489 // Failed to find offset! Return 0 offset.
1493 disposeATSULayoutParameters(¶ms);
1495 if (style->directionalOverride || (style->rtl && !renderer->ATSUMirrors))
1496 free((void *)swappedRun.characters);
1498 return offset - aRun->from;
1501 static bool advanceWidthIteratorOneCharacter(WidthIterator *iterator, float *totalWidth)
1503 float widths[MAX_GLYPH_EXPANSION];
1504 WebTextRenderer *renderers[MAX_GLYPH_EXPANSION];
1505 ATSGlyphRef glyphs[MAX_GLYPH_EXPANSION];
1506 unsigned numGlyphs = advanceWidthIterator(iterator, iterator->currentCharacter + 1, widths, renderers, glyphs);
1509 for (i = 0; i < numGlyphs; ++i)
1512 return numGlyphs != 0;
1515 static int CG_pointToOffset(WebTextRenderer *renderer, const WebCoreTextRun * run, const WebCoreTextStyle *style,
1516 int x, bool includePartialGlyphs)
1518 float delta = (float)x;
1521 initializeWidthIterator(&it, renderer, run, style);
1526 delta -= CG_floatWidthForRun(renderer, run, style, 0, 0, 0, 0, 0);
1528 offset = it.currentCharacter;
1530 if (!advanceWidthIteratorOneCharacter(&it, &w))
1533 if (includePartialGlyphs) {
1534 if (delta - w / 2 >= 0)
1543 offset = it.currentCharacter;
1545 if (!advanceWidthIteratorOneCharacter(&it, &w))
1548 if (includePartialGlyphs) {
1549 if (delta + w / 2 <= 0)
1558 return offset - run->from;
1561 static void freeWidthMap(WidthMap *map)
1564 WidthMap *next = map->next;
1571 static void freeGlyphMap(GlyphMap *map)
1574 GlyphMap *next = map->next;
1581 static inline ATSGlyphRef glyphForCharacter(WebTextRenderer **renderer, UChar32 c)
1583 // this loop is hot, so it is written to avoid LSU stalls
1586 for (map = (*renderer)->characterToGlyphMap; map; map = nextMap) {
1587 UChar32 start = map->startRange;
1588 nextMap = map->next;
1589 if (c >= start && c <= map->endRange) {
1590 GlyphEntry *ge = &map->glyphs[c - start];
1591 *renderer = ge->renderer;
1596 return extendGlyphMap(*renderer, c);
1599 static void initializeWidthIterator(WidthIterator *iterator, WebTextRenderer *renderer, const WebCoreTextRun *run, const WebCoreTextStyle *style)
1601 iterator->renderer = renderer;
1602 iterator->run = run;
1603 iterator->style = style;
1604 iterator->currentCharacter = run->from;
1605 iterator->runWidthSoFar = 0;
1607 // If the padding is non-zero, count the number of spaces in the run
1608 // and divide that by the padding for per space addition.
1609 if (!style->padding) {
1610 iterator->padding = 0;
1611 iterator->padPerSpace = 0;
1613 float numSpaces = 0;
1615 for (k = run->from; k < run->to; k++)
1616 if (isSpace(run->characters[k]))
1619 iterator->padding = style->padding;
1620 iterator->padPerSpace = ceilf(iterator->padding / numSpaces);
1623 // Calculate width up to starting position of the run. This is
1624 // necessary to ensure that our rounding hacks are always consistently
1626 if (run->from == 0) {
1627 iterator->widthToStart = 0;
1629 WebCoreTextRun startPositionRun = *run;
1630 startPositionRun.from = 0;
1631 startPositionRun.to = run->length;
1632 WidthIterator startPositionIterator;
1633 initializeWidthIterator(&startPositionIterator, renderer, &startPositionRun, style);
1634 advanceWidthIterator(&startPositionIterator, run->from, 0, 0, 0);
1635 iterator->widthToStart = startPositionIterator.runWidthSoFar;
1639 static void createATSULayoutParameters(ATSULayoutParameters *params, WebTextRenderer *renderer, const WebCoreTextRun *run, const WebCoreTextStyle *style)
1641 params->renderer = renderer;
1643 params->style = style;
1644 params->layout = createATSUTextLayout(renderer, run, style);
1645 ATSUSetTextLayoutRefCon(params->layout, (UInt32)params);
1648 static void disposeATSULayoutParameters(ATSULayoutParameters *params)
1650 ATSUDisposeTextLayout(params->layout);
1653 static UChar32 normalizeVoicingMarks(WidthIterator *iterator)
1655 unsigned currentCharacter = iterator->currentCharacter;
1656 const WebCoreTextRun *run = iterator->run;
1657 if (currentCharacter + 1 < (unsigned)run->to) {
1658 if (u_getCombiningClass(run->characters[currentCharacter + 1]) == HIRAGANA_KATAKANA_VOICING_MARKS) {
1659 // Normalize into composed form using 3.2 rules.
1660 UChar normalizedCharacters[2] = { 0, 0 };
1661 UErrorCode uStatus = 0;
1662 int32_t resultLength = unorm_normalize(&run->characters[currentCharacter], 2,
1663 UNORM_NFC, UNORM_UNICODE_3_2, &normalizedCharacters[0], 2, &uStatus);
1664 if (resultLength == 1 && uStatus == 0)
1665 return normalizedCharacters[0];
1671 static unsigned advanceWidthIterator(WidthIterator *iterator, unsigned offset, float *widths, WebTextRenderer **renderersUsed, ATSGlyphRef *glyphsUsed)
1673 const WebCoreTextRun *run = iterator->run;
1674 if (offset > (unsigned)run->to)
1677 unsigned numGlyphs = 0;
1679 unsigned currentCharacter = iterator->currentCharacter;
1680 const UniChar *cp = &run->characters[currentCharacter];
1682 const WebCoreTextStyle *style = iterator->style;
1683 bool needCharTransform = style->rtl | style->smallCaps;
1684 bool hasExtraSpacing = style->letterSpacing | style->wordSpacing | style->padding;
1686 float runWidthSoFar = iterator->runWidthSoFar;
1688 while (currentCharacter < offset) {
1691 unsigned clusterLength = 1;
1694 // Deal with Hiragana and Katakana voiced and semi-voiced syllables.
1695 // Normalize into composed form, and then look for glyph with base + combined mark.
1696 // Check above for character range to minimize performance impact.
1697 UChar32 normalized = normalizeVoicingMarks(iterator);
1702 } else if (U16_IS_SURROGATE(c)) {
1703 if (!U16_IS_SURROGATE_LEAD(c))
1706 // Do we have a surrogate pair? If so, determine the full Unicode (32 bit)
1707 // code point before glyph lookup.
1708 // Make sure we have another character and it's a low surrogate.
1709 if (currentCharacter + 1 >= run->length)
1711 UniChar low = cp[1];
1712 if (!U16_IS_TRAIL(low))
1714 c = U16_GET_SUPPLEMENTARY(c, low);
1719 WebTextRenderer *renderer = iterator->renderer;
1721 if (needCharTransform) {
1723 c = u_charMirror(c);
1725 // If small-caps, convert lowercase to upper.
1726 if (style->smallCaps && !u_isUUppercase(c)) {
1727 UChar32 upperC = u_toupper(c);
1730 renderer = getSmallCapsRenderer(renderer);
1735 ATSGlyphRef glyph = glyphForCharacter(&renderer, c);
1737 // Now that we have glyph and font, get its width.
1738 WebGlyphWidth width;
1739 if (c == '\t' && style->tabWidth) {
1740 width = style->tabWidth - fmodf(style->xpos + runWidthSoFar, style->tabWidth);
1742 width = widthForGlyph(renderer, glyph);
1743 // We special case spaces in two ways when applying word rounding.
1744 // First, we round spaces to an adjusted width in all fonts.
1745 // Second, in fixed-pitch fonts we ensure that all characters that
1746 // match the width of the space character have the same width as the space character.
1747 if (width == renderer->spaceWidth && (renderer->treatAsFixedPitch || glyph == renderer->spaceGlyph) && style->applyWordRounding)
1748 width = renderer->adjustedSpaceWidth;
1751 // Try to find a substitute font if this font didn't have a glyph for a character in the
1752 // string. If one isn't found we end up drawing and measuring the 0 glyph, usually a box.
1753 if (glyph == 0 && style->attemptFontSubstitution) {
1754 WebTextRenderer *substituteRenderer = findSubstituteRenderer(renderer, cp, clusterLength, style->families);
1755 if (substituteRenderer) {
1756 WebCoreTextRun clusterRun = { cp, clusterLength, 0, clusterLength };
1757 WebCoreTextStyle clusterStyle = *style;
1758 clusterStyle.padding = 0;
1759 clusterStyle.applyRunRounding = NO;
1760 clusterStyle.attemptFontSubstitution = NO;
1763 float localWidthBuffer[MAX_GLYPH_EXPANSION];
1764 WebTextRenderer *localRendererBuffer[MAX_GLYPH_EXPANSION];
1765 ATSGlyphRef localGlyphBuffer[MAX_GLYPH_EXPANSION];
1766 CG_floatWidthForRun(substituteRenderer, &clusterRun, &clusterStyle, localWidthBuffer, localRendererBuffer, localGlyphBuffer, 0, &cNumGlyphs);
1767 if (cNumGlyphs == 1) {
1768 ASSERT(substituteRenderer == localRendererBuffer[0]);
1769 width = localWidthBuffer[0];
1770 glyph = localGlyphBuffer[0];
1771 updateGlyphMapEntry(renderer, c, glyph, substituteRenderer);
1772 renderer = substituteRenderer;
1777 if (hasExtraSpacing) {
1778 // Account for letter-spacing.
1779 if (width && style->letterSpacing)
1780 width += style->letterSpacing;
1783 // Account for padding. WebCore uses space padding to justify text.
1784 // We distribute the specified padding over the available spaces in the run.
1785 if (style->padding) {
1786 // Use left over padding if not evenly divisible by number of spaces.
1787 if (iterator->padding < iterator->padPerSpace) {
1788 width += iterator->padding;
1789 iterator->padding = 0;
1791 width += iterator->padPerSpace;
1792 iterator->padding -= iterator->padPerSpace;
1796 // Account for word spacing.
1797 // We apply additional space between "words" by adding width to the space character.
1798 if (currentCharacter != 0 && !isSpace(cp[-1]) && style->wordSpacing)
1799 width += style->wordSpacing;
1803 // Advance past the character we just dealt with.
1804 cp += clusterLength;
1805 currentCharacter += clusterLength;
1807 // Account for float/integer impedance mismatch between CG and KHTML. "Words" (characters
1808 // followed by a character defined by isRoundingHackCharacter()) are always an integer width.
1809 // We adjust the width of the last character of a "word" to ensure an integer width.
1810 // If we move KHTML to floats we can remove this (and related) hacks.
1812 // Force characters that are used to determine word boundaries for the rounding hack
1813 // to be integer width, so following words will start on an integer boundary.
1814 if (style->applyWordRounding && isRoundingHackCharacter(c))
1815 width = ceilf(width);
1817 // Check to see if the next character is a "rounding hack character", if so, adjust
1818 // width so that the total run width will be on an integer boundary.
1819 if ((style->applyWordRounding && currentCharacter < run->length && isRoundingHackCharacter(*cp))
1820 || (style->applyRunRounding && currentCharacter >= (unsigned)run->to)) {
1821 float totalWidth = iterator->widthToStart + runWidthSoFar + width;
1822 width += ceilf(totalWidth) - totalWidth;
1825 runWidthSoFar += width;
1828 ASSERT(!renderersUsed);
1829 ASSERT(!glyphsUsed);
1831 ASSERT(renderersUsed);
1834 *renderersUsed++ = renderer;
1835 *glyphsUsed++ = glyph;
1841 iterator->currentCharacter = currentCharacter;
1842 iterator->runWidthSoFar = runWidthSoFar;
1847 static bool fillStyleWithAttributes(ATSUStyle style, NSFont *theFont)
1851 ATSUFontID fontId = WKGetNSFontATSUFontId(theFont);
1852 LOG(FontCache, "fillStyleWithAttributes: font = %p,%@, _atsFontID = %x\n", theFont, theFont, (unsigned)fontId);
1855 ATSUAttributeTag tag = kATSUFontTag;
1856 ByteCount size = sizeof(ATSUFontID);
1857 ATSUFontID *valueArray[1] = {&fontId};
1858 OSStatus status = ATSUSetAttributes(style, 1, &tag, &size, (void *)valueArray);
1859 if (status != noErr) {
1860 LOG(FontCache, "fillStyleWithAttributes failed(%d): font = %p,%@, _atsFontID = %x\n", (int)status, theFont, theFont, (unsigned)fontId);
1866 static bool shouldUseATSU(const WebCoreTextRun *run)
1871 const UniChar *characters = run->characters;
1874 // Start from 0 since drawing and highlighting also measure the characters before run->from
1875 for (i = 0; i < to; i++) {
1876 UniChar c = characters[i];
1877 if (c < 0x300) // U+0300 through U+036F Combining diacritical marks
1882 if (c < 0x0591) // U+0591 through U+1059 Arabic, Hebrew, Syriac, Thaana, Devanagari, Bengali, Gurmukhi, Gujarati, Oriya, Tamil, Telugu, Kannada, Malayalam, Sinhala, Thai, Lao, Tibetan, Myanmar
1887 if (c < 0x1100) // 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)
1892 if (c < 0x1780) // U+1780 through U+18AF Khmer, Mongolian
1897 if (c < 0x1900) // U+1900 through U+194F Limbu (Unicode 4.0)
1902 if (c < 0x20D0) // U+20D0 through U+20FF Combining marks for symbols
1907 if (c < 0xFE20) // U+FE20 through U+FE2F Combining half marks