libs-gui/Source/NSPrinter.m

1367 lines
40 KiB
Mathematica
Raw Normal View History

/*
NSPrinter.m
Class representing a printer's or printer model's capabilities.
Copyright (C) 1996, 1997 Free Software Foundation, Inc.
Authors: Simon Frankau <sgf@frankau.demon.co.uk>
Date: June 1997 - January 1998
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 Library 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
Library General Public License for more details.
If you are interested in a warranty or support for this source code,
contact Scott Christley <scottc@net-community.com> for more information.
You should have received a copy of the GNU Library General Public
License along with this library; if not, write to the Free
Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111 USA.
*/
/* NB:
* There are a few FIXMEs in the functionality left.
* Parsing of the PPDs is somewhat suboptimal.
* (I think it's best to leave optimisation until more of GNUstep is done).
* The *OpenUI, *CloseUI, *OpenGroup and *CloseGroup are not processed.
* (This is not required in the OpenStep standard, but could be useful).
*/
#include <gnustep/gui/config.h>
#include <Foundation/NSAutoreleasePool.h>
#include <Foundation/NSArray.h>
#include <Foundation/NSDictionary.h>
#include <Foundation/NSString.h>
#include <Foundation/NSBundle.h>
#include <Foundation/NSCharacterSet.h>
#include <Foundation/NSDictionary.h>
#include <Foundation/NSException.h>
#include <Foundation/NSFileManager.h>
#include <Foundation/NSPathUtilities.h>
#include <Foundation/NSScanner.h>
#include <Foundation/NSString.h>
#include <Foundation/NSUtilities.h>
#include <Foundation/NSValue.h>
#include <Foundation/NSMapTable.h>
#include <AppKit/AppKitExceptions.h>
#include <AppKit/NSGraphics.h>
#include <AppKit/NSPrinter.h>
// Define size used for the name and type maps - just use a small table
#define NAMEMAPSIZE 0
#define TYPEMAPSIZE 0
// The maximum level of nesting of *Include directives
#define MAX_PPD_INCLUDES 4
// A macro to skip whitespace over lines
#define skipSpace(x) [x scanCharactersFromSet:\
[NSCharacterSet whitespaceAndNewlineCharacterSet]\
intoString:NULL]
static NSString *NSPrinter_PATH = @"PrinterTypes";
static NSString *NSPrinter_INDEXFILE = @"Printers";
//
// Class variables:
//
// Maps holding NSPrinters with the types of printers, and the real printers
static NSMapTable *typeMap = NULL;
static NSMapTable *nameMap = NULL;
// Dictionary of real printers, from which NSPrinters can be made
static NSDictionary *nameDict = nil;
// An array to cache the available printer types
static NSArray *printerTypesAvailable = nil;
//
// 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;
// Array of Repeated Keywords (Appendix B of the PostScript Printer
// Description File Format Specification).
static NSArray *repKeys = nil;
// Array to collect the values of symbol values in.
static NSMutableDictionary *PPDSymbolValues;
// File name of the file being processed
static NSString *PPDFileName;
#ifndef LIB_FOUNDATION_LIBRARY
static void __NSRetainNothing(void *table, const void *anObject)
{
}
static void __NSReleaseNothing(void *table, void *anObject)
{
}
static NSString* __NSDescribeObjects(void *table, const void *anObject)
{
return [(NSObject*)anObject description];
}
static const NSMapTableValueCallBacks NSNonRetainedObjectMapValueCallBacks = {
(void (*)(NSMapTable *, const void *))__NSRetainNothing,
(void (*)(NSMapTable *, void *))__NSReleaseNothing,
(NSString *(*)(NSMapTable *, const void *))__NSDescribeObjects
};
#endif /* LIB_FOUNDATION_LIBRARY */
// Convert a character to a value between 0 and 15
static 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 substring in PPD printer file."];
// NOT REACHED
return 0; /* Quiet compiler warnings */
}
// Function to convert hexadecimal substrings
static 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];
skipSpace(scanner);
while (![scanner scanString:@">" intoString:NULL])
{
location = [scanner scanLocation];
if (location+2 > stringLength)
{
[NSException
raise:NSPPDParseException
format:@"Badly formatted hexadecimal substring in PPD printer file."];
// NOT REACHED
}
value = [value stringByAppendingFormat:@"%c",
16 * gethex([qString characterAtIndex:location])
+ gethex([qString characterAtIndex:location+1])];
[scanner setScanLocation:location+2];
skipSpace(scanner);
}
if([scanner scanUpToString:@"<" intoString:&part])
{
value = [value stringByAppendingString:part];
}
}
return value;
}
static NSString *getFile(NSString *name, NSString *type)
{
return [NSBundle pathForGNUstepResource: name
ofType: type
inDirectory: NSPrinter_PATH];
}
@interface NSPrinter (private)
+ allocMaps;
- initWithPPD:(NSString *)PPDstring
withName:(NSString *)name
withType:(NSString *)type
withHost:(NSString *)host
withNote:(NSString *)note
fromFile:(NSString *)file
isReal:(BOOL)real;
- loadPPD:(NSString *)PPDstring
inclusionNum:(int)includeNum;
- addPPDKeyword:(NSString *)mainKeyword
withScanner:(NSScanner *)PPDdata;
- addPPDUIConstraint:(NSScanner *)constraint;
- addPPDOrderDependency:(NSScanner *)dependency;
- addValue:(NSString *)value
andValueTranslation:(NSString *)valueTranslation
andOptionTranslation:(NSString *)optionTranslation
forKey:(NSString *)key;
- addString:(NSString *)string
forKey:(NSString *)key
inTable:(NSMutableDictionary *)table;
@end
@implementation NSPrinter
//
// Class methods
//
+ (void)initialize
{
if (self == [NSPrinter class])
{
// Initial version
[self setVersion:1];
}
}
//
// Finding an NSPrinter
//
+ (NSPrinter *)printerWithName:(NSString *)name
{
NSString *path;
NSArray *printerInfo;
NSPrinter *printer;
/* Contents of printerInfo array:
* [0]: NSString of the printer's type
* [1]: NSString of the printer's host
* [2]: NSString of the printer's note
*/
// Make sure the printer names dictionary etc. exists
if (!nameMap)
[self allocMaps];
printer = NSMapGet(nameMap, name);
// If the NSPrinter object for the printer already exists, return it
if (printer)
return printer;
// Otherwise, try to find the information in the nameDict
printerInfo = [nameDict objectForKey:name];
// Make sure you can find the printer name in the dictionary
if (!printerInfo)
{
[NSException raise:NSGenericException
format:@"Could not find printer named %s", [name cString]];
// NOT REACHED
}
// Create it
path = getFile([printerInfo objectAtIndex:0], @"ppd");
// If not found
if (path == nil || [path length] == 0)
{
[NSException raise:NSGenericException
format:@"Could not find PPD file %s.ppd",
[[printerInfo objectAtIndex:0] cString]];
// NOT REACHED
}
printer = [[[self alloc]
initWithPPD:[NSString stringWithContentsOfFile:path]
withName:name
withType:[printerInfo objectAtIndex:0]
withHost:[printerInfo objectAtIndex:1]
withNote:[printerInfo objectAtIndex:2]
fromFile:[printerInfo objectAtIndex:0]
isReal:YES] autorelease];
// Once created, put it in the map table
NSMapInsert(nameMap, name, printer);
return printer;
}
+ (NSPrinter *)printerWithType:(NSString *)type
{
NSString *path;
NSPrinter *printer = nil;
// Make sure the printer types dictionary exists
if (!typeMap)
[self allocMaps];
else
printer = NSMapGet(typeMap, type);
// If the NSPrinter is already created, use it
if (printer)
return printer;
path = getFile(type, @"ppd");
// If not found
if (path == nil || [path length] == 0)
{
[NSException raise:NSGenericException
format:@"Could not find PPD file %s.ppd",
[type cString]];
// NOT REACHED
}
printer = [[[self alloc]
initWithPPD:[NSString stringWithContentsOfFile:path]
withName:type withType:type withHost:@"" withNote:@""
fromFile:path isReal:NO] autorelease];
// Once created, put it in the hash table
NSMapInsert(typeMap, type, printer);
return printer;
}
+ (NSArray *)printerNames
{
if(!nameDict)
[NSPrinter allocMaps];
return [nameDict allKeys];
}
+ (NSArray *)printerTypes
{
NSEnumerator *pathEnum;
NSBundle *lbdle;
NSString *lpath;
NSArray *ppdpaths;
NSMutableArray *printers;
NSString *path;
NSAutoreleasePool *subpool; // There's a lot of temp strings used...
int i, max;
if (printerTypesAvailable)
return printerTypesAvailable;
printers = [[NSMutableArray array] retain];
subpool = [[NSAutoreleasePool alloc] init];
pathEnum = [NSSearchPathForDirectoriesInDomains(GSLibrariesDirectory,
NSAllDomainsMask, YES) objectEnumerator];
while ((lpath = [pathEnum nextObject]))
{
lbdle = [NSBundle bundleWithPath: lpath];
ppdpaths = [lbdle pathsForResourcesOfType:@"ppd"
inDirectory:NSPrinter_PATH];
// FIXME - should get name from contents of PPD, not filename
max = [ppdpaths count];
for (i = 0; i < max; i++)
{
path = [[ppdpaths objectAtIndex:i] lastPathComponent];
[printers addObject:[path substringToIndex:[path length]-4]];
}
}
[subpool release];
printerTypesAvailable = printers;
return printers;
}
//
// Instance methods
//
//
// Deallocation of instance variables
//
- (void)dealloc
{
// Remove object from the appropriate table
if (isRealPrinter)
NSMapRemove(nameMap, printerName);
else
NSMapRemove(typeMap, printerType);
[printerHost release];
[printerName release];
[printerNote release];
[printerType release];
[PPD release];
[PPDOptionTranslation release];
[PPDArgumentTranslation release];
[PPDOrderDependency release];
[PPDUIConstraints release];
[super dealloc];
}
//
// 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;
NSCharacterSet *whitespace;
if (cacheAcceptsBinary != -1)
return cacheAcceptsBinary;
result = [self stringForKey:@"Protocols" inTable:@"PPD"];
if (!result)
{
cacheAcceptsBinary = NO;
return NO;
}
protocols = [NSScanner scannerWithString:result];
whitespace = [NSCharacterSet whitespaceCharacterSet];
while(![protocols isAtEnd])
{
[protocols scanUpToCharactersFromSet:whitespace intoString:&result];
if ([result isEqual:@"BCP"])
{
cacheAcceptsBinary = YES;
return YES;
}
}
cacheAcceptsBinary = NO;
return NO;
}
- (NSRect)imageRectForPaper:(NSString *)paperName
{
return [self rectForKey:[NSString
stringWithFormat:@"ImageableArea/%s",
[paperName cString]]
inTable:@"PPD"];
}
- (NSSize)pageSizeForPaper:(NSString *)paperName
{
return [self sizeForKey:[NSString
stringWithFormat:@"PaperDimension/%s",
[paperName cString]]
inTable:@"PPD"];
}
- (BOOL)isColor
{
return [self booleanForKey:@"ColorDevice" inTable:@"PPD"];
}
- (BOOL)isFontAvailable:(NSString *)fontName
{
return [self isKey:[NSString stringWithFormat:@"Font/%s", [fontName cString]]
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;
if (cacheOutputOrder != -1)
return cacheOutputOrder;
result = [self stringForKey:@"DefaultOutputOrder" inTable:@"PPD"];
if (!result)
{
cacheOutputOrder = NO;
return NO;
}
if ([result isEqual:@"Reverse"])
{
cacheOutputOrder = YES;
return YES;
}
cacheOutputOrder = NO;
return NO;
}
//
// Querying the NSPrinter Tables
//
/*
* Caching of converted values:
* To speed up retrieving non-string values, once they are read and converted
* they are then cached. Since these use the converted value of a string in
* whatever table they are in, this assumes there is only one string
* associated with the key, and so stringListForKey will not be used. The
* second place in the array attached for the key is therefore used to cache
* the information, and generally assumptions are made which are Bad Things,
* but it seems quicker and simpler than other ways and shouldn't go wrong in
* normal use (?).
*/
- (BOOL)booleanForKey:(NSString *)key
inTable:(NSString *)table
{
NSMutableArray *result;
result = (NSMutableArray *)[self stringListForKey:key inTable:table];
if (!result)
return NO;
if ([result count] == 2)
{
// Retrieve cached result
return [(NSNumber *)[result objectAtIndex:1] boolValue];
}
if ([[result objectAtIndex:0] isEqual:@"True"])
{
// Cache result
[result addObject:[NSNumber numberWithBool:YES]];
return YES;
}
// Cache result
[result addObject:[NSNumber numberWithBool:NO]];
return NO;
}
- (NSDictionary *)deviceDescription
{
/* FIXME: This is all rather dodgy - I don't have detailed information.
* I think I'll wait until NSWindow's deviceDescription is
* implemented, and then use that, since I'm not sure as to what sort
* of objects the values should be, and it would be nice to get the
* deviceDescriptions methods to match in the way they work.
*/
NSDictionary *result;
int dpi = [self intForKey:@"DefaultResolution" inTable:@"PPD"];
BOOL color = [self booleanForKey:@"ColorDevice" inTable:@"PPD"];
NSString *colorSpaceName;
int bits = [self intForKey:@"DefaultBitsPerPixel" inTable:@"PPD"];
NSSize paperSize = [self pageSizeForPaper:
[self stringForKey:@"DefaultPageSize"
inTable:@"PPD"]];
// Guess 300 dpi
if (!dpi)
dpi = 300;
// FIXME: Should NSDeviceWhiteColorSpace be NSDeviceBlackColorSpace?
// FIXME #2: Are they calibrated?
// Basically I'm not sure which color spaces should be used...
if (color)
colorSpaceName = NSDeviceCMYKColorSpace;
else
colorSpaceName = NSDeviceWhiteColorSpace;
if (!bits) // Either not found, or 'None'
bits=1;
// If the paper size wasn't found, try Letter
if (!(paperSize.width && paperSize.height))
paperSize = NSMakeSize(612,792);
// Create the dictionary...
result = [NSDictionary dictionaryWithObjectsAndKeys:
[NSNumber numberWithInt:dpi], NSDeviceResolution,
colorSpaceName, NSDeviceColorSpaceName,
[NSNumber numberWithInt:bits], NSDeviceBitsPerSample,
[NSNumber numberWithBool:NO], NSDeviceIsScreen,
[NSNumber numberWithBool:YES], NSDeviceIsPrinter,
[NSValue valueWithSize:paperSize], NSDeviceSize,
nil];
return result;
}
- (float)floatForKey:(NSString *)key
inTable:(NSString *)table
{
NSMutableArray *result;
float number;
result = (NSMutableArray *)[self stringListForKey:key inTable:table];
if (!result)
return 0.0;
if ([result count] == 2)
{
// Retrieve cached result
return [(NSNumber *)[result objectAtIndex:1] floatValue];
}
number = [(NSString *)[result objectAtIndex:0] floatValue];
// Cache result
[result addObject:[NSNumber numberWithFloat:number]];
// And return it
return number;
}
- (int)intForKey:(NSString *)key
inTable:(NSString *)table
{
NSMutableArray *result;
int number;
result = (NSMutableArray *)[self stringListForKey:key inTable:table];
if (!result)
return 0;
if ([result count] == 2)
{
// Retrieve cached result
return [(NSNumber *)[result objectAtIndex:1] intValue];
}
number = [(NSString *)[result objectAtIndex:0] intValue];
// Cache result
[result addObject:[NSNumber numberWithInt:number]];
// And return it
return number;
}
- (NSRect)rectForKey:(NSString *)key
inTable:(NSString *)table
{
NSMutableArray *result;
NSScanner *bits;
float x1, y1, x2, y2;
NSRect rectangle;
result = (NSMutableArray *)[self stringListForKey:key inTable:table];
if (!result)
return NSZeroRect;
if ([result count] == 2)
{
// Retrieve cached result
return [(NSValue *)[result objectAtIndex:1] rectValue];
}
bits = [NSScanner scannerWithString:[result objectAtIndex:0]];
if ([bits scanFloat:&x1] &&
[bits scanFloat:&y1] &&
[bits scanFloat:&x2] &&
[bits scanFloat:&y2])
{
rectangle = NSMakeRect(x1, y1, x2-x1, y2-y1);
// Cache result
[result addObject:[NSValue valueWithRect:rectangle]];
// And return it
return rectangle;
}
return NSZeroRect;
}
- (NSSize)sizeForKey:(NSString *)key
inTable:(NSString *)table
{
NSMutableArray *result;
NSScanner *bits;
float x, y;
NSSize size;
result = (NSMutableArray *)[self stringListForKey:key inTable:table];
if (!result)
return NSZeroSize;
if ([result count] == 2)
{
// Retrieve cached result
return [(NSValue *)[result objectAtIndex:1] sizeValue];
}
bits = [NSScanner scannerWithString:[result objectAtIndex:0]];
if ([bits scanFloat:&x] &&
[bits scanFloat:&y])
{
size = NSMakeSize(x,y);
// Cache result
[result addObject:[NSValue valueWithSize:size]];
// And return it
return size;
}
return NSZeroSize;
}
- (NSString *)stringForKey:(NSString *)key
inTable:(NSString *)table
{
NSMutableDictionary *checkMe = nil;
NSMutableArray *result;
// Select correct table
if ([table isEqual:@"PPD"])
checkMe = PPD;
else if ([table isEqual:@"PPDOptionTranslation"])
checkMe = PPDOptionTranslation;
else if ([table isEqual:@"PPDArgumentTranslation"])
checkMe = PPDArgumentTranslation;
else if ([table isEqual:@"PPDOrderDependency"])
checkMe = PPDOrderDependency;
else if ([table isEqual:@"PPDUIConstraints"])
checkMe = PPDUIConstraints;
else
{
[NSException raise:NSGenericException
format:@"Could not find PPD table %s", [table cString]];
// NOT REACHED
}
// And check it
result = [checkMe objectForKey:key];
if (!result)
// Not found
return nil;
return [result objectAtIndex:0];
}
- (NSArray *)stringListForKey:(NSString *)key
inTable:(NSString *)table
{
NSMutableDictionary *checkMe = nil;
NSMutableArray *result;
// Select correct Table
if ([table isEqual:@"PPD"])
checkMe = PPD;
else if ([table isEqual:@"PPDOptionTranslation"])
checkMe = PPDOptionTranslation;
else if ([table isEqual:@"PPDArgumentTranslation"])
checkMe = PPDArgumentTranslation;
else if ([table isEqual:@"PPDOrderDependency"])
checkMe = PPDOrderDependency;
else if ([table isEqual:@"PPDUIConstraints"])
checkMe = PPDUIConstraints;
else
{
[NSException raise:NSGenericException
format:@"Could not find PPD table %s", [table cString]];
// NOT REACHED
}
// And check it
result = [checkMe objectForKey:key];
if (!result)
// Not found
return nil;
if ([[result objectAtIndex:0] isEqual:@""])
{
NSMutableArray *oldResult = result;
result = [NSMutableArray array];
[result addObjectsFromArray:oldResult];
[result removeObjectAtIndex:0];
}
return result;
}
- (NSPrinterTableStatus)statusForTable:(NSString *)table
{
NSMutableDictionary *checkMe;
// Select correct table
if ([table isEqual:@"PPD"])
checkMe = PPD;
else if ([table isEqual:@"PPDOptionTranslation"])
checkMe = PPDOptionTranslation;
else if ([table isEqual:@"PPDArgumentTranslation"])
checkMe = PPDArgumentTranslation;
else if ([table isEqual:@"PPDOrderDependency"])
checkMe = PPDOrderDependency;
else if ([table isEqual:@"PPDUIConstraints"])
checkMe = PPDUIConstraints;
else
return NSPrinterTableNotFound;
if (checkMe)
return NSPrinterTableOK;
// Shouldn't happen!
return NSPrinterTableError;
}
- (BOOL)isKey:(NSString *)key
inTable:(NSString *)table
{
NSMutableDictionary *checkMe = nil;
NSMutableArray *result;
// Select correct table
if ([table isEqual:@"PPD"])
checkMe = PPD;
else if ([table isEqual:@"PPDOptionTranslation"])
checkMe = PPDOptionTranslation;
else if ([table isEqual:@"PPDArgumentTranslation"])
checkMe = PPDArgumentTranslation;
else if ([table isEqual:@"PPDOrderDependency"])
checkMe = PPDOrderDependency;
else if ([table isEqual:@"PPDUIConstraints"])
checkMe = PPDUIConstraints;
else
{
[NSException raise:NSGenericException
format:@"Could not find PPD table %s", [table cString]];
// NOT REACHED
}
// And check it
result = [checkMe objectForKey:key];
if (!result)
// Not found
return NO;
return YES;
}
//
// NSCoding protocol
//
- (void) encodeWithCoder: (NSCoder*)aCoder
{
// [super encodeWithCoder:aCoder];
[aCoder encodeObject: printerHost];
[aCoder encodeObject: printerName];
[aCoder encodeObject: printerNote];
[aCoder encodeObject: printerType];
[aCoder encodeValueOfObjCType: @encode(int) at: &cacheAcceptsBinary];
[aCoder encodeValueOfObjCType: @encode(int) at: &cacheOutputOrder];
[aCoder encodeValueOfObjCType: @encode(BOOL) at: &isRealPrinter];
[aCoder encodeObject: PPD];
[aCoder encodeObject: PPDOptionTranslation];
[aCoder encodeObject: PPDArgumentTranslation];
[aCoder encodeObject: PPDOrderDependency];
[aCoder encodeObject: PPDUIConstraints];
}
- (id) initWithCoder: (NSCoder*)aDecoder
{
// self = [super initWithCoder:aDecoder];
[aDecoder decodeValueOfObjCType: @encode(id) at: &printerHost];
[aDecoder decodeValueOfObjCType: @encode(id) at: &printerName];
[aDecoder decodeValueOfObjCType: @encode(id) at: &printerNote];
[aDecoder decodeValueOfObjCType: @encode(id) at: &printerType];
[aDecoder decodeValueOfObjCType: @encode(int) at: &cacheAcceptsBinary];
[aDecoder decodeValueOfObjCType: @encode(int) at: &cacheOutputOrder];
[aDecoder decodeValueOfObjCType: @encode(BOOL) at: &isRealPrinter];
[aDecoder decodeValueOfObjCType: @encode(id) at: &PPD];
[aDecoder decodeValueOfObjCType: @encode(id) at: &PPDOptionTranslation];
[aDecoder decodeValueOfObjCType: @encode(id) at: &PPDArgumentTranslation];
[aDecoder decodeValueOfObjCType: @encode(id) at: &PPDOrderDependency];
[aDecoder decodeValueOfObjCType: @encode(id) at: &PPDUIConstraints];
return self;
}
//
// Private Methods
//
//
// Allocate the printer name to PPD filename index dictionary
//
+ allocMaps
{
NSString *path;
// Allocate name and type maps
typeMap = NSCreateMapTable(NSObjectMapKeyCallBacks,
NSNonRetainedObjectMapValueCallBacks,
TYPEMAPSIZE);
nameMap = NSCreateMapTable(NSObjectMapKeyCallBacks,
NSNonRetainedObjectMapValueCallBacks,
NAMEMAPSIZE);
// Load the index file
path = getFile(NSPrinter_INDEXFILE, nil);
// If not found
if (path == nil || [path length] == 0)
{
[NSException raise:NSGenericException
format:@"Could not find index of printers, file %s",
[NSPrinter_INDEXFILE cString]];
// NOT REACHED
}
// And create the name dictionary, loading it
nameDict = [[NSDictionary dictionaryWithContentsOfFile:path] retain];
return self;
}
//
// Initialisation method
//
// To keep loading of PPDs relatively fast, not much checking is done on it.
- initWithPPD:(NSString *)PPDstring
withName:(NSString *)name
withType:(NSString *)type
withHost:(NSString *)host
withNote:(NSString *)note
fromFile:(NSString *)file
isReal:(BOOL)real
{
NSAutoreleasePool *subpool;
NSEnumerator *objEnum;
NSMutableArray *valArray;
// Initialise instance variables
printerName = [name retain];
printerType = [type retain];
printerHost = [host retain];
printerNote = [note retain];
cacheAcceptsBinary = cacheOutputOrder = -1;
isRealPrinter = real;
PPD = [[NSMutableDictionary dictionary] retain];
PPDOptionTranslation = [[NSMutableDictionary dictionary] retain];
PPDArgumentTranslation = [[NSMutableDictionary dictionary] retain];
PPDOrderDependency = [[NSMutableDictionary dictionary] retain];
PPDUIConstraints = [[NSMutableDictionary dictionary] retain];
// Create a temporary autorelease pool, as many temporary objects are used
subpool = [[NSAutoreleasePool alloc] init];
// Create character sets used during scanning
newlineSet = [NSCharacterSet characterSetWithCharactersInString:@"\n\r"];
keyEndSet = [NSCharacterSet characterSetWithCharactersInString:@"\n\r\t: "];
optKeyEndSet = [NSCharacterSet characterSetWithCharactersInString:@"\n\r:/"];
valueEndSet = [NSCharacterSet characterSetWithCharactersInString:@"\n\r/"];
// Allowed repeated keys, used during scanning.
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];
// Set the file name to use
PPDFileName = file;
// 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];
// And scan the PPD itself
[self loadPPD:PPDstring inclusionNum:0];
// Search the PPD dictionary for symbolvalues, and substitute them.
objEnum = [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, ^%s in PPD file %s.ppd",
[oldValue cString],
[PPDFileName cString]];
// NOT REACHED
}
[valArray replaceObjectAtIndex:i withObject:newValue];
}
}
}
#if 0
// DISABLED: Though the following keywords *should* be present, there seems
// to be few problems is they are omitted. Many of the .ppd files I have
// don't have *LanguageEncoding, for example.
// Make sure all the required keys are present
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 *%s not found in PPD file %s.ppd",
[checkVal cString],
[PPDFileName cString]];
// NOT REACHED
}
}
#endif
// Release the local autoreleasePool
[subpool release];
return self;
}
- loadPPD:(NSString *)PPDstring
inclusionNum:(int)includeNum
{
NSScanner *PPDdata;
NSString *keyword;
// 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 (1) // Check it is not at end only after skipping the blanks
{
// Get to the start of a new keyword, skipping blank lines
skipSpace(PPDdata);
if ([PPDdata isAtEnd])
break;
// All new entries should starts '*'
if (![PPDdata scanString:@"*" intoString:NULL])
{
[NSException raise:NSPPDParseException
format:@"Line not starting * in PPD file %s.ppd",
[PPDFileName cString]];
// NOT REACHED
}
// Skip lines starting '*%', '*End', '*SymbolLength', or '*SymbolEnd'
if ([PPDdata scanString:@"%" intoString:NULL]
|| [PPDdata scanString:@"End" intoString:NULL]
|| [PPDdata scanString:@"SymbolLength" intoString:NULL]
|| [PPDdata scanString:@"SymbolEnd" 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
if ([PPDdata scanCharactersFromSet:newlineSet intoString:NULL])
continue;
// Add the line to the relevant table
if ([keyword isEqual:@"OrderDependency"])
[self addPPDOrderDependency:PPDdata];
else if ([keyword isEqual:@"UIConstraints"])
[self addPPDUIConstraint:PPDdata];
else if ([keyword isEqual:@"Include"])
{
NSString *fileName;
NSString *path;
[PPDdata scanString:@":" intoString:NULL];
// Find the filename between two "s
[PPDdata scanString:@"\"" intoString:NULL];
[PPDdata scanUpToString:@"\"" intoString:&fileName];
[PPDdata scanString:@"\"" intoString:NULL];
// Load the file
path = getFile(fileName, nil);
// If not found
if (path == nil || [path length] == 0)
{
[NSException raise:NSPPDIncludeNotFoundException
format:@"Could not find included PPD file %s",
[fileName cString]];
// NOT REACHED
}
includeNum++;
if (includeNum > MAX_PPD_INCLUDES)
{
[NSException raise:NSPPDIncludeStackOverflowException
format:@"Too many *Includes in PPD"];
// NOT REACHED
}
[self loadPPD:[NSString stringWithContentsOfFile:path]
inclusionNum:includeNum];
}
else if ([keyword isEqual:@"SymbolValue"])
{
NSString *symbolName;
NSString *symbolVal;
if (![PPDdata scanString:@"^" intoString:NULL])
{
[NSException
raise:NSPPDParseException
format:@"Badly formatted *SymbolValue in PPD file %s.ppd",
[PPDFileName cString]];
// NOT REACHED
}
[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];
}
return self;
}
- addPPDKeyword:(NSString *)mainKeyword
withScanner:(NSScanner *)PPDdata
{
NSString *optionKeyword = nil;
NSString *optionTranslation = nil;
NSString *value = nil;
NSString *valueTranslation = 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 %s.ppd",
[PPDFileName cString]];
// NOT REACHED
}
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 = 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 = interpretQuotedValue(optionTranslation);
if (valueTranslation)
valueTranslation = 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=[mainKeyword
stringByAppendingFormat:@"/%s",
[optionKeyword cString]];
if ([self isKey:mainAndOptionKeyword inTable:@"PPD"])
return self;
[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 (![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;
[self addValue:value
andValueTranslation:valueTranslation
andOptionTranslation:optionTranslation
forKey:mainKeyword];
}
return self;
}
- addPPDUIConstraint:(NSScanner *)constraint
{
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 PPDFileName %s.ppd",
[PPDFileName cString]];
// NOT REACHED
}
// 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:@"/%s",[optionKey1 cString]];
[self addString:mainKey2
forKey:mainKey1
inTable:PPDUIConstraints];
[self addString:optionKey2
forKey:mainKey1
inTable:PPDUIConstraints];
return self;
}
- addPPDOrderDependency:(NSScanner *)dependency
{
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 %s.ppd",
[PPDFileName cString]];
// NOT REACHED
}
[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:@"/%s",
[optionKeyword cString]];
[self addString:realValue forKey:keyword inTable:PPDOrderDependency];
[self addString:section forKey:keyword inTable:PPDOrderDependency];
return self;
}
//
// Adds the various values to the relevant tables, for the given key
//
- 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];
return self;
}
//
// Adds the string to the array of strings
//
- addString:(NSString *)string
forKey:(NSString *)key
inTable:(NSMutableDictionary *)table
{
NSMutableArray *array;
array = (NSMutableArray *)[table objectForKey:key];
if (array)
// Add string to existing array
[array addObject:string];
else
// Create the array if it does not exist
[table setObject:[NSMutableArray arrayWithObject:string] forKey:key];
return self;
}
@end