Skip to content

Commit aaae821

Browse files
committed
Add FCFontAssetInstaller.*
1 parent bc7a4c2 commit aaae821

File tree

3 files changed

+289
-0
lines changed

3 files changed

+289
-0
lines changed
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
/* Implementation of class FCFontAssetInstaller
2+
Copyright (C) 2024 Free Software Foundation, Inc.
3+
4+
By: Gregory John Casamento <[email protected]>
5+
Date: September 5, 2025
6+
7+
This file is part of the GNUstep Library.
8+
9+
This library is free software; you can redistribute it and/or
10+
modify it under the terms of the GNU Lesser General Public
11+
License as published by the Free Software Foundation; either
12+
version 2.1 of the License, or (at your option) any later version.
13+
14+
This library is distributed in the hope that it will be useful,
15+
but WITHOUT ANY WARRANTY; without even the implied warranty of
16+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
17+
Lesser General Public License for more details.
18+
19+
You should have received a copy of the GNU Lesser General Public
20+
License along with this library; if not, write to the Free
21+
Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
22+
Boston, MA 02110 USA.
23+
*/
24+
25+
#ifndef FCFONTASSETINSTALLER_H
26+
#define FCFONTASSETINSTALLER_H
27+
28+
#import <Foundation/NSObject.h>
29+
30+
@interface FCFontAssetInstaller : NSObject
31+
{
32+
NSString *_fontPath;
33+
}
34+
35+
- (instancetype) initWithFontPath: (NSString *)path;
36+
- (BOOL) validateFontError: (NSError **)error;
37+
- (BOOL) installFontError: (NSError **)error;
38+
- (NSString *) systemFontsDirectory;
39+
- (NSString *) userFontsDirectory;
40+
41+
@end
42+
43+
#endif

Source/cairo/GNUmakefile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ cairo_OBJC_FILES = CairoSurface.m \
4040
../fontconfig/FCFaceInfo.m \
4141
../fontconfig/FCFontEnumerator.m \
4242
../fontconfig/FCFontInfo.m \
43+
../fontconfig/FCFontAssetInstaller.m \
4344

4445

4546
ifeq ($(BUILD_SERVER),x11)
Lines changed: 245 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,245 @@
1+
/* Implementation of class FCFontAssetInstaller
2+
Copyright (C) 2024 Free Software Foundation, Inc.
3+
4+
By: Gregory John Casamento <[email protected]>
5+
Date: September 5, 2025
6+
7+
This file is part of the GNUstep Library.
8+
9+
This library is free software; you can redistribute it and/or
10+
modify it under the terms of the GNU Lesser General Public
11+
License as published by the Free Software Foundation; either
12+
version 2.1 of the License, or (at your option) any later version.
13+
14+
This library is distributed in the hope that it will be useful,
15+
but WITHOUT ANY WARRANTY; without even the implied warranty of
16+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
17+
Lesser General Public License for more details.
18+
19+
You should have received a copy of the GNU Lesser General Public
20+
License along with this library; if not, write to the Free
21+
Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
22+
Boston, MA 02110 USA.
23+
*/
24+
25+
#import <Foundation/Foundation.h>
26+
#import "fontconfig/FCFontAssetInstaller.h"
27+
28+
@implementation FCFontAssetInstaller
29+
30+
- (instancetype) initWithFontPath: (NSString *)path
31+
{
32+
self = [super init];
33+
if (self != nil)
34+
{
35+
ASSIGN(_fontPath, path);
36+
}
37+
return self;
38+
}
39+
40+
- (instancetype) init
41+
{
42+
return [self initWithFontPath: nil];
43+
}
44+
45+
- (BOOL) validateFontError: (NSError **)error
46+
{
47+
if (_fontPath == nil)
48+
{
49+
if (error != NULL)
50+
{
51+
NSDictionary *userInfo = [NSDictionary dictionaryWithObject: @"Font path is nil"
52+
forKey: NSLocalizedDescriptionKey];
53+
*error = [NSError errorWithDomain: @"FCFontAssetInstallerErrorDomain"
54+
code: -3001
55+
userInfo: userInfo];
56+
}
57+
return NO;
58+
}
59+
60+
// Check if file exists
61+
if (![[NSFileManager defaultManager] fileExistsAtPath: _fontPath])
62+
{
63+
if (error != NULL)
64+
{
65+
NSDictionary *userInfo = [NSDictionary dictionaryWithObject: @"Font file does not exist"
66+
forKey: NSLocalizedDescriptionKey];
67+
*error = [NSError errorWithDomain: @"FCFontAssetInstallerErrorDomain"
68+
code: -3002
69+
userInfo: userInfo];
70+
}
71+
return NO;
72+
}
73+
74+
// Basic validation - check file size and magic bytes
75+
NSData *fontData = [NSData dataWithContentsOfFile: _fontPath];
76+
if (fontData == nil || [fontData length] < 12)
77+
{
78+
if (error != NULL)
79+
{
80+
NSDictionary *userInfo = [NSDictionary dictionaryWithObject: @"Font file is too small or unreadable"
81+
forKey: NSLocalizedDescriptionKey];
82+
*error = [NSError errorWithDomain: @"FCFontAssetInstallerErrorDomain"
83+
code: -3003
84+
userInfo: userInfo];
85+
}
86+
return NO;
87+
}
88+
89+
// Check for common font file signatures (TTF, OTF, WOFF, WOFF2)
90+
const unsigned char *bytes = [fontData bytes];
91+
92+
// TTF signature: 0x00, 0x01, 0x00, 0x00 or 'true'
93+
// OTF signature: 'OTTO'
94+
// WOFF signature: 'wOFF'
95+
// WOFF2 signature: 'wOF2'
96+
if ((bytes[0] == 0x00 && bytes[1] == 0x01 && bytes[2] == 0x00 && bytes[3] == 0x00) ||
97+
(bytes[0] == 't' && bytes[1] == 'r' && bytes[2] == 'u' && bytes[3] == 'e') ||
98+
(bytes[0] == 'O' && bytes[1] == 'T' && bytes[2] == 'T' && bytes[3] == 'O') ||
99+
(bytes[0] == 'w' && bytes[1] == 'O' && bytes[2] == 'F' && bytes[3] == 'F') ||
100+
(bytes[0] == 'w' && bytes[1] == 'O' && bytes[2] == 'F' && bytes[3] == '2'))
101+
{
102+
return YES;
103+
}
104+
105+
if (error != NULL)
106+
{
107+
NSDictionary *userInfo = [NSDictionary dictionaryWithObject: @"Font file does not have a valid font signature"
108+
forKey: NSLocalizedDescriptionKey];
109+
*error = [NSError errorWithDomain: @"FCFontAssetInstallerErrorDomain"
110+
code: -3004
111+
userInfo: userInfo];
112+
}
113+
return NO;
114+
}
115+
116+
- (BOOL) installFontError: (NSError **)error
117+
{
118+
if (_fontPath == nil)
119+
{
120+
if (error != NULL)
121+
{
122+
NSDictionary *userInfo = [NSDictionary dictionaryWithObject: @"Font path is nil"
123+
forKey: NSLocalizedDescriptionKey];
124+
*error = [NSError errorWithDomain: @"FCFontAssetInstallerErrorDomain"
125+
code: -4001
126+
userInfo: userInfo];
127+
}
128+
return NO;
129+
}
130+
131+
NSString *filename = [_fontPath lastPathComponent];
132+
NSString *destinationDir;
133+
134+
// Determine installation directory based on options
135+
destinationDir = [self systemFontsDirectory];
136+
if (destinationDir == nil)
137+
{
138+
destinationDir = [self userFontsDirectory];
139+
}
140+
141+
if (destinationDir == nil)
142+
{
143+
if (error != NULL)
144+
{
145+
NSDictionary *userInfo = [NSDictionary dictionaryWithObject: @"Cannot determine font installation directory"
146+
forKey: NSLocalizedDescriptionKey];
147+
*error = [NSError errorWithDomain: @"FCFontAssetInstallerErrorDomain"
148+
code: -4002
149+
userInfo: userInfo];
150+
}
151+
return NO;
152+
}
153+
154+
// Create destination directory if needed
155+
NSError *dirError = nil;
156+
if (![[NSFileManager defaultManager] createDirectoryAtPath: destinationDir
157+
withIntermediateDirectories: YES
158+
attributes: nil
159+
error: &dirError])
160+
{
161+
if (error != NULL)
162+
{
163+
NSDictionary *userInfo = [NSDictionary dictionaryWithObjectsAndKeys:
164+
@"Failed to create font installation directory", NSLocalizedDescriptionKey,
165+
[dirError localizedDescription], NSLocalizedFailureReasonErrorKey,
166+
nil];
167+
*error = [NSError errorWithDomain: @"FCFontAssetInstallerErrorDomain"
168+
code: -4003
169+
userInfo: userInfo];
170+
}
171+
return NO;
172+
}
173+
174+
// Copy font to destination
175+
NSString *destinationPath = [destinationDir stringByAppendingPathComponent: filename];
176+
NSError *copyError = nil;
177+
178+
// Remove existing font file if present
179+
if ([[NSFileManager defaultManager] fileExistsAtPath: destinationPath])
180+
{
181+
[[NSFileManager defaultManager] removeItemAtPath: destinationPath error: nil];
182+
}
183+
184+
if (![[NSFileManager defaultManager] copyItemAtPath: _fontPath
185+
toPath: destinationPath
186+
error: &copyError])
187+
{
188+
if (error != NULL)
189+
{
190+
NSDictionary *userInfo = [NSDictionary dictionaryWithObjectsAndKeys:
191+
@"Failed to copy font to installation directory", NSLocalizedDescriptionKey,
192+
[copyError localizedDescription], NSLocalizedFailureReasonErrorKey,
193+
nil];
194+
*error = [NSError errorWithDomain: @"FCFontAssetInstallerErrorDomain"
195+
code: -4004
196+
userInfo: userInfo];
197+
}
198+
return NO;
199+
}
200+
201+
// Notify system of new font (platform-specific)
202+
#ifdef __APPLE__
203+
// On macOS, fonts are automatically detected when placed in font directories
204+
NSLog(@"Font installed to: %@", destinationPath);
205+
#else
206+
NS_DURING
207+
{
208+
NSTask *task = [[NSTask alloc] init];
209+
[task setLaunchPath: @"/usr/bin/fc-cache"];
210+
[task setArguments: [NSArray arrayWithObject: @"-f"]];
211+
[task launch];
212+
[task waitUntilExit];
213+
if ([task terminationStatus] == 0)
214+
{
215+
NSLog(@"Font installed and cache updated: %@", destinationPath);
216+
}
217+
else
218+
{
219+
NSLog(@"Font installed, but fc-cache failed with status: %d", [task terminationStatus]);
220+
}
221+
}
222+
NS_HANDLER
223+
{
224+
NSLog(@"Font installed, but failed to run fc-cache: %@", localException);
225+
}
226+
NS_ENDHANDLER;
227+
#endif
228+
229+
return YES;
230+
}
231+
232+
- (NSString *) systemFontsDirectory
233+
{
234+
return @"/usr/local/share/fonts";
235+
}
236+
237+
- (NSString *) userFontsDirectory
238+
{
239+
NSString *homeDir = NSHomeDirectory();
240+
241+
// Generic Unix/other systems
242+
return [homeDir stringByAppendingPathComponent: @".fonts"];
243+
}
244+
245+
@end

0 commit comments

Comments
 (0)