1/** <title>NSPrinter</title> 2 3 <abstract>Class representing a printer's capabilities.</abstract> 4 5 Copyright (C) 1996, 1997, 2004 Free Software Foundation, Inc. 6 7 Authors: Simon Frankau <sgf@frankau.demon.co.uk> 8 Date: June 1997 9 Modified for Printing Backend Support 10 Author: Chad Hardin <cehardin@mac.com> 11 Date: July 2004 12 13 This file is part of the GNUstep GUI Library. 14 15 This library is free software; you can redistribute it and/or 16 modify it under the terms of the GNU Lesser General Public 17 License as published by the Free Software Foundation; either 18 version 2 of the License, or (at your option) any later version. 19 20 This library is distributed in the hope that it will be useful, 21 but WITHOUT ANY WARRANTY; without even the implied warranty of 22 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 23 Lesser General Public License for more details. 24 25 You should have received a copy of the GNU Lesser General Public 26 License along with this library; see the file COPYING.LIB. 27 If not, see <http://www.gnu.org/licenses/> or write to the 28 Free Software Foundation, 51 Franklin Street, Fifth Floor, 29 Boston, MA 02110-1301, USA. 30*/ 31 32#include "config.h" 33#import <Foundation/NSAutoreleasePool.h> 34#import <Foundation/NSArray.h> 35#import <Foundation/NSDebug.h> 36#import <Foundation/NSDictionary.h> 37#import <Foundation/NSString.h> 38#import <Foundation/NSBundle.h> 39#import <Foundation/NSCharacterSet.h> 40#import <Foundation/NSDictionary.h> 41#import <Foundation/NSEnumerator.h> 42#import <Foundation/NSException.h> 43#import <Foundation/NSFileManager.h> 44#import <Foundation/NSPathUtilities.h> 45#import <Foundation/NSScanner.h> 46#import <Foundation/NSSet.h> 47#import <Foundation/NSString.h> 48#import <Foundation/NSValue.h> 49#import <Foundation/NSMapTable.h> 50#import <Foundation/NSSet.h> 51#import "AppKit/AppKitExceptions.h" 52#import "AppKit/NSGraphics.h" 53#import "AppKit/NSPrinter.h" 54#import "GNUstepGUI/GSPrinting.h" 55 56// 57// Class variables: 58// 59 60// 61// Class variables used during scanning: 62// 63 64// Character sets used in scanning. 65static NSCharacterSet* newlineSet = nil; 66static NSCharacterSet* keyEndSet = nil; 67static NSCharacterSet* optKeyEndSet = nil; 68static NSCharacterSet* valueEndSet = nil; 69 70//Class variable to cache NSPrinters, without this they 71//are created (and PPDs are parsed) ALL the time 72static NSMutableDictionary* printerCache; 73 74 75// 76// Private methods used for PPD Parsing 77// 78@interface NSPrinter (PPDParsingPrivate) 79 80-(void) loadPPDAtPath: (NSString*) PPDstring 81 symbolValues: (NSMutableDictionary*) ppdSymbolValues 82 inclusionSet: (NSMutableSet*) includeSet; 83 84-(void) addPPDKeyword: (NSString*) mainKeyword 85 withScanner: (NSScanner*) PPDdata 86 withPPDPath: (NSString*) ppdPath; 87 88-(void) addPPDUIConstraint: (NSScanner*) constraint 89 withPPDPath: (NSString*) ppdPath; 90 91-(void) addPPDOrderDependency: (NSScanner*) dependency 92 withPPDPath: (NSString*) ppdPath; 93 94-(id) addString: (NSString*) string 95 forKey: (NSString*) key 96 inTable: (NSString*) table; 97 98-(void) addValue: (NSString*) value 99 andValueTranslation: (NSString*) valueTranslation 100 andOptionTranslation: (NSString*) optionTranslation 101 forKey: (NSString*) key; 102 103-(NSString*) interpretQuotedValue: (NSString*) qString; 104 105-(int) gethex: (unichar) character; 106 107@end 108 109 110 111@implementation NSPrinter 112 113// 114// Class methods 115// 116+(void) initialize 117{ 118 if (self == [NSPrinter class]) 119 { 120 // Initial version 121 [self setVersion:1]; 122 } 123 printerCache = RETAIN([NSMutableDictionary dictionary]); 124} 125 126/** Load the appropriate bundle for the Printer 127 (eg: GSLPRPrinter, GSCUPSPrinter). 128*/ 129+(id) allocWithZone: (NSZone*) zone 130{ 131 Class principalClass; 132 133 principalClass = [[GSPrinting printingBundle] principalClass]; 134 135 if (principalClass == nil) 136 return nil; 137 138 return [[principalClass printerClass] allocWithZone: zone]; 139} 140 141 142// 143// Finding an NSPrinter 144// 145+(NSPrinter*) printerWithName: (NSString*) name 146{ 147 NSEnumerator *keyEnum; 148 NSString *key; 149 NSPrinter *printer; 150 151 //First, the cache has to be managed. 152 //Take into account any deleted printers. 153 keyEnum = [[printerCache allKeys] objectEnumerator]; 154 while ((key = [keyEnum nextObject])) 155 { 156 NSEnumerator *namesEnum; 157 NSString *validName; 158 BOOL stillValid = NO; 159 160 namesEnum = [[self printerNames] objectEnumerator]; 161 while ((validName = [namesEnum nextObject])) 162 { 163 if ([validName isEqualToString: key]) 164 { 165 stillValid = YES; 166 break; 167 } 168 } 169 170 if (stillValid == NO) 171 { 172 [printerCache removeObjectForKey: key]; 173 } 174 } 175 176 printer = [printerCache objectForKey: name]; 177 178 if (printer) 179 { 180 return printer; 181 } 182 else 183 { 184 Class principalClass; 185 186 principalClass = [[GSPrinting printingBundle] principalClass]; 187 188 if (principalClass == nil) 189 return nil; 190 191 printer = [[principalClass printerClass] printerWithName: name]; 192 193 if (printer) 194 { 195 [printerCache setObject: printer 196 forKey: name]; 197 } 198 return printer; 199 } 200} 201 202// 203// This now different than the OpenStep spec and instead 204// follows the more useful implementation Apple choosed. In 205// OpenStep, this method would read a PPD and return a NSPrinter 206// based upon values from that PPD, regardless if that printer 207// was actually avaiable for use or not. On the contrary, Apple's 208// implementation looks 209// at all avaiable printers and returns one that has the same 210// type. The reason for this is because they use CUPS. CUPS 211// does not work by maintaining a repository of PPDs. Instead, 212// the CUPS server trasnmits PPDs as they are needed, and only 213// for actual real printers. Since we cannot know how the backend 214// bundles will be handling their PPDs, or if they will even be using 215// PPDs for that matter, (a Win32 printing backend, for example), 216// I've choosen to go with Apple's implementation. Really, I see 217// little use in creating a NSPrinter for a printer that is not 218// available for use in the first place, I am open for commments 219// on this, of course. 220+(NSPrinter*) printerWithType: (NSString*) type 221{ 222 NSEnumerator *printerNamesEnum; 223 NSString *printerName; 224 225 printerNamesEnum = [[self printerNames] objectEnumerator]; 226 227 while ((printerName = [printerNamesEnum nextObject])) 228 { 229 NSPrinter *printer; 230 231 printer = [self printerWithName: printerName]; 232 233 if ([[printer type] isEqualToString: type]) 234 { 235 return printer; 236 } 237 } 238 return nil; 239} 240 241 242+(NSArray*) printerNames 243{ 244 Class principalClass; 245 246 principalClass = [[GSPrinting printingBundle] principalClass]; 247 248 if (principalClass == nil) 249 return nil; 250 251 return [[principalClass printerClass] printerNames]; 252} 253 254 255// See note at +(NSPrinter*) printerWithType:(NSString*) type 256+(NSArray*) printerTypes 257{ 258 NSMutableSet *printerTypes; 259 NSEnumerator *printerNamesEnum; 260 NSString *printerName; 261 NSPrinter *printer; 262 263 printerTypes = [NSMutableSet setWithCapacity:1]; 264 265 printerNamesEnum = [[self printerNames] objectEnumerator]; 266 267 while ((printerName = [printerNamesEnum nextObject])) 268 { 269 printer = [self printerWithName: printerName]; 270 271 [printerTypes addObject: [printer type]]; 272 } 273 274 return [printerTypes allObjects]; 275} 276 277// 278// Instance methods 279// 280 281 282// 283// Printer Attributes 284// 285-(NSString*) host 286{ 287 return _printerHost; 288} 289 290-(NSString*) name 291{ 292 return _printerName; 293} 294 295-(NSString*) note 296{ 297 return _printerNote; 298} 299 300-(NSString*) type 301{ 302 return _printerType; 303} 304 305// 306// Retrieving Specific Information 307// 308-(BOOL) acceptsBinary 309{ 310 // FIXME: I'm not sure if acceptsBinary is the same as BCP protocol? 311 NSString *result; 312 NSScanner *protocols; 313 314 result = [self stringForKey: @"Protocols" 315 inTable: @"PPD"]; 316 if (!result) 317 return NO; 318 319 protocols = [NSScanner scannerWithString: result]; 320 321 while (![protocols isAtEnd]) 322 { 323 [protocols scanUpToCharactersFromSet: [NSCharacterSet whitespaceCharacterSet] 324 intoString: &result]; 325 326 if ([result isEqual:@"BCP"]) 327 return YES; 328 } 329 330 return NO; 331} 332 333-(NSRect) imageRectForPaper: (NSString*) paperName 334{ 335 NSString *key; 336 337 key = [NSString stringWithFormat: @"ImageableArea/%@", paperName]; 338 339 return [self rectForKey: key 340 inTable: @"PPD"]; 341} 342 343-(NSSize) pageSizeForPaper: (NSString*) paperName 344{ 345 NSString *key; 346 347 key = [NSString stringWithFormat: @"PaperDimension/%@", paperName]; 348 349 return [self sizeForKey: key 350 inTable: @"PPD"]; 351} 352 353-(BOOL) isColor 354{ 355 return [self booleanForKey: @"ColorDevice" 356 inTable: @"PPD"]; 357} 358 359-(BOOL) isFontAvailable: (NSString*) fontName 360{ 361 NSString *key; 362 363 key = [NSString stringWithFormat: @"Font/%@", fontName]; 364 return [self isKey: key 365 inTable: @"PPD"]; 366} 367 368-(int) languageLevel 369{ 370 return [self intForKey: @"LanguageLevel" 371 inTable: @"PPD"]; 372} 373 374-(BOOL) isOutputStackInReverseOrder 375{ 376 // FIXME: Is this what is needed? I'm not sure how this is worked out. 377 NSString *result; 378 379 result = [self stringForKey: @"DefaultOutputOrder" 380 inTable: @"PPD"]; 381 382 if (!result) 383 return NO; 384 385 if ([result caseInsensitiveCompare: @"REVERSE"] == NSOrderedSame) 386 return YES; 387 else 388 return NO; 389} 390 391// 392// Querying the NSPrinter Tables 393// 394-(BOOL) booleanForKey: (NSString*) key 395 inTable: (NSString*) table 396{ 397 NSString *result; 398 result = [self stringForKey: key 399 inTable: table]; 400 401 if (!result) //raise exception? 402 return NO; 403 404 if ([result caseInsensitiveCompare: @"TRUE"] == NSOrderedSame) 405 return YES; 406 else 407 return NO; 408} 409 410 411-(NSDictionary*) deviceDescription 412{ 413 NSMutableDictionary *result; 414 415 result = [NSMutableDictionary dictionary]; 416 417 if ([self isKey: @"DefaultResolution" 418 inTable:@"PPD"]) 419 { 420 int dpi = [self intForKey: @"DefaultResolution" 421 inTable: @"PPD"]; 422 423 [result setObject: [NSNumber numberWithInt: dpi] 424 forKey: NSDeviceResolution]; 425 } 426 427 if ([self isKey: @"ColorDevice" 428 inTable: @"PPD"]) 429 { 430 BOOL color = [self booleanForKey: @"ColorDevice" 431 inTable: @"PPD"]; 432 433 // FIXME: Should NSDeviceWhiteColorSpace be NSDeviceBlackColorSpace? 434 // FIXME #2: Are they calibrated? 435 // Basically I'm not sure which color spaces should be used... 436 if (color == YES) 437 { 438 [result setObject: NSDeviceCMYKColorSpace 439 forKey: NSDeviceColorSpaceName]; 440 } 441 else 442 { 443 [result setObject: NSDeviceWhiteColorSpace 444 forKey: NSDeviceColorSpaceName]; 445 } 446 } 447 448 if ([self isKey: @"DefaultBitsPerPixel" 449 inTable: @"PPD"]) 450 { 451 int bits = [self intForKey: @"DefaultBitsPerPixel" 452 inTable: @"PPD"]; 453 454 [result setObject: [NSNumber numberWithInt: bits] 455 forKey: NSDeviceBitsPerSample]; 456 } 457 458 if ([self isKey: @"DefaultPageSize" 459 inTable: @"PPD"]) 460 { 461 NSString* defaultPageSize = [self stringForKey: @"DefaultPageSize" 462 inTable: @"PPD"]; 463 464 if (defaultPageSize) 465 { 466 NSSize paperSize = [self pageSizeForPaper: defaultPageSize]; 467 468 [result setObject: [NSValue valueWithSize:paperSize] 469 forKey: NSDeviceSize]; 470 } 471 } 472 473 [result setObject: [NSNumber numberWithBool:NO] 474 forKey: NSDeviceIsScreen]; 475 476 [result setObject: [NSNumber numberWithBool:YES] 477 forKey: NSDeviceIsPrinter]; 478 479 NSDebugMLLog(@"GSPrinting", @"Device Description: %@", [result description]); 480 return result; 481} 482 483 484-(float) floatForKey: (NSString*) key 485 inTable: (NSString*) table 486{ 487 NSString *result; 488 489 result = [self stringForKey: key 490 inTable: table]; 491 492 if (!result) //raise exception? 493 return 0.0; 494 495 return [result floatValue]; 496} 497 498 499-(int) intForKey: (NSString*) key 500 inTable: (NSString*) table 501{ 502 NSString *result; 503 504 result = [self stringForKey: key 505 inTable: table]; 506 507 if (!result) //raise exception? 508 return 0; 509 510 return [result intValue]; 511} 512 513 514-(NSRect) rectForKey: (NSString*) key 515 inTable: (NSString*) table 516{ 517 NSString *result; 518 NSScanner *bits; 519 double x1, y1, x2, y2; 520 521 result = [self stringForKey: key 522 inTable: table]; 523 524 if (!result) //raise exception? 525 return NSZeroRect; 526 527 bits = [NSScanner scannerWithString: result]; 528 if ([bits scanDouble: &x1] && 529 [bits scanDouble: &y1] && 530 [bits scanDouble: &x2] && 531 [bits scanDouble: &y2]) 532 { 533 return NSMakeRect(x1, y1, x2-x1, y2-y1); 534 } 535 return NSZeroRect; 536} 537 538-(NSSize) sizeForKey: (NSString*) key 539 inTable: (NSString*) table 540{ 541 NSString *result; 542 NSScanner *bits; 543 double x, y; 544 545 result = [self stringForKey: key 546 inTable: table]; 547 548 if (!result) //raise exception? 549 return NSZeroSize; 550 551 bits = [NSScanner scannerWithString: result]; 552 if ([bits scanDouble: &x] && 553 [bits scanDouble: &y]) 554 { 555 return NSMakeSize(x,y); 556 } 557 return NSZeroSize; 558} 559 560 561-(NSString*) stringForKey: (NSString*) key 562 inTable: (NSString*) table 563{ 564 NSArray *results; 565 566 results = [self stringListForKey: key 567 inTable: table]; 568 569 if (results == nil) 570 return nil; 571 572 return [results objectAtIndex: 0]; 573} 574 575-(NSArray*) stringListForKey: (NSString*) key 576 inTable: (NSString*) table 577{ 578 NSDictionary *tableObj; 579 NSMutableArray *result; 580 581 tableObj = [_tables objectForKey: table ]; 582 583 if (tableObj == nil) //raise exception? 584 { 585 return nil; 586 } 587 588 result = [tableObj objectForKey: key]; 589 if ([[result objectAtIndex:0] isEqual:@""]) 590 { 591 NSMutableArray *origResult = result; 592 result = [NSMutableArray array]; 593 [result addObjectsFromArray: origResult]; 594 [result removeObjectAtIndex: 0]; 595 } 596 return result; 597} 598 599 600-(NSPrinterTableStatus) statusForTable: (NSString*) table 601{ 602 NSDictionary *tableObj; 603 604 // Select correct table 605 tableObj = [_tables objectForKey: table]; 606 607 if (tableObj == nil) 608 return NSPrinterTableNotFound; 609 else if (![tableObj isKindOfClass: [NSDictionary class]]) 610 return NSPrinterTableError; 611 else 612 return NSPrinterTableOK; 613} 614 615 616-(BOOL) isKey: (NSString*) key 617 inTable: (NSString*) table 618{ 619 NSMutableDictionary *tableObj; 620 621 // Select correct table 622 tableObj = [_tables objectForKey: table]; 623 624 if (tableObj == nil) //raise exception? 625 { 626 return NO; 627 } 628 629 // And check it 630 if ([tableObj objectForKey: key] == nil) 631 return NO; 632 else 633 return YES; 634} 635 636// 637// NSCoding protocol 638// 639- (void) encodeWithCoder: (NSCoder*)aCoder 640{ 641 if ([aCoder allowsKeyedCoding]) 642 { 643 // TODO: Determine keys for NSPrinter. 644 } 645 else 646 { 647 [aCoder encodeObject: _printerHost]; 648 [aCoder encodeObject: _printerName]; 649 [aCoder encodeObject: _printerNote]; 650 [aCoder encodeObject: _printerType]; 651 [aCoder encodeObject: _tables]; 652 } 653} 654 655- (id) initWithCoder: (NSCoder*)aDecoder 656{ 657 if ([aDecoder allowsKeyedCoding]) 658 { 659 // TODO: Determine keys for NSPrinter. 660 } 661 else 662 { 663 _printerHost = [aDecoder decodeObject]; 664 _printerName = [aDecoder decodeObject]; 665 _printerNote = [aDecoder decodeObject]; 666 _printerType = [aDecoder decodeObject]; 667 _tables = [aDecoder decodeObject]; 668 } 669 return self; 670} 671 672@end 673 674 675 676/// 677///Private implementation of routines that will be usefull 678///for the printing backend bundles that subclass us. 679/// 680@implementation NSPrinter (Private) 681 682// 683// Initialisation method used by backend bundles 684// 685-(id) initWithName: (NSString*) name 686 withType: (NSString*) type 687 withHost: (NSString*) host 688 withNote: (NSString*) note 689{ 690 self = [super init]; 691 692 // Initialise instance variables 693 ASSIGN(_printerName, name); 694 ASSIGN(_printerType, type); 695 ASSIGN(_printerHost, host); 696 ASSIGN(_printerNote, note); 697 698 _tables = RETAIN([NSMutableDictionary dictionary]); 699 700 return self; 701} 702 703// 704// Deallocation of instance variables 705// 706-(void) dealloc 707{ 708 RELEASE(_printerHost); 709 RELEASE(_printerName); 710 RELEASE(_printerNote); 711 RELEASE(_printerType); 712 RELEASE(_tables); 713 714 [super dealloc]; 715} 716 717@end 718 719 720 721@implementation NSPrinter (PPDParsing) 722 723-(BOOL) parsePPDAtPath: (NSString*) ppdPath 724{ 725 NSAutoreleasePool* subpool; 726 NSMutableDictionary* ppdSymbolValues; 727 NSEnumerator* objEnum; 728 NSMutableArray* valArray; 729 730 //make sure the class variables for scanning are created 731 if (!newlineSet) 732 { 733 newlineSet = [NSCharacterSet characterSetWithCharactersInString: @"\n\r"]; 734 RETAIN(newlineSet); 735 } 736 737 if (!keyEndSet) 738 { 739 keyEndSet = [NSCharacterSet characterSetWithCharactersInString: @"\n\r\t: "]; 740 RETAIN(keyEndSet); 741 } 742 743 if (!optKeyEndSet) 744 { 745 optKeyEndSet = [NSCharacterSet characterSetWithCharactersInString: @"\n\r:/"]; 746 RETAIN(optKeyEndSet); 747 } 748 749 if (!valueEndSet) 750 { 751 valueEndSet = [NSCharacterSet characterSetWithCharactersInString: @"\n\r/"]; 752 RETAIN(valueEndSet); 753 } 754 755 756 757 [_tables setObject: [NSMutableDictionary dictionary] 758 forKey: @"PPD"]; 759 760 [_tables setObject: [NSMutableDictionary dictionary] 761 forKey: @"PPDOptionTranslation"]; 762 763 [_tables setObject: [NSMutableDictionary dictionary] 764 forKey: @"PPDArgumentTranslation"]; 765 766 [_tables setObject: [NSMutableDictionary dictionary] 767 forKey: @"PPDOrderDependency"]; 768 769 [_tables setObject: [NSMutableDictionary dictionary] 770 forKey: @"PPDUIConstraints"]; 771 772 773 // Create a temporary autorelease pool, as many temporary objects are used 774 subpool = [[NSAutoreleasePool alloc] init]; 775 776 777 // NB: There are some structure keywords (such as OpenUI/CloseUI) that may 778 // be repeated, but as yet are not used. Since they are structure keywords, 779 // they'll probably need special processing anyway, and so aren't 780 // added to this list. 781 782 // Create dictionary for temporary storage of symbol values 783 ppdSymbolValues = [NSMutableDictionary dictionary]; 784 785 //The inclusion set keeps track of what PPD files have been *Include(d). 786 //If one comes up twice recursion has occurred and we stop it. 787 // And scan the PPD itself 788 [self loadPPDAtPath: ppdPath 789 symbolValues: ppdSymbolValues 790 inclusionSet: [NSMutableSet setWithCapacity:10]]; 791 792 // Search the PPD dictionary for symbolvalues and substitute them. 793 objEnum = [[_tables objectForKey: @"PPD"] objectEnumerator]; 794 while ((valArray = [objEnum nextObject])) 795 { 796 NSString *oldValue; 797 NSString *newValue; 798 int i, max; 799 800 max = [valArray count]; 801 for (i=0 ; i < max ; i++) 802 { 803 oldValue = [valArray objectAtIndex: i]; 804 if ([oldValue isKindOfClass: [NSString class]] 805 && ![oldValue isEqual: @""] 806 && [[oldValue substringToIndex: 1] isEqual: @"^"]) 807 { 808 newValue = [ppdSymbolValues 809 objectForKey: [oldValue substringFromIndex: 1]]; 810 811 if (!newValue) 812 { 813 [NSException raise: NSPPDParseException 814 format: @"Unknown symbol value, ^%@ in PPD file %@.ppd", 815 oldValue, ppdPath]; 816 } 817 818 [valArray replaceObjectAtIndex: i 819 withObject: newValue]; 820 } 821 } 822 } 823 824 825 826 // Make sure all the required keys are present 827 //Too many PPDs don't pass the test.... 828 /* 829 objEnum = [[NSArray arrayWithObjects: @"NickName", 830 @"ModelName", 831 @"PCFileName", 832 @"Product", 833 @"PSVersion", 834 @"FileVersion", 835 @"FormatVersion", 836 @"LanguageEncoding", 837 @"LanguageVersion", 838 @"PageSize", 839 @"PageRegion", 840 @"ImageableArea", 841 @"PaperDimension", 842 @"PPD-Adobe", 843 nil] objectEnumerator]; 844 845 while ((checkVal = [objEnum nextObject])) 846 { 847 if (![self isKey: checkVal 848 inTable: @"PPD"]) 849 { 850 [NSException raise:NSPPDParseException 851 format:@"Required keyword *%@ not found in PPD file %@.ppd", 852 checkVal, PPDPath]; 853 } 854 } 855 */ 856 857 // Release the local autoreleasePool 858 [subpool drain]; 859 860 861//Sometimes it's good to see the tables... 862/* 863 NSDebugMLLog(@"GSPrinting", @"\n\nPPD: %@\n\n", 864 [[_tables objectForKey: @"PPD"] description]); 865 866 NSDebugMLLog(@"GSPrinting", @"\n\nPPDOptionTranslation: %@\n\n", 867 [[_tables objectForKey: @"PPDOptionTranslation"] description]); 868 869 NSDebugMLLog(@"GSPrinting", @"\n\nPPDArgumentTranslation: %@\n\n", 870 [[_tables objectForKey: @"PPDArgumentTranslation"] description]); 871 872 NSDebugMLLog(@"GSPrinting", @"\n\nPPDOrderDependency: %@\n\n", 873 [[_tables objectForKey: @"PPDOrderDependency"] description]); 874 875 NSDebugMLLog(@"GSPrinting", @"\n\nPPDUIConstraints: %@\n\n", 876 [[_tables objectForKey: @"PPDUIConstraints"] description]); 877*/ 878 879 880 return YES; 881} 882 883@end 884 885 886 887 888 889@implementation NSPrinter (PPDParsingPrivate) 890 891 892-(void) loadPPDAtPath: (NSString*) ppdPath 893 symbolValues: (NSMutableDictionary*) ppdSymbolValues 894 inclusionSet: (NSMutableSet*) inclusionSet 895{ 896 NSString* ppdString; 897 NSScanner* ppdData; 898 NSString* keyword; 899 900 901 //See if this ppd has been processed before 902 if ([inclusionSet member: ppdPath]) 903 { 904 //this ppd has been done already! 905 [NSException raise: NSPPDIncludeStackOverflowException 906 format: @"Recursive *Includes! PPD *Include stack: %@", 907 [[inclusionSet allObjects] description] ]; 908 } 909 910 [inclusionSet addObject: ppdPath]; 911 912 ppdString = [NSString stringWithContentsOfFile: ppdPath]; 913 if (nil == ppdString) 914 { 915 // The file isn't readable 916 [NSException raise: NSPPDParseException 917 format: @"PPD file '%@' isn't readable", ppdPath]; 918 } 919 920 // Set up the scanner - Appending a newline means that it should be 921 // able to process the last line correctly 922 ppdData = [NSScanner scannerWithString: 923 [ppdString stringByAppendingString: @"\n"]]; 924 925 [ppdData setCharactersToBeSkipped: [NSCharacterSet whitespaceCharacterSet]]; 926 927 // Main processing starts here... 928 while (YES) //Only check for the end after accounting for whitespace 929 { 930 // Get to the start of a new keyword, skipping blank lines 931 [ppdData scanCharactersFromSet: 932 [NSCharacterSet whitespaceAndNewlineCharacterSet] 933 intoString: NULL]; 934 935 //this could be the end... 936 if ([ppdData isAtEnd]) 937 break; 938 939 // All new entries should starts '*' 940 if (![ppdData scanString: @"*" 941 intoString: NULL]) 942 { 943 [NSException raise: NSPPDParseException 944 format: @"Line not starting with * in PPD file %@", 945 ppdPath]; 946 } 947 948 // Skip lines starting '*%', '*End', '*SymbolLength', or '*SymbolEnd' 949 if ([ppdData scanString: @"%" 950 intoString: NULL] 951 || [ppdData scanString: @"End" //if we get this there is problem, yes? 952 intoString: NULL] 953 || [ppdData scanString: @"SymbolLength" 954 intoString: NULL] 955 || [ppdData scanString: @"SymbolEnd" //if we get this there is problem, yes? 956 intoString: NULL]) 957 { 958 [ppdData scanUpToCharactersFromSet: newlineSet 959 intoString: NULL]; 960 continue; 961 } 962 963 // Read main keyword, up to a colon, space or newline 964 [ppdData scanUpToCharactersFromSet: keyEndSet 965 intoString: &keyword]; 966 967 // Loop if there is no value section, these keywords are ignored 968 if ([ppdData scanCharactersFromSet: newlineSet 969 intoString: NULL]) 970 { 971 continue; 972 } 973 974 // Add the line to the relevant table 975 if ([keyword isEqual: @"OrderDependency"]) 976 { 977 [self addPPDOrderDependency: ppdData 978 withPPDPath: ppdPath]; 979 } 980 else if ([keyword isEqual: @"UIConstraints"]) 981 { 982 [self addPPDUIConstraint: ppdData 983 withPPDPath: ppdPath]; 984 } 985 else if ([keyword isEqual: @"Include"]) 986 { 987 NSFileManager *fileManager; 988 NSString *fileName = nil; 989 NSString *path = nil; 990 991 fileManager = [NSFileManager defaultManager]; 992 993 [ppdData scanString: @":" 994 intoString: NULL]; 995 996 // Find the filename between two "s" 997 [ppdData scanString: @"\"" /*"*/ 998 intoString: NULL]; 999 1000 [ppdData scanUpToString: @"\"" /*"*/ 1001 intoString: &fileName]; 1002 1003 [ppdData scanString: @"\"" /*"*/ 1004 intoString: NULL]; 1005 1006 //the fileName could be an absolute path or just a filename. 1007 if ([fileManager fileExistsAtPath: fileName]) 1008 { 1009 //it was absolute, we are done 1010 path = fileName; 1011 } 1012 //it was not absolute. Check to see if it exists in the 1013 //directory of this ppd 1014 else if ([fileManager fileExistsAtPath: 1015 [[ppdPath stringByDeletingLastPathComponent] 1016 stringByAppendingPathComponent: fileName] ]) 1017 { 1018 path = [[ppdPath stringByDeletingLastPathComponent] 1019 stringByAppendingPathComponent: fileName]; 1020 } 1021 else //could not find the *Include fileName 1022 { 1023 [NSException raise: NSPPDIncludeNotFoundException 1024 format: @"Could not find *Included PPD file %@", path]; 1025 } 1026 1027 [self loadPPDAtPath: path 1028 symbolValues: ppdSymbolValues 1029 inclusionSet: inclusionSet]; 1030 } 1031 else if ([keyword isEqual: @"SymbolValue"]) 1032 { 1033 NSString *symbolName; 1034 NSString *symbolVal; 1035 1036 if (![ppdData scanString: @"^" 1037 intoString: NULL]) 1038 { 1039 [NSException raise: NSPPDParseException 1040 format:@"Badly formatted *SymbolValue in PPD file %@", 1041 ppdPath]; 1042 } 1043 1044 [ppdData scanUpToString: @":" 1045 intoString: &symbolName]; 1046 1047 1048 [ppdData scanString: @":" 1049 intoString: NULL]; 1050 1051 [ppdData scanString: @"\"" /*"*/ 1052 intoString: NULL]; 1053 1054 [ppdData scanUpToString: @"\"" /*"*/ 1055 intoString: &symbolVal]; 1056 1057 if (!symbolVal) 1058 symbolVal = @""; 1059 1060 [ppdData scanString: @"\"" /*"*/ 1061 intoString: NULL]; 1062 1063 [ppdSymbolValues setObject: symbolVal 1064 forKey: symbolName]; 1065 } 1066 else 1067 { 1068 [self addPPDKeyword: keyword 1069 withScanner: ppdData 1070 withPPDPath: ppdPath]; 1071 } 1072 1073 1074 // Skip any other data that don't conform with the specification. 1075 [ppdData scanUpToCharactersFromSet: newlineSet 1076 intoString: NULL]; 1077 } 1078} 1079 1080 1081-(void) addPPDKeyword: (NSString*) mainKeyword 1082 withScanner: (NSScanner*) ppdData 1083 withPPDPath: (NSString*) ppdPath 1084{ 1085 NSArray *repKeys; 1086 NSString* optionKeyword = nil; 1087 NSString* optionTranslation = nil; 1088 NSString* value = nil; 1089 NSString* valueTranslation = nil; 1090 1091 // Array of Repeated Keywords (Appendix B of the PostScript Printer 1092 // Description File Format Specification). 1093 repKeys = [NSArray arrayWithObjects:@"Emulators", 1094 @"Extensions", 1095 @"FaxSupport", 1096 //@"Include", (handled separately) 1097 @"Message", 1098 @"PrinterError", 1099 @"Product", 1100 @"Protocols", 1101 @"PSVersion", 1102 @"Source", 1103 @"Status", 1104 //@"UIConstraints", (handled separately) 1105 // Even though this is not mentioned in the list of repeated keywords, 1106 // it's often repeated anyway, so I'm putting it here. 1107 @"InkName", 1108 nil]; 1109 1110 1111 // Scan off any optionKeyword 1112 [ppdData scanUpToCharactersFromSet: optKeyEndSet 1113 intoString: &optionKeyword]; 1114 1115 if ([ppdData scanCharactersFromSet: newlineSet 1116 intoString: NULL]) 1117 { 1118 [NSException raise: NSPPDParseException 1119 format: @"Keyword has optional keyword but no value in PPD file %@", 1120 ppdPath]; 1121 } 1122 1123 if ([ppdData scanString: @"/" 1124 intoString: NULL]) 1125 { 1126 // Option keyword translation exists - scan it 1127 [ppdData scanUpToString: @":" 1128 intoString: &optionTranslation]; 1129 } 1130 1131 [ppdData scanString: @":" 1132 intoString: NULL]; 1133 1134 // Read the value part 1135 // Values starting with a " are read until the second ", ignoring \n etc. 1136 1137 if ([ppdData scanString: @"\"" /*"*/ 1138 intoString: NULL]) 1139 { 1140 [ppdData scanUpToString: @"\"" /*"*/ 1141 intoString: &value]; 1142 1143 [ppdData scanString: @"\"" /*"*/ 1144 intoString: NULL]; 1145 1146 // It is a QuotedValue if it's in quotes, and there is no option 1147 // key, or the main key is a *JCL keyword 1148 if (!optionKeyword || [[mainKeyword substringToIndex:3] 1149 isEqualToString: @"JCL"]) 1150 { 1151 value = [self interpretQuotedValue: value]; 1152 } 1153 } 1154 else 1155 { 1156 // Otherwise, scan up to the end of line or '/' 1157 [ppdData scanUpToCharactersFromSet: valueEndSet 1158 intoString: &value]; 1159 } 1160 1161 if (!value) 1162 { 1163 value = @""; 1164 } 1165 1166 // If there is a value translation, scan it 1167 if ([ppdData scanString: @"/" 1168 intoString: NULL]) 1169 { 1170 [ppdData scanUpToCharactersFromSet: newlineSet 1171 intoString: &valueTranslation]; 1172 } 1173 1174 // The translations also have to have any hex substrings interpreted 1175 optionTranslation = [self interpretQuotedValue: optionTranslation]; 1176 valueTranslation = [self interpretQuotedValue: valueTranslation]; 1177 1178 // The keyword (or keyword/option pair, if there's a option), should only 1179 // only have one value, unless it's one of the optionless keywords which 1180 // allow multiple instances. 1181 // If a keyword is read twice, 'first instance is correct', according to 1182 // the standard. 1183 // Finally, add the strings to the tables 1184 if (optionKeyword) 1185 { 1186 NSString *mainAndOptionKeyword; 1187 1188 mainAndOptionKeyword=[mainKeyword stringByAppendingFormat: @"/%@", 1189 optionKeyword]; 1190 1191 if ([self isKey: mainAndOptionKeyword 1192 inTable: @"PPD"]) 1193 { 1194 return; 1195 } 1196 1197 [self addValue: value 1198 andValueTranslation: valueTranslation 1199 andOptionTranslation: optionTranslation 1200 forKey: mainAndOptionKeyword]; 1201 1202 // Deal with the oddities of stringForKey:inTable: 1203 // If this method is used to find a keyword with options, using 1204 // just the keyword it should return an empty string 1205 // stringListForKey:inTable:, however, should return the list of 1206 // option keywords. 1207 // This is done by making the first item in the array an empty 1208 // string, which will be skipped by stringListForKey:, if necessary 1209 if (![[_tables objectForKey: @"PPD"] objectForKey: mainKeyword]) 1210 { 1211 [self addString: @"" 1212 forKey: mainKeyword 1213 inTable: @"PPD"]; 1214 1215 [self addString: @"" 1216 forKey: mainKeyword 1217 inTable: @"PPDOptionTranslation"]; 1218 1219 [self addString: @"" 1220 forKey: mainKeyword 1221 inTable: @"PPDArgumentTranslation"]; 1222 1223 } 1224 1225 [self addValue: optionKeyword 1226 andValueTranslation: optionKeyword 1227 andOptionTranslation: optionKeyword 1228 forKey: mainKeyword]; 1229 } 1230 else 1231 { 1232 if ([self isKey: mainKeyword 1233 inTable: @"PPD"] && 1234 ![repKeys containsObject: mainKeyword]) 1235 { 1236 return; 1237 } 1238 1239 [self addValue: value 1240 andValueTranslation: valueTranslation 1241 andOptionTranslation: optionTranslation 1242 forKey: mainKeyword]; 1243 } 1244} 1245 1246 1247-(void) addPPDUIConstraint: (NSScanner*) constraint 1248 withPPDPath: (NSString*) ppdPath 1249{ 1250 NSString* mainKey1 = nil; 1251 NSString* optionKey1 = nil; 1252 NSString* mainKey2 = nil; 1253 NSString* optionKey2 = nil; 1254 1255 // UIConstraint should have no option keyword 1256 if (![constraint scanString: @":" 1257 intoString: NULL]) 1258 { 1259 [NSException raise:NSPPDParseException 1260 format:@"UIConstraints has option keyword in PPD File %@", 1261 ppdPath]; 1262 } 1263 1264 // Skip the '*' 1265 [constraint scanString: @"*" 1266 intoString: NULL]; 1267 1268 // Scan the bits. Stuff not starting with * must be an optionKeyword 1269 [constraint scanUpToCharactersFromSet: [NSCharacterSet whitespaceCharacterSet] 1270 intoString: &mainKey1]; 1271 1272 if (![constraint scanString: @"*" 1273 intoString: NULL]) 1274 { 1275 [constraint scanUpToCharactersFromSet: [NSCharacterSet whitespaceCharacterSet] 1276 intoString: &optionKey1]; 1277 1278 [constraint scanString: @"*" 1279 intoString: NULL]; 1280 1281 } 1282 1283 [constraint scanUpToCharactersFromSet: 1284 [NSCharacterSet whitespaceAndNewlineCharacterSet] 1285 intoString: &mainKey2]; 1286 1287 if (![constraint scanCharactersFromSet: newlineSet 1288 intoString: NULL]) 1289 { 1290 [constraint scanUpToCharactersFromSet: 1291 [NSCharacterSet whitespaceAndNewlineCharacterSet] 1292 intoString: &optionKey2]; 1293 } 1294 else 1295 { 1296 optionKey2 = @""; 1297 } 1298 1299 // Add to table 1300 if (optionKey1) 1301 mainKey1 = [mainKey1 stringByAppendingFormat: @"/%@", optionKey1]; 1302 1303 [self addString: mainKey2 1304 forKey: mainKey1 1305 inTable: @"PPDUIConstraints"]; 1306 1307 [self addString: optionKey2 1308 forKey: mainKey1 1309 inTable: @"PPDUIConstraints"]; 1310 1311} 1312 1313 1314 1315-(void) addPPDOrderDependency: (NSScanner*) dependency 1316 withPPDPath: (NSString*) ppdPath 1317{ 1318 NSString *realValue = nil; 1319 NSString *section = nil; 1320 NSString *keyword = nil; 1321 NSString *optionKeyword = nil; 1322 1323 // Order dependency should have no option keyword 1324 if (![dependency scanString: @":" 1325 intoString: NULL]) 1326 { 1327 [NSException raise: NSPPDParseException 1328 format:@"OrderDependency has option keyword in PPD file %@", 1329 ppdPath]; 1330 } 1331 1332 [dependency scanUpToCharactersFromSet: [NSCharacterSet whitespaceCharacterSet] 1333 intoString: &realValue]; 1334 1335 [dependency scanUpToCharactersFromSet: [NSCharacterSet whitespaceCharacterSet] 1336 intoString: §ion]; 1337 1338 [dependency scanString: @"*" 1339 intoString: NULL]; 1340 1341 [dependency scanUpToCharactersFromSet: 1342 [NSCharacterSet whitespaceAndNewlineCharacterSet] 1343 intoString: &keyword]; 1344 1345 if (![dependency scanCharactersFromSet: newlineSet 1346 intoString: NULL]) 1347 { 1348 // Optional keyword exists 1349 [dependency scanUpToCharactersFromSet: 1350 [NSCharacterSet whitespaceAndNewlineCharacterSet] 1351 intoString: &optionKeyword]; 1352 } 1353 1354 // Go to next line of PPD file 1355 [dependency scanCharactersFromSet: newlineSet 1356 intoString: NULL]; 1357 1358 // Add to table 1359 if (optionKeyword) 1360 keyword = [keyword stringByAppendingFormat: @"/%@", optionKeyword]; 1361 1362 [self addString: realValue 1363 forKey: keyword 1364 inTable: @"PPDOrderDependency"]; 1365 1366 [self addString: section 1367 forKey: keyword 1368 inTable: @"PPDOrderDependency"]; 1369 1370} 1371 1372 1373// 1374// Adds the various values to the relevant tables, for the given key 1375// 1376-(void) addValue: (NSString*) value 1377 andValueTranslation: (NSString*) valueTranslation 1378 andOptionTranslation: (NSString*) optionTranslation 1379 forKey: (NSString*) key 1380{ 1381 [self addString: value 1382 forKey: key 1383 inTable: @"PPD"]; 1384 1385 if (valueTranslation) 1386 { 1387 [self addString: valueTranslation 1388 forKey: key 1389 inTable: @"PPDArgumentTranslation"]; 1390 } 1391 1392 if (optionTranslation) 1393 { 1394 [self addString: optionTranslation 1395 forKey: key 1396 inTable: @"PPDOptionTranslation"]; 1397 } 1398} 1399 1400 1401 1402// 1403// Adds the string to the array of strings. 1404// Or creates the array if it does not exist and adds the string 1405// 1406-(id) addString: (NSString*) string 1407 forKey: (NSString*) key 1408 inTable: (NSString*) table 1409{ 1410 NSMutableDictionary *tableObj; 1411 NSMutableArray *array; 1412 1413 tableObj = [_tables objectForKey: table]; 1414 1415 if (tableObj == nil) 1416 NSDebugMLLog(@"GSPrinting", @"Could not find table %@!", table); 1417 1418 array = (NSMutableArray*)[tableObj objectForKey:key]; 1419 1420 if (array == nil) //it does not exist, create it 1421 { 1422 array = [NSMutableArray array]; 1423 [tableObj setObject: array 1424 forKey: key]; 1425 } 1426 1427 [array addObject: string]; 1428 1429 return self; 1430} 1431 1432 1433 1434// Function to convert hexadecimal substrings 1435-(NSString*) interpretQuotedValue: (NSString*) qString 1436{ 1437 NSScanner *scanner; 1438 NSCharacterSet *emptySet; 1439 NSString *value = nil; 1440 NSString *part; 1441 int stringLength; 1442 int location; 1443 NSRange range; 1444 1445 if (!qString) 1446 { 1447 return nil; 1448 } 1449 1450 // Don't bother unless there's something to convert 1451 range = [qString rangeOfString: @"<"]; 1452 if (!range.length) 1453 return qString; 1454 1455 scanner = [NSScanner scannerWithString: qString]; 1456 emptySet = [NSCharacterSet characterSetWithCharactersInString: @""]; 1457 [scanner setCharactersToBeSkipped: emptySet]; 1458 1459 if (![scanner scanUpToString: @"<" 1460 intoString: &value]) 1461 { 1462 value = [NSString string]; 1463 } 1464 1465 stringLength = [qString length]; 1466 1467 while (![scanner isAtEnd]) 1468 { 1469 [scanner scanString: @"<" 1470 intoString: NULL]; 1471 1472 // "<<" is a valid part of a PS string 1473 if ([scanner scanString: @"<" 1474 intoString: NULL]) 1475 { 1476 value = [value stringByAppendingString: @"<<"]; 1477 } 1478 else 1479 { 1480 [scanner scanCharactersFromSet: [NSCharacterSet whitespaceAndNewlineCharacterSet] 1481 intoString: NULL]; 1482 1483 while (![scanner scanString: @">" 1484 intoString: NULL]) 1485 { 1486 location = [scanner scanLocation]; 1487 if (location+2 > stringLength) 1488 { 1489 [NSException raise: NSPPDParseException 1490 format: @"Badly formatted hexadecimal substring '%@' in \ 1491 PPD printer file.", qString]; 1492 // NOT REACHED 1493 } 1494 value = [value stringByAppendingFormat: @"%c", 1495 16 * [self gethex: [qString characterAtIndex: location]] 1496 + [self gethex: [qString characterAtIndex: location+1]]]; 1497 1498 [scanner setScanLocation: location+2]; 1499 1500 [scanner scanCharactersFromSet: [NSCharacterSet whitespaceAndNewlineCharacterSet] 1501 intoString: NULL]; 1502 } 1503 } 1504 1505 if ([scanner scanUpToString:@"<" intoString:&part]) 1506 { 1507 value = [value stringByAppendingString: part]; 1508 } 1509 } 1510 1511 return value; 1512} 1513 1514// Convert a character to a value between 0 and 15 1515-(int) gethex: (unichar) character 1516{ 1517 switch (character) 1518 { 1519 case '0': return 0; 1520 case '1': return 1; 1521 case '2': return 2; 1522 case '3': return 3; 1523 case '4': return 4; 1524 case '5': return 5; 1525 case '6': return 6; 1526 case '7': return 7; 1527 case '8': return 8; 1528 case '9': return 9; 1529 case 'A': return 10; 1530 case 'B': return 11; 1531 case 'C': return 12; 1532 case 'D': return 13; 1533 case 'E': return 14; 1534 case 'F': return 15; 1535 case 'a': return 10; 1536 case 'b': return 11; 1537 case 'c': return 12; 1538 case 'd': return 13; 1539 case 'e': return 14; 1540 case 'f': return 15; 1541 } 1542 [NSException 1543 raise: NSPPDParseException 1544 format: @"Badly formatted hexadeximal character '%d' in PPD printer file.", 1545 character]; 1546 1547 return 0; /* Quiet compiler warnings */ 1548} 1549 1550@end 1551