1/** <title>GSGhostscriptImageRep</title> 2 3 <abstract>Ghostscript image representation.</abstract> 4 5 Copyright (C) 2011 Free Software Foundation, Inc. 6 7 Author: Eric Wasylishen <ewasylishen@gmail.com> 8 Date: June 2011 9 10 This file is part of the GNUstep Application Kit Library. 11 12 This library is free software; you can redistribute it and/or 13 modify it under the terms of the GNU Lesser General Public 14 License as published by the Free Software Foundation; either 15 version 2 of the License, or (at your option) any later version. 16 17 This library is distributed in the hope that it will be useful, 18 but WITHOUT ANY WARRANTY; without even the implied warranty of 19 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 20 Lesser General Public License for more details. 21 22 You should have received a copy of the GNU Lesser General Public 23 License along with this library; see the file COPYING.LIB. 24 If not, see <http://www.gnu.org/licenses/> or write to the 25 Free Software Foundation, 51 Franklin Street, Fifth Floor, 26 Boston, MA 02110-1301, USA. 27*/ 28 29#import <Foundation/NSArray.h> 30#import <Foundation/NSAffineTransform.h> 31#import <Foundation/NSCharacterSet.h> 32#import <Foundation/NSCoder.h> 33#import <Foundation/NSData.h> 34#import <Foundation/NSException.h> 35#import <Foundation/NSFileManager.h> 36#import <Foundation/NSProcessInfo.h> 37#import <Foundation/NSString.h> 38#import <Foundation/NSTask.h> 39#import <Foundation/NSUserDefaults.h> 40#import "AppKit/NSImageRep.h" 41#import "AppKit/NSPasteboard.h" 42#import "AppKit/NSGraphicsContext.h" 43#import "GNUstepGUI/GSGhostscriptImageRep.h" 44 45@implementation GSGhostscriptImageRep 46 47+ (BOOL) canInitWithData: (NSData *)data 48{ 49 char buf[4]; 50 51 if ([data length] < 4) 52 { 53 return NO; 54 } 55 56 [data getBytes: buf length: 4]; 57 58 // Simple check for PostScript or EPS, Windows EPS, PDF 59 60 if ((buf[0] == '%' && buf[1] == '!' && buf[2] == 'P' && buf[3] == 'S') || 61 (buf[0] == '\xc5' && buf[1] == '\xd0' && buf[2] == '\xd3' && buf[3] == '\xc6') || 62 (buf[0] == '%' && buf[1] == 'P' && buf[2] == 'D' && buf[3] == 'F')) 63 { 64 return YES; 65 } 66 else 67 { 68 return NO; 69 } 70} 71 72+ (NSArray *) imageUnfilteredFileTypes 73{ 74 static NSArray *types = nil; 75 76 if (types == nil) 77 { 78 types = [[NSArray alloc] initWithObjects: @"ps", @"eps", @"pdf", nil]; 79 } 80 81 return types; 82} 83 84+ (NSArray *) imageUnfilteredPasteboardTypes 85{ 86 static NSArray *types = nil; 87 88 if (types == nil) 89 { 90 types = [[NSArray alloc] initWithObjects: NSPostScriptPboardType, 91 NSPDFPboardType, 92 nil]; 93 } 94 95 return types; 96} 97 98// Locating Ghostscript 99 100- (NSString *) _PATHSeparator 101{ 102 NSProcessInfo *pinfo = [NSProcessInfo processInfo]; 103 const NSInteger os = [pinfo operatingSystem]; 104 105 if (os == NSWindowsNTOperatingSystem || 106 os == NSWindows95OperatingSystem) 107 { 108 return @";"; 109 } 110 else 111 { 112 return @":"; 113 } 114} 115 116- (NSArray *) _PATHDirectories 117{ 118 NSProcessInfo *pinfo = [NSProcessInfo processInfo]; 119 NSString *PATH = [[pinfo environment] objectForKey: @"PATH"]; 120 NSString *separator = [self _PATHSeparator]; 121 122 if (PATH != nil) 123 { 124 return [PATH componentsSeparatedByString: separator]; 125 } 126 else 127 { 128 return [NSArray array]; 129 } 130} 131 132- (NSString *) _pathForExecutable: (NSString *)executable 133{ 134 NSFileManager *fm = [NSFileManager defaultManager]; 135 NSArray *PATHDirectories = [self _PATHDirectories]; 136 NSEnumerator *enumerator = [PATHDirectories objectEnumerator]; 137 id object; 138 139 while ((object = [enumerator nextObject]) != nil) 140 { 141 NSString *path = [object stringByAppendingPathComponent: executable]; 142 if ([fm isExecutableFileAtPath: path]) 143 { 144 return path; 145 } 146 } 147 return nil; 148} 149 150- (NSString *) _ghostscriptExecutablePath 151{ 152 NSString *result = [[NSUserDefaults standardUserDefaults] stringForKey: @"GSGhostscriptExecutablePath"]; 153 154 if (result == nil) 155 { 156 static BOOL searched = NO; 157 static NSString *resultOfSearch = nil; 158 159 // Only search PATH once. 160 if (!searched) 161 { 162 searched = YES; 163 164 ASSIGN(resultOfSearch, [self _pathForExecutable: @"gs"]); 165 166 if (resultOfSearch == nil) 167 { 168 ASSIGN(resultOfSearch, [self _pathForExecutable: @"gswin32c.exe"]); 169 } 170 171 if (resultOfSearch == nil) 172 { 173 NSLog(@"GNUstep was unable to locate the Ghostscript executable in your PATH. If you would like to use Ghostscript to render PDF, PS, or PS images, please set the GSGhostscriptExecutablePath user default to the full path to the gs executable or ensure Ghostscript is installed and located in your PATH."); 174 } 175 } 176 177 result = resultOfSearch; 178 } 179 180 return result; 181} 182 183// Launching Ghostscript 184 185- (NSData *) _pngWithGhostscriptData: (NSData *)psData atResolution: (CGFloat)res 186{ 187 NSTask *task = [[[NSTask alloc] init] autorelease]; 188 NSPipe *inputPipe = [NSPipe pipe]; 189 NSPipe *outputPipe = [NSPipe pipe]; 190 NSFileHandle *inputHandle = [inputPipe fileHandleForWriting]; 191 NSFileHandle *outputHandle = [outputPipe fileHandleForReading]; 192 NSData *result = nil; 193 NSString *launchPath = [self _ghostscriptExecutablePath]; 194 195 NS_DURING 196 { 197 [task setLaunchPath: launchPath]; 198 [task setArguments: [NSArray arrayWithObjects: @"-dSAFER", 199 @"-q", 200 @"-o", 201 @"-", // Write output image to stdout 202 @"-sDEVICE=pngalpha", 203 [NSString stringWithFormat: @"-r%d", (int)res], 204 @"-dTextAlphaBits=4", 205 @"-dGraphicsAlphaBits=4", 206 @"-dDOINTERPOLATE", 207 @"-dFirstPage=1", 208 @"-dLastPage=1", // pngalpha device can only print 1 page 209 @"-", // Read input from stdin 210 nil]]; 211 [task setStandardInput: inputPipe]; 212 [task setStandardOutput: outputPipe]; 213 [task launch]; 214 215 [inputHandle writeData: psData]; 216 [inputHandle closeFile]; 217 218 result = [outputHandle readDataToEndOfFile]; 219 [outputHandle closeFile]; 220 221 if (![task isRunning] && [task terminationStatus] != 0) 222 { 223 NSLog(@"Ghostscript returned exit status %d", [task terminationStatus]); 224 } 225 } 226 NS_HANDLER 227 { 228 static BOOL warned = NO; 229 if (!warned) 230 { 231 warned = YES; 232 NSLog(@"An error occurred while attempting to invoke Ghostscript at the following path: %@", launchPath); 233 } 234 } 235 NS_ENDHANDLER 236 237 return result; 238} 239 240 241 242// Initializing a New Instance 243+ (id) imageRepWithData: (NSData *)psData 244{ 245 return AUTORELEASE([[self alloc] initWithData: psData]); 246} 247 248- (id) initWithData: (NSData *)psData 249{ 250 NSData *pngData; 251 252 ASSIGN(_psData, psData); 253 254 pngData = [self _pngWithGhostscriptData: _psData atResolution: 72.0]; 255 256 if (pngData == nil) 257 { 258 [self release]; 259 return nil; 260 } 261 262 ASSIGN(_bitmap, [NSBitmapImageRep imageRepWithData: pngData]); 263 264 [self setSize: [_bitmap size]]; 265 [self setAlpha: [_bitmap hasAlpha]]; 266 [self setBitsPerSample: NSImageRepMatchesDevice]; 267 [self setPixelsWide: NSImageRepMatchesDevice]; 268 [self setPixelsHigh: NSImageRepMatchesDevice]; 269 // FIXME: Other properties? 270 271 return self; 272} 273 274// Drawing the Image 275- (BOOL) draw 276{ 277 if (_bitmap != nil) 278 { 279 // FIXME: Re-cache at a higher resolution if needed 280 281 return [_bitmap draw]; 282 } 283 return NO; 284} 285 286// NSCopying protocol 287- (id) copyWithZone: (NSZone *)zone 288{ 289 GSGhostscriptImageRep *copy = [super copyWithZone: zone]; 290 291 copy->_psData = [_psData copyWithZone: zone]; 292 copy->_bitmap = [_bitmap copyWithZone: zone]; 293 294 return copy; 295} 296 297// NSCoding protocol 298- (void) encodeWithCoder: (NSCoder*)aCoder 299{ 300 // FIXME: 301 [super encodeWithCoder: aCoder]; 302 [_psData encodeWithCoder: aCoder]; 303} 304 305- (id) initWithCoder: (NSCoder*)aDecoder 306{ 307 // FIXME: 308 NSData *data; 309 310 self = [super initWithCoder: aDecoder]; 311 data = [aDecoder decodeObject]; 312 return [self initWithData: data]; 313} 314 315@end 316