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