diff --git a/ChangeLog b/ChangeLog index 46314f0..9534253 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,13 @@ +2011-04-14 Eric Wasylishen + + * Source/cairo/CairoFontEnumerator.m: + * Headers/cairo/CairoFontEnumerator.h: Override + -matchingFontDescriptorsFor: to delegate the matching to Fontconfig + + This uses two utility classes which map Fontconfig patterns + to and from NSFontDescriptor attributes dictionaries, preserving + as much detail as possible. + 2011-04-14 Adam Fedor * Version: Bump version diff --git a/Headers/cairo/CairoFontEnumerator.h b/Headers/cairo/CairoFontEnumerator.h index a05478f..8962f97 100644 --- a/Headers/cairo/CairoFontEnumerator.h +++ b/Headers/cairo/CairoFontEnumerator.h @@ -37,6 +37,22 @@ + (CairoFaceInfo *) fontWithName: (NSString *)name; @end +@interface FontconfigPatternGenerator : NSObject +{ + NSDictionary *_attributes; + FcPattern *_pat; +} +- (FcPattern *)createPatternWithAttributes: (NSDictionary *)attributes; +@end + +@interface FontconfigPatternParser : NSObject +{ + NSMutableDictionary *_attributes; + FcPattern *_pat; +} +- (NSDictionary*)attributesFromPattern: (FcPattern *)pat; +@end + @interface FontconfigCharacterSet : NSCharacterSet { FcCharSet *_charset; diff --git a/Source/cairo/CairoFontEnumerator.m b/Source/cairo/CairoFontEnumerator.m index ecf1362..597aaf8 100644 --- a/Source/cairo/CairoFontEnumerator.m +++ b/Source/cairo/CairoFontEnumerator.m @@ -41,6 +41,7 @@ #include #include #include +#include #include "gsc/GSGState.h" #include "cairo/CairoFontEnumerator.h" @@ -235,8 +236,556 @@ static NSArray *faFromFc(FcPattern *pat) return @"Courier"; } +/** + * Overrides the implementation in GSFontInfo, and delegates the + * matching to Fontconfig. + */ +- (NSArray *) matchingFontDescriptorsFor: (NSDictionary *)attributes +{ + NSMutableArray *descriptors; + FcResult result; + FcPattern *matchedpat, *pat; + FontconfigPatternGenerator *generator; + + descriptors = [NSMutableArray array]; + + generator = [[FontconfigPatternGenerator alloc] init]; + pat = [generator createPatternWithAttributes: attributes]; + DESTROY(generator); + + FcConfigSubstitute(NULL, pat, FcMatchPattern); + FcDefaultSubstitute(pat); + result = FcResultMatch; + matchedpat = FcFontMatch(NULL, pat, &result); + if (result != FcResultMatch) + { + NSLog(@"Warning, FcFontMatch failed with code: %d", result); + } + else + { + result = FcResultMatch; + FcFontSet *fontSet = FcFontSort(NULL, matchedpat, FcFalse, NULL, &result); + if (result == FcResultMatch) + { + int i; + for (i=0; infont; i++) + { + FontconfigPatternParser *parser = [[FontconfigPatternParser alloc] init]; + // FIXME: do we need to match this pattern? + FcPattern *matchingpat = fontSet->fonts[i]; + NSDictionary *attribs = [parser attributesFromPattern: matchingpat]; + [parser release]; + + [descriptors addObject: [NSFontDescriptor fontDescriptorWithFontAttributes: attribs]]; + } + } + else + { + NSLog(@"ERROR! FcFontSort failed"); + } + + FcFontSetDestroy(fontSet); + FcPatternDestroy(matchedpat); + } + + FcPatternDestroy(pat); + return descriptors; +} + @end + + +@implementation FontconfigPatternGenerator + +- (void)addName: (NSString*)name +{ + // FIXME: Fontconfig ignores PostScript names of fonts; we need + // https://bugs.freedesktop.org/show_bug.cgi?id=18095 fixed. + + // This is a heuristic to try to 'parse' a PostScript font name, + // however, since they are just unique identifiers for fonts and + // don't need to follow any naming convention, this may fail + + NSRange dash = [name rangeOfString: @"-"]; + if (dash.location == NSNotFound) + { + FcPatternAddString(_pat, FC_FAMILY, (const FcChar8 *)[name UTF8String]); + } + else + { + NSString *weightAndSlant = [name substringFromIndex: dash.location + 1]; + NSString *family = [name substringToIndex: dash.location]; + + FcPatternAddString(_pat, FC_FAMILY, (const FcChar8 *)[family UTF8String]); + + if (NSNotFound != [weightAndSlant rangeOfString: @"Light"].location) + { + FcPatternAddInteger(_pat, FC_WEIGHT, FC_WEIGHT_LIGHT); + } + else if (NSNotFound != [weightAndSlant rangeOfString: @"Medium"].location) + { + FcPatternAddInteger(_pat, FC_WEIGHT, FC_WEIGHT_MEDIUM); + } + else if (NSNotFound != [weightAndSlant rangeOfString: @"Demibold"].location) + { + FcPatternAddInteger(_pat, FC_WEIGHT, FC_WEIGHT_DEMIBOLD); + } + else if (NSNotFound != [weightAndSlant rangeOfString: @"Bold"].location) + { + FcPatternAddInteger(_pat, FC_WEIGHT, FC_WEIGHT_BOLD); + } + else if (NSNotFound != [weightAndSlant rangeOfString: @"Black"].location) + { + FcPatternAddInteger(_pat, FC_WEIGHT, FC_WEIGHT_BLACK); + } + + if (NSNotFound != [weightAndSlant rangeOfString: @"Italic"].location) + { + FcPatternAddInteger(_pat, FC_SLANT, FC_SLANT_ITALIC); + } + else if (NSNotFound != [weightAndSlant rangeOfString: @"Oblique"].location) + { + FcPatternAddInteger(_pat, FC_SLANT, FC_SLANT_OBLIQUE); + } + + if (NSNotFound != [weightAndSlant rangeOfString: @"Condensed"].location) + { + FcPatternAddInteger(_pat, FC_WIDTH, FC_WIDTH_CONDENSED); + } + else if (NSNotFound != [weightAndSlant rangeOfString: @"Expanded"].location) + { + FcPatternAddInteger(_pat, FC_WIDTH, FC_WIDTH_EXPANDED); + } + } +} + +- (void)addVisibleName: (NSString*)name +{ + FcPatternAddString(_pat, FC_FULLNAME, (const FcChar8 *)[name UTF8String]); +} + +- (void)addFamilyName: (NSString*)name +{ + FcPatternAddString(_pat, FC_FAMILY, (const FcChar8 *)[name UTF8String]); +} + +- (void)addStyleName: (NSString*)style +{ + FcPatternAddString(_pat, FC_STYLE, (const FcChar8 *)[style UTF8String]); +} + +- (void)addTraits: (NSDictionary*)traits +{ + if ([traits objectForKey: NSFontSymbolicTrait]) + { + NSFontSymbolicTraits symTraits = [[traits objectForKey: NSFontSymbolicTrait] intValue]; + + if (symTraits & NSFontItalicTrait) + { + // NOTE: May be overridden by NSFontSlantTrait + FcPatternAddInteger(_pat, FC_SLANT, FC_SLANT_ITALIC); + } + if (symTraits & NSFontBoldTrait) + { + // NOTE: May be overridden by NSFontWeightTrait + FcPatternAddInteger(_pat, FC_WEIGHT, FC_WEIGHT_BOLD); + } + if (symTraits & NSFontExpandedTrait) + { + // NOTE: May be overridden by NSFontWidthTrait + FcPatternAddInteger(_pat, FC_WIDTH, FC_WIDTH_EXPANDED); + } + if (symTraits & NSFontCondensedTrait) + { + // NOTE: May be overridden by NSFontWidthTrait + FcPatternAddInteger(_pat, FC_WIDTH, FC_WIDTH_CONDENSED); + } + if (symTraits & NSFontMonoSpaceTrait) + { + // If you run "fc-match :spacing=100", you get "DejaVu Sans" even though you would + // expect to get "DejaVu Sans Mono". So, we also add "monospace" as a weak family + // name to fix the problem. + FcPatternAddInteger(_pat, FC_SPACING, FC_MONO); + + FcValue value; + value.type = FcTypeString; + value.u.s = (FcChar8*)"monospace"; + FcPatternAddWeak(_pat, FC_FAMILY, value, FcTrue); + } + if (symTraits & NSFontVerticalTrait) + { + // FIXME: What is this supposed to mean? + } + if (symTraits & NSFontUIOptimizedTrait) + { + // NOTE: Fontconfig can't express this + } + + NSFontFamilyClass class = symTraits & NSFontFamilyClassMask; + char *addWeakFamilyName = NULL; + switch (class) + { + default: + case NSFontUnknownClass: + case NSFontOrnamentalsClass: + case NSFontScriptsClass: + case NSFontSymbolicClass: + // FIXME: Is there some way to convey these to Fontconfig? + break; + case NSFontOldStyleSerifsClass: + case NSFontTransitionalSerifsClass: + case NSFontModernSerifsClass: + case NSFontClarendonSerifsClass: + case NSFontSlabSerifsClass: + case NSFontFreeformSerifsClass: + addWeakFamilyName = "serif"; + break; + case NSFontSansSerifClass: + addWeakFamilyName = "sans"; + break; + } + if (addWeakFamilyName) + { + FcValue value; + value.type = FcTypeString; + value.u.s = (const FcChar8 *)addWeakFamilyName; + FcPatternAddWeak(_pat, FC_FAMILY, value, FcTrue); + } + } + + if ([traits objectForKey: NSFontWeightTrait]) + { + /** + * Scale: -1 is thinnest, 0 is normal, 1 is heaviest + */ + double weight = [[traits objectForKey: NSFontWeightTrait] doubleValue]; + weight = MAX(-1, MIN(1, weight)); + int fcWeight; + if (weight <= 0) + { + fcWeight = FC_WEIGHT_THIN + ((weight + 1.0) * (FC_WEIGHT_NORMAL - FC_WEIGHT_THIN)); + } + else + { + fcWeight = FC_WEIGHT_NORMAL + (weight * (FC_WEIGHT_ULTRABLACK - FC_WEIGHT_NORMAL)); + } + FcPatternAddInteger(_pat, FC_WEIGHT, fcWeight); + } + + if ([traits objectForKey: NSFontWidthTrait]) + { + /** + * Scale: -1 is most condensed, 0 is normal, 1 is most spread apart + */ + double width = [[traits objectForKey: NSFontWidthTrait] doubleValue]; + width = MAX(-1, MIN(1, width)); + int fcWidth; + if (width <= 0) + { + fcWidth = FC_WIDTH_ULTRACONDENSED + ((width + 1.0) * (FC_WIDTH_NORMAL - FC_WIDTH_ULTRACONDENSED)); + } + else + { + fcWidth = FC_WIDTH_NORMAL + (width * (FC_WIDTH_ULTRAEXPANDED - FC_WIDTH_NORMAL)); + } + FcPatternAddInteger(_pat, FC_WIDTH, fcWidth); + } + + if ([traits objectForKey: NSFontSlantTrait]) + { + /** + * Scale: -1 is 30 degree counterclockwise slant, 0 is no slant, 1 + * is 30 degree clockwise slant + */ + double slant = [[traits objectForKey: NSFontSlantTrait] doubleValue]; + + // NOTE: Fontconfig can't express this as a scale + if (slant > 0) + { + FcPatternAddInteger(_pat, FC_SLANT, FC_SLANT_ITALIC); + } + else + { + FcPatternAddInteger(_pat, FC_SLANT, FC_SLANT_ROMAN); + } + } +} + +- (void)addSize: (NSNumber*)size +{ + FcPatternAddDouble(_pat, FC_SIZE, [size doubleValue]); +} + +- (void)addCharacterSet: (NSCharacterSet*)characterSet +{ + if ([characterSet isKindOfClass: [FontconfigCharacterSet class]]) + { + // Fast case + FcPatternAddCharSet(_pat, FC_CHARSET, [(FontconfigCharacterSet*)characterSet fontconfigCharSet]); + } + else + { + // Slow case + FcCharSet *fcSet = FcCharSetCreate(); + uint32_t plane; + for (plane=0; plane<=16; plane++) + { + if ([characterSet hasMemberInPlane: plane]) + { + uint32_t codePoint; + for (codePoint = plane<<16; codePoint <= 0xffff + (plane<<16); codePoint++) + { + if ([characterSet longCharacterIsMember: codePoint]) + { + FcCharSetAddChar(fcSet, codePoint); + } + } + } + } + + FcPatternAddCharSet(_pat, FC_CHARSET, fcSet); + FcCharSetDestroy(fcSet); + } +} + +- (void)handleKey: (NSString*)key selector: (SEL)selector valueClass: (Class)valueClass +{ + id value = [_attributes objectForKey: key]; + if (value) + { + if ([value isKindOfClass: valueClass]) + { + if ([self respondsToSelector: selector]) + { + [self performSelector: selector withObject: value]; + } + } + else + { + NSLog(@"NSFontDescriptor: Ignoring invalid value %@ for attribute %@", value, key); + } + } +} + +- (void)addAttributes +{ + [self handleKey: NSFontNameAttribute selector: @selector(addName:) valueClass: [NSString class]]; + [self handleKey: NSFontVisibleNameAttribute selector: @selector(addVisibleName:) valueClass: [NSString class]]; + [self handleKey: NSFontFamilyAttribute selector: @selector(addFamilyName:) valueClass: [NSString class]]; + [self handleKey: NSFontFaceAttribute selector: @selector(addStyleName:) valueClass: [NSString class]]; + [self handleKey: NSFontTraitsAttribute selector: @selector(addTraits:) valueClass: [NSDictionary class]]; + [self handleKey: NSFontSizeAttribute selector: @selector(addSize:) valueClass: [NSNumber class]]; + [self handleKey: NSFontCharacterSetAttribute selector: @selector(addCharacterSet:) valueClass: [NSCharacterSet class]]; +} + +- (FcPattern *)createPatternWithAttributes: (NSDictionary *)attributes +{ + _attributes = attributes; + _pat = FcPatternCreate(); + [self addAttributes]; + + return _pat; +} + +@end + + +@implementation FontconfigPatternParser + +- (NSString*)readFontconfigString: (const char *)key fromPattern: (FcPattern*)pat +{ + unsigned char *string = NULL; + if (FcResultMatch == FcPatternGetString(pat, key, 0, &string)) + { + if (string) + { + return [NSString stringWithUTF8String: (const char *)string]; + } + } + return nil; +} + +- (NSNumber*)readFontconfigInteger: (const char *)key fromPattern: (FcPattern*)pat +{ + int value; + if (FcResultMatch == FcPatternGetInteger(pat, key, 0, &value)) + { + return [NSNumber numberWithInt: value]; + } + return nil; +} + +- (NSNumber*)readFontconfigDouble: (const char *)key fromPattern: (FcPattern*)pat +{ + double value; + if (FcResultMatch == FcPatternGetDouble(pat, key, 0, &value)) + { + return [NSNumber numberWithDouble: value]; + } + return nil; +} + + + +- (NSString*)readNameFromPattern: (FcPattern*)pat +{ + // FIXME: Hack which generates a PostScript-style name from the + // family name and style + + NSString *family = [self readFontconfigString: FC_FAMILY fromPattern: pat]; + NSString *style = [self readFontconfigString: FC_STYLE fromPattern: pat]; + if (style) + { + return [NSString stringWithFormat: @"%@-%@", family, style]; + } + else + { + return family; + } +} +- (NSString*)readVisibleNameFromPattern: (FcPattern*)pat +{ + // FIXME: try to get the localized one + return [self readFontconfigString: FC_FULLNAME fromPattern: pat]; +} +- (NSString*)readFamilyNameFromPattern: (FcPattern*)pat +{ + // FIXME: try to get the localized one + return [self readFontconfigString: FC_FAMILY fromPattern: pat]; +} +- (NSString*)readStyleNameFromPattern: (FcPattern*)pat +{ + // FIXME: try to get the localized one + return [self readFontconfigString: FC_STYLE fromPattern: pat]; +} +- (NSDictionary*)readTraitsFromPattern: (FcPattern*)pat +{ + NSMutableDictionary *traits = [NSMutableDictionary dictionary]; + + NSFontSymbolicTraits symTraits = 0; + + int value; + if (FcResultMatch == FcPatternGetInteger(pat, FC_SLANT, 0, &value)) + { + if (value == FC_SLANT_ITALIC) + { + symTraits |= NSFontItalicTrait; + } + } + if (FcResultMatch == FcPatternGetInteger(pat, FC_WEIGHT, 0, &value)) + { + if (value >= FC_WEIGHT_BOLD) + { + symTraits |= NSFontBoldTrait; + } + + double weight; + if (value <= FC_WEIGHT_NORMAL) + { + weight = ((value - FC_WEIGHT_THIN) / (double)(FC_WEIGHT_NORMAL - FC_WEIGHT_THIN)) - 1.0; + } + else + { + weight = (value - FC_WEIGHT_NORMAL) / (double)(FC_WEIGHT_ULTRABLACK - FC_WEIGHT_NORMAL); + } + + [traits setObject: [NSNumber numberWithDouble: weight] + forKey: NSFontWeightTrait]; + } + if (FcResultMatch == FcPatternGetInteger(pat, FC_WIDTH, 0, &value)) + { + if (value >= FC_WIDTH_EXPANDED) + { + symTraits |= NSFontExpandedTrait; + } + if (value <= FC_WIDTH_CONDENSED) + { + symTraits |= NSFontCondensedTrait; + } + + double width; + if (value <= FC_WIDTH_NORMAL) + { + width = ((value - FC_WIDTH_ULTRACONDENSED) / (double)(FC_WIDTH_NORMAL - FC_WIDTH_ULTRACONDENSED)) - 1.0; + } + else + { + width = (value - FC_WIDTH_NORMAL) / (double)(FC_WIDTH_ULTRAEXPANDED - FC_WIDTH_NORMAL); + } + + [traits setObject: [NSNumber numberWithDouble: width] + forKey: NSFontWidthTrait]; + } + if (FcResultMatch == FcPatternGetInteger(pat, FC_SPACING, 0, &value)) + { + if (value == FC_MONO || value == FC_CHARCELL) + { + symTraits |= NSFontMonoSpaceTrait; + } + } + + if (symTraits != 0) + { + [traits setObject: [NSNumber numberWithUnsignedInt: symTraits] + forKey: NSFontSymbolicTrait]; + } + + return traits; +} + +- (NSNumber*)readSizeFromPattern: (FcPattern*)pat +{ + return [self readFontconfigDouble: FC_SIZE fromPattern: pat]; +} + +- (NSCharacterSet*)readCharacterSetFromPattern: (FcPattern*)pat +{ + FcCharSet *value; + if (FcResultMatch == FcPatternGetCharSet(pat, FC_CHARSET, 0, &value)) + { + return [[[FontconfigCharacterSet alloc] initWithFontconfigCharSet: value] autorelease]; + } + return nil; +} + +- (void)handleKey: (NSString*)key selector: (SEL)sel +{ + if ([self respondsToSelector: sel]) + { + id (*readMethod)(id, SEL, FcPattern*) = (id (*)(id, SEL, FcPattern*))[self methodForSelector: sel]; + id result = readMethod(self, sel, _pat); + if (result != nil) + { + [_attributes setObject: result + forKey: key]; + } + } +} + +- (void)parseAttributes +{ + [self handleKey: NSFontNameAttribute selector: @selector(readNameFromPattern:)]; + [self handleKey: NSFontVisibleNameAttribute selector: @selector(readVisibleNameFromPattern:)]; + [self handleKey: NSFontFamilyAttribute selector: @selector(readFamilyNameFromPattern:)]; + [self handleKey: NSFontFaceAttribute selector: @selector(readStyleNameFromPattern:)]; + [self handleKey: NSFontTraitsAttribute selector: @selector(readTraitsFromPattern:)]; + [self handleKey: NSFontSizeAttribute selector: @selector(readSizeFromPattern:)]; + [self handleKey: NSFontCharacterSetAttribute selector: @selector(readCharacterSetFromPattern:)]; +} + +- (NSDictionary*)attributesFromPattern: (FcPattern *)pat +{ + _attributes = [NSMutableDictionary dictionary]; + _pat = pat; + [self parseAttributes]; + return _attributes; +} + +@end + + + @implementation FontconfigCharacterSet - (id)initWithFontconfigCharSet: (FcCharSet*)charset @@ -281,3 +830,4 @@ static NSArray *faFromFc(FcPattern *pat) //} @end +