/** NSPrinter Class representing a printer's or printer model's capabilities. Copyright (C) 1996, 1997 Free Software Foundation, Inc. Authors: Simon Frankau Date: June 1997 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 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // 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 %@", name]; // 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 %@.ppd", [printerInfo objectAtIndex:0]]; // NOT REACHED } printer = AUTORELEASE([[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]); // 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 %@.ppd", type]; // NOT REACHED } printer = AUTORELEASE([[self alloc] initWithPPD:[NSString stringWithContentsOfFile:path] withName:type withType:type withHost:@"" withNote:@"" fromFile:path isReal:NO]); // 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 = RETAIN([NSMutableArray array]); 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]]; } } RELEASE(subpool); 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); RELEASE(_printerHost); RELEASE(_printerName); RELEASE(_printerNote); RELEASE(_printerType); RELEASE(_PPD); RELEASE(_PPDOptionTranslation); RELEASE(_PPDArgumentTranslation); RELEASE(_PPDOrderDependency); RELEASE(_PPDUIConstraints); [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/%@", paperName] inTable:@"PPD"]; } - (NSSize)pageSizeForPaper:(NSString *)paperName { return [self sizeForKey:[NSString stringWithFormat:@"PaperDimension/%@", paperName] inTable:@"PPD"]; } - (BOOL)isColor { return [self booleanForKey:@"ColorDevice" inTable:@"PPD"]; } - (BOOL)isFontAvailable:(NSString *)fontName { return [self isKey:[NSString stringWithFormat:@"Font/%@", fontName] 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 %@", table]; // 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 %@", table]; // 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 %@", table]; // 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 %@", NSPrinter_INDEXFILE]; // NOT REACHED } // And create the name dictionary, loading it nameDict = RETAIN([NSDictionary dictionaryWithContentsOfFile:path]); 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 = RETAIN(name); _printerType = RETAIN(type); _printerHost = RETAIN(host); _printerNote = RETAIN(note); _cacheAcceptsBinary = _cacheOutputOrder = -1; _isRealPrinter = real; _PPD = RETAIN([NSMutableDictionary dictionary]); _PPDOptionTranslation = RETAIN([NSMutableDictionary dictionary]); _PPDArgumentTranslation = RETAIN([NSMutableDictionary dictionary]); _PPDOrderDependency = RETAIN([NSMutableDictionary dictionary]); _PPDUIConstraints = RETAIN([NSMutableDictionary dictionary]); // 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, ^%@ in PPD file %@.ppd", oldValue, PPDFileName]; // 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 *%@ not found in PPD file %@.ppd", checkVal, PPDFileName]; // NOT REACHED } } #endif // Release the local autoreleasePool RELEASE(subpool); 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 %@.ppd", PPDFileName]; // 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 %@", fileName]; // 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 %@.ppd", PPDFileName]; // 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 %@.ppd", PPDFileName]; // 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:@"/%@", optionKeyword]; 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 %@.ppd", PPDFileName]; // 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:@"/%@", optionKey1]; [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 %@.ppd", PPDFileName]; // NOT REACHED } [dependency scanUpToCharactersFromSet:[NSCharacterSet whitespaceCharacterSet] intoString:&realValue]; [dependency scanUpToCharactersFromSet:[NSCharacterSet whitespaceCharacterSet] intoString:§ion]; [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]; [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