mirror of
https://github.com/gnustep/libs-back.git
synced 2025-02-23 11:51:27 +00:00
1105 lines
30 KiB
Objective-C
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
|
|
|