97ae56e7bafb81e59732a4fb5948d6ec505c94ec
[WebKit-https.git] / WebKit / WebCoreSupport.subproj / WebTextRenderer.m
1 /*      
2     WebTextRenderer.m       
3     Copyright 2002, Apple, Inc. All rights reserved.
4 */
5
6 #import "WebTextRenderer.h"
7
8 #import <Cocoa/Cocoa.h>
9
10 #import <ApplicationServices/ApplicationServices.h>
11 #import <CoreGraphics/CoreGraphicsPrivate.h>
12
13 #import <WebKit/WebGlyphBuffer.h>
14 #import <WebKit/WebKitLogging.h>
15 #import <WebKit/WebTextRendererFactory.h>
16 #import <WebKit/WebUnicode.h>
17
18 #import <QD/ATSUnicodePriv.h>
19
20 #import <float.h>
21
22 #define NON_BREAKING_SPACE 0x00A0
23 #define SPACE 0x0020
24
25 #define IS_CONTROL_CHARACTER(c) ((c) < 0x0020 || (c) == 0x007F)
26
27 #define ROUND_TO_INT(x) (unsigned int)((x)+.5)
28 #define CEIL_TO_INT(x) ((int)(x + (1.0 - FLT_EPSILON)))
29
30 #define LOCAL_BUFFER_SIZE 1024
31
32 // Covers most of latin1.
33 #define INITIAL_BLOCK_SIZE 0x200
34
35 // Get additional blocks of glyphs and widths in bigger chunks.
36 // This will typically be for other character sets.
37 #define INCREMENTAL_BLOCK_SIZE 0x400
38
39 #define UNINITIALIZED_GLYPH_WIDTH 65535
40
41 // combining char, hangul jamo, or Apple corporate variant tag
42 #define JunseongStart 0x1160
43 #define JonseongEnd 0x11F9
44 #define IsHangulConjoiningJamo(X) (X >= JunseongStart && X <= JonseongEnd)
45 #define IsNonBaseChar(X) ((CFCharacterSetIsCharacterMember(nonBaseChars, X) || IsHangulConjoiningJamo(X) || (((X) & 0x1FFFF0) == 0xF870)))
46
47
48 typedef float WebGlyphWidth;
49
50 struct WidthMap {
51     ATSGlyphRef startRange;
52     ATSGlyphRef endRange;
53     WidthMap *next;
54     WebGlyphWidth *widths;
55 };
56
57 struct GlyphMap {
58     UniChar startRange;
59     UniChar endRange;
60     GlyphMap *next;
61     ATSGlyphRef *glyphs;
62 };
63
64
65 @interface NSLanguage : NSObject 
66 {
67 }
68 + (NSLanguage *)defaultLanguage;
69 @end
70
71 @interface NSFont (WebPrivate)
72 - (ATSUFontID)_atsFontID;
73 - (CGFontRef)_backingCGSFont;
74 // Private method to find a font for a character.
75 + (NSFont *) findFontLike:(NSFont *)aFont forCharacter:(UInt32)c inLanguage:(NSLanguage *) language;
76 + (NSFont *) findFontLike:(NSFont *)aFont forString:(NSString *)string withRange:(NSRange)range inLanguage:(NSLanguage *) language;
77 - (NSGlyph)_defaultGlyphForChar:(unichar)uu;
78 @end
79
80 @class NSCGSFont;
81
82
83 static CFCharacterSetRef nonBaseChars = NULL;
84
85
86 @interface WebTextRenderer (WebPrivate)
87 - (WidthMap *)extendGlyphToWidthMapToInclude:(ATSGlyphRef)glyphID;
88 - (ATSGlyphRef)extendCharacterToGlyphMapToInclude:(UniChar) c;
89 @end
90
91
92 static void freeWidthMap (WidthMap *map)
93 {
94     if (!map)
95         return;
96     freeWidthMap (map->next);
97     free (map->widths);
98     free (map);
99 }
100
101
102 static void freeGlyphMap (GlyphMap *map)
103 {
104     if (!map)
105         return;
106     freeGlyphMap (map->next);
107     free (map->glyphs);
108     free (map);
109 }
110
111
112 static inline ATSGlyphRef glyphForCharacter (GlyphMap *map, UniChar c)
113 {
114     if (map == 0)
115         return nonGlyphID;
116         
117     if (c >= map->startRange && c <= map->endRange)
118         return ((ATSGlyphRef *)map->glyphs)[c-map->startRange];
119         
120     return glyphForCharacter (map->next, c);
121 }
122  
123  
124 static void setGlyphForCharacter (GlyphMap *map, ATSGlyphRef glyph, UniChar c)
125 {
126     if (map == 0)
127         return;
128         
129     if (c >= map->startRange && c <= map->endRange)
130         ((ATSGlyphRef *)map->glyphs)[c-map->startRange] = glyph;
131     else    
132         setGlyphForCharacter (map->next, glyph, c);
133 }
134
135
136 #ifdef _TIMING        
137 static double totalCGGetAdvancesTime = 0;
138 #endif
139
140 static inline WebGlyphWidth widthForGlyph (WebTextRenderer *renderer, WidthMap *map, ATSGlyphRef glyph)
141 {
142     WebGlyphWidth width;
143     BOOL errorResult;
144     
145     if (map == 0){
146         map = [renderer extendGlyphToWidthMapToInclude: glyph];
147         return widthForGlyph (renderer, map, glyph);
148     }
149         
150     if (glyph >= map->startRange && glyph <= map->endRange){
151         width = ((WebGlyphWidth *)map->widths)[glyph-map->startRange];
152         if (width == UNINITIALIZED_GLYPH_WIDTH){
153
154 #ifdef _TIMING        
155             double startTime = CFAbsoluteTimeGetCurrent();
156 #endif
157             errorResult = CGFontGetGlyphScaledAdvances ([renderer->font _backingCGSFont], &glyph, 1, &map->widths[glyph-map->startRange], [renderer->font pointSize]);
158             if (errorResult == 0)
159                 [NSException raise:NSInternalInconsistencyException format:@"Optimization assumption violation:  unable to cache glyph widths - for %@ %f",  [renderer->font displayName], [renderer->font pointSize]];
160     
161 #ifdef _TIMING        
162             double thisTime = CFAbsoluteTimeGetCurrent() - startTime;
163             totalCGGetAdvancesTime += thisTime;
164 #endif
165             return ((WebGlyphWidth *)map->widths)[glyph-map->startRange];
166         }
167         return width;
168     }
169
170     return widthForGlyph (renderer, map->next, glyph);
171 }
172
173
174 static inline  WebGlyphWidth widthForCharacter (WebTextRenderer *renderer, UniChar c)
175 {
176     return widthForGlyph (renderer, renderer->glyphToWidthMap, glyphForCharacter(renderer->characterToGlyphMap, c));
177 }
178
179
180 static void FillStyleWithAttributes(ATSUStyle style, NSFont *theFont)
181 {
182     if (theFont) {
183         ATSUFontID fontId = (ATSUFontID)[theFont _atsFontID];
184         ATSUAttributeTag tag = kATSUFontTag;
185         ByteCount size = sizeof(ATSUFontID);
186         ATSUFontID *valueArray[1] = {&fontId};
187
188         if (fontId) {
189             if (ATSUSetAttributes(style, 1, &tag, &size, (void **)valueArray) != noErr)
190                 [NSException raise:NSInternalInconsistencyException format:@"Failed to set font (%@) ATSUStyle 0x%X", theFont, style];
191         }
192     }
193 }
194
195
196 static unsigned int findLengthOfCharacterCluster(const UniChar *characters, unsigned int length)
197 {
198     unsigned int k;
199
200     if (length <= 1)
201         return length;
202     
203     if (IsNonBaseChar(characters[0]))
204         return 1;
205                 
206     // Find all the non base characters after the current character.
207     for (k = 1; k < length; k++)
208         if (!IsNonBaseChar(characters[k]))
209             break;
210     return k;
211 }
212
213
214 @implementation WebTextRenderer
215
216 static BOOL bufferTextDrawing = NO;
217
218 + (BOOL)shouldBufferTextDrawing
219 {
220     return bufferTextDrawing;
221 }
222
223 + (void)initialize
224 {
225     WebKitInitializeUnicode();
226     nonBaseChars = CFCharacterSetGetPredefined(kCFCharacterSetNonBase);
227     bufferTextDrawing = [[[NSUserDefaults standardUserDefaults] stringForKey:@"BufferTextDrawing"] isEqual: @"YES"];
228 }
229
230
231 - (NSFont *)substituteFontForString: (NSString *)string
232 {
233     NSFont *substituteFont;
234
235     substituteFont = [NSFont findFontLike:font forString:string withRange:NSMakeRange (0,[string length]) inLanguage:[NSLanguage defaultLanguage]];
236
237     if ([substituteFont isEqual: font])
238         return nil;
239         
240     return substituteFont;
241 }
242
243
244 - (NSFont *)substituteFontForCharacters: (const unichar *)characters length: (int)numCharacters
245 {
246     NSFont *substituteFont;
247     NSString *string = [[NSString alloc] initWithCharactersNoCopy:(unichar *)characters length: numCharacters freeWhenDone: NO];
248
249     substituteFont = [self substituteFontForString: string];
250
251     [string release];
252     
253     return substituteFont;
254 }
255
256
257 /* Convert non-breaking spaces into spaces, and skip control characters. */
258 - (void)convertCharacters: (const UniChar *)characters length: (unsigned)numCharacters toGlyphs: (ATSGlyphVector *)glyphs skipControlCharacters:(BOOL)skipControlCharacters
259 {
260     unsigned i, numCharactersInBuffer;
261     UniChar localBuffer[LOCAL_BUFFER_SIZE];
262     UniChar *buffer = localBuffer;
263     OSStatus status;
264     
265     for (i = 0; i < numCharacters; i++) {
266         UniChar c = characters[i];
267         if ((skipControlCharacters && IS_CONTROL_CHARACTER(c)) || c == NON_BREAKING_SPACE) {
268             break;
269         }
270     }
271     
272     if (i < numCharacters) {
273         if (numCharacters > LOCAL_BUFFER_SIZE) {
274             buffer = (UniChar *)malloc(sizeof(UniChar) * numCharacters);
275         }
276         
277         numCharactersInBuffer = 0;
278         for (i = 0; i < numCharacters; i++) {
279             UniChar c = characters[i];
280             if (c == NON_BREAKING_SPACE) {
281                 buffer[numCharactersInBuffer++] = SPACE;
282             } else if (!(skipControlCharacters && IS_CONTROL_CHARACTER(c))) {
283                 buffer[numCharactersInBuffer++] = characters[i];
284             }
285         }
286         
287         characters = buffer;
288         numCharacters = numCharactersInBuffer;
289     }
290     
291     status = ATSUConvertCharToGlyphs(styleGroup, characters, 0, numCharacters, 0, glyphs);
292     
293     if (buffer != localBuffer) {
294         free(buffer);
295     }
296 }
297
298
299 - initWithFont:(NSFont *)f
300 {
301     if ([f glyphPacking] != NSNativeShortGlyphPacking &&
302         [f glyphPacking] != NSTwoByteGlyphPacking)
303         [NSException raise:NSInternalInconsistencyException format:@"%@: Don't know how to pack glyphs for font %@ %f", self, [f displayName], [f pointSize]];
304         
305     [super init];
306     
307     font = [f retain];
308     ascent = -1;
309     descent = -1;
310     lineSpacing = -1;
311     
312     OSStatus errCode;
313     ATSUStyle style;
314     
315     if ((errCode = ATSUCreateStyle(&style)) != noErr)
316         [NSException raise:NSInternalInconsistencyException format:@"%@: Failed to alloc ATSUStyle %d", self, errCode];
317
318     FillStyleWithAttributes(style, font);
319
320     if ((errCode = ATSUGetStyleGroup(style, &styleGroup)) != noErr) {
321         [NSException raise:NSInternalInconsistencyException format:@"%@: Failed to create attribute group from ATSUStyle 0x%X %d", self, style, errCode];
322     }
323     
324     ATSUDisposeStyle(style);
325
326     spaceGlyph = nonGlyphID;
327     
328     return self;
329 }
330
331
332 - (void)dealloc
333 {
334     [font release];
335
336     if (styleGroup)
337         ATSUDisposeStyleGroup(styleGroup);
338
339     freeWidthMap (glyphToWidthMap);
340     freeGlyphMap (characterToGlyphMap);
341     
342     [super dealloc];
343 }
344
345
346 - (int)widthForCharacters:(const UniChar *)characters length:(unsigned)stringLength
347 {
348     return ROUND_TO_INT([self floatWidthForCharacters:characters stringLength:stringLength fromCharacterPosition:0 numberOfCharacters:stringLength withPadding: 0 applyRounding:YES attemptFontSubstitution: YES widths: 0]);
349 }
350
351 - (int)widthForString:(NSString *)string
352 {
353     UniChar localCharacterBuffer[LOCAL_BUFFER_SIZE];
354     UniChar *characterBuffer = localCharacterBuffer;
355     const UniChar *usedCharacterBuffer = CFStringGetCharactersPtr((CFStringRef)string);
356     unsigned int length;
357     int width;
358
359     // Get the characters from the string into a buffer.
360     length = [string length];
361     if (!usedCharacterBuffer) {
362         if (length > LOCAL_BUFFER_SIZE)
363             characterBuffer = (UniChar *)malloc(length * sizeof(UniChar));
364         [string getCharacters:characterBuffer];
365         usedCharacterBuffer = characterBuffer;
366     }
367
368     width = [self widthForCharacters:usedCharacterBuffer length:length];
369     
370     if (characterBuffer != localCharacterBuffer)
371         free(characterBuffer);
372
373     return width;
374 }
375
376
377 - (int)ascent
378 {
379     if (ascent < 0)  {
380         ascent = ROUND_TO_INT([font ascender]);
381     }
382     return ascent;
383 }
384
385
386 - (int)descent
387 {
388     if (descent < 0)  {
389         descent = ROUND_TO_INT(-[font descender]);
390     }
391     return descent;
392 }
393
394
395 - (int)lineSpacing
396 {
397     if (lineSpacing < 0) {
398         lineSpacing = ROUND_TO_INT([font defaultLineHeightForFont]);
399     }
400     return lineSpacing;
401 }
402
403
404 - (float)xHeight
405 {
406     return [font xHeight];
407 }
408
409
410 // Useful page for testing http://home.att.net/~jameskass
411 static void _drawGlyphs(NSFont *font, NSColor *color, CGGlyph *glyphs, CGSize *advances, float x, float y, int numGlyphs)
412 {
413     CGContextRef cgContext;
414
415     if ([WebTextRenderer shouldBufferTextDrawing] && [[WebTextRendererFactory sharedFactory] coalesceTextDrawing]){
416         // Add buffered glyphs and advances
417         // FIXME:  If we ever use this again, need to add RTL.
418         WebGlyphBuffer *gBuffer = [[WebTextRendererFactory sharedFactory] glyphBufferForFont: font andColor: color];
419         [gBuffer addGlyphs: glyphs advances: advances count: numGlyphs at: x : y];
420     }
421     else {
422         cgContext = (CGContextRef)[[NSGraphicsContext currentContext] graphicsPort];
423         // Setup the color and font.
424         [color set];
425         [font set];
426
427         CGContextSetTextPosition (cgContext, x, y);
428         CGContextShowGlyphsWithAdvances (cgContext, glyphs, advances, numGlyphs);
429     }
430 }
431
432 - (void)drawCharacters:(const UniChar *)characters stringLength: (unsigned int)length fromCharacterPosition: (int)from toCharacterPosition: (int)to atPoint:(NSPoint)point withPadding: (int)padding withTextColor:(NSColor *)textColor backgroundColor: (NSColor *)backgroundColor rightToLeft: (BOOL)rtl
433 {
434     float *widthBuffer, localWidthBuffer[LOCAL_BUFFER_SIZE];
435     CGGlyph *glyphBuffer, localGlyphBuffer[LOCAL_BUFFER_SIZE];
436     NSFont **fontBuffer, *localFontBuffer[LOCAL_BUFFER_SIZE];
437     CGSize *advances, localAdvanceBuffer[LOCAL_BUFFER_SIZE];
438     int numGlyphs, i;
439     float startX, nextX, backgroundWidth = 0.0;
440     NSFont *currentFont;
441     
442     if (length == 0)
443         return;
444         
445     // FIXME:  the character to glyph translation must result in less than
446     // length glyphs.  This isn't always true.
447     if (length > LOCAL_BUFFER_SIZE) {
448         advances = (CGSize *)calloc(length, sizeof(CGSize));
449         widthBuffer = (float *)calloc(length, sizeof(float));
450         glyphBuffer = (CGGlyph *)calloc(length, sizeof(ATSGlyphRef));
451         fontBuffer = (NSFont **)calloc(length, sizeof(NSFont *));
452     } else {
453         advances = localAdvanceBuffer;
454         widthBuffer = localWidthBuffer;
455         glyphBuffer = localGlyphBuffer;
456         fontBuffer = localFontBuffer;
457     }
458
459     [self _floatWidthForCharacters:characters 
460         stringLength:length 
461         fromCharacterPosition: 0 
462         numberOfCharacters: length
463         withPadding: padding
464         applyRounding: YES
465         attemptFontSubstitution: YES 
466         widths: widthBuffer 
467         fonts: fontBuffer
468         glyphs: glyphBuffer
469         numGlyphs: &numGlyphs];
470     
471     if (from == -1)
472         from = 0;
473     if (to == -1)
474         to = numGlyphs;
475
476     for (i = 0; (int)i < MIN(to,(int)numGlyphs); i++){
477         advances[i].width = widthBuffer[i];
478         advances[i].height = 0;
479     }
480
481     startX = point.x;
482     for (i = 0; (int)i < MIN(from,(int)numGlyphs); i++)
483         startX += advances[i].width;
484
485     for (i = from; (int)i < MIN(to,(int)numGlyphs); i++)
486         backgroundWidth += advances[i].width;
487
488     if (backgroundColor != nil){
489         [backgroundColor set];
490         [NSBezierPath fillRect:NSMakeRect(startX, point.y - [self ascent], backgroundWidth, [self lineSpacing])];
491     }
492     
493     // Finally, draw the glyphs.
494     if (from < (int)numGlyphs){
495         int lastFrom = from;
496         int pos = from;
497
498         if (rtl && numGlyphs > 1){
499             int i;
500             int end = numGlyphs;
501             CGGlyph gswap1, gswap2;
502             CGSize aswap1, aswap2;
503             NSFont *fswap1, *fswap2;
504             
505             for (i = pos, end = numGlyphs; i < (numGlyphs - pos)/2; i++){
506                 gswap1 = glyphBuffer[i];
507                 gswap2 = glyphBuffer[--end];
508                 glyphBuffer[i] = gswap2;
509                 glyphBuffer[end] = gswap1;
510             }
511             for (i = pos, end = numGlyphs; i < (numGlyphs - pos)/2; i++){
512                 aswap1 = advances[i];
513                 aswap2 = advances[--end];
514                 advances[i] = aswap2;
515                 advances[end] = aswap1;
516             }
517             for (i = pos, end = numGlyphs; i < (numGlyphs - pos)/2; i++){
518                 fswap1 = fontBuffer[i];
519                 fswap2 = fontBuffer[--end];
520                 fontBuffer[i] = fswap2;
521                 fontBuffer[end] = fswap1;
522             }
523         }
524         
525         currentFont = fontBuffer[pos];
526         nextX = startX;
527         while (pos < to){
528             if ((fontBuffer[pos] != 0 && fontBuffer[pos] != currentFont)){
529                 _drawGlyphs(currentFont, textColor, &glyphBuffer[lastFrom], &advances[lastFrom], startX, point.y, pos - lastFrom);
530                 lastFrom = pos;
531                 currentFont = fontBuffer[pos];
532                 startX = nextX;
533             }
534             nextX += advances[pos].width;
535             pos++;
536         }
537         _drawGlyphs(currentFont, textColor, &glyphBuffer[lastFrom], &advances[lastFrom], startX, point.y, pos - lastFrom);
538     }
539
540     if (advances != localAdvanceBuffer) {
541         free(advances);
542         free(widthBuffer);
543         free(glyphBuffer);
544         free(fontBuffer);
545     }
546 }
547
548
549 - (void)drawUnderlineForCharacters:(const UniChar *)characters stringLength:(unsigned)length atPoint:(NSPoint)point withColor:(NSColor *)color
550 {
551     NSGraphicsContext *graphicsContext = [NSGraphicsContext currentContext];
552     int width = [self widthForCharacters:characters length:length];
553     CGContextRef cgContext;
554     float lineWidth;
555     
556     // This will draw the text from the top of the bounding box down.
557     // Qt expects to draw from the baseline.
558     // Remember that descender is negative.
559     point.y -= [self lineSpacing] - [self descent];
560     
561     BOOL flag = [graphicsContext shouldAntialias];
562
563     [graphicsContext setShouldAntialias: NO];
564
565     [color set];
566
567     cgContext = (CGContextRef)[graphicsContext graphicsPort];
568     lineWidth = 0.0;
569     if ([graphicsContext isDrawingToScreen] && lineWidth == 0.0) {
570         CGSize size = CGSizeApplyAffineTransform(CGSizeMake(1.0, 1.0), CGAffineTransformInvert(CGContextGetCTM(cgContext)));
571         lineWidth = size.width;
572     }
573     CGContextSetLineWidth(cgContext, lineWidth);
574     CGContextMoveToPoint(cgContext, point.x, point.y + [font defaultLineHeightForFont] + 1.5 - [self descent]);
575     CGContextAddLineToPoint(cgContext, point.x + width, point.y + [font defaultLineHeightForFont] + 1.5 - [self descent]);
576     CGContextStrokePath(cgContext);
577
578     [graphicsContext setShouldAntialias: flag];
579 }
580
581
582 - (float)slowFloatWidthForCharacters: (const UniChar *)characters stringLength: (unsigned)length fromCharacterPostion: (int)pos numberOfCharacters: (int)len applyRounding: (BOOL)applyRounding
583 {
584     float totalWidth = 0;
585     unsigned int charPos = 0, clusterLength, i, numGlyphs;
586     ATSGlyphVector glyphVector;
587     WebGlyphWidth glyphWidth;
588     ATSLayoutRecord *glyphRecord;
589     ATSGlyphRef glyphID;
590     float lastWidth = 0;
591     
592     ATSInitializeGlyphVector(length, 0, &glyphVector);
593     [self convertCharacters: characters length: length toGlyphs: &glyphVector skipControlCharacters: YES];
594     numGlyphs = glyphVector.numGlyphs;
595     glyphRecord = (ATSLayoutRecord *)glyphVector.firstRecord;
596     for (i = 0; i < numGlyphs; i++){
597         glyphID = glyphRecord->glyphID;
598
599         // Drop out early if we've measured to the end of the requested
600         // fragment.
601         if ((int)charPos - pos >= len){
602             if (glyphID == spaceGlyph){
603                 //totalWidth -= lastWidth;
604                 //totalWidth += ROUND_TO_INT(lastWidth);
605                 totalWidth += CEIL_TO_INT(totalWidth) - totalWidth;
606             }
607             break;
608         }
609
610         // No need to measure until we reach start of string.
611         if ((int)charPos < pos)
612             continue;
613         
614         clusterLength = findLengthOfCharacterCluster (&characters[charPos], length - charPos);
615
616         glyphRecord = (ATSLayoutRecord *)((char *)glyphRecord + glyphVector.recordSize);
617         glyphWidth = widthForGlyph(self, glyphToWidthMap, glyphID);
618         if (glyphID == spaceGlyph && applyRounding){
619             if (totalWidth > 0 && lastWidth > 0){
620                 //totalWidth -= lastWidth;
621                 //totalWidth += ROUND_TO_INT(lastWidth);
622                 totalWidth += CEIL_TO_INT(totalWidth) - totalWidth;
623             }
624             glyphWidth = ROUND_TO_INT(glyphWidth);
625         }
626         lastWidth = glyphWidth;
627         
628         if ((int)charPos >= pos)
629             totalWidth += lastWidth;
630
631         charPos += clusterLength;
632     }
633     ATSClearGlyphVector(&glyphVector);
634     
635     if (applyRounding)
636         totalWidth += CEIL_TO_INT(totalWidth) - totalWidth;
637         
638     return totalWidth;
639 }
640
641
642 - (float)floatWidthForCharacters:(const UniChar *)characters stringLength:(unsigned)stringLength characterPosition: (int)pos
643 {
644     // Return the width of the first complete character at the specified position.  Even though
645     // the first 'character' may contain more than one unicode characters this method will
646     // work correctly.
647     return [self floatWidthForCharacters:characters stringLength:stringLength fromCharacterPosition:pos numberOfCharacters:1 withPadding: 0 applyRounding: YES attemptFontSubstitution: YES widths: nil];
648 }
649
650
651 - (float)floatWidthForCharacters:(const UniChar *)characters stringLength:(unsigned)stringLength fromCharacterPosition: (int)pos numberOfCharacters: (int)len
652 {
653     return [self floatWidthForCharacters:characters stringLength:stringLength fromCharacterPosition:pos numberOfCharacters:len withPadding: 0 applyRounding: YES attemptFontSubstitution: YES widths: nil];
654 }
655
656
657 - (float)floatWidthForCharacters:(const UniChar *)characters stringLength:(unsigned)stringLength fromCharacterPosition: (int)pos numberOfCharacters: (int)len withPadding: (int)padding applyRounding: (BOOL)applyRounding attemptFontSubstitution: (BOOL)attemptSubstitution widths: (float *)widthBuffer
658 {
659     return [self _floatWidthForCharacters:characters stringLength:stringLength fromCharacterPosition:pos numberOfCharacters:len withPadding: 0 applyRounding: YES attemptFontSubstitution: YES widths: widthBuffer fonts: nil  glyphs: nil numGlyphs: nil];
660 }
661
662 - (float)_floatWidthForCharacters:(const UniChar *)characters stringLength:(unsigned)stringLength fromCharacterPosition: (int)pos numberOfCharacters: (int)len withPadding: (int)padding applyRounding: (BOOL)applyRounding attemptFontSubstitution: (BOOL)attemptSubstitution widths: (float *)widthBuffer fonts: (NSFont **)fontBuffer glyphs: (CGGlyph *)glyphBuffer numGlyphs: (int *)_numGlyphs
663 {
664     float totalWidth = 0;
665     unsigned int i, clusterLength;
666     NSFont *substituteFont = nil;
667     ATSGlyphRef glyphID;
668     float lastWidth = 0;
669     uint numSpaces = 0;
670     int padPerSpace = 0;
671     int numGlyphs = 0;
672
673     if (len <= 0){
674         if (_numGlyphs)
675             *_numGlyphs = 0;
676         return 0;
677     }
678         
679     // If the padding is non-zero, count the number of spaces in the string
680     // and divide that by the padding for per space addition.
681     if (padding > 0){
682         for (i = pos; i < (uint)pos+len; i++){
683             if (characters[i] == NON_BREAKING_SPACE || characters[i] == SPACE)
684                 numSpaces++;
685         }
686         padPerSpace = CEIL_TO_INT ((((float)padding) / ((float)numSpaces)));
687     }
688
689     //printf("width: font %s, size %.1f, text \"%s\"\n", [[font fontName] cString], [font pointSize], [[NSString stringWithCharacters:characters length:length] UTF8String]);
690     for (i = 0; i < stringLength; i++) {
691         UniChar c = characters[i];
692         
693         // Skip control characters.
694         if (IS_CONTROL_CHARACTER(c)) {
695             continue;
696         }
697         
698         if (c == NON_BREAKING_SPACE) {
699             c = SPACE;
700         }
701         
702         // Drop out early if we've measured to the end of the requested
703         // fragment.
704         if ((int)i - pos >= len) {
705             // Check if next character is a space. If so, we have to apply rounding.
706             if (c == SPACE && applyRounding) {
707                 float delta = CEIL_TO_INT(totalWidth) - totalWidth;
708                 totalWidth += delta;
709                 if (widthBuffer)
710                     widthBuffer[numGlyphs - 1] += delta;
711             }
712             break;
713         }
714
715         //if (IsNonBaseChar(c)) {
716         //    return [self slowFloatWidthForCharacters: &characters[pos] stringLength: stringLength-pos fromCharacterPostion: 0 numberOfCharacters: len applyRounding: applyRounding];
717         //}
718
719         glyphID = glyphForCharacter(characterToGlyphMap, c);
720         if (glyphID == nonGlyphID) {
721             glyphID = [self extendCharacterToGlyphMapToInclude: c];
722         }
723         
724         // Try to find a substitute font if this font didn't have a glyph for a character in the
725         // string.  If one isn't found we end up drawing and measuring the 0 glyph, usually a box.
726         if (glyphID == 0 && attemptSubstitution) {
727             clusterLength = findLengthOfCharacterCluster (&characters[i], stringLength - i);
728             substituteFont = [self substituteFontForCharacters: &characters[i] length: clusterLength];
729             if (substituteFont) {
730                 int cNumGlyphs;
731                 lastWidth = [[[WebTextRendererFactory sharedFactory] rendererWithFont: substituteFont] 
732                                 _floatWidthForCharacters: &characters[i] 
733                                 stringLength: clusterLength 
734                                 fromCharacterPosition: 0 numberOfCharacters: clusterLength 
735                                 withPadding: 0 applyRounding: NO attemptFontSubstitution: NO 
736                                 widths: ((widthBuffer != 0 ) ? (&widthBuffer[numGlyphs]) : nil)
737                                 fonts: nil
738                                 glyphs: ((glyphBuffer != 0 ) ? (&glyphBuffer[numGlyphs]) : nil)
739                                 numGlyphs: &cNumGlyphs];
740                 if (fontBuffer){
741                     int j;
742                     for (j = 0; j < cNumGlyphs; j++)
743                         fontBuffer[numGlyphs+j] = substituteFont;
744                 }
745                 numGlyphs += cNumGlyphs;
746             }
747         }
748         
749         // If we have a valid glyph OR if we couldn't find a substitute font
750         // measure the glyph.
751         if (glyphID > 0 || ((glyphID == 0) && substituteFont == nil)) {
752             if (glyphID == spaceGlyph && applyRounding) {
753                 if (lastWidth > 0){
754                     float delta = CEIL_TO_INT(totalWidth) - totalWidth;
755                     totalWidth += delta;
756                     if (widthBuffer)
757                         widthBuffer[numGlyphs - 1] += delta;
758                 }   
759                 lastWidth = ROUND_TO_INT(widthForGlyph(self, glyphToWidthMap, glyphID));
760                 if (padding > 0){
761                     // Only use left over padding if note evenly divisible by 
762                     // number of spaces.
763                     if (padding < padPerSpace){
764                         lastWidth += padding;
765                         padding = 0;
766                     }
767                     else {
768                         lastWidth += padPerSpace;
769                         padding -= padPerSpace;
770                     }
771                 }
772             }
773             else
774                 lastWidth = widthForGlyph(self, glyphToWidthMap, glyphID);
775             
776             if (fontBuffer)
777                 fontBuffer[numGlyphs] = font;
778             if (glyphBuffer)
779                 glyphBuffer[numGlyphs] = glyphID;
780             if (widthBuffer)
781                 widthBuffer[numGlyphs] = lastWidth;
782             numGlyphs++;
783         }
784         
785         totalWidth += lastWidth;       
786     }
787
788     // Don't ever apply rounding for single character.  Single character measurement
789     // intra word needs to be non-ceiled.
790     if ((len > 1 || stringLength == 1) && applyRounding){
791         float delta = CEIL_TO_INT(totalWidth) - totalWidth;
792         totalWidth += delta;
793         if (widthBuffer)
794             widthBuffer[numGlyphs-1] += delta;
795     }
796
797     if (_numGlyphs)
798         *_numGlyphs = numGlyphs;
799     
800     return totalWidth;
801 }
802
803 - (ATSGlyphRef)extendCharacterToGlyphMapToInclude:(UniChar) c
804 {
805     GlyphMap *map = (GlyphMap *)calloc (1, sizeof(GlyphMap));
806     ATSLayoutRecord *glyphRecord;
807     ATSGlyphVector glyphVector;
808     UniChar end, start;
809     unsigned int blockSize;
810     ATSGlyphRef glyphID;
811     
812     if (characterToGlyphMap == 0)
813         blockSize = INITIAL_BLOCK_SIZE;
814     else
815         blockSize = INCREMENTAL_BLOCK_SIZE;
816     start = (c / blockSize) * blockSize;
817     end = start + (blockSize - 1);
818         
819     LOG(FontCache, "%@ (0x%04x) adding glyphs for 0x%04x to 0x%04x", font, c, start, end);
820
821     map->startRange = start;
822     map->endRange = end;
823     
824     unsigned int i, count = end - start + 1;
825     short unsigned int buffer[INCREMENTAL_BLOCK_SIZE+2];
826     
827     for (i = 0; i < count; i++){
828         if (IsNonBaseChar(i+start))
829             buffer[i] = 0;
830         else
831             buffer[i] = i+start;
832     }
833
834     ATSInitializeGlyphVector(count, 0, &glyphVector);
835     [self convertCharacters: &buffer[0] length: count toGlyphs: &glyphVector skipControlCharacters: NO];
836     if (glyphVector.numGlyphs != count)
837         [NSException raise:NSInternalInconsistencyException format:@"Optimization assumption violation:  count and glyphID count not equal - for %@ %f", self, [font displayName], [font pointSize]];
838             
839     map->glyphs = (ATSGlyphRef *)malloc (count * sizeof(ATSGlyphRef));
840     glyphRecord = (ATSLayoutRecord *)glyphVector.firstRecord;
841     for (i = 0; i < count; i++) {
842         map->glyphs[i] = glyphRecord->glyphID;
843         glyphRecord = (ATSLayoutRecord *)((char *)glyphRecord + glyphVector.recordSize);
844     }
845     ATSClearGlyphVector(&glyphVector);
846     
847     if (characterToGlyphMap == 0)
848         characterToGlyphMap = map;
849     else {
850         GlyphMap *lastMap = characterToGlyphMap;
851         while (lastMap->next != 0)
852             lastMap = lastMap->next;
853         lastMap->next = map;
854     }
855
856     if (spaceGlyph == nonGlyphID)
857         spaceGlyph = glyphForCharacter (characterToGlyphMap, SPACE);
858
859     glyphID = map->glyphs[c - start];
860     
861     // Special case for characters 007F-00A0.
862     if (glyphID == 0 && c >= 0x7F && c <= 0xA0){
863         glyphID = [font _defaultGlyphForChar: c];
864         map->glyphs[c - start] = glyphID;
865     }
866
867     return glyphID;
868 }
869
870
871 - (WidthMap *)extendGlyphToWidthMapToInclude:(ATSGlyphRef)glyphID
872 {
873     WidthMap *map = (WidthMap *)calloc (1, sizeof(WidthMap));
874     unsigned int end;
875     ATSGlyphRef start;
876     unsigned int blockSize;
877     unsigned int i, count;
878     
879     if (glyphToWidthMap == 0){
880         if ([font numberOfGlyphs] < INITIAL_BLOCK_SIZE)
881             blockSize = [font numberOfGlyphs];
882          else
883             blockSize = INITIAL_BLOCK_SIZE;
884     }
885     else
886         blockSize = INCREMENTAL_BLOCK_SIZE;
887     start = (glyphID / blockSize) * blockSize;
888     end = ((unsigned int)start) + blockSize; 
889     if (end > 0xffff)
890         end = 0xffff;
891
892     LOG(FontCache, "%@ (0x%04x) adding widths for range 0x%04x to 0x%04x", font, glyphID, start, end);
893
894     map->startRange = start;
895     map->endRange = end;
896     count = end - start + 1;
897
898     map->widths = (WebGlyphWidth *)malloc (count * sizeof(WebGlyphWidth));
899
900     for (i = 0; i < count; i++){
901         map->widths[i] = UNINITIALIZED_GLYPH_WIDTH;
902     }
903
904     if (glyphToWidthMap == 0)
905         glyphToWidthMap = map;
906     else {
907         WidthMap *lastMap = glyphToWidthMap;
908         while (lastMap->next != 0)
909             lastMap = lastMap->next;
910         lastMap->next = map;
911     }
912
913 #ifdef _TIMING
914     LOG(FontCache, "%@ total time to advances lookup %f seconds", font, totalCGGetAdvancesTime);
915 #endif
916     return map;
917 }
918
919 @end