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