libs-back/Source/fontconfig/FCFontEnumerator.m

1105 lines
30 KiB
Objective-C

/*
FCFontEnumerator.m
Copyright (C) 2003 Free Software Foundation, Inc.
August 31, 2003
Written by Banlu Kemiyatorn <object at gmail dot com>
Base on original code of Alex Malmberg
Rewrite: Fred Kiefer <fredkiefer@gmx.de>
Date: Jan 2006
This file is part of GNUstep.
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; see the file COPYING.LIB.
If not, see <http://www.gnu.org/licenses/> or write to the
Free Software Foundation, 51 Franklin Street, Fifth Floor,
Boston, MA 02110-1301, USA.
*/
#include <Foundation/NSObject.h>
#include <Foundation/NSArray.h>
#include <Foundation/NSSet.h>
#include <Foundation/NSDictionary.h>
#include <Foundation/NSValue.h>
#include <Foundation/NSPathUtilities.h>
#include <Foundation/NSFileManager.h>
#include <Foundation/NSUserDefaults.h>
#include <Foundation/NSBundle.h>
#include <Foundation/NSDebug.h>
#include <GNUstepGUI/GSFontInfo.h>
#include <AppKit/NSAffineTransform.h>
#include <AppKit/NSBezierPath.h>
#include <AppKit/NSFontDescriptor.h>
#include "gsc/GSGState.h"
#include "fontconfig/FCFontEnumerator.h"
#include "fontconfig/FCFontInfo.h"
// Old versions of fontconfig don't have FC_WEIGHT_ULTRABLACK defined.
// Use the maximal value instead.
#ifndef FC_WEIGHT_ULTRABLACK
#define FC_WEIGHT_ULTRABLACK FC_WEIGHT_BLACK
#endif
static float
convertWeight (int fcWeight, int bottomValue, int topValue)
{
/*
This is the distance between topValue and bottomValue expressed as a
fraction between zero and one. We do this to express the range of
fontconfig font weights in a useful manner.
*/
if (fcWeight <= bottomValue)
{
return 0.0;
}
else if (fcWeight >= topValue)
{
return 1.0;
}
else
{
return (float) (fcWeight - bottomValue) * (1.0f / (topValue - bottomValue));
}
}
static NSComparisonResult
sortFontFacesArray(id fontArr1, id fontArr2, void *context)
{
/*
Order of array:
0: Font name
1: Font style
2: Font weight
3: Font traits
*/
NSString *style1 = [fontArr1 objectAtIndex: 1];
NSString *style2 = [fontArr2 objectAtIndex: 1];
float weight1 = [[fontArr1 objectAtIndex: 2] floatValue];
float weight2 = [[fontArr2 objectAtIndex: 2] floatValue];
unsigned int traits1 = [[fontArr1 objectAtIndex: 3] unsignedIntValue];
unsigned int traits2 = [[fontArr2 objectAtIndex: 3] unsignedIntValue];
// order first by weight
if (weight1 < weight2)
return NSOrderedAscending;
if (weight1 > weight2)
return NSOrderedDescending;
// Italic next
if ((traits1 & NSItalicFontMask) < (traits2 & NSItalicFontMask))
return NSOrderedAscending;
if ((traits1 & NSItalicFontMask) > (traits2 & NSItalicFontMask))
return NSOrderedDescending;
// now do condensed
if ((traits1 & NSCondensedFontMask) < (traits2 & NSCondensedFontMask))
return NSOrderedAscending;
if ((traits1 & NSCondensedFontMask) > (traits2 & NSCondensedFontMask))
return NSOrderedDescending;
// ...and expanded
if ((traits1 & NSExpandedFontMask) < (traits2 & NSExpandedFontMask))
return NSOrderedAscending;
if ((traits1 & NSExpandedFontMask) > (traits2 & NSExpandedFontMask))
return NSOrderedDescending;
// Special case: "Regular" sorts before non-Regular, for many reasons.
if ([style1 isEqualToString: @"Regular"] && ![style2 isEqualToString: @"Regular"])
return NSOrderedAscending;
if ([style2 isEqualToString: @"Regular"] && ![style1 isEqualToString: @"Regular"])
return NSOrderedDescending;
if ([style1 isEqualToString: @"Normal"] && ![style2 isEqualToString: @"Normal"])
return NSOrderedAscending;
if ([style2 isEqualToString: @"Normal"] && ![style1 isEqualToString: @"Normal"])
return NSOrderedDescending;
if ([style1 isEqualToString: @"Roman"] && ![style2 isEqualToString: @"Roman"])
return NSOrderedAscending;
if ([style2 isEqualToString: @"Roman"] && ![style1 isEqualToString: @"Roman"])
return NSOrderedDescending;
// Otherwise, alphabetize
return [style1 compare: style2];
}
@implementation FCFontEnumerator
NSMutableDictionary * __allFonts;
+ (FCFaceInfo *) fontWithName: (NSString *) name
{
FCFaceInfo *face;
face = [__allFonts objectForKey: name];
if (!face)
{
NSDebugLLog(@"NSFont", @"Font not found %@", name);
}
return face;
}
+ (Class) faceInfoClass
{
[self subclassResponsibility: _cmd];
return nil;
}
// Make a GNUstep style font descriptor from a FcPattern
static NSArray *faFromFc(FcPattern *pat)
{
int weight, slant, spacing, width;
float nsweight;
unsigned int nstraits = 0;
char *fcfamily, *fcstyle;
NSString *styleStr = nil;
NSString *name = nil;
NSMutableString *family, *style;
#ifdef FC_POSTSCRIPT_NAME
char *fcname;
#endif
if (FcPatternGetString(pat, FC_STYLE, 0, (FcChar8 **)&fcstyle) == FcResultMatch)
{
styleStr = [NSString stringWithUTF8String: fcstyle];
}
// NSLog (@"family: %@, style: %s/%@", name, fcstyle, style);
if (FcPatternGetInteger(pat, FC_WEIGHT, 0, &weight) != FcResultMatch
|| FcPatternGetInteger(pat, FC_SLANT, 0, &slant) != FcResultMatch
|| FcPatternGetString(pat, FC_FAMILY, 0, (FcChar8 **)&fcfamily)
!= FcResultMatch)
return nil;
if (FcPatternGetInteger(pat, FC_SPACING, 0, &spacing) == FcResultMatch)
if (spacing==FC_MONO || spacing==FC_CHARCELL)
nstraits |= NSFixedPitchFontMask;
family = [NSMutableString stringWithUTF8String: fcfamily];
style = [NSMutableString stringWithCapacity: 100];
if (weight < FC_WEIGHT_ULTRALIGHT)
{
[style appendString: @"Thin"];
nsweight = 1 + convertWeight (weight, FC_WEIGHT_THIN, FC_WEIGHT_ULTRALIGHT);
}
else if (weight < FC_WEIGHT_LIGHT)
{
[style appendString: @"Ultralight"];
nsweight = 2 + convertWeight (weight, FC_WEIGHT_ULTRALIGHT, FC_WEIGHT_LIGHT);
}
else if (weight < FC_WEIGHT_BOOK)
{
[style appendString: @"Light"];
nsweight = 3 + convertWeight (weight, FC_WEIGHT_LIGHT, FC_WEIGHT_BOOK);
}
else if (weight < FC_WEIGHT_REGULAR)
{
[style appendString: @"Book"];
nsweight = 4 + convertWeight (weight, FC_WEIGHT_BOOK, FC_WEIGHT_REGULAR);
}
else if (weight < FC_WEIGHT_MEDIUM)
{
nsweight = 5 + convertWeight (weight, FC_WEIGHT_REGULAR, FC_WEIGHT_MEDIUM);
}
else if (weight < FC_WEIGHT_DEMIBOLD)
{
[style appendString: @"Medium"];
nsweight = 6 + convertWeight (weight, FC_WEIGHT_MEDIUM, FC_WEIGHT_DEMIBOLD);
}
else if (weight < FC_WEIGHT_BOLD)
{
[style appendString: @"Demibold"];
nsweight = 7 + convertWeight (weight, FC_WEIGHT_DEMIBOLD, FC_WEIGHT_BOLD);
}
else if (weight < FC_WEIGHT_ULTRABOLD)
{
[style appendString: @"Bold"];
nsweight = 9 + convertWeight (weight, FC_WEIGHT_BOLD, FC_WEIGHT_ULTRABOLD);
nstraits |= NSBoldFontMask;
}
else if (weight < FC_WEIGHT_BLACK)
{
[style appendString: @"Ultrabold"];
nsweight = 11 + convertWeight (weight, FC_WEIGHT_ULTRABOLD, FC_WEIGHT_BLACK);
nstraits |= NSBoldFontMask;
}
else if (weight < FC_WEIGHT_ULTRABLACK)
{
[style appendString: @"Black"];
nsweight = 12 + convertWeight (weight, FC_WEIGHT_BLACK, FC_WEIGHT_ULTRABLACK);
nstraits |= NSBoldFontMask;
}
else
{
[style appendString: @"Ultrablack"];
nsweight = 13 + convertWeight (weight, FC_WEIGHT_ULTRABLACK, FC_WEIGHT_ULTRABLACK + 20);
nstraits |= NSBoldFontMask;
}
if (FcPatternGetInteger(pat, FC_WIDTH, 0, &width) == FcResultMatch)
{
if (width < FC_WIDTH_EXTRACONDENSED)
{
[style appendString: @"Ultracondensed"];
nstraits |= NSCondensedFontMask;
}
else if (width < FC_WIDTH_CONDENSED)
{
[style appendString: @"Extracondensed"];
nstraits |= NSCondensedFontMask;
}
else if (width < FC_WIDTH_SEMICONDENSED)
{
[style appendString: @"Condensed"];
nstraits |= NSCondensedFontMask;
}
else if (width < FC_WIDTH_SEMIEXPANDED)
{
// do nothing, this is "regular"
}
else if (width < FC_WIDTH_EXPANDED)
{
[style appendString: @"Semiexpanded"];
nstraits |= NSExpandedFontMask;
}
else if (width < FC_WIDTH_EXTRAEXPANDED)
{
[style appendString: @"Expanded"];
nstraits |= NSExpandedFontMask;
}
else if (width < FC_WIDTH_ULTRAEXPANDED)
{
[style appendString: @"Extraexpanded"];
nstraits |= NSExpandedFontMask;
}
else
{
[style appendString: @"Ultraexpanded"];
nstraits |= NSExpandedFontMask;
}
}
switch (slant)
{
case FC_SLANT_ROMAN:
break;
case FC_SLANT_ITALIC:
[style appendString: @"Italic"];
nstraits |= NSItalicFontMask;
break;
case FC_SLANT_OBLIQUE:
[style appendString: @"Oblique"];
nstraits |= NSItalicFontMask;
break;
}
if (styleStr == nil)
{
styleStr = (NSString *)style;
}
if (![styleStr length])
{
styleStr = @"Regular";
}
#ifdef FC_POSTSCRIPT_NAME
if (FcPatternGetString(pat, FC_POSTSCRIPT_NAME, 0, (FcChar8 **)&fcname) == FcResultMatch)
{
name = [NSString stringWithUTF8String: fcname];
}
#endif
if (!name || ![name length]) // no psname
{
NSMutableString *tmpStr;
tmpStr = [NSMutableString stringWithCapacity: 100];
NSDebugLLog(@"NSFont", @"Warning: synthesizing PSName for '%@ %@'", family, styleStr);
[tmpStr appendString: family];
if ([styleStr length] > 0 && ![styleStr isEqualToString: @"Regular"])
{
[tmpStr appendString: @"-"];
[tmpStr appendString: styleStr];
}
name = [NSString stringWithString: tmpStr];
}
return [NSArray arrayWithObjects: name,
styleStr,
[NSNumber numberWithFloat: nsweight],
[NSNumber numberWithUnsignedInt: nstraits],
nil];
}
- (void) enumerateFontsAndFamilies
{
int i;
NSMutableDictionary *fcxft_allFontFamilies = [NSMutableDictionary new];
NSMutableDictionary *fcxft_allFonts = [NSMutableDictionary new];
NSMutableArray *fcxft_allFontNames = [NSMutableArray new];
Class faceInfoClass = [[self class] faceInfoClass];
FcPattern *pat = FcPatternCreate();
FcObjectSet *os = FcObjectSetBuild(FC_FAMILY, FC_STYLE, FC_FULLNAME,
#ifdef FC_POSTSCRIPT_NAME
FC_POSTSCRIPT_NAME,
#endif
FC_SLANT, FC_WEIGHT, FC_WIDTH,
FC_SPACING, NULL);
FcFontSet *fs = FcFontList(NULL, pat, os);
FcPatternDestroy(pat);
FcObjectSetDestroy(os);
for (i = 0; i < fs->nfont; i++)
{
char *family;
if (FcPatternGetString(fs->fonts[i], FC_FAMILY, 0, (FcChar8 **)&family)
== FcResultMatch)
{
NSArray *fontArray;
if ((fontArray = faFromFc(fs->fonts[i])))
{
NSString *name = [fontArray objectAtIndex: 0];
if (![fcxft_allFontNames containsObject: name])
{
NSString *familyString;
NSMutableArray *familyArray;
FCFaceInfo *aFont;
familyString = [NSString stringWithUTF8String: family];
familyArray = [fcxft_allFontFamilies objectForKey: familyString];
if (familyArray == nil)
{
NSDebugLLog(@"NSFont", @"Found font family %@", familyString);
familyArray = [[NSMutableArray alloc] init];
[fcxft_allFontFamilies setObject: familyArray
forKey: familyString];
RELEASE(familyArray);
}
NSDebugLLog(@"NSFont", @"fc enumerator: adding font: %@", name);
[familyArray addObject: fontArray];
[fcxft_allFontNames addObject: name];
aFont = [[faceInfoClass alloc] initWithfamilyName: familyString
weight: [[fontArray objectAtIndex: 2] floatValue]
traits: [[fontArray objectAtIndex: 3] unsignedIntValue]
pattern: fs->fonts[i]];
[fcxft_allFonts setObject: aFont forKey: name];
RELEASE(aFont);
}
}
}
}
FcFontSetDestroy (fs);
allFontNames = fcxft_allFontNames;
allFontFamilies = fcxft_allFontFamilies;
__allFonts = fcxft_allFonts;
// Sort font families
{
NSComparisonResult (*fontSort)(id, id, void *) = sortFontFacesArray;
NSEnumerator *e = [allFontFamilies keyEnumerator];
id key;
while ((key = [e nextObject]))
{
[[allFontFamilies objectForKey: key] sortUsingFunction: fontSort context: NULL];
}
}
}
- (NSString *) defaultSystemFontName
{
if ([allFontNames containsObject: @"DejaVuSans"])
{
return @"DejaVuSans";
}
if ([allFontNames containsObject: @"BitstreamVeraSans-Roman"])
{
return @"BitstreamVeraSans-Roman";
}
if ([allFontNames containsObject: @"FreeSans"])
{
return @"FreeSans";
}
if ([allFontNames containsObject: @"Tahoma"])
{
return @"Tahoma";
}
if ([allFontNames containsObject: @"ArialMT"])
{
return @"ArialMT";
}
return @"Helvetica";
}
- (NSString *) defaultBoldSystemFontName
{
if ([allFontNames containsObject: @"DejaVuSans-Bold"])
{
return @"DejaVuSans-Bold";
}
if ([allFontNames containsObject: @"BitstreamVeraSans-Bold"])
{
return @"BitstreamVeraSans-Bold";
}
if ([allFontNames containsObject: @"FreeSans-Bold"])
{
return @"FreeSans-Bold";
}
if ([allFontNames containsObject: @"Tahoma-Bold"])
{
return @"Tahoma-Bold";
}
if ([allFontNames containsObject: @"Arial-BoldMT"])
{
return @"Arial-BoldMT";
}
return @"Helvetica-Bold";
}
- (NSString *) defaultFixedPitchFontName
{
if ([allFontNames containsObject: @"DejaVuSansMono"])
{
return @"DejaVuSansMono";
}
if ([allFontNames containsObject: @"BitstreamVeraSansMono-Roman"])
{
return @"BitstreamVeraSansMono-Roman";
}
if ([allFontNames containsObject: @"FreeMono"])
{
return @"FreeMono";
}
if ([allFontNames containsObject: @"CourierNewPSMT"])
{
return @"CourierNewPSMT";
}
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
{
FcFontSet *fontSet;
result = FcResultMatch;
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
{
#ifdef FC_POSTSCRIPT_NAME
FcPatternAddString(_pat, FC_POSTSCRIPT_NAME, (const FcChar8 *)[name UTF8String]);
#else
// 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);
}
}
#endif
}
- (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)
{
FcValue value;
// 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);
value.type = FcTypeString;
value.u.s = (FcChar8*)"monospace";
FcPatternAddWeak(_pat, FC_FAMILY, value, FcTrue);
}
if (symTraits & NSFontVerticalTrait)
{
// Fontconfig can't express this (it means sideways letters)
}
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];
int fcWeight;
weight = MAX(-1, MIN(1, weight));
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];
int fcWidth;
width = MAX(-1, MIN(1, width));
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);
}
}
#define ADD_TO_PATTERN(key, handlerMethod, valueClass) \
do { \
id value = [_attributes objectForKey: key]; \
if (value) \
{ \
if ([value isKindOfClass: valueClass]) \
{ \
[self handlerMethod value]; \
} \
else \
{ \
NSLog(@"NSFontDescriptor: Ignoring invalid value %@ for attribute %@", value, key); \
} \
} \
} while (0);
- (void)addAttributes
{
ADD_TO_PATTERN(NSFontNameAttribute, addName:, [NSString class]);
ADD_TO_PATTERN(NSFontVisibleNameAttribute, addVisibleName:, [NSString class]);
ADD_TO_PATTERN(NSFontFamilyAttribute, addFamilyName:, [NSString class]);
ADD_TO_PATTERN(NSFontFaceAttribute, addStyleName:, [NSString class]);
ADD_TO_PATTERN(NSFontTraitsAttribute, addTraits:, [NSDictionary class]);
ADD_TO_PATTERN(NSFontSizeAttribute, addSize:, [NSNumber class]);
ADD_TO_PATTERN(NSFontCharacterSetAttribute, addCharacterSet:, [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
{
#ifdef FC_POSTSCRIPT_NAME
NSString *name = [self readFontconfigString: FC_POSTSCRIPT_NAME fromPattern: pat];
#endif
NSString *family = [self readFontconfigString: FC_FAMILY fromPattern: pat];
NSString *style = [self readFontconfigString: FC_STYLE fromPattern: pat];
#ifdef FC_POSTSCRIPT_NAME
if (name)
return name;
else
#endif
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_ROMAN)
{
symTraits |= NSFontItalicTrait;
}
}
if (FcResultMatch == FcPatternGetInteger(pat, FC_WEIGHT, 0, &value))
{
double weight;
if (value >= FC_WEIGHT_BOLD)
{
symTraits |= NSFontBoldTrait;
}
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))
{
double width;
if (value >= FC_WIDTH_EXPANDED)
{
symTraits |= NSFontExpandedTrait;
}
if (value <= FC_WIDTH_CONDENSED)
{
symTraits |= NSFontCondensedTrait;
}
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;
}
#define READ_FROM_PATTERN(key, readMethod) \
do { \
id result = [self readMethod _pat]; \
if (result != nil) \
{ \
[_attributes setObject: result \
forKey: key]; \
} \
} while (0);
- (void)parseAttributes
{
READ_FROM_PATTERN(NSFontNameAttribute, readNameFromPattern:);
READ_FROM_PATTERN(NSFontVisibleNameAttribute, readVisibleNameFromPattern:);
READ_FROM_PATTERN(NSFontFamilyAttribute, readFamilyNameFromPattern:);
READ_FROM_PATTERN(NSFontFaceAttribute, readStyleNameFromPattern:);
READ_FROM_PATTERN(NSFontTraitsAttribute, readTraitsFromPattern:);
READ_FROM_PATTERN(NSFontSizeAttribute, readSizeFromPattern:);
READ_FROM_PATTERN(NSFontCharacterSetAttribute, readCharacterSetFromPattern:);
}
- (NSDictionary*)attributesFromPattern: (FcPattern *)pat
{
_attributes = [NSMutableDictionary dictionary];
_pat = pat;
[self parseAttributes];
return _attributes;
}
@end
@implementation FontconfigCharacterSet
- (id)initWithFontconfigCharSet: (FcCharSet*)charset
{
if ((self = [super init]))
{
_charset = FcCharSetCopy(charset);
}
return self;
}
- (id)mutableCopyWithZone: (NSZone*)aZone
{
return [[NSMutableCharacterSet characterSetWithBitmapRepresentation:
[self bitmapRepresentation]] retain];
}
- (void)dealloc
{
FcCharSetDestroy(_charset);
[super dealloc];
}
- (FcCharSet*)fontconfigCharSet
{
return _charset;
}
- (BOOL)characterIsMember: (unichar)c
{
return FcCharSetHasChar(_charset, c);
}
- (BOOL)longCharacterIsMember: (UTF32Char)c
{
return FcCharSetHasChar(_charset, c);
}
// FIXME: Implement for better performance
//- (NSData *)bitmapRepresentation
//{
//}
@end