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
49 #define SYNTHETIC_OBLIQUE_ANGLE 14
51 // Should be more than enough for normal usage.
52 #define NUM_SUBSTITUTE_FONT_MAPS 10
54 // According to http://www.unicode.org/Public/UNIDATA/UCD.html#Canonical_Combining_Class_Values
55 #define HIRAGANA_KATAKANA_VOICING_MARKS 8
58 #define NO_BREAK_SPACE 0x00A0
59 #define ZERO_WIDTH_SPACE 0x200B
60 #define POP_DIRECTIONAL_FORMATTING 0x202C
61 #define LEFT_TO_RIGHT_OVERRIDE 0x202D
62 #define RIGHT_TO_LEFT_OVERRIDE 0x202E
64 // MAX_GLYPH_EXPANSION is the maximum numbers of glyphs that may be
65 // use to represent a single Unicode code point.
66 #define MAX_GLYPH_EXPANSION 4
67 #define LOCAL_BUFFER_SIZE 2048
70 #define INITIAL_BLOCK_SIZE 0x200
72 // Get additional blocks of glyphs and widths in bigger chunks.
73 // This will typically be for other character sets.
74 #define INCREMENTAL_BLOCK_SIZE 0x400
76 #define CONTEXT_DPI (72.0)
77 #define SCALE_EM_TO_UNITS(X, U_PER_EM) (X * ((1.0 * CONTEXT_DPI) / (CONTEXT_DPI * U_PER_EM)))
79 typedef float WebGlyphWidth;
82 ATSGlyphRef startRange;
85 WebGlyphWidth *widths;
88 typedef struct GlyphEntry {
90 WebTextRenderer *renderer;
100 typedef struct WidthIterator {
101 WebTextRenderer *renderer;
102 const WebCoreTextRun *run;
103 const WebCoreTextStyle *style;
104 unsigned currentCharacter;
111 typedef struct ATSULayoutParameters
113 const WebCoreTextRun *run;
114 const WebCoreTextStyle *style;
115 ATSUTextLayout layout;
116 WebTextRenderer **renderers;
118 bool hasSyntheticBold;
119 bool syntheticBoldPass;
120 } ATSULayoutParameters;
122 static WebTextRenderer *rendererForAlternateFont(WebTextRenderer *, WebCoreFont);
124 static WidthMap *extendWidthMap(WebTextRenderer *, ATSGlyphRef);
125 static ATSGlyphRef extendGlyphMap(WebTextRenderer *, UChar32);
126 static void updateGlyphMapEntry(WebTextRenderer *, UChar32, ATSGlyphRef, WebTextRenderer *substituteRenderer);
128 static void freeWidthMap(WidthMap *);
129 static void freeGlyphMap(GlyphMap *);
132 static float CG_floatWidthForRun(WebTextRenderer *, const WebCoreTextRun *, const WebCoreTextStyle *,
133 float *widthBuffer, WebTextRenderer **rendererBuffer, CGGlyph *glyphBuffer, float *startPosition, int *numGlyphsResult);
134 static float ATSU_floatWidthForRun(WebTextRenderer *, const WebCoreTextRun *, const WebCoreTextStyle *);
137 static void CG_draw(WebTextRenderer *, const WebCoreTextRun *, const WebCoreTextStyle *, const WebCoreTextGeometry *);
138 static void ATSU_draw(WebTextRenderer *, const WebCoreTextRun *, const WebCoreTextStyle *, const WebCoreTextGeometry *);
140 // Selection point detection in runs.
141 static int CG_pointToOffset(WebTextRenderer *, const WebCoreTextRun *, const WebCoreTextStyle *,
142 int x, bool includePartialGlyphs);
143 static int ATSU_pointToOffset(WebTextRenderer *, const WebCoreTextRun *, const WebCoreTextStyle *,
144 int x, bool includePartialGlyphs);
146 // Drawing highlight.
147 static void CG_drawHighlight(WebTextRenderer *, const WebCoreTextRun *, const WebCoreTextStyle *, const WebCoreTextGeometry *);
148 static void ATSU_drawHighlight(WebTextRenderer *, const WebCoreTextRun *, const WebCoreTextStyle *, const WebCoreTextGeometry *);
150 static bool setUpFont(WebTextRenderer *);
152 // Iterator functions
153 static void initializeWidthIterator(WidthIterator *iterator, WebTextRenderer *renderer, const WebCoreTextRun *run, const WebCoreTextStyle *style);
154 static unsigned advanceWidthIterator(WidthIterator *iterator, unsigned offset, float *widths, WebTextRenderer **renderersUsed, ATSGlyphRef *glyphsUsed);
156 static bool fillStyleWithAttributes(ATSUStyle style, NSFont *theFont);
157 static bool shouldUseATSU(const WebCoreTextRun *run);
160 static NSString *pathFromFont(NSFont *font);
163 static void createATSULayoutParameters(ATSULayoutParameters *params, WebTextRenderer *renderer, const WebCoreTextRun *run, const WebCoreTextStyle *style);
164 static void disposeATSULayoutParameters(ATSULayoutParameters *params);
167 static bool alwaysUseATSU = NO;
169 // Character property functions.
171 static inline bool isSpace(UChar32 c)
173 return c == SPACE || c == '\t' || c == '\n' || c == NO_BREAK_SPACE;
176 static const uint8_t isRoundingHackCharacterTable[0x100] = {
177 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,
178 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 /*?*/,
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,
181 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,
182 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,
183 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,
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
187 static inline bool isRoundingHackCharacter(UChar32 c)
189 return (((c & ~0xFF) == 0 && isRoundingHackCharacterTable[c]));
192 // Map utility functions
194 static inline WebGlyphWidth widthForGlyph(WebTextRenderer *renderer, ATSGlyphRef glyph)
197 for (map = renderer->glyphToWidthMap; 1; map = map->next) {
199 map = extendWidthMap(renderer, glyph);
200 if (glyph >= map->startRange && glyph <= map->endRange)
203 WebGlyphWidth width = map->widths[glyph - map->startRange];
206 NSFont *font = renderer->font.font;
207 float pointSize = [font pointSize];
208 CGAffineTransform m = CGAffineTransformMakeScale(pointSize, pointSize);
210 if (!WKGetGlyphTransformedAdvances(font, &m, &glyph, &advance)) {
211 ERROR("Unable to cache glyph widths for %@ %f", [font displayName], pointSize);
214 width = advance.width + renderer->syntheticBoldOffset;
215 map->widths[glyph - map->startRange] = width;
219 static OSStatus overrideLayoutOperation(ATSULayoutOperationSelector iCurrentOperation, ATSULineRef iLineRef, UInt32 iRefCon, void *iOperationCallbackParameterPtr, ATSULayoutOperationCallbackStatus *oCallbackStatus)
221 ATSULayoutParameters *params = (ATSULayoutParameters *)iRefCon;
224 ATSLayoutRecord *layoutRecords;
225 const WebCoreTextStyle *style = params->style;
227 if (style->applyWordRounding) {
228 status = ATSUDirectGetLayoutDataArrayPtrFromLineRef(iLineRef, kATSUDirectDataLayoutRecordATSLayoutRecordCurrent, true, (void **)&layoutRecords, &count);
229 if (status != noErr) {
230 *oCallbackStatus = kATSULayoutOperationCallbackStatusContinue;
234 Fixed lastNativePos = 0;
235 float lastAdjustedPos = 0;
236 const WebCoreTextRun *run = params->run;
237 const UniChar *characters = run->characters + run->from;
238 WebTextRenderer **renderers = params->renderers + run->from;
239 WebTextRenderer *renderer;
240 WebTextRenderer *lastRenderer = 0;
242 ByteCount offset = layoutRecords[0].originalOffset;
243 nextCh = *(UniChar *)(((char *)characters)+offset);
245 bool syntheticBoldPass = params->syntheticBoldPass;
246 Fixed syntheticBoldOffset;
247 ATSGlyphRef spaceGlyph;
248 // In the CoreGraphics code path, the rounding hack is applied in logical order.
249 // Here it is applied in visual left-to-right order, which may be better.
251 for (i = 1; i < count; i++) {
252 bool isLastChar = i == count - 1;
253 renderer = renderers[offset / 2];
254 if (renderer != lastRenderer) {
255 lastRenderer = renderer;
256 // The CoreGraphics interpretation of NSFontAntialiasedIntegerAdvancementsRenderingMode seems
257 // to be "round each glyph's width to the nearest integer". This is not the same as ATSUI
258 // does in any of its device-metrics modes.
259 shouldRound = [renderer->font.font renderingMode] == NSFontAntialiasedIntegerAdvancementsRenderingMode;
260 if (syntheticBoldPass) {
261 syntheticBoldOffset = FloatToFixed(renderer->syntheticBoldOffset);
262 spaceGlyph = renderer->spaceGlyph;
266 offset = layoutRecords[i].originalOffset;
267 // Use space for nextCh at the end of the loop so that we get inside the rounding hack code.
268 // We won't actually round unless the other conditions are satisfied.
269 nextCh = isLastChar ? ' ' : *(UniChar *)(((char *)characters)+offset);
271 float width = FixedToFloat(layoutRecords[i].realPos - lastNativePos);
272 lastNativePos = layoutRecords[i].realPos;
274 width = roundf(width);
275 width += renderer->syntheticBoldOffset;
276 if (renderer->treatAsFixedPitch ? width == renderer->spaceWidth : (layoutRecords[i-1].flags & kATSGlyphInfoIsWhiteSpace))
277 width = renderer->adjustedSpaceWidth;
278 if (isRoundingHackCharacter(ch))
279 width = ceilf(width);
280 lastAdjustedPos = lastAdjustedPos + width;
281 if (isRoundingHackCharacter(nextCh))
283 || style->applyRunRounding
284 || (run->to < (int)run->length && isRoundingHackCharacter(characters[run->to - run->from])))
285 lastAdjustedPos = ceilf(lastAdjustedPos);
286 if (syntheticBoldPass) {
287 if (syntheticBoldOffset)
288 layoutRecords[i-1].realPos += syntheticBoldOffset;
290 layoutRecords[i-1].glyphID = spaceGlyph;
292 layoutRecords[i].realPos = FloatToFixed(lastAdjustedPos);
295 status = ATSUDirectReleaseLayoutDataArrayPtr(iLineRef, kATSUDirectDataLayoutRecordATSLayoutRecordCurrent, (void **)&layoutRecords);
297 *oCallbackStatus = kATSULayoutOperationCallbackStatusHandled;
301 @implementation WebTextRenderer
303 static NSString *webFallbackFontFamily(void)
305 static NSString *webFallbackFontFamily = nil;
306 if (!webFallbackFontFamily)
307 webFallbackFontFamily = [[[NSFont systemFontOfSize:16.0] familyName] retain];
308 return webFallbackFontFamily;
311 - initWithFont:(WebCoreFont)f
315 // Quartz can only handle fonts with these glyph packings. Other packings have
317 if ([f.font glyphPacking] != NSNativeShortGlyphPacking && [f.font glyphPacking] != NSTwoByteGlyphPacking) {
318 // Apparently there are many deprecated fonts out there with unsupported packing types.
319 // Log and use fallback font.
320 // This change fixes the many crashes reported in 3782533.
321 // Most likely, the problem is encountered when people upgrade from OS 9, or have OS 9 fonts installed on OS X.
322 NSLog(@"%s:%d Unable to use deprecated font %@ %f, using system font instead", __FILE__, __LINE__, [f.font displayName], [f.font pointSize]);
323 f.font = [NSFont systemFontOfSize:[f.font pointSize]];
328 syntheticBoldOffset = f.syntheticBold ? ceilf([f.font pointSize] / 24.0f) : 0.f;
330 bool failedSetup = false;
331 if (!setUpFont(self)) {
332 // Ack! Something very bad happened, like a corrupt font.
333 // Try looking for an alternate 'base' font for this renderer.
335 // Special case hack to use "Times New Roman" in place of "Times".
336 // "Times RO" is a common font whose family name is "Times".
337 // It overrides the normal "Times" family font.
338 // It also appears to have a corrupt regular variant.
339 NSString *fallbackFontFamily;
340 if ([[font.font familyName] isEqual:@"Times"])
341 fallbackFontFamily = @"Times New Roman";
343 fallbackFontFamily = webFallbackFontFamily();
345 // Try setting up the alternate font.
346 // This is a last ditch effort to use a substitute font when something has gone wrong.
348 NSFont *initialFont = font.font;
350 font.font = [[NSFontManager sharedFontManager] convertFont:font.font toFamily:fallbackFontFamily];
352 NSString *filePath = pathFromFont(initialFont);
354 filePath = @"not known";
356 if (!setUpFont(self)) {
357 if ([fallbackFontFamily isEqual:@"Times New Roman"]) {
358 // OK, couldn't setup Times New Roman as an alternate to Times, fallback
359 // on the system font. If this fails we have no alternative left.
360 font.font = [[NSFontManager sharedFontManager] convertFont:font.font toFamily:webFallbackFontFamily()];
361 if (!setUpFont(self)) {
362 // We tried, Times, Times New Roman, and the system font. No joy. We have to give up.
363 ERROR("%@ unable to initialize with font %@ at %@", self, initialFont, filePath);
367 // We tried the requested font and the system font. No joy. We have to give up.
368 ERROR("%@ unable to initialize with font %@ at %@", self, initialFont, filePath);
373 // Report the problem.
374 ERROR("Corrupt font detected, using %@ in place of %@ located at \"%@\".",
375 [font.font familyName], [initialFont familyName], filePath);
378 // If all else fails, try to set up using the system font.
379 // This is probably because Times and Times New Roman are both unavailable.
381 font.font = [NSFont systemFontOfSize:[font.font pointSize]];
382 ERROR("%@ failed to set up font, using system font %s", self, font.font);
390 WKGetFontMetrics(font.font, &iAscent, &iDescent, &iLineGap, &unitsPerEm);
391 float pointSize = [font.font pointSize];
392 float fAscent = SCALE_EM_TO_UNITS(iAscent, unitsPerEm) * pointSize;
393 float fDescent = -SCALE_EM_TO_UNITS(iDescent, unitsPerEm) * pointSize;
394 float fLineGap = SCALE_EM_TO_UNITS(iLineGap, unitsPerEm) * pointSize;
396 // We need to adjust Times, Helvetica, and Courier to closely match the
397 // vertical metrics of their Microsoft counterparts that are the de facto
398 // web standard. The AppKit adjustment of 20% is too big and is
399 // incorrectly added to line spacing, so we use a 15% adjustment instead
400 // and add it to the ascent.
401 NSString *familyName = [font.font familyName];
402 if ([familyName isEqualToString:@"Times"] || [familyName isEqualToString:@"Helvetica"] || [familyName isEqualToString:@"Courier"])
403 fAscent += floorf(((fAscent + fDescent) * 0.15f) + 0.5f);
405 ascent = lroundf(fAscent);
406 descent = lroundf(fDescent);
407 lineGap = lroundf(fLineGap);
409 lineSpacing = ascent + descent + lineGap;
416 static void destroy(WebTextRenderer *renderer)
418 if (renderer->styleGroup)
419 WKReleaseStyleGroup(renderer->styleGroup);
421 freeWidthMap(renderer->glyphToWidthMap);
422 freeGlyphMap(renderer->characterToGlyphMap);
424 if (renderer->ATSUStyleInitialized)
425 ATSUDisposeStyle(renderer->_ATSUStyle);
433 [smallCapsRenderer release];
462 // Measure the actual character "x", because AppKit synthesizes X height rather than getting it from the font.
463 // Unfortunately, NSFont will round this for us so we don't quite get the right value.
464 NSGlyph xGlyph = [font.font glyphWithName:@"x"];
466 NSRect xBox = [font.font boundingRectForGlyph:xGlyph];
467 // Use the maximum of either width or height because "x" is nearly square
468 // and web pages that foolishly use this metric for width will be laid out
469 // poorly if we return an accurate height. Classic case is Times 13 point,
470 // which has an "x" that is 7x6 pixels.
471 return MAX(NSMaxX(xBox), NSMaxY(xBox));
474 return [font.font xHeight];
477 - (void)drawRun:(const WebCoreTextRun *)run style:(const WebCoreTextStyle *)style geometry:(const WebCoreTextGeometry *)geometry
479 if (shouldUseATSU(run))
480 ATSU_draw(self, run, style, geometry);
482 CG_draw(self, run, style, geometry);
485 - (float)floatWidthForRun:(const WebCoreTextRun *)run style:(const WebCoreTextStyle *)style
487 if (shouldUseATSU(run))
488 return ATSU_floatWidthForRun(self, run, style);
489 return CG_floatWidthForRun(self, run, style, 0, 0, 0, 0, 0);
492 - (void)drawLineForCharacters:(NSPoint)point yOffset:(float)yOffset width:(int)width color:(NSColor *)color thickness:(float)thickness
494 NSGraphicsContext *graphicsContext = [NSGraphicsContext currentContext];
496 bool flag = [graphicsContext shouldAntialias];
498 // We don't want antialiased lines on screen, but we do when printing (else they are too thick).
499 if ([graphicsContext isDrawingToScreen]) {
500 [graphicsContext setShouldAntialias:NO];
505 CGContextRef cgContext = (CGContextRef)[graphicsContext graphicsPort];
507 // Hack to make thickness 2 underlines for international text input look right
508 if (thickness > 1.5F && thickness < 2.5F) {
512 if (thickness == 0.0F) {
513 if ([graphicsContext isDrawingToScreen]) {
514 CGSize size = CGSizeApplyAffineTransform(CGSizeMake(1.0F, 1.0F), CGAffineTransformInvert(CGContextGetCTM(cgContext)));
515 CGContextSetLineWidth(cgContext, size.width);
517 // See bugzilla bug 4255 for details of why we do this when printing
518 CGContextSetLineWidth(cgContext, 0.5F);
521 CGContextSetLineWidth(cgContext, thickness);
524 // Use CGContextStrokeLineSegments.
525 // With Q2DX turned on CGContextStrokeLineSegments sometimes fails to draw lines. See 3952084.
526 // Tiger shipped with Q2DX disabled, tho, so we can use CGContextStrokeLineSegments.
527 CGPoint linePoints[2];
528 linePoints[0].x = point.x;
529 linePoints[0].y = point.y + 1.5F + yOffset;
530 linePoints[1].x = point.x - 1.0F + width;
531 linePoints[1].y = linePoints[0].y;
532 CGContextStrokeLineSegments(cgContext, linePoints, 2);
534 [graphicsContext setShouldAntialias: flag];
537 - (void)drawHighlightForRun:(const WebCoreTextRun *)run style:(const WebCoreTextStyle *)style geometry:(const WebCoreTextGeometry *)geometry
539 if (shouldUseATSU(run))
540 ATSU_drawHighlight(self, run, style, geometry);
542 CG_drawHighlight(self, run, style, geometry);
545 - (int)misspellingLineThickness
550 - (int)misspellingLinePatternWidth
555 // the number of transparent pixels after the dot
556 - (int)misspellingLinePatternGapWidth
561 - (void)drawLineForMisspelling:(NSPoint)point withWidth:(int)width
563 // Constants for pattern color
564 static NSColor *spellingPatternColor = nil;
565 static bool usingDot = false;
566 int patternHeight = [self misspellingLineThickness];
567 int patternWidth = [self misspellingLinePatternWidth];
569 // Initialize pattern color if needed
570 if (!spellingPatternColor) {
571 NSImage *image = [NSImage imageNamed:@"SpellingDot"];
572 ASSERT(image); // if image is not available, we want to know
573 NSColor *color = (image ? [NSColor colorWithPatternImage:image] : nil);
577 color = [NSColor redColor];
578 spellingPatternColor = [color retain];
581 // Make sure to draw only complete dots.
582 // NOTE: Code here used to shift the underline to the left and increase the width
583 // to make sure everything gets underlined, but that results in drawing out of
584 // bounds (e.g. when at the edge of a view) and could make it appear that the
585 // space between adjacent misspelled words was underlined.
587 // allow slightly more considering that the pattern ends with a transparent pixel
588 int widthMod = width % patternWidth;
589 if (patternWidth - widthMod > [self misspellingLinePatternGapWidth])
594 NSGraphicsContext *currentContext = [NSGraphicsContext currentContext];
595 CGContextRef context = (CGContextRef)[currentContext graphicsPort];
596 CGContextSaveGState(context);
598 [spellingPatternColor set];
600 CGPoint transformedOrigin = CGPointApplyAffineTransform(CGPointMake(point.x, point.y), CGContextGetCTM(context));
601 CGContextSetPatternPhase(context, CGSizeMake(transformedOrigin.x, transformedOrigin.y));
603 NSRectFillUsingOperation(NSMakeRect(point.x, point.y, width, patternHeight), NSCompositeSourceOver);
605 CGContextRestoreGState(context);
608 - (int)pointToOffset:(const WebCoreTextRun *)run style:(const WebCoreTextStyle *)style position:(int)x includePartialGlyphs:(BOOL)includePartialGlyphs
610 if (shouldUseATSU(run))
611 return ATSU_pointToOffset(self, run, style, x, includePartialGlyphs);
612 return CG_pointToOffset(self, run, style, x, includePartialGlyphs);
615 + (void)setAlwaysUseATSU:(bool)f
622 static WebTextRenderer *getSmallCapsRenderer(WebTextRenderer *renderer)
624 if (!renderer->smallCapsRenderer) {
626 float size = [renderer->font.font pointSize] * SMALLCAPS_FONTSIZE_MULTIPLIER;
627 WebCoreFont smallCapsFont;
628 WebCoreInitializeFont(&smallCapsFont);
629 smallCapsFont.font = [[NSFontManager sharedFontManager] convertFont:renderer->font.font toSize:size];
630 renderer->smallCapsRenderer = [rendererForAlternateFont(renderer, smallCapsFont) retain];
632 NSLog(@"uncaught exception selecting font for small caps: %@", localException);
635 return renderer->smallCapsRenderer;
638 static inline bool fontContainsString(NSFont *font, NSString *string)
640 NSCharacterSet *set = [[font coveredCharacterSet] invertedSet];
641 return set && [string rangeOfCharacterFromSet:set].location == NSNotFound;
644 static NSFont *findSubstituteFont(WebTextRenderer *renderer, NSString *string, NSString **families)
646 NSFont *substituteFont = nil;
648 // First search the CSS family fallback list.
649 // Start at 1 (2nd font) because we've already failed on the first lookup.
650 NSString *family = nil;
652 while (families && families[i]) {
653 family = families[i++];
654 NSFont *f = [[WebTextRendererFactory sharedFactory] cachedFontFromFamily:family
655 traits:[[NSFontManager sharedFontManager] traitsOfFont:renderer->font.font]
656 size:[renderer->font.font pointSize]];
657 if (f && fontContainsString(f, string)) {
663 // Now do string based lookup.
664 if (substituteFont == nil)
665 substituteFont = WKGetFontInLanguageForRange(renderer->font.font, string, NSMakeRange(0, [string length]));
667 // Now do character based lookup.
668 if (substituteFont == nil && [string length] == 1)
669 substituteFont = WKGetFontInLanguageForCharacter(renderer->font.font, [string characterAtIndex:0]);
671 // Check to make sure this is a distinct font.
672 if (substituteFont && [[substituteFont screenFont] isEqual:[renderer->font.font screenFont]])
673 substituteFont = nil;
675 // Now that we have a substitute font, attempt to match it to the best variation.
676 // If we have a good match return that, otherwise return the font the AppKit has found.
677 if (substituteFont) {
678 NSFontManager *manager = [NSFontManager sharedFontManager];
679 NSFont *bestVariation = [manager fontWithFamily:[substituteFont familyName]
680 traits:[manager traitsOfFont:renderer->font.font]
681 weight:[manager weightOfFont:renderer->font.font]
682 size:[renderer->font.font pointSize]];
684 substituteFont = bestVariation;
687 return substituteFont;
690 static WebTextRenderer *rendererForAlternateFont(WebTextRenderer *renderer, WebCoreFont alternateFont)
692 if (!alternateFont.font)
695 NSFontManager *fontManager = [NSFontManager sharedFontManager];
696 NSFontTraitMask fontTraits = [fontManager traitsOfFont:renderer->font.font];
697 if (renderer->font.syntheticBold)
698 fontTraits |= NSBoldFontMask;
699 if (renderer->font.syntheticOblique)
700 fontTraits |= NSItalicFontMask;
701 NSFontTraitMask alternateFontTraits = [fontManager traitsOfFont:alternateFont.font];
703 alternateFont.syntheticBold = (fontTraits & NSBoldFontMask) && !(alternateFontTraits & NSBoldFontMask);
704 alternateFont.syntheticOblique = (fontTraits & NSItalicFontMask) && !(alternateFontTraits & NSItalicFontMask);
705 alternateFont.forPrinter = renderer->font.forPrinter;
707 return [[WebTextRendererFactory sharedFactory] rendererWithFont:alternateFont];
710 static WebTextRenderer *findSubstituteRenderer(WebTextRenderer *renderer, const unichar *characters, int numCharacters, NSString **families)
712 WebCoreFont substituteFont;
713 WebCoreInitializeFont(&substituteFont);
714 NSString *string = [[NSString alloc] initWithCharactersNoCopy:(unichar *)characters length: numCharacters freeWhenDone: NO];
715 substituteFont.font = findSubstituteFont(renderer, string, families);
717 return rendererForAlternateFont(renderer, substituteFont);
720 // Nasty hack to determine if we should round or ceil space widths.
721 // If the font is monospace or fake monospace we ceil to ensure that
722 // every character and the space are the same width. Otherwise we round.
723 static bool computeWidthForSpace(WebTextRenderer *renderer)
725 renderer->spaceGlyph = extendGlyphMap(renderer, SPACE);
726 if (renderer->spaceGlyph == 0)
729 float width = widthForGlyph(renderer, renderer->spaceGlyph);
731 renderer->spaceWidth = width;
733 renderer->treatAsFixedPitch = [[WebTextRendererFactory sharedFactory] isFontFixedPitch:renderer->font];
734 renderer->adjustedSpaceWidth = renderer->treatAsFixedPitch ? ceilf(width) : roundf(width);
739 static bool setUpFont(WebTextRenderer *renderer)
741 renderer->font.font = renderer->font.forPrinter ? [renderer->font.font printerFont] : [renderer->font.font screenFont];
744 if (ATSUCreateStyle(&fontStyle) != noErr)
747 if (!fillStyleWithAttributes(fontStyle, renderer->font.font)) {
748 ATSUDisposeStyle(fontStyle);
752 if (WKGetATSStyleGroup(fontStyle, &renderer->styleGroup) != noErr) {
753 ATSUDisposeStyle(fontStyle);
757 ATSUDisposeStyle(fontStyle);
759 if (!computeWidthForSpace(renderer)) {
760 freeGlyphMap(renderer->characterToGlyphMap);
761 renderer->characterToGlyphMap = 0;
762 WKReleaseStyleGroup(renderer->styleGroup);
763 renderer->styleGroup = 0;
772 static NSString *pathFromFont(NSFont *font)
775 OSStatus status = ATSFontGetFileSpecification(FMGetATSFontRefFromFont((FMFont)WKGetNSFontATSUFontId(font)), &oFile);
776 if (status == noErr) {
779 err = FSpMakeFSRef(&oFile, &fileRef);
781 UInt8 filePathBuffer[PATH_MAX];
782 status = FSRefMakePath(&fileRef, filePathBuffer, PATH_MAX);
784 return [NSString stringWithUTF8String:(const char *)filePathBuffer];
792 // Useful page for testing http://home.att.net/~jameskass
793 static void drawGlyphs(NSFont *font, NSColor *color, CGGlyph *glyphs, CGSize *advances, float x, float y, int numGlyphs,
794 float syntheticBoldOffset, bool syntheticOblique)
796 NSGraphicsContext *gContext = [NSGraphicsContext currentContext];
797 CGContextRef cgContext = (CGContextRef)[gContext graphicsPort];
799 bool originalShouldUseFontSmoothing = WKCGContextGetShouldSmoothFonts(cgContext);
800 CGContextSetShouldSmoothFonts(cgContext, [WebView _shouldUseFontSmoothing]);
803 if ([gContext isDrawingToScreen]) {
804 drawFont = [font screenFont];
805 if (drawFont != font)
806 // 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).
807 ERROR("Attempting to set non-screen font (%@) when drawing to screen. Using screen font anyway, may result in incorrect metrics.",
808 [[[font fontDescriptor] fontAttributes] objectForKey:NSFontNameAttribute]);
810 drawFont = [font printerFont];
811 if (drawFont != font)
812 NSLog(@"Attempting to set non-printer font (%@) when printing. Using printer font anyway, may result in incorrect metrics.",
813 [[[font fontDescriptor] fontAttributes] objectForKey:NSFontNameAttribute]);
816 CGContextSetFont(cgContext, WKGetCGFontFromNSFont(drawFont));
818 CGAffineTransform matrix;
819 memcpy(&matrix, [drawFont matrix], sizeof(matrix));
820 if ([gContext isFlipped]) {
821 matrix.b = -matrix.b;
822 matrix.d = -matrix.d;
824 if (syntheticOblique)
825 matrix = CGAffineTransformConcat(matrix, CGAffineTransformMake(1, 0, -tanf(SYNTHETIC_OBLIQUE_ANGLE * acosf(0) / 90), 1, 0, 0));
826 CGContextSetTextMatrix(cgContext, matrix);
828 WKSetCGFontRenderingMode(cgContext, drawFont);
829 CGContextSetFontSize(cgContext, 1.0f);
833 CGContextSetTextPosition(cgContext, x, y);
834 CGContextShowGlyphsWithAdvances(cgContext, glyphs, advances, numGlyphs);
835 if (syntheticBoldOffset) {
836 CGContextSetTextPosition(cgContext, x + syntheticBoldOffset, y);
837 CGContextShowGlyphsWithAdvances(cgContext, glyphs, advances, numGlyphs);
840 CGContextSetShouldSmoothFonts(cgContext, originalShouldUseFontSmoothing);
843 static void CG_drawHighlight(WebTextRenderer *renderer, const WebCoreTextRun * run, const WebCoreTextStyle *style, const WebCoreTextGeometry *geometry)
845 if (run->length == 0)
848 if (style->backgroundColor == nil)
851 [style->backgroundColor set];
853 float yPos = geometry->useFontMetricsForSelectionYAndHeight
854 ? geometry->point.y - renderer->ascent - (renderer->lineGap / 2) : geometry->selectionY;
855 float height = geometry->useFontMetricsForSelectionYAndHeight
856 ? renderer->lineSpacing : geometry->selectionHeight;
858 WebCoreTextRun completeRun = *run;
859 completeRun.from = 0;
860 completeRun.to = run->length;
863 initializeWidthIterator(&it, renderer, &completeRun, style);
865 advanceWidthIterator(&it, run->from, 0, 0, 0);
866 float beforeWidth = it.runWidthSoFar;
867 // apply rounding as if this is the end of the run, since that's how RenderText::selectionRect() works
868 if ((style->applyWordRounding && isRoundingHackCharacter(run->characters[run->from]))
869 || style->applyRunRounding)
870 beforeWidth = ceilf(beforeWidth);
871 advanceWidthIterator(&it, run->to, 0, 0, 0);
872 float backgroundWidth = it.runWidthSoFar - beforeWidth;
874 advanceWidthIterator(&it, run->length, 0, 0, 0);
875 float totalWidth = it.runWidthSoFar;
876 if (style->applyRunRounding)
877 totalWidth = ceilf(totalWidth);
878 [NSBezierPath fillRect:NSMakeRect(geometry->point.x + roundf(totalWidth - backgroundWidth - beforeWidth), yPos, roundf(backgroundWidth), height)];
880 [NSBezierPath fillRect:NSMakeRect(geometry->point.x + roundf(beforeWidth), yPos, roundf(backgroundWidth), height)];
884 static void CG_draw(WebTextRenderer *renderer, const WebCoreTextRun *run, const WebCoreTextStyle *style, const WebCoreTextGeometry *geometry)
886 float *widthBuffer, localWidthBuffer[LOCAL_BUFFER_SIZE];
887 CGGlyph *glyphBuffer, localGlyphBuffer[LOCAL_BUFFER_SIZE];
888 WebTextRenderer **rendererBuffer, *localRendererBuffer[LOCAL_BUFFER_SIZE];
889 CGSize *advances, localAdvanceBuffer[LOCAL_BUFFER_SIZE];
890 int numGlyphs = 0, i;
892 unsigned length = run->length;
894 if (run->length == 0)
897 if (length * MAX_GLYPH_EXPANSION > LOCAL_BUFFER_SIZE) {
898 advances = malloc(length * MAX_GLYPH_EXPANSION * sizeof(CGSize));
899 widthBuffer = malloc(length * MAX_GLYPH_EXPANSION * sizeof(float));
900 glyphBuffer = malloc(length * MAX_GLYPH_EXPANSION * sizeof(ATSGlyphRef));
901 rendererBuffer = malloc(length * MAX_GLYPH_EXPANSION * sizeof(WebTextRenderer *));
903 advances = localAdvanceBuffer;
904 widthBuffer = localWidthBuffer;
905 glyphBuffer = localGlyphBuffer;
906 rendererBuffer = localRendererBuffer;
909 CG_floatWidthForRun(renderer, run, style, widthBuffer, rendererBuffer, glyphBuffer, &startX, &numGlyphs);
911 // Eek. We couldn't generate ANY glyphs for the run.
915 // Fill the advances array.
916 for (i = 0; i < numGlyphs; i++) {
917 advances[i].width = widthBuffer[i];
918 advances[i].height = 0;
921 // Calculate the starting point of the glyphs to be displayed by adding
922 // all the advances up to the first glyph.
923 startX += geometry->point.x;
925 if (style->backgroundColor != nil)
926 CG_drawHighlight(renderer, run, style, geometry);
928 // Swap the order of the glyphs if right-to-left.
931 int mid = numGlyphs / 2;
933 for (i = 0, end = numGlyphs - 1; i < mid; ++i, --end) {
934 CGGlyph gswap1 = glyphBuffer[i];
935 CGGlyph gswap2 = glyphBuffer[end];
936 glyphBuffer[i] = gswap2;
937 glyphBuffer[end] = gswap1;
939 CGSize aswap1 = advances[i];
940 CGSize aswap2 = advances[end];
941 advances[i] = aswap2;
942 advances[end] = aswap1;
944 WebTextRenderer *rswap1 = rendererBuffer[i];
945 WebTextRenderer *rswap2 = rendererBuffer[end];
946 rendererBuffer[i] = rswap2;
947 rendererBuffer[end] = rswap1;
951 // Draw each contiguous run of glyphs that use the same renderer.
952 WebTextRenderer *currentRenderer = rendererBuffer[0];
953 float nextX = startX;
956 while (nextGlyph < numGlyphs) {
957 WebTextRenderer *nextRenderer = rendererBuffer[nextGlyph];
958 if (nextRenderer != currentRenderer) {
959 drawGlyphs(currentRenderer->font.font, style->textColor, &glyphBuffer[lastFrom], &advances[lastFrom],
960 startX, geometry->point.y, nextGlyph - lastFrom,
961 currentRenderer->syntheticBoldOffset, currentRenderer->font.syntheticOblique);
962 lastFrom = nextGlyph;
963 currentRenderer = nextRenderer;
966 nextX += advances[nextGlyph].width;
969 drawGlyphs(currentRenderer->font.font, style->textColor, &glyphBuffer[lastFrom], &advances[lastFrom],
970 startX, geometry->point.y, nextGlyph - lastFrom,
971 currentRenderer->syntheticBoldOffset, currentRenderer->font.syntheticOblique);
973 if (advances != localAdvanceBuffer) {
977 free(rendererBuffer);
981 static float CG_floatWidthForRun(WebTextRenderer *renderer, const WebCoreTextRun *run, const WebCoreTextStyle *style, float *widthBuffer, WebTextRenderer **rendererBuffer, CGGlyph *glyphBuffer, float *startPosition, int *numGlyphsResult)
984 WebCoreTextRun completeRun;
985 const WebCoreTextRun *aRun;
990 completeRun.to = run->length;
993 initializeWidthIterator(&it, renderer, aRun, style);
994 int numGlyphs = advanceWidthIterator(&it, run->to, widthBuffer, rendererBuffer, glyphBuffer);
995 float runWidth = it.runWidthSoFar;
998 *startPosition = it.widthToStart;
1000 advanceWidthIterator(&it, run->length, 0, 0, 0);
1001 *startPosition = it.runWidthSoFar - runWidth;
1004 if (numGlyphsResult)
1005 *numGlyphsResult = numGlyphs;
1009 static void updateGlyphMapEntry(WebTextRenderer *renderer, UChar32 c, ATSGlyphRef glyph, WebTextRenderer *substituteRenderer)
1012 for (map = renderer->characterToGlyphMap; map; map = map->next) {
1013 UChar32 start = map->startRange;
1014 if (c >= start && c <= map->endRange) {
1016 map->glyphs[i].glyph = glyph;
1017 // This renderer will leak.
1018 // No problem though; we want it to stick around forever.
1019 // Max theoretical retain counts applied here will be num_fonts_on_system * num_glyphs_in_font.
1020 map->glyphs[i].renderer = [substituteRenderer retain];
1026 static ATSGlyphRef extendGlyphMap(WebTextRenderer *renderer, UChar32 c)
1028 GlyphMap *map = malloc(sizeof(GlyphMap));
1029 ATSLayoutRecord *glyphRecord;
1030 char glyphVector[WKGlyphVectorSize];
1034 if (renderer->characterToGlyphMap == 0)
1035 blockSize = INITIAL_BLOCK_SIZE;
1037 blockSize = INCREMENTAL_BLOCK_SIZE;
1038 start = (c / blockSize) * blockSize;
1039 end = start + (blockSize - 1);
1041 LOG(FontCache, "%@ (0x%04x) adding glyphs for 0x%04x to 0x%04x", renderer->font, c, start, end);
1043 map->startRange = start;
1044 map->endRange = end;
1048 unsigned count = end - start + 1;
1049 unsigned short buffer[INCREMENTAL_BLOCK_SIZE * 2 + 2];
1050 unsigned bufferLength;
1052 if (start < 0x10000) {
1053 bufferLength = count;
1054 for (i = 0; i < count; i++)
1055 buffer[i] = i + start;
1058 // Control characters must not render at all.
1059 for (i = 0; i < 0x20; ++i)
1060 buffer[i] = ZERO_WIDTH_SPACE;
1061 buffer[0x7F] = ZERO_WIDTH_SPACE;
1063 // But \n, \t, and nonbreaking space must render as a space.
1066 buffer[NO_BREAK_SPACE] = ' ';
1069 bufferLength = count * 2;
1070 for (i = 0; i < count; i++) {
1072 buffer[i * 2] = U16_LEAD(c);
1073 buffer[i * 2 + 1] = U16_TRAIL(c);
1077 OSStatus status = WKInitializeGlyphVector(count, &glyphVector);
1078 if (status != noErr) {
1079 // This should never happen, perhaps indicates a bad font! If it does the
1080 // font substitution code will find an alternate font.
1085 WKConvertCharToGlyphs(renderer->styleGroup, &buffer[0], bufferLength, &glyphVector);
1086 unsigned numGlyphs = WKGetGlyphVectorNumGlyphs(&glyphVector);
1087 if (numGlyphs != count) {
1088 // This should never happen, perhaps indicates a bad font?
1089 // If it does happen, the font substitution code will find an alternate font.
1090 WKClearGlyphVector(&glyphVector);
1095 map->glyphs = malloc(count * sizeof(GlyphEntry));
1096 glyphRecord = (ATSLayoutRecord *)WKGetGlyphVectorFirstRecord(glyphVector);
1097 for (i = 0; i < count; i++) {
1098 map->glyphs[i].glyph = glyphRecord->glyphID;
1099 map->glyphs[i].renderer = renderer;
1100 glyphRecord = (ATSLayoutRecord *)((char *)glyphRecord + WKGetGlyphVectorRecordSize(glyphVector));
1102 WKClearGlyphVector(&glyphVector);
1104 if (renderer->characterToGlyphMap == 0)
1105 renderer->characterToGlyphMap = map;
1107 GlyphMap *lastMap = renderer->characterToGlyphMap;
1108 while (lastMap->next != 0)
1109 lastMap = lastMap->next;
1110 lastMap->next = map;
1113 ATSGlyphRef glyph = map->glyphs[c - start].glyph;
1115 // Special case for characters 007F-00A0.
1116 if (glyph == 0 && c >= 0x7F && c <= 0xA0) {
1117 glyph = WKGetDefaultGlyphForChar(renderer->font.font, c);
1118 map->glyphs[c - start].glyph = glyph;
1124 static WidthMap *extendWidthMap(WebTextRenderer *renderer, ATSGlyphRef glyph)
1126 WidthMap *map = (WidthMap *)calloc(1, sizeof(WidthMap));
1132 NSFont *f = renderer->font.font;
1133 if (renderer->glyphToWidthMap == 0) {
1134 if ([f numberOfGlyphs] < INITIAL_BLOCK_SIZE)
1135 blockSize = [f numberOfGlyphs];
1137 blockSize = INITIAL_BLOCK_SIZE;
1139 blockSize = INCREMENTAL_BLOCK_SIZE;
1141 if (blockSize == 0) {
1144 start = (glyph / blockSize) * blockSize;
1146 end = ((unsigned)start) + blockSize;
1148 LOG(FontCache, "%@ (0x%04x) adding widths for range 0x%04x to 0x%04x", renderer->font, glyph, start, end);
1150 map->startRange = start;
1151 map->endRange = end;
1152 count = end - start + 1;
1154 map->widths = malloc(count * sizeof(WebGlyphWidth));
1155 for (i = 0; i < count; i++)
1156 map->widths[i] = NAN;
1158 if (renderer->glyphToWidthMap == 0)
1159 renderer->glyphToWidthMap = map;
1161 WidthMap *lastMap = renderer->glyphToWidthMap;
1162 while (lastMap->next != 0)
1163 lastMap = lastMap->next;
1164 lastMap->next = map;
1170 static void initializeATSUStyle(WebTextRenderer *renderer)
1172 // The two NSFont calls in this method (pointSize and _atsFontID) do not raise exceptions.
1174 if (!renderer->ATSUStyleInitialized) {
1176 ByteCount propTableSize;
1178 status = ATSUCreateStyle(&renderer->_ATSUStyle);
1179 if (status != noErr)
1180 FATAL_ALWAYS("ATSUCreateStyle failed (%d)", status);
1182 ATSUFontID fontID = WKGetNSFontATSUFontId(renderer->font.font);
1184 ATSUDisposeStyle(renderer->_ATSUStyle);
1185 ERROR("unable to get ATSUFontID for %@", renderer->font.font);
1189 CGAffineTransform transform = CGAffineTransformMakeScale(1, -1);
1190 if (renderer->font.syntheticOblique)
1191 transform = CGAffineTransformConcat(transform, CGAffineTransformMake(1, 0, -tanf(SYNTHETIC_OBLIQUE_ANGLE * acosf(0) / 90), 1, 0, 0));
1192 Fixed fontSize = FloatToFixed([renderer->font.font pointSize]);
1193 // Turn off automatic kerning until it is supported in the CG code path (6136 in bugzilla)
1194 Fract kerningInhibitFactor = FloatToFract(1.0);
1195 ATSUAttributeTag styleTags[4] = { kATSUSizeTag, kATSUFontTag, kATSUFontMatrixTag, kATSUKerningInhibitFactorTag };
1196 ByteCount styleSizes[4] = { sizeof(Fixed), sizeof(ATSUFontID), sizeof(CGAffineTransform), sizeof(Fract) };
1197 ATSUAttributeValuePtr styleValues[4] = { &fontSize, &fontID, &transform, &kerningInhibitFactor };
1198 status = ATSUSetAttributes(renderer->_ATSUStyle, 4, styleTags, styleSizes, styleValues);
1199 if (status != noErr)
1200 FATAL_ALWAYS("ATSUSetAttributes failed (%d)", status);
1201 status = ATSFontGetTable(fontID, 'prop', 0, 0, 0, &propTableSize);
1202 if (status == noErr) // naively assume that if a 'prop' table exists then it contains mirroring info
1203 renderer->ATSUMirrors = YES;
1204 else if (status == kATSInvalidFontTableAccess)
1205 renderer->ATSUMirrors = NO;
1207 FATAL_ALWAYS("ATSFontGetTable failed (%d)", status);
1209 // Turn off ligatures such as 'fi' to match the CG code path's behavior, until bugzilla 6135 is fixed.
1210 // Don't be too aggressive: if the font doesn't contain 'a', then assume that any ligatures it contains are
1211 // in characters that always go through ATSUI, and therefore allow them. Geeza Pro is an example.
1212 // See bugzilla 5166.
1213 if ([[renderer->font.font coveredCharacterSet] characterIsMember:'a']) {
1214 ATSUFontFeatureType featureTypes[] = { kLigaturesType };
1215 ATSUFontFeatureSelector featureSelectors[] = { kCommonLigaturesOffSelector };
1216 status = ATSUSetFontFeatures(renderer->_ATSUStyle, 1, featureTypes, featureSelectors);
1219 renderer->ATSUStyleInitialized = YES;
1223 static void createATSULayoutParameters(ATSULayoutParameters *params, WebTextRenderer *renderer, const WebCoreTextRun *run, const WebCoreTextStyle *style)
1226 params->style = style;
1227 // FIXME: It is probably best to always allocate a buffer for RTL, since even if for this
1228 // renderer ATSUMirrors is true, for a substitute renderer it might be false.
1229 WebTextRenderer **renderers = malloc(run->length * sizeof(WebTextRenderer *));
1230 params->renderers = renderers;
1231 UniChar *charBuffer = (style->smallCaps || (style->rtl && !renderer->ATSUMirrors)) ? malloc(run->length * sizeof(UniChar)) : 0;
1232 params->charBuffer = charBuffer;
1233 params->syntheticBoldPass = false;
1235 // The only Cocoa calls here are to NSGraphicsContext, which does not raise exceptions.
1237 ATSUTextLayout layout;
1239 ATSULayoutOperationOverrideSpecifier overrideSpecifier;
1241 initializeATSUStyle(renderer);
1243 // FIXME: This is currently missing the following required features that the CoreGraphics code path has:
1244 // - \n, \t, and nonbreaking space render as a space.
1245 // - Other control characters do not render (other code path uses zero-width spaces).
1247 UniCharCount totalLength = run->length;
1248 UniCharArrayOffset runTo = (run->to == -1 ? totalLength : (unsigned int)run->to);
1249 UniCharArrayOffset runFrom = run->from;
1252 memcpy(charBuffer, run->characters, totalLength * sizeof(UniChar));
1254 UniCharCount runLength = runTo - runFrom;
1256 status = ATSUCreateTextLayoutWithTextPtr(
1257 (charBuffer ? charBuffer : run->characters),
1259 runLength, // length
1260 totalLength, // total length
1262 &runLength, // length of style run
1263 &renderer->_ATSUStyle,
1265 if (status != noErr)
1266 FATAL_ALWAYS("ATSUCreateTextLayoutWithTextPtr failed(%d)", status);
1267 params->layout = layout;
1268 ATSUSetTextLayoutRefCon(layout, (UInt32)params);
1270 CGContextRef cgContext = (CGContextRef)[[NSGraphicsContext currentContext] graphicsPort];
1271 ATSLineLayoutOptions lineLayoutOptions = kATSLineKeepSpacesOutOfMargin | kATSLineHasNoHangers;
1272 Boolean rtl = style->rtl;
1273 overrideSpecifier.operationSelector = kATSULayoutOperationPostLayoutAdjustment;
1274 overrideSpecifier.overrideUPP = overrideLayoutOperation;
1275 ATSUAttributeTag tags[] = { kATSUCGContextTag, kATSULineLayoutOptionsTag, kATSULineDirectionTag, kATSULayoutOperationOverrideTag };
1276 ByteCount sizes[] = { sizeof(CGContextRef), sizeof(ATSLineLayoutOptions), sizeof(Boolean), sizeof(ATSULayoutOperationOverrideSpecifier) };
1277 ATSUAttributeValuePtr values[] = { &cgContext, &lineLayoutOptions, &rtl, &overrideSpecifier };
1279 status = ATSUSetLayoutControls(layout, (style->applyWordRounding ? 4 : 3), tags, sizes, values);
1280 if (status != noErr)
1281 FATAL_ALWAYS("ATSUSetLayoutControls failed(%d)", status);
1283 status = ATSUSetTransientFontMatching(layout, YES);
1284 if (status != noErr)
1285 FATAL_ALWAYS("ATSUSetTransientFontMatching failed(%d)", status);
1287 params->hasSyntheticBold = false;
1288 ATSUFontID ATSUSubstituteFont;
1289 UniCharArrayOffset substituteOffset = runFrom;
1290 UniCharCount substituteLength;
1291 UniCharArrayOffset lastOffset;
1292 WebTextRenderer *substituteRenderer;
1294 while (substituteOffset < runTo) {
1295 lastOffset = substituteOffset;
1296 status = ATSUMatchFontsToText(layout, substituteOffset, kATSUToTextEnd, &ATSUSubstituteFont, &substituteOffset, &substituteLength);
1297 if (status == kATSUFontsMatched || status == kATSUFontsNotMatched) {
1298 substituteRenderer = findSubstituteRenderer(renderer, run->characters+substituteOffset, substituteLength, style->families);
1299 if (substituteRenderer) {
1300 initializeATSUStyle(substituteRenderer);
1301 if (substituteRenderer->_ATSUStyle)
1302 ATSUSetRunStyle(layout, substituteRenderer->_ATSUStyle, substituteOffset, substituteLength);
1304 substituteRenderer = renderer;
1306 substituteOffset = runTo;
1307 substituteLength = 0;
1310 bool isSmallCap = false;
1311 WebTextRenderer *r = renderer;
1312 UniCharArrayOffset i;
1313 for (i = lastOffset; ; i++) {
1314 UniCharArrayOffset firstSmallCap;
1315 if (i == substituteOffset || i == substituteOffset + substituteLength) {
1318 initializeATSUStyle(getSmallCapsRenderer(r));
1319 ATSUSetRunStyle(layout, getSmallCapsRenderer(r)->_ATSUStyle, firstSmallCap, i - firstSmallCap);
1321 if (i == substituteOffset && substituteLength > 0)
1322 r = substituteRenderer;
1326 if (rtl && charBuffer && !r->ATSUMirrors)
1327 charBuffer[i] = u_charMirror(charBuffer[i]);
1328 if (style->smallCaps) {
1329 UniChar c = charBuffer[i];
1332 renderers[i] = isSmallCap ? getSmallCapsRenderer(r) : r;
1333 else if (!u_isUUppercase(c) && (newC = u_toupper(c)) != c) {
1334 charBuffer[i] = newC;
1339 renderers[i] = getSmallCapsRenderer(r);
1343 initializeATSUStyle(getSmallCapsRenderer(r));
1344 ATSUSetRunStyle(layout, getSmallCapsRenderer(r)->_ATSUStyle, firstSmallCap, i - firstSmallCap);
1350 if (renderers[i]->syntheticBoldOffset)
1351 params->hasSyntheticBold = true;
1353 substituteOffset += substituteLength;
1357 static void disposeATSULayoutParameters(ATSULayoutParameters *params)
1359 ATSUDisposeTextLayout(params->layout);
1360 free(params->charBuffer);
1361 free(params->renderers);
1364 static ATSTrapezoid getTextBounds(WebTextRenderer *renderer, const WebCoreTextRun *run, const WebCoreTextStyle *style, NSPoint p)
1368 if (run->to - run->from <= 0) {
1369 ATSTrapezoid nilTrapezoid = { {0, 0}, {0, 0}, {0, 0}, {0, 0} };
1370 return nilTrapezoid;
1373 ATSULayoutParameters params;
1374 createATSULayoutParameters(¶ms, renderer, run, style);
1376 ATSTrapezoid firstGlyphBounds;
1377 ItemCount actualNumBounds;
1378 status = ATSUGetGlyphBounds(params.layout, FloatToFixed(p.x), FloatToFixed(p.y), run->from, run->to - run->from, kATSUseFractionalOrigins, 1, &firstGlyphBounds, &actualNumBounds);
1379 if (status != noErr)
1380 FATAL_ALWAYS("ATSUGetGlyphBounds() failed(%d)", status);
1381 if (actualNumBounds != 1)
1382 FATAL_ALWAYS("unexpected result from ATSUGetGlyphBounds(): actualNumBounds(%d) != 1", actualNumBounds);
1384 disposeATSULayoutParameters(¶ms);
1386 return firstGlyphBounds;
1389 static float ATSU_floatWidthForRun(WebTextRenderer *renderer, const WebCoreTextRun *run, const WebCoreTextStyle *style)
1391 ATSTrapezoid oGlyphBounds = getTextBounds(renderer, run, style, NSZeroPoint);
1392 return MAX(FixedToFloat(oGlyphBounds.upperRight.x), FixedToFloat(oGlyphBounds.lowerRight.x)) -
1393 MIN(FixedToFloat(oGlyphBounds.upperLeft.x), FixedToFloat(oGlyphBounds.lowerLeft.x));
1396 // Be sure to free the run.characters allocated by this function.
1397 static WebCoreTextRun addDirectionalOverride(const WebCoreTextRun *run, bool rtl)
1399 int from = run->from;
1406 UniChar *charactersWithOverride = malloc(sizeof(UniChar) * (run->length + 2));
1408 charactersWithOverride[0] = rtl ? RIGHT_TO_LEFT_OVERRIDE : LEFT_TO_RIGHT_OVERRIDE;
1409 memcpy(&charactersWithOverride[1], &run->characters[0], sizeof(UniChar) * run->length);
1410 charactersWithOverride[run->length + 1] = POP_DIRECTIONAL_FORMATTING;
1412 WebCoreTextRun runWithOverride;
1414 runWithOverride.from = from + 1;
1415 runWithOverride.to = to + 1;
1416 runWithOverride.length = run->length + 2;
1417 runWithOverride.characters = charactersWithOverride;
1419 return runWithOverride;
1422 static void ATSU_drawHighlight(WebTextRenderer *renderer, const WebCoreTextRun *run, const WebCoreTextStyle *style, const WebCoreTextGeometry *geometry)
1424 // The only Cocoa calls made here are to NSColor and NSBezierPath, and they do not raise exceptions.
1426 if (style->backgroundColor == nil)
1429 int from = run->from;
1435 int runLength = to - from;
1439 WebCoreTextRun runWithLead = *run;
1440 runWithLead.from = 0;
1441 WebCoreTextRun *aRun = &runWithLead;
1442 WebCoreTextRun swappedRun;
1444 if (style->directionalOverride) {
1445 swappedRun = addDirectionalOverride(aRun, style->rtl);
1449 float selectedLeftX;
1450 float widthWithLead = ATSU_floatWidthForRun(renderer, aRun, style);
1452 aRun->to -= runLength;
1453 float leadWidth = ATSU_floatWidthForRun(renderer, aRun, style);
1455 float backgroundWidth = roundf(widthWithLead - leadWidth);
1458 selectedLeftX = roundf(geometry->point.x + leadWidth);
1460 aRun->to += run->length - run->from;
1461 float totalWidth = ATSU_floatWidthForRun(renderer, aRun, style);
1462 selectedLeftX = roundf(geometry->point.x + totalWidth - widthWithLead);
1465 [style->backgroundColor set];
1467 float yPos = geometry->useFontMetricsForSelectionYAndHeight
1468 ? geometry->point.y - renderer->ascent : geometry->selectionY;
1469 float height = geometry->useFontMetricsForSelectionYAndHeight
1470 ? renderer->lineSpacing : geometry->selectionHeight;
1471 [NSBezierPath fillRect:NSMakeRect(selectedLeftX, yPos, backgroundWidth, height)];
1473 if (style->directionalOverride)
1474 free((void *)swappedRun.characters);
1478 static void ATSU_draw(WebTextRenderer *renderer, const WebCoreTextRun *run, const WebCoreTextStyle *style, const WebCoreTextGeometry *geometry)
1480 // The only Cocoa calls made here are to NSColor and NSGraphicsContext, and they do not raise exceptions.
1484 const WebCoreTextRun *aRun = run;
1485 WebCoreTextRun swappedRun;
1487 if (style->directionalOverride) {
1488 swappedRun = addDirectionalOverride(run, style->rtl);
1499 int runLength = to - from;
1503 WebCoreTextRun completeRun = *aRun;
1504 completeRun.from = 0;
1505 completeRun.to = aRun->length;
1506 ATSULayoutParameters params;
1507 createATSULayoutParameters(¶ms, renderer, &completeRun, style);
1509 if (style->backgroundColor != nil)
1510 ATSU_drawHighlight(renderer, run, style, geometry);
1512 [style->textColor set];
1514 // ATSUI can't draw beyond -32768 to +32767 so we translate the CTM and tell ATSUI to draw at (0, 0).
1515 NSGraphicsContext *gContext = [NSGraphicsContext currentContext];
1516 CGContextRef context = (CGContextRef)[gContext graphicsPort];
1517 CGContextTranslateCTM(context, geometry->point.x, geometry->point.y);
1518 bool flipped = [gContext isFlipped];
1520 CGContextScaleCTM(context, 1.0, -1.0);
1521 status = ATSUDrawText(params.layout, aRun->from, runLength, 0, 0);
1522 if (status == noErr && params.hasSyntheticBold) {
1523 // Force relayout for the bold pass
1524 ATSUClearLayoutCache(params.layout, 0);
1525 params.syntheticBoldPass = true;
1526 status = ATSUDrawText(params.layout, aRun->from, runLength, 0, 0);
1529 CGContextScaleCTM(context, 1.0, -1.0);
1530 CGContextTranslateCTM(context, -geometry->point.x, -geometry->point.y);
1532 if (status != noErr) {
1533 // Nothing to do but report the error (dev build only).
1534 ERROR("ATSUDrawText() failed(%d)", status);
1537 disposeATSULayoutParameters(¶ms);
1539 if (style->directionalOverride)
1540 free((void *)swappedRun.characters);
1543 static int ATSU_pointToOffset(WebTextRenderer *renderer, const WebCoreTextRun *run, const WebCoreTextStyle *style,
1544 int x, bool includePartialGlyphs)
1546 const WebCoreTextRun *aRun = run;
1547 WebCoreTextRun swappedRun;
1549 // Enclose in LRO/RLO - PDF to force ATSU to render visually.
1550 if (style->directionalOverride) {
1551 swappedRun = addDirectionalOverride(aRun, style->rtl);
1555 ATSULayoutParameters params;
1556 createATSULayoutParameters(¶ms, renderer, aRun, style);
1558 UniCharArrayOffset primaryOffset = aRun->from;
1560 // FIXME: No idea how to avoid including partial glyphs.
1561 // Not even sure if that's the behavior this yields now.
1563 UniCharArrayOffset secondaryOffset = 0;
1564 OSStatus status = ATSUPositionToOffset(params.layout, FloatToFixed(x), FloatToFixed(-1), &primaryOffset, &isLeading, &secondaryOffset);
1566 if (status == noErr) {
1567 offset = (unsigned)primaryOffset;
1569 // Failed to find offset! Return 0 offset.
1573 disposeATSULayoutParameters(¶ms);
1575 if (style->directionalOverride)
1576 free((void *)swappedRun.characters);
1578 return offset - aRun->from;
1581 static bool advanceWidthIteratorOneCharacter(WidthIterator *iterator, float *totalWidth)
1583 float widths[MAX_GLYPH_EXPANSION];
1584 WebTextRenderer *renderers[MAX_GLYPH_EXPANSION];
1585 ATSGlyphRef glyphs[MAX_GLYPH_EXPANSION];
1586 unsigned numGlyphs = advanceWidthIterator(iterator, iterator->currentCharacter + 1, widths, renderers, glyphs);
1589 for (i = 0; i < numGlyphs; ++i)
1592 return numGlyphs != 0;
1595 static int CG_pointToOffset(WebTextRenderer *renderer, const WebCoreTextRun * run, const WebCoreTextStyle *style,
1596 int x, bool includePartialGlyphs)
1598 float delta = (float)x;
1601 initializeWidthIterator(&it, renderer, run, style);
1606 delta -= CG_floatWidthForRun(renderer, run, style, 0, 0, 0, 0, 0);
1608 offset = it.currentCharacter;
1610 if (!advanceWidthIteratorOneCharacter(&it, &w))
1613 if (includePartialGlyphs) {
1614 if (delta - w / 2 >= 0)
1623 offset = it.currentCharacter;
1625 if (!advanceWidthIteratorOneCharacter(&it, &w))
1628 if (includePartialGlyphs) {
1629 if (delta + w / 2 <= 0)
1638 return offset - run->from;
1641 static void freeWidthMap(WidthMap *map)
1644 WidthMap *next = map->next;
1651 static void freeGlyphMap(GlyphMap *map)
1654 GlyphMap *next = map->next;
1661 static inline ATSGlyphRef glyphForCharacter(WebTextRenderer **renderer, UChar32 c)
1663 // this loop is hot, so it is written to avoid LSU stalls
1666 for (map = (*renderer)->characterToGlyphMap; map; map = nextMap) {
1667 UChar32 start = map->startRange;
1668 nextMap = map->next;
1669 if (c >= start && c <= map->endRange) {
1670 GlyphEntry *ge = &map->glyphs[c - start];
1671 *renderer = ge->renderer;
1676 return extendGlyphMap(*renderer, c);
1679 static void initializeWidthIterator(WidthIterator *iterator, WebTextRenderer *renderer, const WebCoreTextRun *run, const WebCoreTextStyle *style)
1681 iterator->renderer = renderer;
1682 iterator->run = run;
1683 iterator->style = style;
1684 iterator->currentCharacter = run->from;
1685 iterator->runWidthSoFar = 0;
1687 // If the padding is non-zero, count the number of spaces in the run
1688 // and divide that by the padding for per space addition.
1689 if (!style->padding) {
1690 iterator->padding = 0;
1691 iterator->padPerSpace = 0;
1693 float numSpaces = 0;
1695 for (k = run->from; k < run->to; k++)
1696 if (isSpace(run->characters[k]))
1699 iterator->padding = style->padding;
1700 iterator->padPerSpace = ceilf(iterator->padding / numSpaces);
1703 // Calculate width up to starting position of the run. This is
1704 // necessary to ensure that our rounding hacks are always consistently
1706 if (run->from == 0) {
1707 iterator->widthToStart = 0;
1709 WebCoreTextRun startPositionRun = *run;
1710 startPositionRun.from = 0;
1711 startPositionRun.to = run->length;
1712 WidthIterator startPositionIterator;
1713 initializeWidthIterator(&startPositionIterator, renderer, &startPositionRun, style);
1714 advanceWidthIterator(&startPositionIterator, run->from, 0, 0, 0);
1715 iterator->widthToStart = startPositionIterator.runWidthSoFar;
1719 static UChar32 normalizeVoicingMarks(WidthIterator *iterator)
1721 unsigned currentCharacter = iterator->currentCharacter;
1722 const WebCoreTextRun *run = iterator->run;
1723 if (currentCharacter + 1 < (unsigned)run->to) {
1724 if (u_getCombiningClass(run->characters[currentCharacter + 1]) == HIRAGANA_KATAKANA_VOICING_MARKS) {
1725 // Normalize into composed form using 3.2 rules.
1726 UChar normalizedCharacters[2] = { 0, 0 };
1727 UErrorCode uStatus = 0;
1728 int32_t resultLength = unorm_normalize(&run->characters[currentCharacter], 2,
1729 UNORM_NFC, UNORM_UNICODE_3_2, &normalizedCharacters[0], 2, &uStatus);
1730 if (resultLength == 1 && uStatus == 0)
1731 return normalizedCharacters[0];
1737 static unsigned advanceWidthIterator(WidthIterator *iterator, unsigned offset, float *widths, WebTextRenderer **renderersUsed, ATSGlyphRef *glyphsUsed)
1739 const WebCoreTextRun *run = iterator->run;
1740 if (offset > (unsigned)run->to)
1743 unsigned numGlyphs = 0;
1745 unsigned currentCharacter = iterator->currentCharacter;
1746 const UniChar *cp = &run->characters[currentCharacter];
1748 const WebCoreTextStyle *style = iterator->style;
1749 bool needCharTransform = style->rtl | style->smallCaps;
1750 bool hasExtraSpacing = style->letterSpacing | style->wordSpacing | style->padding;
1752 float runWidthSoFar = iterator->runWidthSoFar;
1754 while (currentCharacter < offset) {
1757 unsigned clusterLength = 1;
1760 // Deal with Hiragana and Katakana voiced and semi-voiced syllables.
1761 // Normalize into composed form, and then look for glyph with base + combined mark.
1762 // Check above for character range to minimize performance impact.
1763 UChar32 normalized = normalizeVoicingMarks(iterator);
1768 } else if (U16_IS_SURROGATE(c)) {
1769 if (!U16_IS_SURROGATE_LEAD(c))
1772 // Do we have a surrogate pair? If so, determine the full Unicode (32 bit)
1773 // code point before glyph lookup.
1774 // Make sure we have another character and it's a low surrogate.
1775 if (currentCharacter + 1 >= run->length)
1777 UniChar low = cp[1];
1778 if (!U16_IS_TRAIL(low))
1780 c = U16_GET_SUPPLEMENTARY(c, low);
1785 WebTextRenderer *renderer = iterator->renderer;
1787 if (needCharTransform) {
1789 c = u_charMirror(c);
1791 // If small-caps, convert lowercase to upper.
1792 if (style->smallCaps && !u_isUUppercase(c)) {
1793 UChar32 upperC = u_toupper(c);
1796 renderer = getSmallCapsRenderer(renderer);
1801 ATSGlyphRef glyph = glyphForCharacter(&renderer, c);
1803 // Now that we have glyph and font, get its width.
1804 WebGlyphWidth width;
1805 if (c == '\t' && style->tabWidth) {
1806 width = style->tabWidth - fmodf(style->xpos + runWidthSoFar, style->tabWidth);
1808 width = widthForGlyph(renderer, glyph);
1809 // We special case spaces in two ways when applying word rounding.
1810 // First, we round spaces to an adjusted width in all fonts.
1811 // Second, in fixed-pitch fonts we ensure that all characters that
1812 // match the width of the space character have the same width as the space character.
1813 if (width == renderer->spaceWidth && (renderer->treatAsFixedPitch || glyph == renderer->spaceGlyph) && style->applyWordRounding)
1814 width = renderer->adjustedSpaceWidth;
1817 // Try to find a substitute font if this font didn't have a glyph for a character in the
1818 // string. If one isn't found we end up drawing and measuring the 0 glyph, usually a box.
1819 if (glyph == 0 && style->attemptFontSubstitution) {
1820 WebTextRenderer *substituteRenderer = findSubstituteRenderer(renderer, cp, clusterLength, style->families);
1821 if (substituteRenderer) {
1822 WebCoreTextRun clusterRun = { cp, clusterLength, 0, clusterLength };
1823 WebCoreTextStyle clusterStyle = *style;
1824 clusterStyle.padding = 0;
1825 clusterStyle.applyRunRounding = NO;
1826 clusterStyle.attemptFontSubstitution = NO;
1829 float localWidthBuffer[MAX_GLYPH_EXPANSION];
1830 WebTextRenderer *localRendererBuffer[MAX_GLYPH_EXPANSION];
1831 ATSGlyphRef localGlyphBuffer[MAX_GLYPH_EXPANSION];
1832 CG_floatWidthForRun(substituteRenderer, &clusterRun, &clusterStyle, localWidthBuffer, localRendererBuffer, localGlyphBuffer, 0, &cNumGlyphs);
1833 if (cNumGlyphs == 1) {
1834 ASSERT(substituteRenderer == localRendererBuffer[0]);
1835 width = localWidthBuffer[0];
1836 glyph = localGlyphBuffer[0];
1837 updateGlyphMapEntry(renderer, c, glyph, substituteRenderer);
1838 renderer = substituteRenderer;
1843 if (hasExtraSpacing) {
1844 // Account for letter-spacing.
1845 if (width && style->letterSpacing)
1846 width += style->letterSpacing;
1849 // Account for padding. WebCore uses space padding to justify text.
1850 // We distribute the specified padding over the available spaces in the run.
1851 if (style->padding) {
1852 // Use left over padding if not evenly divisible by number of spaces.
1853 if (iterator->padding < iterator->padPerSpace) {
1854 width += iterator->padding;
1855 iterator->padding = 0;
1857 width += iterator->padPerSpace;
1858 iterator->padding -= iterator->padPerSpace;
1862 // Account for word spacing.
1863 // We apply additional space between "words" by adding width to the space character.
1864 if (currentCharacter != 0 && !isSpace(cp[-1]) && style->wordSpacing)
1865 width += style->wordSpacing;
1869 // Advance past the character we just dealt with.
1870 cp += clusterLength;
1871 currentCharacter += clusterLength;
1873 // Account for float/integer impedance mismatch between CG and KHTML. "Words" (characters
1874 // followed by a character defined by isRoundingHackCharacter()) are always an integer width.
1875 // We adjust the width of the last character of a "word" to ensure an integer width.
1876 // If we move KHTML to floats we can remove this (and related) hacks.
1878 // Force characters that are used to determine word boundaries for the rounding hack
1879 // to be integer width, so following words will start on an integer boundary.
1880 if (style->applyWordRounding && isRoundingHackCharacter(c))
1881 width = ceilf(width);
1883 // Check to see if the next character is a "rounding hack character", if so, adjust
1884 // width so that the total run width will be on an integer boundary.
1885 if ((style->applyWordRounding && currentCharacter < run->length && isRoundingHackCharacter(*cp))
1886 || (style->applyRunRounding && currentCharacter >= (unsigned)run->to)) {
1887 float totalWidth = iterator->widthToStart + runWidthSoFar + width;
1888 width += ceilf(totalWidth) - totalWidth;
1891 runWidthSoFar += width;
1894 ASSERT(!renderersUsed);
1895 ASSERT(!glyphsUsed);
1897 ASSERT(renderersUsed);
1900 *renderersUsed++ = renderer;
1901 *glyphsUsed++ = glyph;
1907 iterator->currentCharacter = currentCharacter;
1908 iterator->runWidthSoFar = runWidthSoFar;
1913 static bool fillStyleWithAttributes(ATSUStyle style, NSFont *theFont)
1917 ATSUFontID fontId = WKGetNSFontATSUFontId(theFont);
1918 LOG(FontCache, "fillStyleWithAttributes: font = %p,%@, _atsFontID = %x\n", theFont, theFont, (unsigned)fontId);
1921 ATSUAttributeTag tag = kATSUFontTag;
1922 ByteCount size = sizeof(ATSUFontID);
1923 ATSUFontID *valueArray[1] = {&fontId};
1924 OSStatus status = ATSUSetAttributes(style, 1, &tag, &size, (void *)valueArray);
1925 if (status != noErr) {
1926 LOG(FontCache, "fillStyleWithAttributes failed(%d): font = %p,%@, _atsFontID = %x\n", (int)status, theFont, theFont, (unsigned)fontId);
1932 static bool shouldUseATSU(const WebCoreTextRun *run)
1937 const UniChar *characters = run->characters;
1940 // Start from 0 since drawing and highlighting also measure the characters before run->from
1941 for (i = 0; i < to; i++) {
1942 UniChar c = characters[i];
1943 if (c < 0x300) // U+0300 through U+036F Combining diacritical marks
1948 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
1953 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)
1958 if (c < 0x1780) // U+1780 through U+18AF Khmer, Mongolian
1963 if (c < 0x1900) // U+1900 through U+194F Limbu (Unicode 4.0)
1968 if (c < 0x20D0) // U+20D0 through U+20FF Combining marks for symbols
1973 if (c < 0xFE20) // U+FE20 through U+FE2F Combining half marks