/** NSPrinter Class representing a printer's capabilities. Copyright (C) 1996, 1997, 2004 Free Software Foundation, Inc. Authors: Simon Frankau Date: June 1997 Modified for Printing Backend Support Author: Chad Hardin 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 or write to the Free Software Foundation, 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "config.h" #import #import #import #import #import #import #import #import #import #import #import #import #import #import #import #import #import #import #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; double x1, y1, x2, y2; result = [self stringForKey: key inTable: table]; if (!result) //raise exception? return NSZeroRect; bits = [NSScanner scannerWithString: result]; if ([bits scanDouble: &x1] && [bits scanDouble: &y1] && [bits scanDouble: &x2] && [bits scanDouble: &y2]) { return NSMakeRect(x1, y1, x2-x1, y2-y1); } return NSZeroRect; } -(NSSize) sizeForKey: (NSString*) key inTable: (NSString*) table { NSString *result; NSScanner *bits; double x, y; result = [self stringForKey: key inTable: table]; if (!result) //raise exception? return NSZeroSize; bits = [NSScanner scannerWithString: result]; if ([bits scanDouble: &x] && [bits scanDouble: &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]; if (nil == ppdString) { // The file isn't readable [NSException raise: NSPPDParseException format: @"PPD file '%@' isn't readable", 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]; } // Skip any other data that don't conform with the specification. [ppdData scanUpToCharactersFromSet: newlineSet intoString: NULL]; } } -(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]; [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 (!value) { 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 optionTranslation = [self interpretQuotedValue: optionTranslation]; 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: §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: @"/%@", 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; if (!qString) { return nil; } // 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