back/cairo: use Fontconfig for descriptor matching

git-svn-id: svn+ssh://svn.gna.org/svn/gnustep/libs/back/trunk@32864 72102866-910b-0410-8b05-ffd578937521
This commit is contained in:
ericwa 2011-04-14 23:14:38 +00:00
parent a86d338afc
commit b1b61ab926
3 changed files with 576 additions and 0 deletions

View file

@ -1,3 +1,13 @@
2011-04-14 Eric Wasylishen <ewasylishen@gmail.com>
* 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 <fedor@gnu.org>
* Version: Bump version

View file

@ -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;

View file

@ -41,6 +41,7 @@
#include <GNUstepGUI/GSFontInfo.h>
#include <AppKit/NSAffineTransform.h>
#include <AppKit/NSBezierPath.h>
#include <AppKit/NSFontDescriptor.h>
#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; i<fontSet->nfont; 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