mirror of
https://github.com/gnustep/libs-gui.git
synced 2025-04-23 01:40:55 +00:00
git-svn-id: svn+ssh://svn.gna.org/svn/gnustep/libs/gui/trunk@38385 72102866-910b-0410-8b05-ffd578937521
1545 lines
41 KiB
Objective-C
1545 lines
41 KiB
Objective-C
/** <title>NSPrinter</title>
|
|
|
|
<abstract>Class representing a printer's capabilities.</abstract>
|
|
|
|
Copyright (C) 1996, 1997, 2004 Free Software Foundation, Inc.
|
|
|
|
Authors: Simon Frankau <sgf@frankau.demon.co.uk>
|
|
Date: June 1997
|
|
Modified for Printing Backend Support
|
|
Author: Chad Hardin <cehardin@mac.com>
|
|
Date: July 2004
|
|
|
|
This file is part of the GNUstep GUI Library.
|
|
|
|
This library is free software; you can redistribute it and/or
|
|
modify it under the terms of the GNU Lesser General Public
|
|
License as published by the Free Software Foundation; either
|
|
version 2 of the License, or (at your option) any later version.
|
|
|
|
This library is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
Lesser General Public License for more details.
|
|
|
|
You should have received a copy of the GNU Lesser General Public
|
|
License along with this library; see the file COPYING.LIB.
|
|
If not, see <http://www.gnu.org/licenses/> or write to the
|
|
Free Software Foundation, 51 Franklin Street, Fifth Floor,
|
|
Boston, MA 02110-1301, USA.
|
|
*/
|
|
|
|
#include "config.h"
|
|
#import <Foundation/NSAutoreleasePool.h>
|
|
#import <Foundation/NSArray.h>
|
|
#import <Foundation/NSDebug.h>
|
|
#import <Foundation/NSDictionary.h>
|
|
#import <Foundation/NSString.h>
|
|
#import <Foundation/NSBundle.h>
|
|
#import <Foundation/NSCharacterSet.h>
|
|
#import <Foundation/NSDictionary.h>
|
|
#import <Foundation/NSEnumerator.h>
|
|
#import <Foundation/NSException.h>
|
|
#import <Foundation/NSFileManager.h>
|
|
#import <Foundation/NSPathUtilities.h>
|
|
#import <Foundation/NSScanner.h>
|
|
#import <Foundation/NSSet.h>
|
|
#import <Foundation/NSString.h>
|
|
#import <Foundation/NSValue.h>
|
|
#import <Foundation/NSMapTable.h>
|
|
#import <Foundation/NSSet.h>
|
|
#import "AppKit/AppKitExceptions.h"
|
|
#import "AppKit/NSGraphics.h"
|
|
#import "AppKit/NSPrinter.h"
|
|
#import "GNUstepGUI/GSPrinting.h"
|
|
|
|
//
|
|
// Class variables:
|
|
//
|
|
|
|
//
|
|
// Class variables used during scanning:
|
|
//
|
|
|
|
// Character sets used in scanning.
|
|
static NSCharacterSet* newlineSet = nil;
|
|
static NSCharacterSet* keyEndSet = nil;
|
|
static NSCharacterSet* optKeyEndSet = nil;
|
|
static NSCharacterSet* valueEndSet = nil;
|
|
|
|
//Class variable to cache NSPrinters, without this they
|
|
//are created (and PPDs are parsed) ALL the time
|
|
static NSMutableDictionary* printerCache;
|
|
|
|
|
|
//
|
|
// Private methods used for PPD Parsing
|
|
//
|
|
@interface NSPrinter (PPDParsingPrivate)
|
|
|
|
-(void) loadPPDAtPath: (NSString*) PPDstring
|
|
symbolValues: (NSMutableDictionary*) ppdSymbolValues
|
|
inclusionSet: (NSMutableSet*) includeSet;
|
|
|
|
-(void) addPPDKeyword: (NSString*) mainKeyword
|
|
withScanner: (NSScanner*) PPDdata
|
|
withPPDPath: (NSString*) ppdPath;
|
|
|
|
-(void) addPPDUIConstraint: (NSScanner*) constraint
|
|
withPPDPath: (NSString*) ppdPath;
|
|
|
|
-(void) addPPDOrderDependency: (NSScanner*) dependency
|
|
withPPDPath: (NSString*) ppdPath;
|
|
|
|
-(id) addString: (NSString*) string
|
|
forKey: (NSString*) key
|
|
inTable: (NSString*) table;
|
|
|
|
-(void) addValue: (NSString*) value
|
|
andValueTranslation: (NSString*) valueTranslation
|
|
andOptionTranslation: (NSString*) optionTranslation
|
|
forKey: (NSString*) key;
|
|
|
|
-(NSString*) interpretQuotedValue: (NSString*) qString;
|
|
|
|
-(int) gethex: (unichar) character;
|
|
|
|
@end
|
|
|
|
|
|
|
|
@implementation NSPrinter
|
|
|
|
//
|
|
// Class methods
|
|
//
|
|
+(void) initialize
|
|
{
|
|
if (self == [NSPrinter class])
|
|
{
|
|
// Initial version
|
|
[self setVersion:1];
|
|
}
|
|
printerCache = RETAIN([NSMutableDictionary dictionary]);
|
|
}
|
|
|
|
/** Load the appropriate bundle for the Printer
|
|
(eg: GSLPRPrinter, GSCUPSPrinter).
|
|
*/
|
|
+(id) allocWithZone: (NSZone*) zone
|
|
{
|
|
Class principalClass;
|
|
|
|
principalClass = [[GSPrinting printingBundle] principalClass];
|
|
|
|
if (principalClass == nil)
|
|
return nil;
|
|
|
|
return [[principalClass printerClass] allocWithZone: zone];
|
|
}
|
|
|
|
|
|
//
|
|
// Finding an NSPrinter
|
|
//
|
|
+(NSPrinter*) printerWithName: (NSString*) name
|
|
{
|
|
NSEnumerator *keyEnum;
|
|
NSString *key;
|
|
NSPrinter *printer;
|
|
|
|
//First, the cache has to be managed.
|
|
//Take into account any deleted printers.
|
|
keyEnum = [[printerCache allKeys] objectEnumerator];
|
|
while ((key = [keyEnum nextObject]))
|
|
{
|
|
NSEnumerator *namesEnum;
|
|
NSString *validName;
|
|
BOOL stillValid = NO;
|
|
|
|
namesEnum = [[self printerNames] objectEnumerator];
|
|
while ((validName = [namesEnum nextObject]))
|
|
{
|
|
if ([validName isEqualToString: key])
|
|
{
|
|
stillValid = YES;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (stillValid == NO)
|
|
{
|
|
[printerCache removeObjectForKey: key];
|
|
}
|
|
}
|
|
|
|
printer = [printerCache objectForKey: name];
|
|
|
|
if (printer)
|
|
{
|
|
return printer;
|
|
}
|
|
else
|
|
{
|
|
Class principalClass;
|
|
|
|
principalClass = [[GSPrinting printingBundle] principalClass];
|
|
|
|
if (principalClass == nil)
|
|
return nil;
|
|
|
|
printer = [[principalClass printerClass] printerWithName: name];
|
|
|
|
if (printer)
|
|
{
|
|
[printerCache setObject: printer
|
|
forKey: name];
|
|
}
|
|
return printer;
|
|
}
|
|
}
|
|
|
|
//
|
|
// This now different than the OpenStep spec and instead
|
|
// follows the more useful implementation Apple choosed. In
|
|
// OpenStep, this method would read a PPD and return a NSPrinter
|
|
// based upon values from that PPD, regardless if that printer
|
|
// was actually avaiable for use or not. On the contrary, Apple's
|
|
// implementation looks
|
|
// at all avaiable printers and returns one that has the same
|
|
// type. The reason for this is because they use CUPS. CUPS
|
|
// does not work by maintaining a repository of PPDs. Instead,
|
|
// the CUPS server trasnmits PPDs as they are needed, and only
|
|
// for actual real printers. Since we cannot know how the backend
|
|
// bundles will be handling their PPDs, or if they will even be using
|
|
// PPDs for that matter, (a Win32 printing backend, for example),
|
|
// I've choosen to go with Apple's implementation. Really, I see
|
|
// little use in creating a NSPrinter for a printer that is not
|
|
// available for use in the first place, I am open for commments
|
|
// on this, of course.
|
|
+(NSPrinter*) printerWithType: (NSString*) type
|
|
{
|
|
NSEnumerator *printerNamesEnum;
|
|
NSString *printerName;
|
|
|
|
printerNamesEnum = [[self printerNames] objectEnumerator];
|
|
|
|
while ((printerName = [printerNamesEnum nextObject]))
|
|
{
|
|
NSPrinter *printer;
|
|
|
|
printer = [self printerWithName: printerName];
|
|
|
|
if ([[printer type] isEqualToString: type])
|
|
{
|
|
return printer;
|
|
}
|
|
}
|
|
return nil;
|
|
}
|
|
|
|
|
|
+(NSArray*) printerNames
|
|
{
|
|
Class principalClass;
|
|
|
|
principalClass = [[GSPrinting printingBundle] principalClass];
|
|
|
|
if (principalClass == nil)
|
|
return nil;
|
|
|
|
return [[principalClass printerClass] printerNames];
|
|
}
|
|
|
|
|
|
// See note at +(NSPrinter*) printerWithType:(NSString*) type
|
|
+(NSArray*) printerTypes
|
|
{
|
|
NSMutableSet *printerTypes;
|
|
NSEnumerator *printerNamesEnum;
|
|
NSString *printerName;
|
|
NSPrinter *printer;
|
|
|
|
printerTypes = [NSMutableSet setWithCapacity:1];
|
|
|
|
printerNamesEnum = [[self printerNames] objectEnumerator];
|
|
|
|
while ((printerName = [printerNamesEnum nextObject]))
|
|
{
|
|
printer = [self printerWithName: printerName];
|
|
|
|
[printerTypes addObject: [printer type]];
|
|
}
|
|
|
|
return [printerTypes allObjects];
|
|
}
|
|
|
|
//
|
|
// Instance methods
|
|
//
|
|
|
|
|
|
//
|
|
// Printer Attributes
|
|
//
|
|
-(NSString*) host
|
|
{
|
|
return _printerHost;
|
|
}
|
|
|
|
-(NSString*) name
|
|
{
|
|
return _printerName;
|
|
}
|
|
|
|
-(NSString*) note
|
|
{
|
|
return _printerNote;
|
|
}
|
|
|
|
-(NSString*) type
|
|
{
|
|
return _printerType;
|
|
}
|
|
|
|
//
|
|
// Retrieving Specific Information
|
|
//
|
|
-(BOOL) acceptsBinary
|
|
{
|
|
// FIXME: I'm not sure if acceptsBinary is the same as BCP protocol?
|
|
NSString *result;
|
|
NSScanner *protocols;
|
|
|
|
result = [self stringForKey: @"Protocols"
|
|
inTable: @"PPD"];
|
|
if (!result)
|
|
return NO;
|
|
|
|
protocols = [NSScanner scannerWithString: result];
|
|
|
|
while (![protocols isAtEnd])
|
|
{
|
|
[protocols scanUpToCharactersFromSet: [NSCharacterSet whitespaceCharacterSet]
|
|
intoString: &result];
|
|
|
|
if ([result isEqual:@"BCP"])
|
|
return YES;
|
|
}
|
|
|
|
return NO;
|
|
}
|
|
|
|
-(NSRect) imageRectForPaper: (NSString*) paperName
|
|
{
|
|
NSString *key;
|
|
|
|
key = [NSString stringWithFormat: @"ImageableArea/%@", paperName];
|
|
|
|
return [self rectForKey: key
|
|
inTable: @"PPD"];
|
|
}
|
|
|
|
-(NSSize) pageSizeForPaper: (NSString*) paperName
|
|
{
|
|
NSString *key;
|
|
|
|
key = [NSString stringWithFormat: @"PaperDimension/%@", paperName];
|
|
|
|
return [self sizeForKey: key
|
|
inTable: @"PPD"];
|
|
}
|
|
|
|
-(BOOL) isColor
|
|
{
|
|
return [self booleanForKey: @"ColorDevice"
|
|
inTable: @"PPD"];
|
|
}
|
|
|
|
-(BOOL) isFontAvailable: (NSString*) fontName
|
|
{
|
|
NSString *key;
|
|
|
|
key = [NSString stringWithFormat: @"Font/%@", fontName];
|
|
return [self isKey: key
|
|
inTable: @"PPD"];
|
|
}
|
|
|
|
-(int) languageLevel
|
|
{
|
|
return [self intForKey: @"LanguageLevel"
|
|
inTable: @"PPD"];
|
|
}
|
|
|
|
-(BOOL) isOutputStackInReverseOrder
|
|
{
|
|
// FIXME: Is this what is needed? I'm not sure how this is worked out.
|
|
NSString *result;
|
|
|
|
result = [self stringForKey: @"DefaultOutputOrder"
|
|
inTable: @"PPD"];
|
|
|
|
if (!result)
|
|
return NO;
|
|
|
|
if ([result caseInsensitiveCompare: @"REVERSE"] == NSOrderedSame)
|
|
return YES;
|
|
else
|
|
return NO;
|
|
}
|
|
|
|
//
|
|
// Querying the NSPrinter Tables
|
|
//
|
|
-(BOOL) booleanForKey: (NSString*) key
|
|
inTable: (NSString*) table
|
|
{
|
|
NSString *result;
|
|
result = [self stringForKey: key
|
|
inTable: table];
|
|
|
|
if (!result) //raise exception?
|
|
return NO;
|
|
|
|
if ([result caseInsensitiveCompare: @"TRUE"] == NSOrderedSame)
|
|
return YES;
|
|
else
|
|
return NO;
|
|
}
|
|
|
|
|
|
-(NSDictionary*) deviceDescription
|
|
{
|
|
NSMutableDictionary *result;
|
|
|
|
result = [NSMutableDictionary dictionary];
|
|
|
|
if ([self isKey: @"DefaultResolution"
|
|
inTable:@"PPD"])
|
|
{
|
|
int dpi = [self intForKey: @"DefaultResolution"
|
|
inTable: @"PPD"];
|
|
|
|
[result setObject: [NSNumber numberWithInt: dpi]
|
|
forKey: NSDeviceResolution];
|
|
}
|
|
|
|
if ([self isKey: @"ColorDevice"
|
|
inTable: @"PPD"])
|
|
{
|
|
BOOL color = [self booleanForKey: @"ColorDevice"
|
|
inTable: @"PPD"];
|
|
|
|
// FIXME: Should NSDeviceWhiteColorSpace be NSDeviceBlackColorSpace?
|
|
// FIXME #2: Are they calibrated?
|
|
// Basically I'm not sure which color spaces should be used...
|
|
if (color == YES)
|
|
{
|
|
[result setObject: NSDeviceCMYKColorSpace
|
|
forKey: NSDeviceColorSpaceName];
|
|
}
|
|
else
|
|
{
|
|
[result setObject: NSDeviceWhiteColorSpace
|
|
forKey: NSDeviceColorSpaceName];
|
|
}
|
|
}
|
|
|
|
if ([self isKey: @"DefaultBitsPerPixel"
|
|
inTable: @"PPD"])
|
|
{
|
|
int bits = [self intForKey: @"DefaultBitsPerPixel"
|
|
inTable: @"PPD"];
|
|
|
|
[result setObject: [NSNumber numberWithInt: bits]
|
|
forKey: NSDeviceBitsPerSample];
|
|
}
|
|
|
|
if ([self isKey: @"DefaultPageSize"
|
|
inTable: @"PPD"])
|
|
{
|
|
NSString* defaultPageSize = [self stringForKey: @"DefaultPageSize"
|
|
inTable: @"PPD"];
|
|
|
|
if (defaultPageSize)
|
|
{
|
|
NSSize paperSize = [self pageSizeForPaper: defaultPageSize];
|
|
|
|
[result setObject: [NSValue valueWithSize:paperSize]
|
|
forKey: NSDeviceSize];
|
|
}
|
|
}
|
|
|
|
[result setObject: [NSNumber numberWithBool:NO]
|
|
forKey: NSDeviceIsScreen];
|
|
|
|
[result setObject: [NSNumber numberWithBool:YES]
|
|
forKey: NSDeviceIsPrinter];
|
|
|
|
NSDebugMLLog(@"GSPrinting", @"Device Description: %@", [result description]);
|
|
return result;
|
|
}
|
|
|
|
|
|
-(float) floatForKey: (NSString*) key
|
|
inTable: (NSString*) table
|
|
{
|
|
NSString *result;
|
|
|
|
result = [self stringForKey: key
|
|
inTable: table];
|
|
|
|
if (!result) //raise exception?
|
|
return 0.0;
|
|
|
|
return [result floatValue];
|
|
}
|
|
|
|
|
|
-(int) intForKey: (NSString*) key
|
|
inTable: (NSString*) table
|
|
{
|
|
NSString *result;
|
|
|
|
result = [self stringForKey: key
|
|
inTable: table];
|
|
|
|
if (!result) //raise exception?
|
|
return 0;
|
|
|
|
return [result intValue];
|
|
}
|
|
|
|
|
|
-(NSRect) rectForKey: (NSString*) key
|
|
inTable: (NSString*) table
|
|
{
|
|
NSString *result;
|
|
NSScanner *bits;
|
|
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];
|
|
|
|
if (!value)
|
|
value = @"";
|
|
|
|
[ppdData scanString: @"\"" /*"*/
|
|
intoString: NULL];
|
|
|
|
// It is a QuotedValue if it's in quotes, and there is no option
|
|
// key, or the main key is a *JCL keyword
|
|
if (!optionKeyword || [[mainKeyword substringToIndex:3]
|
|
isEqualToString: @"JCL"])
|
|
{
|
|
value = [self interpretQuotedValue: value];
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Otherwise, scan up to the end of line or '/'
|
|
[ppdData scanUpToCharactersFromSet: valueEndSet
|
|
intoString: &value];
|
|
}
|
|
// If there is a value translation, scan it
|
|
if ([ppdData scanString: @"/"
|
|
intoString: NULL])
|
|
{
|
|
[ppdData scanUpToCharactersFromSet: newlineSet
|
|
intoString: &valueTranslation];
|
|
}
|
|
|
|
// The translations also have to have any hex substrings interpreted
|
|
if (optionTranslation)
|
|
optionTranslation = [self interpretQuotedValue: optionTranslation];
|
|
|
|
if (valueTranslation)
|
|
valueTranslation = [self interpretQuotedValue: valueTranslation];
|
|
|
|
// The keyword (or keyword/option pair, if there's a option), should only
|
|
// only have one value, unless it's one of the optionless keywords which
|
|
// allow multiple instances.
|
|
// If a keyword is read twice, 'first instance is correct', according to
|
|
// the standard.
|
|
// Finally, add the strings to the tables
|
|
if (optionKeyword)
|
|
{
|
|
NSString *mainAndOptionKeyword;
|
|
|
|
mainAndOptionKeyword=[mainKeyword stringByAppendingFormat: @"/%@",
|
|
optionKeyword];
|
|
|
|
if ([self isKey: mainAndOptionKeyword
|
|
inTable: @"PPD"])
|
|
{
|
|
return;
|
|
}
|
|
|
|
[self addValue: value
|
|
andValueTranslation: valueTranslation
|
|
andOptionTranslation: optionTranslation
|
|
forKey: mainAndOptionKeyword];
|
|
|
|
// Deal with the oddities of stringForKey:inTable:
|
|
// If this method is used to find a keyword with options, using
|
|
// just the keyword it should return an empty string
|
|
// stringListForKey:inTable:, however, should return the list of
|
|
// option keywords.
|
|
// This is done by making the first item in the array an empty
|
|
// string, which will be skipped by stringListForKey:, if necessary
|
|
if (![[_tables objectForKey: @"PPD"] objectForKey: mainKeyword])
|
|
{
|
|
[self addString: @""
|
|
forKey: mainKeyword
|
|
inTable: @"PPD"];
|
|
|
|
[self addString: @""
|
|
forKey: mainKeyword
|
|
inTable: @"PPDOptionTranslation"];
|
|
|
|
[self addString: @""
|
|
forKey: mainKeyword
|
|
inTable: @"PPDArgumentTranslation"];
|
|
|
|
}
|
|
|
|
[self addValue: optionKeyword
|
|
andValueTranslation: optionKeyword
|
|
andOptionTranslation: optionKeyword
|
|
forKey: mainKeyword];
|
|
}
|
|
else
|
|
{
|
|
if ([self isKey: mainKeyword
|
|
inTable: @"PPD"] &&
|
|
![repKeys containsObject: mainKeyword])
|
|
{
|
|
return;
|
|
}
|
|
|
|
[self addValue: value
|
|
andValueTranslation: valueTranslation
|
|
andOptionTranslation: optionTranslation
|
|
forKey: mainKeyword];
|
|
}
|
|
}
|
|
|
|
|
|
-(void) addPPDUIConstraint: (NSScanner*) constraint
|
|
withPPDPath: (NSString*) ppdPath
|
|
{
|
|
NSString* mainKey1 = nil;
|
|
NSString* optionKey1 = nil;
|
|
NSString* mainKey2 = nil;
|
|
NSString* optionKey2 = nil;
|
|
|
|
// UIConstraint should have no option keyword
|
|
if (![constraint scanString: @":"
|
|
intoString: NULL])
|
|
{
|
|
[NSException raise:NSPPDParseException
|
|
format:@"UIConstraints has option keyword in PPD File %@",
|
|
ppdPath];
|
|
}
|
|
|
|
// Skip the '*'
|
|
[constraint scanString: @"*"
|
|
intoString: NULL];
|
|
|
|
// Scan the bits. Stuff not starting with * must be an optionKeyword
|
|
[constraint scanUpToCharactersFromSet: [NSCharacterSet whitespaceCharacterSet]
|
|
intoString: &mainKey1];
|
|
|
|
if (![constraint scanString: @"*"
|
|
intoString: NULL])
|
|
{
|
|
[constraint scanUpToCharactersFromSet: [NSCharacterSet whitespaceCharacterSet]
|
|
intoString: &optionKey1];
|
|
|
|
[constraint scanString: @"*"
|
|
intoString: NULL];
|
|
|
|
}
|
|
|
|
[constraint scanUpToCharactersFromSet:
|
|
[NSCharacterSet whitespaceAndNewlineCharacterSet]
|
|
intoString: &mainKey2];
|
|
|
|
if (![constraint scanCharactersFromSet: newlineSet
|
|
intoString: NULL])
|
|
{
|
|
[constraint scanUpToCharactersFromSet:
|
|
[NSCharacterSet whitespaceAndNewlineCharacterSet]
|
|
intoString: &optionKey2];
|
|
}
|
|
else
|
|
{
|
|
optionKey2 = @"";
|
|
}
|
|
|
|
// Add to table
|
|
if (optionKey1)
|
|
mainKey1 = [mainKey1 stringByAppendingFormat: @"/%@", optionKey1];
|
|
|
|
[self addString: mainKey2
|
|
forKey: mainKey1
|
|
inTable: @"PPDUIConstraints"];
|
|
|
|
[self addString: optionKey2
|
|
forKey: mainKey1
|
|
inTable: @"PPDUIConstraints"];
|
|
|
|
}
|
|
|
|
|
|
|
|
-(void) addPPDOrderDependency: (NSScanner*) dependency
|
|
withPPDPath: (NSString*) ppdPath
|
|
{
|
|
NSString *realValue = nil;
|
|
NSString *section = nil;
|
|
NSString *keyword = nil;
|
|
NSString *optionKeyword = nil;
|
|
|
|
// Order dependency should have no option keyword
|
|
if (![dependency scanString: @":"
|
|
intoString: NULL])
|
|
{
|
|
[NSException raise: NSPPDParseException
|
|
format:@"OrderDependency has option keyword in PPD file %@",
|
|
ppdPath];
|
|
}
|
|
|
|
[dependency scanUpToCharactersFromSet: [NSCharacterSet whitespaceCharacterSet]
|
|
intoString: &realValue];
|
|
|
|
[dependency scanUpToCharactersFromSet: [NSCharacterSet whitespaceCharacterSet]
|
|
intoString: §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;
|
|
|
|
// 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
|