1/** <title>NSPrinter</title>
2
3   <abstract>Class representing a printer's capabilities.</abstract>
4
5   Copyright (C) 1996, 1997, 2004 Free Software Foundation, Inc.
6
7   Authors: Simon Frankau <sgf@frankau.demon.co.uk>
8   Date: June 1997
9   Modified for Printing Backend Support
10   Author: Chad Hardin <cehardin@mac.com>
11   Date: July 2004
12
13   This file is part of the GNUstep GUI Library.
14
15   This library is free software; you can redistribute it and/or
16   modify it under the terms of the GNU Lesser General Public
17   License as published by the Free Software Foundation; either
18   version 2 of the License, or (at your option) any later version.
19
20   This library is distributed in the hope that it will be useful,
21   but WITHOUT ANY WARRANTY; without even the implied warranty of
22   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.	 See the GNU
23   Lesser General Public License for more details.
24
25   You should have received a copy of the GNU Lesser General Public
26   License along with this library; see the file COPYING.LIB.
27   If not, see <http://www.gnu.org/licenses/> or write to the
28   Free Software Foundation, 51 Franklin Street, Fifth Floor,
29   Boston, MA 02110-1301, USA.
30*/
31
32#include "config.h"
33#import <Foundation/NSAutoreleasePool.h>
34#import <Foundation/NSArray.h>
35#import <Foundation/NSDebug.h>
36#import <Foundation/NSDictionary.h>
37#import <Foundation/NSString.h>
38#import <Foundation/NSBundle.h>
39#import <Foundation/NSCharacterSet.h>
40#import <Foundation/NSDictionary.h>
41#import <Foundation/NSEnumerator.h>
42#import <Foundation/NSException.h>
43#import <Foundation/NSFileManager.h>
44#import <Foundation/NSPathUtilities.h>
45#import <Foundation/NSScanner.h>
46#import <Foundation/NSSet.h>
47#import <Foundation/NSString.h>
48#import <Foundation/NSValue.h>
49#import <Foundation/NSMapTable.h>
50#import <Foundation/NSSet.h>
51#import "AppKit/AppKitExceptions.h"
52#import "AppKit/NSGraphics.h"
53#import "AppKit/NSPrinter.h"
54#import "GNUstepGUI/GSPrinting.h"
55
56//
57// Class variables:
58//
59
60//
61// Class variables used during scanning:
62//
63
64// Character sets used in scanning.
65static NSCharacterSet* newlineSet = nil;
66static NSCharacterSet* keyEndSet = nil;
67static NSCharacterSet* optKeyEndSet = nil;
68static NSCharacterSet* valueEndSet = nil;
69
70//Class variable to cache NSPrinters, without this they
71//are created (and PPDs are parsed) ALL the time
72static NSMutableDictionary* printerCache;
73
74
75//
76// Private methods used for PPD Parsing
77//
78@interface NSPrinter (PPDParsingPrivate)
79
80-(void) loadPPDAtPath: (NSString*) PPDstring
81         symbolValues: (NSMutableDictionary*) ppdSymbolValues
82         inclusionSet: (NSMutableSet*) includeSet;
83
84-(void) addPPDKeyword: (NSString*) mainKeyword
85          withScanner: (NSScanner*) PPDdata
86          withPPDPath: (NSString*) ppdPath;
87
88-(void) addPPDUIConstraint: (NSScanner*) constraint
89               withPPDPath: (NSString*) ppdPath;
90
91-(void) addPPDOrderDependency: (NSScanner*) dependency
92                  withPPDPath: (NSString*) ppdPath;
93
94-(id) addString: (NSString*) string
95         forKey: (NSString*) key
96        inTable: (NSString*) table;
97
98-(void)       addValue: (NSString*) value
99   andValueTranslation: (NSString*) valueTranslation
100  andOptionTranslation: (NSString*) optionTranslation
101                forKey: (NSString*) key;
102
103-(NSString*) interpretQuotedValue: (NSString*) qString;
104
105-(int) gethex: (unichar) character;
106
107@end
108
109
110
111@implementation NSPrinter
112
113//
114// Class methods
115//
116+(void) initialize
117{
118  if (self == [NSPrinter class])
119    {
120      // Initial version
121      [self setVersion:1];
122    }
123  printerCache = RETAIN([NSMutableDictionary dictionary]);
124}
125
126/** Load the appropriate bundle for the Printer
127    (eg: GSLPRPrinter, GSCUPSPrinter).
128*/
129+(id) allocWithZone: (NSZone*) zone
130{
131  Class principalClass;
132
133  principalClass = [[GSPrinting printingBundle] principalClass];
134
135  if (principalClass == nil)
136    return nil;
137
138  return [[principalClass printerClass] allocWithZone: zone];
139}
140
141
142//
143// Finding an NSPrinter
144//
145+(NSPrinter*) printerWithName: (NSString*) name
146{
147  NSEnumerator *keyEnum;
148  NSString *key;
149  NSPrinter *printer;
150
151  //First, the cache has to be managed.
152  //Take into account any deleted printers.
153  keyEnum = [[printerCache allKeys] objectEnumerator];
154  while ((key = [keyEnum nextObject]))
155    {
156      NSEnumerator *namesEnum;
157      NSString *validName;
158      BOOL stillValid = NO;
159
160      namesEnum = [[self printerNames] objectEnumerator];
161      while ((validName = [namesEnum nextObject]))
162        {
163          if ([validName isEqualToString: key])
164            {
165              stillValid = YES;
166              break;
167            }
168        }
169
170      if (stillValid == NO)
171        {
172          [printerCache removeObjectForKey: key];
173        }
174    }
175
176  printer = [printerCache objectForKey: name];
177
178  if (printer)
179    {
180      return printer;
181    }
182  else
183    {
184      Class principalClass;
185
186      principalClass = [[GSPrinting printingBundle] principalClass];
187
188      if (principalClass == nil)
189        return nil;
190
191      printer =  [[principalClass printerClass] printerWithName: name];
192
193      if (printer)
194        {
195          [printerCache setObject: printer
196                           forKey: name];
197        }
198      return printer;
199    }
200}
201
202//
203// This now different than the OpenStep spec and instead
204// follows the more useful implementation Apple choosed.  In
205// OpenStep, this method would read a PPD and return a NSPrinter
206// based upon values from that PPD, regardless if that printer
207// was actually avaiable for use or not.  On the contrary, Apple's
208// implementation looks
209// at all avaiable printers and returns one that has the same
210// type.  The reason for this is because they use CUPS.  CUPS
211// does not work by maintaining a repository of PPDs.  Instead,
212// the CUPS server trasnmits PPDs as they are needed, and only
213// for actual real printers.  Since we cannot know how the backend
214// bundles will be handling their PPDs, or if they will even be using
215// PPDs for that matter, (a Win32 printing backend, for example),
216// I've choosen to go with Apple's implementation.  Really, I see
217// little use in creating a NSPrinter for a printer that is not
218// available for use in the first place, I am open for commments
219// on this, of course.
220+(NSPrinter*) printerWithType: (NSString*) type
221{
222  NSEnumerator *printerNamesEnum;
223  NSString *printerName;
224
225  printerNamesEnum = [[self printerNames] objectEnumerator];
226
227  while ((printerName = [printerNamesEnum nextObject]))
228    {
229      NSPrinter *printer;
230
231      printer = [self printerWithName: printerName];
232
233      if ([[printer type] isEqualToString: type])
234        {
235          return printer;
236        }
237    }
238  return nil;
239}
240
241
242+(NSArray*) printerNames
243{
244  Class principalClass;
245
246  principalClass = [[GSPrinting printingBundle] principalClass];
247
248  if (principalClass == nil)
249    return nil;
250
251  return  [[principalClass printerClass] printerNames];
252}
253
254
255// See note at +(NSPrinter*) printerWithType:(NSString*) type
256+(NSArray*) printerTypes
257{
258  NSMutableSet *printerTypes;
259  NSEnumerator *printerNamesEnum;
260  NSString *printerName;
261  NSPrinter *printer;
262
263  printerTypes = [NSMutableSet setWithCapacity:1];
264
265  printerNamesEnum = [[self printerNames] objectEnumerator];
266
267  while ((printerName = [printerNamesEnum nextObject]))
268    {
269      printer = [self printerWithName: printerName];
270
271      [printerTypes addObject: [printer type]];
272    }
273
274  return [printerTypes allObjects];
275}
276
277//
278// Instance methods
279//
280
281
282//
283// Printer Attributes
284//
285-(NSString*) host
286{
287  return _printerHost;
288}
289
290-(NSString*) name
291{
292  return _printerName;
293}
294
295-(NSString*) note
296{
297  return _printerNote;
298}
299
300-(NSString*) type
301{
302  return _printerType;
303}
304
305//
306// Retrieving Specific Information
307//
308-(BOOL) acceptsBinary
309{
310  // FIXME: I'm not sure if acceptsBinary is the same as BCP protocol?
311  NSString *result;
312  NSScanner *protocols;
313
314  result = [self stringForKey: @"Protocols"
315                      inTable: @"PPD"];
316  if (!result)
317      return NO;
318
319  protocols = [NSScanner scannerWithString: result];
320
321  while (![protocols isAtEnd])
322    {
323      [protocols scanUpToCharactersFromSet: [NSCharacterSet whitespaceCharacterSet]
324                                intoString: &result];
325
326      if ([result isEqual:@"BCP"])
327	  return YES;
328    }
329
330  return NO;
331}
332
333-(NSRect) imageRectForPaper: (NSString*) paperName
334{
335  NSString *key;
336
337  key = [NSString stringWithFormat: @"ImageableArea/%@", paperName];
338
339  return [self rectForKey: key
340                  inTable: @"PPD"];
341}
342
343-(NSSize) pageSizeForPaper: (NSString*) paperName
344{
345  NSString *key;
346
347  key = [NSString stringWithFormat: @"PaperDimension/%@", paperName];
348
349  return [self sizeForKey: key
350                  inTable: @"PPD"];
351}
352
353-(BOOL) isColor
354{
355  return [self booleanForKey: @"ColorDevice"
356                     inTable: @"PPD"];
357}
358
359-(BOOL) isFontAvailable: (NSString*) fontName
360{
361  NSString *key;
362
363  key = [NSString stringWithFormat: @"Font/%@", fontName];
364  return [self isKey: key
365             inTable: @"PPD"];
366}
367
368-(int) languageLevel
369{
370  return [self intForKey: @"LanguageLevel"
371                 inTable: @"PPD"];
372}
373
374-(BOOL) isOutputStackInReverseOrder
375{
376  // FIXME: Is this what is needed? I'm not sure how this is worked out.
377  NSString *result;
378
379  result = [self stringForKey: @"DefaultOutputOrder"
380                      inTable: @"PPD"];
381
382  if (!result)
383      return NO;
384
385  if ([result caseInsensitiveCompare: @"REVERSE"] == NSOrderedSame)
386    return YES;
387  else
388    return NO;
389}
390
391//
392// Querying the NSPrinter Tables
393//
394-(BOOL) booleanForKey: (NSString*) key
395              inTable: (NSString*) table
396{
397  NSString *result;
398  result = [self stringForKey: key
399                      inTable: table];
400
401  if (!result)  //raise exception?
402    return NO;
403
404  if ([result caseInsensitiveCompare: @"TRUE"] == NSOrderedSame)
405    return YES;
406  else
407    return NO;
408}
409
410
411-(NSDictionary*) deviceDescription
412{
413  NSMutableDictionary *result;
414
415  result = [NSMutableDictionary dictionary];
416
417  if ([self isKey: @"DefaultResolution"
418          inTable:@"PPD"])
419    {
420      int dpi = [self intForKey: @"DefaultResolution"
421                        inTable: @"PPD"];
422
423      [result setObject: [NSNumber numberWithInt: dpi]
424                forKey: NSDeviceResolution];
425    }
426
427  if ([self isKey: @"ColorDevice"
428          inTable: @"PPD"])
429    {
430      BOOL color = [self booleanForKey: @"ColorDevice"
431                      inTable: @"PPD"];
432
433      // FIXME: Should NSDeviceWhiteColorSpace be NSDeviceBlackColorSpace?
434      // FIXME #2: Are they calibrated?
435      // Basically I'm not sure which color spaces should be used...
436      if (color == YES)
437        {
438          [result setObject: NSDeviceCMYKColorSpace
439                     forKey: NSDeviceColorSpaceName];
440        }
441      else
442        {
443          [result setObject: NSDeviceWhiteColorSpace
444                     forKey: NSDeviceColorSpaceName];
445        }
446    }
447
448  if ([self isKey: @"DefaultBitsPerPixel"
449          inTable: @"PPD"])
450    {
451      int bits = [self intForKey: @"DefaultBitsPerPixel"
452                         inTable: @"PPD"];
453
454      [result setObject: [NSNumber numberWithInt: bits]
455                 forKey: NSDeviceBitsPerSample];
456    }
457
458  if ([self isKey: @"DefaultPageSize"
459          inTable: @"PPD"])
460    {
461      NSString* defaultPageSize = [self stringForKey: @"DefaultPageSize"
462                                             inTable: @"PPD"];
463
464      if (defaultPageSize)
465        {
466          NSSize paperSize = [self pageSizeForPaper: defaultPageSize];
467
468          [result setObject: [NSValue valueWithSize:paperSize]
469                     forKey: NSDeviceSize];
470        }
471    }
472
473  [result setObject: [NSNumber numberWithBool:NO]
474             forKey: NSDeviceIsScreen];
475
476  [result setObject: [NSNumber numberWithBool:YES]
477             forKey: NSDeviceIsPrinter];
478
479  NSDebugMLLog(@"GSPrinting", @"Device Description: %@", [result description]);
480  return result;
481}
482
483
484-(float) floatForKey: (NSString*) key
485             inTable: (NSString*) table
486{
487  NSString *result;
488
489  result = [self stringForKey: key
490                      inTable: table];
491
492  if (!result)  //raise exception?
493    return 0.0;
494
495  return [result floatValue];
496}
497
498
499-(int) intForKey: (NSString*) key
500         inTable: (NSString*) table
501{
502  NSString *result;
503
504  result = [self stringForKey: key
505                      inTable: table];
506
507  if (!result) //raise exception?
508    return 0;
509
510  return [result intValue];
511}
512
513
514-(NSRect) rectForKey: (NSString*) key
515             inTable: (NSString*) table
516{
517  NSString *result;
518  NSScanner *bits;
519  double x1, y1, x2, y2;
520
521  result = [self stringForKey: key
522                      inTable: table];
523
524  if (!result)  //raise exception?
525    return NSZeroRect;
526
527  bits = [NSScanner scannerWithString: result];
528  if ([bits scanDouble: &x1] &&
529      [bits scanDouble: &y1] &&
530      [bits scanDouble: &x2] &&
531      [bits scanDouble: &y2])
532    {
533      return NSMakeRect(x1, y1, x2-x1, y2-y1);
534    }
535  return NSZeroRect;
536}
537
538-(NSSize) sizeForKey: (NSString*) key
539             inTable: (NSString*) table
540{
541  NSString *result;
542  NSScanner *bits;
543  double x, y;
544
545  result = [self stringForKey: key
546                      inTable: table];
547
548  if (!result)  //raise exception?
549    return NSZeroSize;
550
551  bits = [NSScanner scannerWithString: result];
552  if ([bits scanDouble: &x] &&
553      [bits scanDouble: &y])
554    {
555      return NSMakeSize(x,y);
556    }
557  return NSZeroSize;
558}
559
560
561-(NSString*) stringForKey: (NSString*) key
562                  inTable: (NSString*) table
563{
564  NSArray *results;
565
566  results = [self stringListForKey: key
567                           inTable: table];
568
569  if (results == nil)
570    return nil;
571
572  return [results objectAtIndex: 0];
573}
574
575-(NSArray*) stringListForKey: (NSString*) key
576                     inTable: (NSString*) table
577{
578  NSDictionary *tableObj;
579  NSMutableArray *result;
580
581  tableObj = [_tables objectForKey: table ];
582
583  if (tableObj == nil) //raise exception?
584    {
585      return nil;
586    }
587
588  result = [tableObj objectForKey: key];
589  if ([[result objectAtIndex:0] isEqual:@""])
590    {
591      NSMutableArray *origResult = result;
592      result = [NSMutableArray array];
593      [result addObjectsFromArray: origResult];
594      [result removeObjectAtIndex: 0];
595    }
596  return result;
597}
598
599
600-(NSPrinterTableStatus) statusForTable: (NSString*) table
601{
602  NSDictionary *tableObj;
603
604  // Select correct table
605  tableObj = [_tables objectForKey: table];
606
607  if (tableObj == nil)
608    return NSPrinterTableNotFound;
609  else if (![tableObj isKindOfClass: [NSDictionary class]])
610    return NSPrinterTableError;
611  else
612    return NSPrinterTableOK;
613}
614
615
616-(BOOL) isKey: (NSString*) key
617      inTable: (NSString*) table
618{
619  NSMutableDictionary *tableObj;
620
621  // Select correct table
622  tableObj = [_tables objectForKey: table];
623
624  if (tableObj == nil) //raise exception?
625    {
626      return NO;
627    }
628
629  // And check it
630  if ([tableObj objectForKey: key] == nil)
631    return NO;
632  else
633    return YES;
634}
635
636//
637// NSCoding protocol
638//
639- (void) encodeWithCoder: (NSCoder*)aCoder
640{
641  if ([aCoder allowsKeyedCoding])
642    {
643      // TODO: Determine keys for NSPrinter.
644    }
645  else
646    {
647      [aCoder encodeObject: _printerHost];
648      [aCoder encodeObject: _printerName];
649      [aCoder encodeObject: _printerNote];
650      [aCoder encodeObject: _printerType];
651      [aCoder encodeObject: _tables];
652    }
653}
654
655- (id) initWithCoder: (NSCoder*)aDecoder
656{
657  if ([aDecoder allowsKeyedCoding])
658    {
659      // TODO: Determine keys for NSPrinter.
660    }
661  else
662    {
663      _printerHost = [aDecoder decodeObject];
664      _printerName = [aDecoder decodeObject];
665      _printerNote = [aDecoder decodeObject];
666      _printerType = [aDecoder decodeObject];
667      _tables = [aDecoder decodeObject];
668    }
669  return self;
670}
671
672@end
673
674
675
676///
677///Private implementation of routines that will be usefull
678///for the printing backend bundles that subclass us.
679///
680@implementation NSPrinter (Private)
681
682//
683// Initialisation method used by backend bundles
684//
685-(id) initWithName: (NSString*) name
686          withType: (NSString*) type
687          withHost: (NSString*) host
688          withNote: (NSString*) note
689{
690  self = [super init];
691
692  // Initialise instance variables
693  ASSIGN(_printerName, name);
694  ASSIGN(_printerType, type);
695  ASSIGN(_printerHost, host);
696  ASSIGN(_printerNote, note);
697
698  _tables = RETAIN([NSMutableDictionary dictionary]);
699
700  return self;
701}
702
703//
704// Deallocation of instance variables
705//
706-(void) dealloc
707{
708  RELEASE(_printerHost);
709  RELEASE(_printerName);
710  RELEASE(_printerNote);
711  RELEASE(_printerType);
712  RELEASE(_tables);
713
714  [super dealloc];
715}
716
717@end
718
719
720
721@implementation NSPrinter (PPDParsing)
722
723-(BOOL) parsePPDAtPath: (NSString*) ppdPath
724{
725  NSAutoreleasePool* subpool;
726  NSMutableDictionary* ppdSymbolValues;
727  NSEnumerator* objEnum;
728  NSMutableArray* valArray;
729
730  //make sure the class variables for scanning are created
731  if (!newlineSet)
732    {
733      newlineSet = [NSCharacterSet characterSetWithCharactersInString: @"\n\r"];
734      RETAIN(newlineSet);
735    }
736
737  if (!keyEndSet)
738    {
739      keyEndSet = [NSCharacterSet characterSetWithCharactersInString: @"\n\r\t: "];
740      RETAIN(keyEndSet);
741    }
742
743  if (!optKeyEndSet)
744    {
745      optKeyEndSet = [NSCharacterSet characterSetWithCharactersInString: @"\n\r:/"];
746      RETAIN(optKeyEndSet);
747    }
748
749  if (!valueEndSet)
750    {
751      valueEndSet = [NSCharacterSet characterSetWithCharactersInString: @"\n\r/"];
752      RETAIN(valueEndSet);
753    }
754
755
756
757  [_tables setObject: [NSMutableDictionary dictionary]
758              forKey: @"PPD"];
759
760  [_tables setObject: [NSMutableDictionary dictionary]
761              forKey: @"PPDOptionTranslation"];
762
763  [_tables setObject: [NSMutableDictionary dictionary]
764              forKey: @"PPDArgumentTranslation"];
765
766  [_tables setObject: [NSMutableDictionary dictionary]
767              forKey: @"PPDOrderDependency"];
768
769  [_tables setObject: [NSMutableDictionary dictionary]
770              forKey: @"PPDUIConstraints"];
771
772
773  // Create a temporary autorelease pool, as many temporary objects are used
774  subpool = [[NSAutoreleasePool alloc] init];
775
776
777  // NB: There are some structure keywords (such as OpenUI/CloseUI) that may
778  // be repeated, but as yet are not used. Since they are structure keywords,
779  // they'll probably need special processing anyway, and so aren't
780  // added to this list.
781
782  // Create dictionary for temporary storage of symbol values
783  ppdSymbolValues = [NSMutableDictionary dictionary];
784
785  //The inclusion set keeps track of what PPD files have been *Include(d).
786  //If one comes up twice recursion has occurred and we stop it.
787  // And scan the PPD itself
788  [self loadPPDAtPath: ppdPath
789         symbolValues: ppdSymbolValues
790         inclusionSet: [NSMutableSet setWithCapacity:10]];
791
792  // Search the PPD dictionary for symbolvalues and substitute them.
793  objEnum = [[_tables objectForKey: @"PPD"] objectEnumerator];
794  while ((valArray = [objEnum nextObject]))
795    {
796      NSString *oldValue;
797      NSString *newValue;
798      int i, max;
799
800      max = [valArray count];
801      for (i=0 ; i < max ; i++)
802        {
803          oldValue = [valArray objectAtIndex: i];
804          if ([oldValue isKindOfClass: [NSString class]]
805              && ![oldValue isEqual: @""]
806              &&  [[oldValue substringToIndex: 1] isEqual: @"^"])
807              {
808                newValue = [ppdSymbolValues
809                             objectForKey: [oldValue substringFromIndex: 1]];
810
811	            if (!newValue)
812                    {
813                      [NSException raise: NSPPDParseException
814                       format: @"Unknown symbol value, ^%@ in PPD file %@.ppd",
815                       oldValue, ppdPath];
816                    }
817
818                  [valArray replaceObjectAtIndex: i
819                                      withObject: newValue];
820             }
821        }
822    }
823
824
825
826  // Make sure all the required keys are present
827  //Too many PPDs don't pass the test....
828  /*
829  objEnum = [[NSArray arrayWithObjects: @"NickName",
830                  @"ModelName",
831                  @"PCFileName",
832                  @"Product",
833                  @"PSVersion",
834                  @"FileVersion",
835                  @"FormatVersion",
836                  @"LanguageEncoding",
837                  @"LanguageVersion",
838                  @"PageSize",
839                  @"PageRegion",
840                  @"ImageableArea",
841                  @"PaperDimension",
842                  @"PPD-Adobe",
843                  nil] objectEnumerator];
844
845  while ((checkVal = [objEnum nextObject]))
846    {
847      if (![self isKey: checkVal
848               inTable: @"PPD"])
849        {
850          [NSException raise:NSPPDParseException
851           format:@"Required keyword *%@ not found in PPD file %@.ppd",
852           checkVal, PPDPath];
853        }
854    }
855  */
856
857  // Release the local autoreleasePool
858  [subpool drain];
859
860
861//Sometimes it's good to see the tables...
862/*
863  NSDebugMLLog(@"GSPrinting", @"\n\nPPD: %@\n\n",
864               [[_tables objectForKey: @"PPD"] description]);
865
866  NSDebugMLLog(@"GSPrinting", @"\n\nPPDOptionTranslation: %@\n\n",
867               [[_tables objectForKey: @"PPDOptionTranslation"] description]);
868
869  NSDebugMLLog(@"GSPrinting", @"\n\nPPDArgumentTranslation: %@\n\n",
870               [[_tables objectForKey: @"PPDArgumentTranslation"] description]);
871
872  NSDebugMLLog(@"GSPrinting", @"\n\nPPDOrderDependency: %@\n\n",
873               [[_tables objectForKey: @"PPDOrderDependency"] description]);
874
875  NSDebugMLLog(@"GSPrinting", @"\n\nPPDUIConstraints: %@\n\n",
876               [[_tables objectForKey: @"PPDUIConstraints"] description]);
877*/
878
879
880  return YES;
881}
882
883@end
884
885
886
887
888
889@implementation NSPrinter (PPDParsingPrivate)
890
891
892-(void) loadPPDAtPath: (NSString*) ppdPath
893         symbolValues: (NSMutableDictionary*) ppdSymbolValues
894         inclusionSet: (NSMutableSet*) inclusionSet
895{
896  NSString* ppdString;
897  NSScanner* ppdData;
898  NSString* keyword;
899
900
901  //See if this ppd has been processed before
902  if ([inclusionSet member: ppdPath])
903    {
904      //this ppd has been done already!
905      [NSException raise: NSPPDIncludeStackOverflowException
906                         format: @"Recursive *Includes! PPD *Include stack: %@",
907                         [[inclusionSet allObjects] description] ];
908    }
909
910  [inclusionSet addObject: ppdPath];
911
912  ppdString = [NSString stringWithContentsOfFile: ppdPath];
913  if (nil == ppdString)
914    {
915      // The file isn't readable
916      [NSException raise: NSPPDParseException
917                  format: @"PPD file '%@' isn't readable", ppdPath];
918    }
919
920  // Set up the scanner - Appending a newline means that it should be
921  // able to process the last line correctly
922  ppdData = [NSScanner scannerWithString:
923			 [ppdString stringByAppendingString: @"\n"]];
924
925  [ppdData setCharactersToBeSkipped: [NSCharacterSet whitespaceCharacterSet]];
926
927  // Main processing starts here...
928  while (YES)  //Only check for the end after accounting for whitespace
929    {
930      // Get to the start of a new keyword, skipping blank lines
931      [ppdData scanCharactersFromSet:
932               [NSCharacterSet whitespaceAndNewlineCharacterSet]
933                          intoString: NULL];
934
935      //this could be the end...
936      if ([ppdData isAtEnd])
937        break;
938
939      // All new entries should starts '*'
940      if (![ppdData scanString: @"*"
941                    intoString: NULL])
942        {
943          [NSException raise: NSPPDParseException
944                      format: @"Line not starting with * in PPD file %@",
945                      ppdPath];
946        }
947
948      // Skip lines starting '*%', '*End', '*SymbolLength', or '*SymbolEnd'
949      if ([ppdData scanString: @"%"
950                      intoString: NULL]
951          || [ppdData scanString: @"End" //if we get this there is problem, yes?
952                      intoString: NULL]
953          || [ppdData scanString: @"SymbolLength"
954                      intoString: NULL]
955          || [ppdData scanString: @"SymbolEnd" //if we get this there is problem, yes?
956                      intoString: NULL])
957        {
958          [ppdData scanUpToCharactersFromSet: newlineSet
959                                  intoString: NULL];
960          continue;
961        }
962
963      // Read main keyword, up to a colon, space or newline
964      [ppdData scanUpToCharactersFromSet: keyEndSet
965                              intoString: &keyword];
966
967      // Loop if there is no value section, these keywords are ignored
968      if ([ppdData scanCharactersFromSet: newlineSet
969                              intoString: NULL])
970        {
971          continue;
972        }
973
974      // Add the line to the relevant table
975      if ([keyword isEqual: @"OrderDependency"])
976        {
977          [self addPPDOrderDependency: ppdData
978                          withPPDPath: ppdPath];
979        }
980      else if ([keyword isEqual: @"UIConstraints"])
981        {
982          [self addPPDUIConstraint: ppdData
983                       withPPDPath: ppdPath];
984        }
985      else if ([keyword isEqual: @"Include"])
986        {
987          NSFileManager *fileManager;
988          NSString *fileName = nil;
989          NSString *path = nil;
990
991          fileManager = [NSFileManager defaultManager];
992
993          [ppdData scanString: @":"
994                   intoString: NULL];
995
996          // Find the filename between two "s"
997          [ppdData scanString: @"\""             /*"*/
998                   intoString: NULL];
999
1000          [ppdData scanUpToString: @"\""         /*"*/
1001                       intoString: &fileName];
1002
1003          [ppdData scanString: @"\""            /*"*/
1004                   intoString: NULL];
1005
1006          //the fileName could be an absolute path or just a filename.
1007          if ([fileManager fileExistsAtPath: fileName])
1008            {
1009              //it was absolute, we are done
1010              path = fileName;
1011            }
1012          //it was not absolute.  Check to see if it exists in the
1013          //directory of this ppd
1014          else if ([fileManager fileExistsAtPath:
1015                     [[ppdPath stringByDeletingLastPathComponent]
1016                       stringByAppendingPathComponent: fileName] ])
1017            {
1018              path = [[ppdPath stringByDeletingLastPathComponent]
1019                      stringByAppendingPathComponent: fileName];
1020            }
1021          else  //could not find the *Include fileName
1022            {
1023              [NSException raise: NSPPDIncludeNotFoundException
1024                         format: @"Could not find *Included PPD file %@", path];
1025            }
1026
1027          [self loadPPDAtPath: path
1028                 symbolValues: ppdSymbolValues
1029                 inclusionSet: inclusionSet];
1030        }
1031      else if ([keyword isEqual: @"SymbolValue"])
1032        {
1033          NSString *symbolName;
1034          NSString *symbolVal;
1035
1036          if (![ppdData scanString: @"^"
1037                        intoString: NULL])
1038            {
1039              [NSException raise: NSPPDParseException
1040               format:@"Badly formatted *SymbolValue in PPD file %@",
1041               ppdPath];
1042            }
1043
1044          [ppdData scanUpToString: @":"
1045                       intoString: &symbolName];
1046
1047
1048          [ppdData scanString: @":"
1049                   intoString: NULL];
1050
1051          [ppdData scanString: @"\""              /*"*/
1052                   intoString: NULL];
1053
1054          [ppdData scanUpToString: @"\""          /*"*/
1055                       intoString: &symbolVal];
1056
1057          if (!symbolVal)
1058            symbolVal = @"";
1059
1060          [ppdData scanString: @"\""             /*"*/
1061                   intoString: NULL];
1062
1063          [ppdSymbolValues setObject: symbolVal
1064                              forKey: symbolName];
1065        }
1066      else
1067        {
1068          [self addPPDKeyword: keyword
1069                  withScanner: ppdData
1070                  withPPDPath: ppdPath];
1071        }
1072
1073
1074      // Skip any other data that don't conform with the specification.
1075      [ppdData scanUpToCharactersFromSet: newlineSet
1076			      intoString: NULL];
1077    }
1078}
1079
1080
1081-(void) addPPDKeyword: (NSString*) mainKeyword
1082          withScanner: (NSScanner*) ppdData
1083          withPPDPath: (NSString*) ppdPath
1084{
1085  NSArray *repKeys;
1086  NSString* optionKeyword = nil;
1087  NSString* optionTranslation = nil;
1088  NSString* value = nil;
1089  NSString* valueTranslation = nil;
1090
1091  // Array of Repeated Keywords (Appendix B of the PostScript Printer
1092  // Description File Format Specification).
1093  repKeys = [NSArray arrayWithObjects:@"Emulators",
1094		     @"Extensions",
1095		     @"FaxSupport",
1096		   //@"Include", (handled separately)
1097		     @"Message",
1098		     @"PrinterError",
1099		     @"Product",
1100		     @"Protocols",
1101		     @"PSVersion",
1102		     @"Source",
1103		     @"Status",
1104		   //@"UIConstraints", (handled separately)
1105  // Even though this is not mentioned in the list of repeated keywords,
1106  // it's often repeated anyway, so I'm putting it here.
1107		     @"InkName",
1108		     nil];
1109
1110
1111  // Scan off any optionKeyword
1112  [ppdData scanUpToCharactersFromSet: optKeyEndSet
1113                          intoString: &optionKeyword];
1114
1115  if ([ppdData scanCharactersFromSet: newlineSet
1116                          intoString: NULL])
1117    {
1118      [NSException raise: NSPPDParseException
1119       format: @"Keyword has optional keyword but no value in PPD file %@",
1120       ppdPath];
1121    }
1122
1123  if ([ppdData scanString: @"/"
1124               intoString: NULL])
1125    {
1126      // Option keyword translation exists - scan it
1127      [ppdData scanUpToString: @":"
1128                   intoString: &optionTranslation];
1129    }
1130
1131  [ppdData scanString: @":"
1132           intoString: NULL];
1133
1134  // Read the value part
1135  // Values starting with a " are read until the second ", ignoring \n etc.
1136
1137  if ([ppdData scanString: @"\""             /*"*/
1138               intoString: NULL])
1139    {
1140      [ppdData scanUpToString: @"\""         /*"*/
1141                   intoString: &value];
1142
1143      [ppdData scanString: @"\""             /*"*/
1144               intoString: NULL];
1145
1146      // It is a QuotedValue if it's in quotes, and there is no option
1147      // key, or the main key is a *JCL keyword
1148      if (!optionKeyword || [[mainKeyword substringToIndex:3]
1149                               isEqualToString: @"JCL"])
1150        {
1151          value = [self interpretQuotedValue: value];
1152        }
1153    }
1154  else
1155    {
1156      // Otherwise, scan up to the end of line or '/'
1157      [ppdData scanUpToCharactersFromSet: valueEndSet
1158                              intoString: &value];
1159    }
1160
1161  if (!value)
1162    {
1163      value = @"";
1164    }
1165
1166  // If there is a value translation, scan it
1167  if ([ppdData scanString: @"/"
1168               intoString: NULL])
1169    {
1170      [ppdData scanUpToCharactersFromSet: newlineSet
1171                              intoString: &valueTranslation];
1172    }
1173
1174  // The translations also have to have any hex substrings interpreted
1175  optionTranslation = [self interpretQuotedValue: optionTranslation];
1176  valueTranslation = [self interpretQuotedValue: valueTranslation];
1177
1178  // The keyword (or keyword/option pair, if there's a option), should only
1179  // only have one value, unless it's one of the optionless keywords which
1180  // allow multiple instances.
1181  // If a keyword is read twice, 'first instance is correct', according to
1182  // the standard.
1183  // Finally, add the strings to the tables
1184  if (optionKeyword)
1185    {
1186      NSString *mainAndOptionKeyword;
1187
1188      mainAndOptionKeyword=[mainKeyword stringByAppendingFormat: @"/%@",
1189                            optionKeyword];
1190
1191      if ([self isKey: mainAndOptionKeyword
1192              inTable: @"PPD"])
1193        {
1194          return;
1195        }
1196
1197      [self             addValue: value
1198             andValueTranslation: valueTranslation
1199            andOptionTranslation: optionTranslation
1200                          forKey: mainAndOptionKeyword];
1201
1202      // Deal with the oddities of stringForKey:inTable:
1203      // If this method is used to find a keyword with options, using
1204      // just the keyword it should return an empty string
1205      // stringListForKey:inTable:, however, should return the list of
1206      // option keywords.
1207      // This is done by making the first item in the array an empty
1208      // string, which will be skipped by stringListForKey:, if necessary
1209      if (![[_tables objectForKey: @"PPD"] objectForKey: mainKeyword])
1210        {
1211          [self addString: @""
1212                   forKey: mainKeyword
1213                  inTable: @"PPD"];
1214
1215          [self addString: @""
1216                   forKey: mainKeyword
1217                  inTable: @"PPDOptionTranslation"];
1218
1219          [self addString: @""
1220                   forKey: mainKeyword
1221                  inTable: @"PPDArgumentTranslation"];
1222
1223        }
1224
1225      [self            addValue: optionKeyword
1226            andValueTranslation: optionKeyword
1227           andOptionTranslation: optionKeyword
1228                         forKey: mainKeyword];
1229    }
1230  else
1231    {
1232      if ([self isKey: mainKeyword
1233              inTable: @"PPD"] &&
1234         ![repKeys containsObject: mainKeyword])
1235        {
1236          return;
1237        }
1238
1239      [self            addValue: value
1240            andValueTranslation: valueTranslation
1241           andOptionTranslation: optionTranslation
1242                         forKey: mainKeyword];
1243    }
1244}
1245
1246
1247-(void) addPPDUIConstraint: (NSScanner*) constraint
1248               withPPDPath: (NSString*) ppdPath
1249{
1250  NSString* mainKey1 = nil;
1251  NSString* optionKey1 = nil;
1252  NSString* mainKey2 = nil;
1253  NSString* optionKey2 = nil;
1254
1255  // UIConstraint should have no option keyword
1256  if (![constraint scanString: @":"
1257                   intoString: NULL])
1258    {
1259      [NSException raise:NSPPDParseException
1260       format:@"UIConstraints has option keyword in PPD File %@",
1261       ppdPath];
1262    }
1263
1264  // Skip the '*'
1265  [constraint scanString: @"*"
1266              intoString: NULL];
1267
1268  // Scan the bits. Stuff not starting with * must be an optionKeyword
1269  [constraint scanUpToCharactersFromSet: [NSCharacterSet whitespaceCharacterSet]
1270                             intoString: &mainKey1];
1271
1272  if (![constraint scanString: @"*"
1273                   intoString: NULL])
1274    {
1275      [constraint scanUpToCharactersFromSet: [NSCharacterSet whitespaceCharacterSet]
1276		                     intoString: &optionKey1];
1277
1278      [constraint scanString: @"*"
1279                  intoString: NULL];
1280
1281    }
1282
1283  [constraint scanUpToCharactersFromSet:
1284              [NSCharacterSet whitespaceAndNewlineCharacterSet]
1285                             intoString: &mainKey2];
1286
1287  if (![constraint scanCharactersFromSet: newlineSet
1288                              intoString: NULL])
1289    {
1290      [constraint scanUpToCharactersFromSet:
1291                  [NSCharacterSet whitespaceAndNewlineCharacterSet]
1292                                 intoString: &optionKey2];
1293    }
1294  else
1295    {
1296      optionKey2 = @"";
1297    }
1298
1299  // Add to table
1300  if (optionKey1)
1301    mainKey1 = [mainKey1 stringByAppendingFormat: @"/%@", optionKey1];
1302
1303  [self addString: mainKey2
1304           forKey: mainKey1
1305          inTable: @"PPDUIConstraints"];
1306
1307  [self addString: optionKey2
1308           forKey: mainKey1
1309          inTable: @"PPDUIConstraints"];
1310
1311}
1312
1313
1314
1315-(void) addPPDOrderDependency: (NSScanner*) dependency
1316                  withPPDPath: (NSString*) ppdPath
1317{
1318  NSString *realValue = nil;
1319  NSString *section = nil;
1320  NSString *keyword = nil;
1321  NSString *optionKeyword = nil;
1322
1323  // Order dependency should have no option keyword
1324  if (![dependency scanString: @":"
1325                   intoString: NULL])
1326    {
1327      [NSException raise: NSPPDParseException
1328       format:@"OrderDependency has option keyword in PPD file %@",
1329       ppdPath];
1330    }
1331
1332  [dependency scanUpToCharactersFromSet: [NSCharacterSet whitespaceCharacterSet]
1333                             intoString: &realValue];
1334
1335  [dependency scanUpToCharactersFromSet: [NSCharacterSet whitespaceCharacterSet]
1336                             intoString: &section];
1337
1338  [dependency scanString: @"*"
1339              intoString: NULL];
1340
1341  [dependency scanUpToCharactersFromSet:
1342              [NSCharacterSet whitespaceAndNewlineCharacterSet]
1343                             intoString: &keyword];
1344
1345  if (![dependency scanCharactersFromSet: newlineSet
1346                              intoString: NULL])
1347    {
1348      // Optional keyword exists
1349      [dependency scanUpToCharactersFromSet:
1350                  [NSCharacterSet whitespaceAndNewlineCharacterSet]
1351                                 intoString: &optionKeyword];
1352    }
1353
1354  // Go to next line of PPD file
1355  [dependency scanCharactersFromSet: newlineSet
1356                         intoString: NULL];
1357
1358  // Add to table
1359  if (optionKeyword)
1360    keyword = [keyword stringByAppendingFormat: @"/%@", optionKeyword];
1361
1362  [self addString: realValue
1363           forKey: keyword
1364          inTable: @"PPDOrderDependency"];
1365
1366  [self addString: section
1367           forKey: keyword
1368          inTable: @"PPDOrderDependency"];
1369
1370}
1371
1372
1373//
1374// Adds the various values to the relevant tables, for the given key
1375//
1376-(void)           addValue: (NSString*) value
1377       andValueTranslation: (NSString*) valueTranslation
1378      andOptionTranslation: (NSString*) optionTranslation
1379                    forKey: (NSString*) key
1380{
1381  [self addString: value
1382           forKey: key
1383          inTable: @"PPD"];
1384
1385  if (valueTranslation)
1386    {
1387      [self addString: valueTranslation
1388               forKey: key
1389              inTable: @"PPDArgumentTranslation"];
1390    }
1391
1392  if (optionTranslation)
1393    {
1394      [self addString: optionTranslation
1395               forKey: key
1396              inTable: @"PPDOptionTranslation"];
1397    }
1398}
1399
1400
1401
1402//
1403// Adds the string to the array of strings.
1404// Or creates the array if it does not exist and adds the string
1405//
1406-(id) addString: (NSString*) string
1407         forKey: (NSString*) key
1408        inTable: (NSString*) table
1409{
1410  NSMutableDictionary *tableObj;
1411  NSMutableArray *array;
1412
1413  tableObj = [_tables objectForKey: table];
1414
1415  if (tableObj == nil)
1416      NSDebugMLLog(@"GSPrinting", @"Could not find table %@!", table);
1417
1418  array = (NSMutableArray*)[tableObj objectForKey:key];
1419
1420  if (array == nil) //it does not exist, create it
1421    {
1422      array = [NSMutableArray array];
1423      [tableObj setObject: array
1424                   forKey: key];
1425    }
1426
1427  [array addObject: string];
1428
1429  return self;
1430}
1431
1432
1433
1434// Function to convert hexadecimal substrings
1435-(NSString*) interpretQuotedValue: (NSString*) qString
1436{
1437  NSScanner *scanner;
1438  NSCharacterSet *emptySet;
1439  NSString *value = nil;
1440  NSString *part;
1441  int stringLength;
1442  int location;
1443  NSRange range;
1444
1445  if (!qString)
1446    {
1447      return nil;
1448    }
1449
1450  // Don't bother unless there's something to convert
1451  range = [qString rangeOfString: @"<"];
1452  if (!range.length)
1453    return qString;
1454
1455  scanner = [NSScanner scannerWithString: qString];
1456  emptySet = [NSCharacterSet characterSetWithCharactersInString: @""];
1457  [scanner setCharactersToBeSkipped: emptySet];
1458
1459  if (![scanner scanUpToString: @"<"
1460                    intoString: &value])
1461    {
1462      value = [NSString string];
1463    }
1464
1465  stringLength = [qString length];
1466
1467  while (![scanner isAtEnd])
1468    {
1469      [scanner scanString: @"<"
1470               intoString: NULL];
1471
1472      // "<<" is a valid part of a PS string
1473      if ([scanner scanString: @"<"
1474		   intoString: NULL])
1475        {
1476	  value = [value stringByAppendingString: @"<<"];
1477	}
1478      else
1479        {
1480	  [scanner scanCharactersFromSet: [NSCharacterSet whitespaceAndNewlineCharacterSet]
1481		   intoString: NULL];
1482
1483	  while (![scanner scanString: @">"
1484			   intoString: NULL])
1485	    {
1486	      location = [scanner scanLocation];
1487	      if (location+2 > stringLength)
1488	        {
1489		  [NSException raise: NSPPDParseException
1490			       format: @"Badly formatted hexadecimal substring '%@' in \
1491                                  PPD printer file.", qString];
1492		  // NOT REACHED
1493		}
1494	      value = [value stringByAppendingFormat: @"%c",
1495			     16 * [self gethex: [qString characterAtIndex: location]]
1496			     + [self gethex: [qString characterAtIndex: location+1]]];
1497
1498	      [scanner setScanLocation: location+2];
1499
1500	      [scanner scanCharactersFromSet: [NSCharacterSet whitespaceAndNewlineCharacterSet]
1501		       intoString: NULL];
1502	    }
1503	}
1504
1505      if ([scanner scanUpToString:@"<" intoString:&part])
1506        {
1507	  value = [value stringByAppendingString: part];
1508	}
1509    }
1510
1511  return value;
1512}
1513
1514// Convert a character to a value between 0 and 15
1515-(int) gethex: (unichar) character
1516{
1517  switch (character)
1518    {
1519      case '0': return 0;
1520      case '1': return 1;
1521      case '2': return 2;
1522      case '3': return 3;
1523      case '4': return 4;
1524      case '5': return 5;
1525      case '6': return 6;
1526      case '7': return 7;
1527      case '8': return 8;
1528      case '9': return 9;
1529      case 'A': return 10;
1530      case 'B': return 11;
1531      case 'C': return 12;
1532      case 'D': return 13;
1533      case 'E': return 14;
1534      case 'F': return 15;
1535      case 'a': return 10;
1536      case 'b': return 11;
1537      case 'c': return 12;
1538      case 'd': return 13;
1539      case 'e': return 14;
1540      case 'f': return 15;
1541    }
1542  [NSException
1543      raise: NSPPDParseException
1544      format: @"Badly formatted hexadeximal character '%d' in PPD printer file.",
1545      character];
1546
1547  return 0; /* Quiet compiler warnings */
1548}
1549
1550@end
1551