libs-gui/Source/NSPrinter.m
David Chisnall b4e3915c33 Turn release messages sent to autorelease pools into drain messages. No
functionality change in non-GC mode, in GC mode it invokes a quick GC pass to
try to delete the short-lived objects.

Also deleted some [pool release] lines just before exit() or return-from-main
statements.  These cause objects to be swapped in and destructors to be run to
no benefit (the OS will reclaim this memory without requiring stuff to be
swapped in when the process exits).



git-svn-id: svn+ssh://svn.gna.org/svn/gnustep/libs/gui/trunk@33146 72102866-910b-0410-8b05-ffd578937521
2011-05-27 12:42:37 +00:00

1534 lines
40 KiB
Objective-C

/** <title>NSPrinter</title>
<abstract>Class representing a printer's capabilities.</abstract>
Copyright (C) 1996, 1997, 2004 Free Software Foundation, Inc.
Authors: Simon Frankau <sgf@frankau.demon.co.uk>
Date: June 1997
Modified for Printing Backend Support
Author: Chad Hardin <cehardin@mac.com>
Date: July 2004
This file is part of the GNUstep GUI Library.
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 "config.h"
#import <Foundation/NSAutoreleasePool.h>
#import <Foundation/NSArray.h>
#import <Foundation/NSDebug.h>
#import <Foundation/NSDictionary.h>
#import <Foundation/NSString.h>
#import <Foundation/NSBundle.h>
#import <Foundation/NSCharacterSet.h>
#import <Foundation/NSDictionary.h>
#import <Foundation/NSEnumerator.h>
#import <Foundation/NSException.h>
#import <Foundation/NSFileManager.h>
#import <Foundation/NSPathUtilities.h>
#import <Foundation/NSScanner.h>
#import <Foundation/NSSet.h>
#import <Foundation/NSString.h>
#import <Foundation/NSValue.h>
#import <Foundation/NSMapTable.h>
#import <Foundation/NSSet.h>
#import "AppKit/AppKitExceptions.h"
#import "AppKit/NSGraphics.h"
#import "AppKit/NSPrinter.h"
#import "GNUstepGUI/GSPrinting.h"
//
// Class variables:
//
//
// Class variables used during scanning:
//
// Character sets used in scanning.
static NSCharacterSet* newlineSet = nil;
static NSCharacterSet* keyEndSet = nil;
static NSCharacterSet* optKeyEndSet = nil;
static NSCharacterSet* valueEndSet = nil;
//Class variable to cache NSPrinters, without this they
//are created (and PPDs are parsed) ALL the time
static NSMutableDictionary* printerCache;
//
// Private methods used for PPD Parsing
//
@interface NSPrinter (PPDParsingPrivate)
-(void) loadPPDAtPath: (NSString*) PPDstring
symbolValues: (NSMutableDictionary*) ppdSymbolValues
inclusionSet: (NSMutableSet*) includeSet;
-(void) addPPDKeyword: (NSString*) mainKeyword
withScanner: (NSScanner*) PPDdata
withPPDPath: (NSString*) ppdPath;
-(void) addPPDUIConstraint: (NSScanner*) constraint
withPPDPath: (NSString*) ppdPath;
-(void) addPPDOrderDependency: (NSScanner*) dependency
withPPDPath: (NSString*) ppdPath;
-(id) addString: (NSString*) string
forKey: (NSString*) key
inTable: (NSString*) table;
-(void) addValue: (NSString*) value
andValueTranslation: (NSString*) valueTranslation
andOptionTranslation: (NSString*) optionTranslation
forKey: (NSString*) key;
-(NSString*) interpretQuotedValue: (NSString*) qString;
-(int) gethex: (unichar) character;
@end
@implementation NSPrinter
//
// Class methods
//
+(void) initialize
{
if (self == [NSPrinter class])
{
// Initial version
[self setVersion:1];
}
printerCache = RETAIN([NSMutableDictionary dictionary]);
}
/** Load the appropriate bundle for the Printer
(eg: GSLPRPrinter, GSCUPSPrinter).
*/
+(id) allocWithZone: (NSZone*) zone
{
Class principalClass;
principalClass = [[GSPrinting printingBundle] principalClass];
if (principalClass == nil)
return nil;
return [[principalClass printerClass] allocWithZone: zone];
}
//
// Finding an NSPrinter
//
+(NSPrinter*) printerWithName: (NSString*) name
{
NSEnumerator *keyEnum;
NSString *key;
NSPrinter *printer;
//First, the cache has to be managed.
//Take into account any deleted printers.
keyEnum = [[printerCache allKeys] objectEnumerator];
while ((key = [keyEnum nextObject]))
{
NSEnumerator *namesEnum;
NSString *validName;
BOOL stillValid = NO;
namesEnum = [[self printerNames] objectEnumerator];
while ((validName = [namesEnum nextObject]))
{
if ([validName isEqualToString: key])
{
stillValid = YES;
break;
}
}
if (stillValid == NO)
{
[printerCache removeObjectForKey: key];
}
}
printer = [printerCache objectForKey: name];
if (printer)
{
return printer;
}
else
{
Class principalClass;
principalClass = [[GSPrinting printingBundle] principalClass];
if (principalClass == nil)
return nil;
printer = [[principalClass printerClass] printerWithName: name];
if (printer)
{
[printerCache setObject: printer
forKey: name];
}
return printer;
}
}
//
// This now different than the OpenStep spec and instead
// follows the more useful implementation Apple choosed. In
// OpenStep, this method would read a PPD and return a NSPrinter
// based upon values from that PPD, regardless if that printer
// was actually avaiable for use or not. On the contrary, Apple's
// implementation looks
// at all avaiable printers and returns one that has the same
// type. The reason for this is because they use CUPS. CUPS
// does not work by maintaining a repository of PPDs. Instead,
// the CUPS server trasnmits PPDs as they are needed, and only
// for actual real printers. Since we cannot know how the backend
// bundles will be handling their PPDs, or if they will even be using
// PPDs for that matter, (a Win32 printing backend, for example),
// I've choosen to go with Apple's implementation. Really, I see
// little use in creating a NSPrinter for a printer that is not
// available for use in the first place, I am open for commments
// on this, of course.
+(NSPrinter*) printerWithType: (NSString*) type
{
NSEnumerator *printerNamesEnum;
NSString *printerName;
printerNamesEnum = [[self printerNames] objectEnumerator];
while ((printerName = [printerNamesEnum nextObject]))
{
NSPrinter *printer;
printer = [self printerWithName: printerName];
if ([[printer type] isEqualToString: type])
{
return printer;
}
}
return nil;
}
+(NSArray*) printerNames
{
Class principalClass;
principalClass = [[GSPrinting printingBundle] principalClass];
if (principalClass == nil)
return nil;
return [[principalClass printerClass] printerNames];
}
// See note at +(NSPrinter*) printerWithType:(NSString*) type
+(NSArray*) printerTypes
{
NSMutableSet *printerTypes;
NSEnumerator *printerNamesEnum;
NSString *printerName;
NSPrinter *printer;
printerTypes = [NSMutableSet setWithCapacity:1];
printerNamesEnum = [[self printerNames] objectEnumerator];
while ((printerName = [printerNamesEnum nextObject]))
{
printer = [self printerWithName: printerName];
[printerTypes addObject: [printer type]];
}
return [printerTypes allObjects];
}
//
// Instance methods
//
//
// Printer Attributes
//
-(NSString*) host
{
return _printerHost;
}
-(NSString*) name
{
return _printerName;
}
-(NSString*) note
{
return _printerNote;
}
-(NSString*) type
{
return _printerType;
}
//
// Retrieving Specific Information
//
-(BOOL) acceptsBinary
{
// FIXME: I'm not sure if acceptsBinary is the same as BCP protocol?
NSString *result;
NSScanner *protocols;
result = [self stringForKey: @"Protocols"
inTable: @"PPD"];
if (!result)
return NO;
protocols = [NSScanner scannerWithString: result];
while (![protocols isAtEnd])
{
[protocols scanUpToCharactersFromSet: [NSCharacterSet whitespaceCharacterSet]
intoString: &result];
if ([result isEqual:@"BCP"])
return YES;
}
return NO;
}
-(NSRect) imageRectForPaper: (NSString*) paperName
{
NSString *key;
key = [NSString stringWithFormat: @"ImageableArea/%@", paperName];
return [self rectForKey: key
inTable: @"PPD"];
}
-(NSSize) pageSizeForPaper: (NSString*) paperName
{
NSString *key;
key = [NSString stringWithFormat: @"PaperDimension/%@", paperName];
return [self sizeForKey: key
inTable: @"PPD"];
}
-(BOOL) isColor
{
return [self booleanForKey: @"ColorDevice"
inTable: @"PPD"];
}
-(BOOL) isFontAvailable: (NSString*) fontName
{
NSString *key;
key = [NSString stringWithFormat: @"Font/%@", fontName];
return [self isKey: key
inTable: @"PPD"];
}
-(int) languageLevel
{
return [self intForKey: @"LanguageLevel"
inTable: @"PPD"];
}
-(BOOL) isOutputStackInReverseOrder
{
// FIXME: Is this what is needed? I'm not sure how this is worked out.
NSString *result;
result = [self stringForKey: @"DefaultOutputOrder"
inTable: @"PPD"];
if (!result)
return NO;
if ([result caseInsensitiveCompare: @"REVERSE"] == NSOrderedSame)
return YES;
else
return NO;
}
//
// Querying the NSPrinter Tables
//
-(BOOL) booleanForKey: (NSString*) key
inTable: (NSString*) table
{
NSString *result;
result = [self stringForKey: key
inTable: table];
if (!result) //raise exception?
return NO;
if ([result caseInsensitiveCompare: @"TRUE"] == NSOrderedSame)
return YES;
else
return NO;
}
-(NSDictionary*) deviceDescription
{
NSMutableDictionary *result;
result = [NSMutableDictionary dictionary];
if ([self isKey: @"DefaultResolution"
inTable:@"PPD"])
{
int dpi = [self intForKey: @"DefaultResolution"
inTable: @"PPD"];
[result setObject: [NSNumber numberWithInt: dpi]
forKey: NSDeviceResolution];
}
if ([self isKey: @"ColorDevice"
inTable: @"PPD"])
{
BOOL color = [self booleanForKey: @"ColorDevice"
inTable: @"PPD"];
// FIXME: Should NSDeviceWhiteColorSpace be NSDeviceBlackColorSpace?
// FIXME #2: Are they calibrated?
// Basically I'm not sure which color spaces should be used...
if (color == YES)
{
[result setObject: NSDeviceCMYKColorSpace
forKey: NSDeviceColorSpaceName];
}
else
{
[result setObject: NSDeviceWhiteColorSpace
forKey: NSDeviceColorSpaceName];
}
}
if ([self isKey: @"DefaultBitsPerPixel"
inTable: @"PPD"])
{
int bits = [self intForKey: @"DefaultBitsPerPixel"
inTable: @"PPD"];
[result setObject: [NSNumber numberWithInt: bits]
forKey: NSDeviceBitsPerSample];
}
if ([self isKey: @"DefaultPageSize"
inTable: @"PPD"])
{
NSString* defaultPageSize = [self stringForKey: @"DefaultPageSize"
inTable: @"PPD"];
if (defaultPageSize)
{
NSSize paperSize = [self pageSizeForPaper: defaultPageSize];
[result setObject: [NSValue valueWithSize:paperSize]
forKey: NSDeviceSize];
}
}
[result setObject: [NSNumber numberWithBool:NO]
forKey: NSDeviceIsScreen];
[result setObject: [NSNumber numberWithBool:YES]
forKey: NSDeviceIsPrinter];
NSDebugMLLog(@"GSPrinting", @"Device Description: %@", [result description]);
return result;
}
-(float) floatForKey: (NSString*) key
inTable: (NSString*) table
{
NSString *result;
result = [self stringForKey: key
inTable: table];
if (!result) //raise exception?
return 0.0;
return [result floatValue];
}
-(int) intForKey: (NSString*) key
inTable: (NSString*) table
{
NSString *result;
result = [self stringForKey: key
inTable: table];
if (!result) //raise exception?
return 0;
return [result intValue];
}
-(NSRect) rectForKey: (NSString*) key
inTable: (NSString*) table
{
NSString *result;
NSScanner *bits;
float x1, y1, x2, y2;
result = [self stringForKey: key
inTable: table];
if (!result) //raise exception?
return NSZeroRect;
bits = [NSScanner scannerWithString: result];
if ([bits scanFloat: &x1] &&
[bits scanFloat: &y1] &&
[bits scanFloat: &x2] &&
[bits scanFloat: &y2])
{
return NSMakeRect(x1, y1, x2-x1, y2-y1);
}
return NSZeroRect;
}
-(NSSize) sizeForKey: (NSString*) key
inTable: (NSString*) table
{
NSString *result;
NSScanner *bits;
float x, y;
result = [self stringForKey: key
inTable: table];
if (!result) //raise exception?
return NSZeroSize;
bits = [NSScanner scannerWithString: result];
if ([bits scanFloat: &x] &&
[bits scanFloat: &y])
{
return NSMakeSize(x,y);
}
return NSZeroSize;
}
-(NSString*) stringForKey: (NSString*) key
inTable: (NSString*) table
{
NSArray *results;
results = [self stringListForKey: key
inTable: table];
if (results == nil)
return nil;
return [results objectAtIndex: 0];
}
-(NSArray*) stringListForKey: (NSString*) key
inTable: (NSString*) table
{
NSDictionary *tableObj;
NSMutableArray *result;
tableObj = [_tables objectForKey: table ];
if (tableObj == nil) //raise exception?
{
return nil;
}
result = [tableObj objectForKey: key];
if ([[result objectAtIndex:0] isEqual:@""])
{
NSMutableArray *origResult = result;
result = [NSMutableArray array];
[result addObjectsFromArray: origResult];
[result removeObjectAtIndex: 0];
}
return result;
}
-(NSPrinterTableStatus) statusForTable: (NSString*) table
{
NSDictionary *tableObj;
// Select correct table
tableObj = [_tables objectForKey: table];
if (tableObj == nil)
return NSPrinterTableNotFound;
else if (![tableObj isKindOfClass: [NSDictionary class]])
return NSPrinterTableError;
else
return NSPrinterTableOK;
}
-(BOOL) isKey: (NSString*) key
inTable: (NSString*) table
{
NSMutableDictionary *tableObj;
// Select correct table
tableObj = [_tables objectForKey: table];
if (tableObj == nil) //raise exception?
{
return NO;
}
// And check it
if ([tableObj objectForKey: key] == nil)
return NO;
else
return YES;
}
//
// NSCoding protocol
//
- (void) encodeWithCoder: (NSCoder*)aCoder
{
if ([aCoder allowsKeyedCoding])
{
// TODO: Determine keys for NSPrinter.
}
else
{
[aCoder encodeObject: _printerHost];
[aCoder encodeObject: _printerName];
[aCoder encodeObject: _printerNote];
[aCoder encodeObject: _printerType];
[aCoder encodeObject: _tables];
}
}
- (id) initWithCoder: (NSCoder*)aDecoder
{
if ([aDecoder allowsKeyedCoding])
{
// TODO: Determine keys for NSPrinter.
}
else
{
_printerHost = [aDecoder decodeObject];
_printerName = [aDecoder decodeObject];
_printerNote = [aDecoder decodeObject];
_printerType = [aDecoder decodeObject];
_tables = [aDecoder decodeObject];
}
return self;
}
@end
///
///Private implementation of routines that will be usefull
///for the printing backend bundles that subclass us.
///
@implementation NSPrinter (Private)
//
// Initialisation method used by backend bundles
//
-(id) initWithName: (NSString*) name
withType: (NSString*) type
withHost: (NSString*) host
withNote: (NSString*) note
{
self = [super init];
// Initialise instance variables
ASSIGN(_printerName, name);
ASSIGN(_printerType, type);
ASSIGN(_printerHost, host);
ASSIGN(_printerNote, note);
_tables = RETAIN([NSMutableDictionary dictionary]);
return self;
}
//
// Deallocation of instance variables
//
-(void) dealloc
{
RELEASE(_printerHost);
RELEASE(_printerName);
RELEASE(_printerNote);
RELEASE(_printerType);
RELEASE(_tables);
[super dealloc];
}
@end
@implementation NSPrinter (PPDParsing)
-(BOOL) parsePPDAtPath: (NSString*) ppdPath
{
NSAutoreleasePool* subpool;
NSMutableDictionary* ppdSymbolValues;
NSEnumerator* objEnum;
NSMutableArray* valArray;
//make sure the class variables for scanning are created
if (!newlineSet)
{
newlineSet = [NSCharacterSet characterSetWithCharactersInString: @"\n\r"];
RETAIN(newlineSet);
}
if (!keyEndSet)
{
keyEndSet = [NSCharacterSet characterSetWithCharactersInString: @"\n\r\t: "];
RETAIN(keyEndSet);
}
if (!optKeyEndSet)
{
optKeyEndSet = [NSCharacterSet characterSetWithCharactersInString: @"\n\r:/"];
RETAIN(optKeyEndSet);
}
if (!valueEndSet)
{
valueEndSet = [NSCharacterSet characterSetWithCharactersInString: @"\n\r/"];
RETAIN(valueEndSet);
}
[_tables setObject: [NSMutableDictionary dictionary]
forKey: @"PPD"];
[_tables setObject: [NSMutableDictionary dictionary]
forKey: @"PPDOptionTranslation"];
[_tables setObject: [NSMutableDictionary dictionary]
forKey: @"PPDArgumentTranslation"];
[_tables setObject: [NSMutableDictionary dictionary]
forKey: @"PPDOrderDependency"];
[_tables setObject: [NSMutableDictionary dictionary]
forKey: @"PPDUIConstraints"];
// Create a temporary autorelease pool, as many temporary objects are used
subpool = [[NSAutoreleasePool alloc] init];
// NB: There are some structure keywords (such as OpenUI/CloseUI) that may
// be repeated, but as yet are not used. Since they are structure keywords,
// they'll probably need special processing anyway, and so aren't
// added to this list.
// Create dictionary for temporary storage of symbol values
ppdSymbolValues = [NSMutableDictionary dictionary];
//The inclusion set keeps track of what PPD files have been *Include(d).
//If one comes up twice recursion has occurred and we stop it.
// And scan the PPD itself
[self loadPPDAtPath: ppdPath
symbolValues: ppdSymbolValues
inclusionSet: [NSMutableSet setWithCapacity:10]];
// Search the PPD dictionary for symbolvalues and substitute them.
objEnum = [[_tables objectForKey: @"PPD"] objectEnumerator];
while ((valArray = [objEnum nextObject]))
{
NSString *oldValue;
NSString *newValue;
int i, max;
max = [valArray count];
for (i=0 ; i < max ; i++)
{
oldValue = [valArray objectAtIndex: i];
if ([oldValue isKindOfClass: [NSString class]]
&& ![oldValue isEqual: @""]
&& [[oldValue substringToIndex: 1] isEqual: @"^"])
{
newValue = [ppdSymbolValues
objectForKey: [oldValue substringFromIndex: 1]];
if (!newValue)
{
[NSException raise: NSPPDParseException
format: @"Unknown symbol value, ^%@ in PPD file %@.ppd",
oldValue, ppdPath];
}
[valArray replaceObjectAtIndex: i
withObject: newValue];
}
}
}
// Make sure all the required keys are present
//Too many PPDs don't pass the test....
/*
objEnum = [[NSArray arrayWithObjects: @"NickName",
@"ModelName",
@"PCFileName",
@"Product",
@"PSVersion",
@"FileVersion",
@"FormatVersion",
@"LanguageEncoding",
@"LanguageVersion",
@"PageSize",
@"PageRegion",
@"ImageableArea",
@"PaperDimension",
@"PPD-Adobe",
nil] objectEnumerator];
while ((checkVal = [objEnum nextObject]))
{
if (![self isKey: checkVal
inTable: @"PPD"])
{
[NSException raise:NSPPDParseException
format:@"Required keyword *%@ not found in PPD file %@.ppd",
checkVal, PPDPath];
}
}
*/
// Release the local autoreleasePool
[subpool drain];
//Sometimes it's good to see the tables...
/*
NSDebugMLLog(@"GSPrinting", @"\n\nPPD: %@\n\n",
[[_tables objectForKey: @"PPD"] description]);
NSDebugMLLog(@"GSPrinting", @"\n\nPPDOptionTranslation: %@\n\n",
[[_tables objectForKey: @"PPDOptionTranslation"] description]);
NSDebugMLLog(@"GSPrinting", @"\n\nPPDArgumentTranslation: %@\n\n",
[[_tables objectForKey: @"PPDArgumentTranslation"] description]);
NSDebugMLLog(@"GSPrinting", @"\n\nPPDOrderDependency: %@\n\n",
[[_tables objectForKey: @"PPDOrderDependency"] description]);
NSDebugMLLog(@"GSPrinting", @"\n\nPPDUIConstraints: %@\n\n",
[[_tables objectForKey: @"PPDUIConstraints"] description]);
*/
return YES;
}
@end
@implementation NSPrinter (PPDParsingPrivate)
-(void) loadPPDAtPath: (NSString*) ppdPath
symbolValues: (NSMutableDictionary*) ppdSymbolValues
inclusionSet: (NSMutableSet*) inclusionSet
{
NSString* ppdString;
NSScanner* ppdData;
NSString* keyword;
//See if this ppd has been processed before
if ([inclusionSet member: ppdPath])
{
//this ppd has been done already!
[NSException raise: NSPPDIncludeStackOverflowException
format: @"Recursive *Includes! PPD *Include stack: %@",
[[inclusionSet allObjects] description] ];
}
[inclusionSet addObject: ppdPath];
ppdString = [NSString stringWithContentsOfFile: ppdPath];
// Set up the scanner - Appending a newline means that it should be
// able to process the last line correctly
ppdData = [NSScanner scannerWithString:
[ppdString stringByAppendingString: @"\n"]];
[ppdData setCharactersToBeSkipped: [NSCharacterSet whitespaceCharacterSet]];
// Main processing starts here...
while (YES) //Only check for the end after accounting for whitespace
{
// Get to the start of a new keyword, skipping blank lines
[ppdData scanCharactersFromSet:
[NSCharacterSet whitespaceAndNewlineCharacterSet]
intoString: NULL];
//this could be the end...
if ([ppdData isAtEnd])
break;
// All new entries should starts '*'
if (![ppdData scanString: @"*"
intoString: NULL])
{
[NSException raise: NSPPDParseException
format: @"Line not starting with * in PPD file %@",
ppdPath];
}
// Skip lines starting '*%', '*End', '*SymbolLength', or '*SymbolEnd'
if ([ppdData scanString: @"%"
intoString: NULL]
|| [ppdData scanString: @"End" //if we get this there is problem, yes?
intoString: NULL]
|| [ppdData scanString: @"SymbolLength"
intoString: NULL]
|| [ppdData scanString: @"SymbolEnd" //if we get this there is problem, yes?
intoString: NULL])
{
[ppdData scanUpToCharactersFromSet: newlineSet
intoString: NULL];
continue;
}
// Read main keyword, up to a colon, space or newline
[ppdData scanUpToCharactersFromSet: keyEndSet
intoString: &keyword];
// Loop if there is no value section, these keywords are ignored
if ([ppdData scanCharactersFromSet: newlineSet
intoString: NULL])
{
continue;
}
// Add the line to the relevant table
if ([keyword isEqual: @"OrderDependency"])
{
[self addPPDOrderDependency: ppdData
withPPDPath: ppdPath];
}
else if ([keyword isEqual: @"UIConstraints"])
{
[self addPPDUIConstraint: ppdData
withPPDPath: ppdPath];
}
else if ([keyword isEqual: @"Include"])
{
NSFileManager *fileManager;
NSString *fileName = nil;
NSString *path = nil;
fileManager = [NSFileManager defaultManager];
[ppdData scanString: @":"
intoString: NULL];
// Find the filename between two "s"
[ppdData scanString: @"\"" /*"*/
intoString: NULL];
[ppdData scanUpToString: @"\"" /*"*/
intoString: &fileName];
[ppdData scanString: @"\"" /*"*/
intoString: NULL];
//the fileName could be an absolute path or just a filename.
if ([fileManager fileExistsAtPath: fileName])
{
//it was absolute, we are done
path = fileName;
}
//it was not absolute. Check to see if it exists in the
//directory of this ppd
else if ([fileManager fileExistsAtPath:
[[ppdPath stringByDeletingLastPathComponent]
stringByAppendingPathComponent: fileName] ])
{
path = [[ppdPath stringByDeletingLastPathComponent]
stringByAppendingPathComponent: fileName];
}
else //could not find the *Include fileName
{
[NSException raise: NSPPDIncludeNotFoundException
format: @"Could not find *Included PPD file %@", path];
}
[self loadPPDAtPath: path
symbolValues: ppdSymbolValues
inclusionSet: inclusionSet];
}
else if ([keyword isEqual: @"SymbolValue"])
{
NSString *symbolName;
NSString *symbolVal;
if (![ppdData scanString: @"^"
intoString: NULL])
{
[NSException raise: NSPPDParseException
format:@"Badly formatted *SymbolValue in PPD file %@",
ppdPath];
}
[ppdData scanUpToString: @":"
intoString: &symbolName];
[ppdData scanString: @":"
intoString: NULL];
[ppdData scanString: @"\"" /*"*/
intoString: NULL];
[ppdData scanUpToString: @"\"" /*"*/
intoString: &symbolVal];
if (!symbolVal)
symbolVal = @"";
[ppdData scanString: @"\"" /*"*/
intoString: NULL];
[ppdSymbolValues setObject: symbolVal
forKey: symbolName];
}
else
{
[self addPPDKeyword: keyword
withScanner: ppdData
withPPDPath: ppdPath];
}
}
}
-(void) addPPDKeyword: (NSString*) mainKeyword
withScanner: (NSScanner*) ppdData
withPPDPath: (NSString*) ppdPath
{
NSArray *repKeys;
NSString* optionKeyword = nil;
NSString* optionTranslation = nil;
NSString* value = nil;
NSString* valueTranslation = nil;
// Array of Repeated Keywords (Appendix B of the PostScript Printer
// Description File Format Specification).
repKeys = [NSArray arrayWithObjects:@"Emulators",
@"Extensions",
@"FaxSupport",
//@"Include", (handled separately)
@"Message",
@"PrinterError",
@"Product",
@"Protocols",
@"PSVersion",
@"Source",
@"Status",
//@"UIConstraints", (handled separately)
// Even though this is not mentioned in the list of repeated keywords,
// it's often repeated anyway, so I'm putting it here.
@"InkName",
nil];
// Scan off any optionKeyword
[ppdData scanUpToCharactersFromSet: optKeyEndSet
intoString: &optionKeyword];
if ([ppdData scanCharactersFromSet: newlineSet
intoString: NULL])
{
[NSException raise: NSPPDParseException
format: @"Keyword has optional keyword but no value in PPD file %@",
ppdPath];
}
if ([ppdData scanString: @"/"
intoString: NULL])
{
// Option keyword translation exists - scan it
[ppdData scanUpToString: @":"
intoString: &optionTranslation];
}
[ppdData scanString: @":"
intoString: NULL];
// Read the value part
// Values starting with a " are read until the second ", ignoring \n etc.
if ([ppdData scanString: @"\"" /*"*/
intoString: NULL])
{
[ppdData scanUpToString: @"\"" /*"*/
intoString: &value];
if (!value)
value = @"";
[ppdData scanString: @"\"" /*"*/
intoString: NULL];
// It is a QuotedValue if it's in quotes, and there is no option
// key, or the main key is a *JCL keyword
if (!optionKeyword || [[mainKeyword substringToIndex:3]
isEqualToString: @"JCL"])
{
value = [self interpretQuotedValue: value];
}
}
else
{
// Otherwise, scan up to the end of line or '/'
[ppdData scanUpToCharactersFromSet: valueEndSet
intoString: &value];
}
// If there is a value translation, scan it
if ([ppdData scanString: @"/"
intoString: NULL])
{
[ppdData scanUpToCharactersFromSet: newlineSet
intoString: &valueTranslation];
}
// The translations also have to have any hex substrings interpreted
if (optionTranslation)
optionTranslation = [self interpretQuotedValue: optionTranslation];
if (valueTranslation)
valueTranslation = [self interpretQuotedValue: valueTranslation];
// The keyword (or keyword/option pair, if there's a option), should only
// only have one value, unless it's one of the optionless keywords which
// allow multiple instances.
// If a keyword is read twice, 'first instance is correct', according to
// the standard.
// Finally, add the strings to the tables
if (optionKeyword)
{
NSString *mainAndOptionKeyword;
mainAndOptionKeyword=[mainKeyword stringByAppendingFormat: @"/%@",
optionKeyword];
if ([self isKey: mainAndOptionKeyword
inTable: @"PPD"])
{
return;
}
[self addValue: value
andValueTranslation: valueTranslation
andOptionTranslation: optionTranslation
forKey: mainAndOptionKeyword];
// Deal with the oddities of stringForKey:inTable:
// If this method is used to find a keyword with options, using
// just the keyword it should return an empty string
// stringListForKey:inTable:, however, should return the list of
// option keywords.
// This is done by making the first item in the array an empty
// string, which will be skipped by stringListForKey:, if necessary
if (![[_tables objectForKey: @"PPD"] objectForKey: mainKeyword])
{
[self addString: @""
forKey: mainKeyword
inTable: @"PPD"];
[self addString: @""
forKey: mainKeyword
inTable: @"PPDOptionTranslation"];
[self addString: @""
forKey: mainKeyword
inTable: @"PPDArgumentTranslation"];
}
[self addValue: optionKeyword
andValueTranslation: optionKeyword
andOptionTranslation: optionKeyword
forKey: mainKeyword];
}
else
{
if ([self isKey: mainKeyword
inTable: @"PPD"] &&
![repKeys containsObject: mainKeyword])
{
return;
}
[self addValue: value
andValueTranslation: valueTranslation
andOptionTranslation: optionTranslation
forKey: mainKeyword];
}
}
-(void) addPPDUIConstraint: (NSScanner*) constraint
withPPDPath: (NSString*) ppdPath
{
NSString* mainKey1 = nil;
NSString* optionKey1 = nil;
NSString* mainKey2 = nil;
NSString* optionKey2 = nil;
// UIConstraint should have no option keyword
if (![constraint scanString: @":"
intoString: NULL])
{
[NSException raise:NSPPDParseException
format:@"UIConstraints has option keyword in PPD File %@",
ppdPath];
}
// Skip the '*'
[constraint scanString: @"*"
intoString: NULL];
// Scan the bits. Stuff not starting with * must be an optionKeyword
[constraint scanUpToCharactersFromSet: [NSCharacterSet whitespaceCharacterSet]
intoString: &mainKey1];
if (![constraint scanString: @"*"
intoString: NULL])
{
[constraint scanUpToCharactersFromSet: [NSCharacterSet whitespaceCharacterSet]
intoString: &optionKey1];
[constraint scanString: @"*"
intoString: NULL];
}
[constraint scanUpToCharactersFromSet:
[NSCharacterSet whitespaceAndNewlineCharacterSet]
intoString: &mainKey2];
if (![constraint scanCharactersFromSet: newlineSet
intoString: NULL])
{
[constraint scanUpToCharactersFromSet:
[NSCharacterSet whitespaceAndNewlineCharacterSet]
intoString: &optionKey2];
}
else
{
optionKey2 = @"";
}
// Add to table
if (optionKey1)
mainKey1 = [mainKey1 stringByAppendingFormat: @"/%@", optionKey1];
[self addString: mainKey2
forKey: mainKey1
inTable: @"PPDUIConstraints"];
[self addString: optionKey2
forKey: mainKey1
inTable: @"PPDUIConstraints"];
}
-(void) addPPDOrderDependency: (NSScanner*) dependency
withPPDPath: (NSString*) ppdPath
{
NSString *realValue = nil;
NSString *section = nil;
NSString *keyword = nil;
NSString *optionKeyword = nil;
// Order dependency should have no option keyword
if (![dependency scanString: @":"
intoString: NULL])
{
[NSException raise: NSPPDParseException
format:@"OrderDependency has option keyword in PPD file %@",
ppdPath];
}
[dependency scanUpToCharactersFromSet: [NSCharacterSet whitespaceCharacterSet]
intoString: &realValue];
[dependency scanUpToCharactersFromSet: [NSCharacterSet whitespaceCharacterSet]
intoString: &section];
[dependency scanString: @"*"
intoString: NULL];
[dependency scanUpToCharactersFromSet:
[NSCharacterSet whitespaceAndNewlineCharacterSet]
intoString: &keyword];
if (![dependency scanCharactersFromSet: newlineSet
intoString: NULL])
{
// Optional keyword exists
[dependency scanUpToCharactersFromSet:
[NSCharacterSet whitespaceAndNewlineCharacterSet]
intoString: &optionKeyword];
}
// Go to next line of PPD file
[dependency scanCharactersFromSet: newlineSet
intoString: NULL];
// Add to table
if (optionKeyword)
keyword = [keyword stringByAppendingFormat: @"/%@", optionKeyword];
[self addString: realValue
forKey: keyword
inTable: @"PPDOrderDependency"];
[self addString: section
forKey: keyword
inTable: @"PPDOrderDependency"];
}
//
// Adds the various values to the relevant tables, for the given key
//
-(void) addValue: (NSString*) value
andValueTranslation: (NSString*) valueTranslation
andOptionTranslation: (NSString*) optionTranslation
forKey: (NSString*) key
{
[self addString: value
forKey: key
inTable: @"PPD"];
if (valueTranslation)
{
[self addString: valueTranslation
forKey: key
inTable: @"PPDArgumentTranslation"];
}
if (optionTranslation)
{
[self addString: optionTranslation
forKey: key
inTable: @"PPDOptionTranslation"];
}
}
//
// Adds the string to the array of strings.
// Or creates the array if it does not exist and adds the string
//
-(id) addString: (NSString*) string
forKey: (NSString*) key
inTable: (NSString*) table
{
NSMutableDictionary *tableObj;
NSMutableArray *array;
tableObj = [_tables objectForKey: table];
if (tableObj == nil)
NSDebugMLLog(@"GSPrinting", @"Could not find table %@!", table);
array = (NSMutableArray*)[tableObj objectForKey:key];
if (array == nil) //it does not exist, create it
{
array = [NSMutableArray array];
[tableObj setObject: array
forKey: key];
}
[array addObject: string];
return self;
}
// Function to convert hexadecimal substrings
-(NSString*) interpretQuotedValue: (NSString*) qString
{
NSScanner *scanner;
NSCharacterSet *emptySet;
NSString *value = nil;
NSString *part;
int stringLength;
int location;
NSRange range;
// Don't bother unless there's something to convert
range = [qString rangeOfString: @"<"];
if (!range.length)
return qString;
scanner = [NSScanner scannerWithString: qString];
emptySet = [NSCharacterSet characterSetWithCharactersInString: @""];
[scanner setCharactersToBeSkipped: emptySet];
if (![scanner scanUpToString: @"<"
intoString: &value])
{
value = [NSString string];
}
stringLength = [qString length];
while (![scanner isAtEnd])
{
[scanner scanString: @"<"
intoString: NULL];
// "<<" is a valid part of a PS string
if ([scanner scanString: @"<"
intoString: NULL])
{
value = [value stringByAppendingString: @"<<"];
}
else
{
[scanner scanCharactersFromSet: [NSCharacterSet whitespaceAndNewlineCharacterSet]
intoString: NULL];
while (![scanner scanString: @">"
intoString: NULL])
{
location = [scanner scanLocation];
if (location+2 > stringLength)
{
[NSException raise: NSPPDParseException
format: @"Badly formatted hexadecimal substring '%@' in \
PPD printer file.", qString];
// NOT REACHED
}
value = [value stringByAppendingFormat: @"%c",
16 * [self gethex: [qString characterAtIndex: location]]
+ [self gethex: [qString characterAtIndex: location+1]]];
[scanner setScanLocation: location+2];
[scanner scanCharactersFromSet: [NSCharacterSet whitespaceAndNewlineCharacterSet]
intoString: NULL];
}
}
if ([scanner scanUpToString:@"<" intoString:&part])
{
value = [value stringByAppendingString: part];
}
}
return value;
}
// Convert a character to a value between 0 and 15
-(int) gethex: (unichar) character
{
switch (character)
{
case '0': return 0;
case '1': return 1;
case '2': return 2;
case '3': return 3;
case '4': return 4;
case '5': return 5;
case '6': return 6;
case '7': return 7;
case '8': return 8;
case '9': return 9;
case 'A': return 10;
case 'B': return 11;
case 'C': return 12;
case 'D': return 13;
case 'E': return 14;
case 'F': return 15;
case 'a': return 10;
case 'b': return 11;
case 'c': return 12;
case 'd': return 13;
case 'e': return 14;
case 'f': return 15;
}
[NSException
raise: NSPPDParseException
format: @"Badly formatted hexadeximal character '%d' in PPD printer file.",
character];
return 0; /* Quiet compiler warnings */
}
@end