i Changes to support cursive letter forms. It works, but I've
[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 #ifdef CURSIVE_SHAPES
462     if (rtl){
463         UniChar *shaped;
464         int lengthOut;
465         characters = shapedString ((UniChar *)&characters[from], length, from, to, 1, &lengthOut);
466         printf ("%d input, %d output\n", length, lengthOut);
467         for (i = 0; i < (int)length; i++){
468             printf ("0x%04x shaped to 0x%04x\n", characters[i], shaped[i]);
469         }
470     }
471 #endif
472
473     [self _floatWidthForCharacters:characters 
474         stringLength:length 
475         fromCharacterPosition: 0 
476         numberOfCharacters: length
477         withPadding: padding
478         applyRounding: YES
479         attemptFontSubstitution: YES 
480         widths: widthBuffer 
481         fonts: fontBuffer
482         glyphs: glyphBuffer
483         numGlyphs: &numGlyphs];
484     
485     if (from == -1)
486         from = 0;
487     if (to == -1)
488         to = numGlyphs;
489
490     for (i = 0; (int)i < MIN(to,(int)numGlyphs); i++){
491         advances[i].width = widthBuffer[i];
492         advances[i].height = 0;
493     }
494
495     startX = point.x;
496     for (i = 0; (int)i < MIN(from,(int)numGlyphs); i++)
497         startX += advances[i].width;
498
499     for (i = from; (int)i < MIN(to,(int)numGlyphs); i++)
500         backgroundWidth += advances[i].width;
501
502     if (backgroundColor != nil){
503         [backgroundColor set];
504         [NSBezierPath fillRect:NSMakeRect(startX, point.y - [self ascent], backgroundWidth, [self lineSpacing])];
505     }
506     
507     // Finally, draw the glyphs.
508     if (from < (int)numGlyphs){
509         int lastFrom = from;
510         int pos = from;
511
512 #ifndef CURSIVE_SHAPES
513         if (rtl && numGlyphs > 1){
514             int i;
515             int end = numGlyphs;
516             CGGlyph gswap1, gswap2;
517             CGSize aswap1, aswap2;
518             NSFont *fswap1, *fswap2;
519             
520             for (i = pos, end = numGlyphs; i < (numGlyphs - pos)/2; i++){
521                 gswap1 = glyphBuffer[i];
522                 gswap2 = glyphBuffer[--end];
523                 glyphBuffer[i] = gswap2;
524                 glyphBuffer[end] = gswap1;
525             }
526             for (i = pos, end = numGlyphs; i < (numGlyphs - pos)/2; i++){
527                 aswap1 = advances[i];
528                 aswap2 = advances[--end];
529                 advances[i] = aswap2;
530                 advances[end] = aswap1;
531             }
532             for (i = pos, end = numGlyphs; i < (numGlyphs - pos)/2; i++){
533                 fswap1 = fontBuffer[i];
534                 fswap2 = fontBuffer[--end];
535                 fontBuffer[i] = fswap2;
536                 fontBuffer[end] = fswap1;
537             }
538         }
539 #endif        
540         currentFont = fontBuffer[pos];
541         nextX = startX;
542         while (pos < to){
543             if ((fontBuffer[pos] != 0 && fontBuffer[pos] != currentFont)){
544                 _drawGlyphs(currentFont, textColor, &glyphBuffer[lastFrom], &advances[lastFrom], startX, point.y, pos - lastFrom);
545                 lastFrom = pos;
546                 currentFont = fontBuffer[pos];
547                 startX = nextX;
548             }
549             nextX += advances[pos].width;
550             pos++;
551         }
552         _drawGlyphs(currentFont, textColor, &glyphBuffer[lastFrom], &advances[lastFrom], startX, point.y, pos - lastFrom);
553     }
554
555     if (advances != localAdvanceBuffer) {
556         free(advances);
557         free(widthBuffer);
558         free(glyphBuffer);
559         free(fontBuffer);
560     }
561 }
562
563
564 - (void)drawUnderlineForCharacters:(const UniChar *)characters stringLength:(unsigned)length atPoint:(NSPoint)point withColor:(NSColor *)color
565 {
566     NSGraphicsContext *graphicsContext = [NSGraphicsContext currentContext];
567     int width = [self widthForCharacters:characters length:length];
568     CGContextRef cgContext;
569     float lineWidth;
570     
571     // This will draw the text from the top of the bounding box down.
572     // Qt expects to draw from the baseline.
573     // Remember that descender is negative.
574     point.y -= [self lineSpacing] - [self descent];
575     
576     BOOL flag = [graphicsContext shouldAntialias];
577
578     [graphicsContext setShouldAntialias: NO];
579
580     [color set];
581
582     cgContext = (CGContextRef)[graphicsContext graphicsPort];
583     lineWidth = 0.0;
584     if ([graphicsContext isDrawingToScreen] && lineWidth == 0.0) {
585         CGSize size = CGSizeApplyAffineTransform(CGSizeMake(1.0, 1.0), CGAffineTransformInvert(CGContextGetCTM(cgContext)));
586         lineWidth = size.width;
587     }
588     CGContextSetLineWidth(cgContext, lineWidth);
589     CGContextMoveToPoint(cgContext, point.x, point.y + [font defaultLineHeightForFont] + 1.5 - [self descent]);
590     CGContextAddLineToPoint(cgContext, point.x + width, point.y + [font defaultLineHeightForFont] + 1.5 - [self descent]);
591     CGContextStrokePath(cgContext);
592
593     [graphicsContext setShouldAntialias: flag];
594 }
595
596
597 - (float)floatWidthForCharacters:(const UniChar *)characters stringLength:(unsigned)stringLength characterPosition: (int)pos
598 {
599     // Return the width of the first complete character at the specified position.  Even though
600     // the first 'character' may contain more than one unicode characters this method will
601     // work correctly.
602     return [self floatWidthForCharacters:characters stringLength:stringLength fromCharacterPosition:pos numberOfCharacters:1 withPadding: 0 applyRounding: YES attemptFontSubstitution: YES widths: nil];
603 }
604
605
606 - (float)floatWidthForCharacters:(const UniChar *)characters stringLength:(unsigned)stringLength fromCharacterPosition: (int)pos numberOfCharacters: (int)len
607 {
608     return [self floatWidthForCharacters:characters stringLength:stringLength fromCharacterPosition:pos numberOfCharacters:len withPadding: 0 applyRounding: YES attemptFontSubstitution: YES widths: nil];
609 }
610
611
612 - (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
613 {
614     return [self _floatWidthForCharacters:characters stringLength:stringLength fromCharacterPosition:pos numberOfCharacters:len withPadding: 0 applyRounding: YES attemptFontSubstitution: YES widths: widthBuffer fonts: nil  glyphs: nil numGlyphs: nil];
615 }
616
617 #ifdef DEBUG_COMBINING
618 static const char *directionNames[] = {
619         "DirectionL",   // Left Letter 
620         "DirectionR",   // Right Letter
621         "DirectionEN",  // European Number
622         "DirectionES",  // European Separator
623         "DirectionET",  // European Terminator (post/prefix e.g. $ and %)
624         "DirectionAN",  // Arabic Number
625         "DirectionCS",  // Common Separator 
626         "DirectionB",   // Paragraph Separator (aka as PS)
627         "DirectionS",   // Segment Separator (TAB)
628         "DirectionWS",  // White space
629         "DirectionON",  // Other Neutral
630
631         // types for explicit controls
632         "DirectionLRE", 
633         "DirectionLRO", 
634
635         "DirectionAL",  // Arabic Letter (Right-to-left)
636
637         "DirectionRLE", 
638         "DirectionRLO", 
639         "DirectionPDF", 
640
641         "DirectionNSM",         // Non-spacing Mark
642         "DirectionBN"   // Boundary neutral (type of RLE etc after explicit levels)
643 };
644
645 static const char *joiningNames[] = {
646         "JoiningOther",
647         "JoiningDual",
648         "JoiningRight",
649         "JoiningCausing"
650 };
651 #endif
652
653 - (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
654 {
655     float totalWidth = 0;
656     unsigned int i, clusterLength;
657     NSFont *substituteFont = nil;
658     ATSGlyphRef glyphID;
659     float lastWidth = 0;
660     uint numSpaces = 0;
661     int padPerSpace = 0;
662     int numGlyphs = 0;
663
664     if (len <= 0){
665         if (_numGlyphs)
666             *_numGlyphs = 0;
667         return 0;
668     }
669         
670     // If the padding is non-zero, count the number of spaces in the string
671     // and divide that by the padding for per space addition.
672     if (padding > 0){
673         for (i = pos; i < (uint)pos+len; i++){
674             if (characters[i] == NON_BREAKING_SPACE || characters[i] == SPACE)
675                 numSpaces++;
676         }
677         padPerSpace = CEIL_TO_INT ((((float)padding) / ((float)numSpaces)));
678     }
679
680     //printf("width: font %s, size %.1f, text \"%s\"\n", [[font fontName] cString], [font pointSize], [[NSString stringWithCharacters:characters length:length] UTF8String]);
681     for (i = 0; i < stringLength; i++) {
682         UniChar c = characters[i];
683         
684         // Skip control characters.
685         if (IS_CONTROL_CHARACTER(c)) {
686             continue;
687         }
688         
689         if (c == NON_BREAKING_SPACE) {
690             c = SPACE;
691         }
692         
693         // Drop out early if we've measured to the end of the requested
694         // fragment.
695         if ((int)i - pos >= len) {
696             // Check if next character is a space. If so, we have to apply rounding.
697             if (c == SPACE && applyRounding) {
698                 float delta = CEIL_TO_INT(totalWidth) - totalWidth;
699                 totalWidth += delta;
700                 if (widthBuffer)
701                     widthBuffer[numGlyphs - 1] += delta;
702             }
703             break;
704         }
705
706         glyphID = glyphForCharacter(characterToGlyphMap, c);
707         if (glyphID == nonGlyphID) {
708             glyphID = [self extendCharacterToGlyphMapToInclude: c];
709         }
710
711 #ifdef DEBUG_DIACRITICAL
712         if (IsNonBaseChar(c)){
713             printf ("NonBaseCharacter 0x%04x, joining attribute %d(%s), combining class %d, direction %d, glyph %d, width %f\n", c, WebCoreUnicodeJoiningFunction(c), joiningNames(WebCoreUnicodeJoiningFunction(c)), WebCoreUnicodeCombiningClassFunction(c), WebCoreUnicodeDirectionFunction(c), glyphID, widthForGlyph(self, glyphToWidthMap, glyphID));
714         }
715 #endif
716         
717         // Try to find a substitute font if this font didn't have a glyph for a character in the
718         // string.  If one isn't found we end up drawing and measuring the 0 glyph, usually a box.
719         if (glyphID == 0 && attemptSubstitution) {
720             clusterLength = findLengthOfCharacterCluster (&characters[i], stringLength - i);
721             substituteFont = [self substituteFontForCharacters: &characters[i] length: clusterLength];
722             if (substituteFont) {
723                 int cNumGlyphs;
724                 lastWidth = [[[WebTextRendererFactory sharedFactory] rendererWithFont: substituteFont] 
725                                 _floatWidthForCharacters: &characters[i] 
726                                 stringLength: clusterLength 
727                                 fromCharacterPosition: 0 numberOfCharacters: clusterLength 
728                                 withPadding: 0 applyRounding: NO attemptFontSubstitution: NO 
729                                 widths: ((widthBuffer != 0 ) ? (&widthBuffer[numGlyphs]) : nil)
730                                 fonts: nil
731                                 glyphs: ((glyphBuffer != 0 ) ? (&glyphBuffer[numGlyphs]) : nil)
732                                 numGlyphs: &cNumGlyphs];
733                 if (fontBuffer){
734                     int j;
735                     for (j = 0; j < cNumGlyphs; j++)
736                         fontBuffer[numGlyphs+j] = substituteFont;
737                 }
738                 numGlyphs += cNumGlyphs;
739             }
740         }
741         
742         // If we have a valid glyph OR if we couldn't find a substitute font
743         // measure the glyph.
744         if (glyphID > 0 || ((glyphID == 0) && substituteFont == nil)) {
745             if (glyphID == spaceGlyph && applyRounding) {
746                 if (lastWidth > 0){
747                     float delta = CEIL_TO_INT(totalWidth) - totalWidth;
748                     totalWidth += delta;
749                     if (widthBuffer)
750                         widthBuffer[numGlyphs - 1] += delta;
751                 }   
752                 lastWidth = ROUND_TO_INT(widthForGlyph(self, glyphToWidthMap, glyphID));
753                 if (padding > 0){
754                     // Only use left over padding if note evenly divisible by 
755                     // number of spaces.
756                     if (padding < padPerSpace){
757                         lastWidth += padding;
758                         padding = 0;
759                     }
760                     else {
761                         lastWidth += padPerSpace;
762                         padding -= padPerSpace;
763                     }
764                 }
765             }
766             else
767                 lastWidth = widthForGlyph(self, glyphToWidthMap, glyphID);
768             
769             if (fontBuffer)
770                 fontBuffer[numGlyphs] = font;
771             if (glyphBuffer)
772                 glyphBuffer[numGlyphs] = glyphID;
773             if (widthBuffer)
774                 widthBuffer[numGlyphs] = lastWidth;
775             numGlyphs++;
776         }
777 #ifdef DEBUG_COMBINING        
778         printf ("Character 0x%04x, joining attribute %d(%s), combining class %d, direction %d(%s)\n", c, WebCoreUnicodeJoiningFunction(c), joiningNames[WebCoreUnicodeJoiningFunction(c)], WebCoreUnicodeCombiningClassFunction(c), WebCoreUnicodeDirectionFunction(c), directionNames[WebCoreUnicodeDirectionFunction(c)]);
779 #endif
780         
781         totalWidth += lastWidth;       
782     }
783
784     // Don't ever apply rounding for single character.  Single character measurement
785     // intra word needs to be non-ceiled.
786     if ((len > 1 || stringLength == 1) && applyRounding){
787         float delta = CEIL_TO_INT(totalWidth) - totalWidth;
788         totalWidth += delta;
789         if (widthBuffer)
790             widthBuffer[numGlyphs-1] += delta;
791     }
792
793     if (_numGlyphs)
794         *_numGlyphs = numGlyphs;
795     
796     return totalWidth;
797 }
798
799 - (ATSGlyphRef)extendCharacterToGlyphMapToInclude:(UniChar) c
800 {
801     GlyphMap *map = (GlyphMap *)calloc (1, sizeof(GlyphMap));
802     ATSLayoutRecord *glyphRecord;
803     ATSGlyphVector glyphVector;
804     UniChar end, start;
805     unsigned int blockSize;
806     ATSGlyphRef glyphID;
807     
808     if (characterToGlyphMap == 0)
809         blockSize = INITIAL_BLOCK_SIZE;
810     else
811         blockSize = INCREMENTAL_BLOCK_SIZE;
812     start = (c / blockSize) * blockSize;
813     end = start + (blockSize - 1);
814         
815     LOG(FontCache, "%@ (0x%04x) adding glyphs for 0x%04x to 0x%04x", font, c, start, end);
816
817     map->startRange = start;
818     map->endRange = end;
819     
820     unsigned int i, count = end - start + 1;
821     short unsigned int buffer[INCREMENTAL_BLOCK_SIZE+2];
822     
823     for (i = 0; i < count; i++){
824         //if (IsNonBaseChar(i+start))
825         //    buffer[i] = 0;
826         //else
827             buffer[i] = i+start;
828     }
829
830     ATSInitializeGlyphVector(count, 0, &glyphVector);
831     [self convertCharacters: &buffer[0] length: count toGlyphs: &glyphVector skipControlCharacters: NO];
832     if (glyphVector.numGlyphs != count)
833         [NSException raise:NSInternalInconsistencyException format:@"Optimization assumption violation:  count and glyphID count not equal - for %@ %f", self, [font displayName], [font pointSize]];
834             
835     map->glyphs = (ATSGlyphRef *)malloc (count * sizeof(ATSGlyphRef));
836     glyphRecord = (ATSLayoutRecord *)glyphVector.firstRecord;
837     for (i = 0; i < count; i++) {
838         map->glyphs[i] = glyphRecord->glyphID;
839         glyphRecord = (ATSLayoutRecord *)((char *)glyphRecord + glyphVector.recordSize);
840     }
841     ATSClearGlyphVector(&glyphVector);
842     
843     if (characterToGlyphMap == 0)
844         characterToGlyphMap = map;
845     else {
846         GlyphMap *lastMap = characterToGlyphMap;
847         while (lastMap->next != 0)
848             lastMap = lastMap->next;
849         lastMap->next = map;
850     }
851
852     if (spaceGlyph == nonGlyphID)
853         spaceGlyph = glyphForCharacter (characterToGlyphMap, SPACE);
854
855     glyphID = map->glyphs[c - start];
856     
857     // Special case for characters 007F-00A0.
858     if (glyphID == 0 && c >= 0x7F && c <= 0xA0){
859         glyphID = [font _defaultGlyphForChar: c];
860         map->glyphs[c - start] = glyphID;
861     }
862
863     return glyphID;
864 }
865
866
867 - (WidthMap *)extendGlyphToWidthMapToInclude:(ATSGlyphRef)glyphID
868 {
869     WidthMap *map = (WidthMap *)calloc (1, sizeof(WidthMap));
870     unsigned int end;
871     ATSGlyphRef start;
872     unsigned int blockSize;
873     unsigned int i, count;
874     
875     if (glyphToWidthMap == 0){
876         if ([font numberOfGlyphs] < INITIAL_BLOCK_SIZE)
877             blockSize = [font numberOfGlyphs];
878          else
879             blockSize = INITIAL_BLOCK_SIZE;
880     }
881     else
882         blockSize = INCREMENTAL_BLOCK_SIZE;
883     start = (glyphID / blockSize) * blockSize;
884     end = ((unsigned int)start) + blockSize; 
885     if (end > 0xffff)
886         end = 0xffff;
887
888     LOG(FontCache, "%@ (0x%04x) adding widths for range 0x%04x to 0x%04x", font, glyphID, start, end);
889
890     map->startRange = start;
891     map->endRange = end;
892     count = end - start + 1;
893
894     map->widths = (WebGlyphWidth *)malloc (count * sizeof(WebGlyphWidth));
895
896     for (i = 0; i < count; i++){
897         map->widths[i] = UNINITIALIZED_GLYPH_WIDTH;
898     }
899
900     if (glyphToWidthMap == 0)
901         glyphToWidthMap = map;
902     else {
903         WidthMap *lastMap = glyphToWidthMap;
904         while (lastMap->next != 0)
905             lastMap = lastMap->next;
906         lastMap->next = map;
907     }
908
909 #ifdef _TIMING
910     LOG(FontCache, "%@ total time to advances lookup %f seconds", font, totalCGGetAdvancesTime);
911 #endif
912     return map;
913 }
914
915 @end