printer.device V44 (OS 3.5) Changes Overview · supports up to 10 units with different settings · supports RTG systems based on the CyberGfx API · simplified and extended handling of errors · drivers can flexibly extended using tag lists · extended support for driver-based color conversion and correction · 24 bit color space (16 mio. colors) for printer drivers · flexible printer settings can be controlled by the driver Changes for the printer preferences The new version of printer.device brings some changes to the printer preferences. 1. printer.device supports 10 units - each unit has its own set of preferences associated with it, each of which can be set by the user using the "printer" preferences. Opening printer.device and reading the settings functions the same as in previous version with the single difference that these actions can be performed on each of the 10 units. In order to select the unit to choose there are two possibilities: A. Simple unit selection by number, e.g. as an integer gadget. B. Comfortable selection using a popup menu displaying the names that the user can assign to each unit. In order to do this, the preference files ENV:Sys:printer.prefs (for unit 0) and ENV:Sys/printer1.prefs through ENV:Sys/printer9.prefs must be read. All of these files are IFF files and may possibly contain a FORM PDEV. This FORM will then contain the following structure (include file prefs/printertxt.h): folgende Struktur (Includedatei prefs/printertxt.h): struct PrinterDeviceUnitPrefs { LONG pd_Reserved[4]; LONG pd_UnitNum; UBYTE pd_UnitName[UNITNAMESIZE]; }; The pd_UnitNum field contains the unit number, the pd_UnitName filed contains the symbolic name. However, this string may be empty (pd_UnitName[0] == 0) in which case a replacement text must be used (e.g. Unit N where N is the actual unit number). The following program will read the names of all 10 units from the preferences files and print them to the console. The name will be replaced with "Unit N" (where N is the unit number) if the user did not specify a name for the unit. /* includes */ #include #include #include #include /* prototypes */ #include #include #include #include #include BPTR stdout; /*****************************************************************************/ #define IFFPrefChunkCnt 2 static LONG IFFPrefChunks[] = { ID_PREF, ID_PRHD, ID_PREF, ID_PDEV, }; void ReadUnitName(char *filename, char name[], int unitnum) { BPTR fp; BOOL ok; struct IFFHandle *iff; struct ContextNode *cn; struct PrefHeader phead; struct PrinterDeviceUnitPrefs pdev; sprintf(name,"Unit %ld",unitnum); if (fp = Open(filename, MODE_OLDFILE)) { if (iff = AllocIFF()) { iff->iff_Stream = fp; InitIFFasDOS(iff); if (!OpenIFF(iff, IFFF_READ)) { if (!ParseIFF(iff, IFFPARSE_STEP)) { cn = CurrentChunk(iff); if (cn->cn_ID == ID_FORM && cn->cn_Type == ID_PREF) { if (!StopChunks(iff, IFFPrefChunks, IFFPrefChunkCnt)) { ok = TRUE; while (ok) { if (ParseIFF(iff, IFFPARSE_SCAN)) break; cn = CurrentChunk(iff); if (cn->cn_Type == ID_PREF) { switch (cn->cn_ID) { case ID_PRHD: if (ReadChunkBytes(iff, &phead, sizeof(struct PrefHeader)) != sizeof(struct PrefHeader)) { ok = FALSE; break; } if (phead.ph_Version != 0) { ok = FALSE; break; } break; case ID_PDEV: if (ReadChunkBytes(iff, &pdev, sizeof(pdev)) == sizeof(pdev)) { if (pdev.pd_UnitName[0]) strcpy(name,pdev.pd_UnitName); } break; default: break; } } } } } } CloseIFF(iff); } FreeIFF(iff); } Close(fp); } } void main(void) { char filename[30]; char name[UNITNAMESIZE]; int i; stdout = Output(); ReadUnitName("ENV:Sys/printer.prefs",name,0); printf("Printer Unit Names:\n 0: %s\n",name); strcpy(filename,"ENV:Sys/printerN.prefs"); for (i = 1; i < 10; i++) { filename[15] = '0' + i; ReadUnitName(filename,name,i); printf(" %ld: %s\n",i,name); } } Any application should read this list not only upon start up but also every time something is printed, e.g. when opening the printer dialog. This enables the list to reflect any name changes the user might have made in the preferences since the list was last read. 2. The past has shown that the printer preferences do not provide enough options. Therefore many printer drivers interpret the settings in a different way. This can easily confuse the user (e.g. when two identical densities represent different amounts of ink usage). Drivers are now able to provide such information to the application which in turn can display it in the printer settings. In order to achieve this, a distinction between old and new drivers must be made. New drivers are recognized by their version (>= 44) and the PPCB_EXTENDED flag in the PrinterExtendedData structure (include devices/prtbase.h). If this is the case, the driver will include a new field called ped_TagList. This taglist stores some information that is important for the printer preferences. PRTA_DitherNames: The value of this tag is an array of 3 STRPTRs and holds the names for the three possible dither settings. Up to now these always were "Ordered", "Halftone" and "Floyd-Steinberg". The driver can now assign new names that reflect the way the driver interprets these settings. PRTA_ShadingNames: The value of this tag is an array of 4 STRPTRs and is used to hold the new names for the 4 possible shading options. In previous versions these were always "Black & White", "Grey Scale 1", "Colored" and "Grey Scale 2". This is the actual order, although in the preferences they usually were re-ordered to "Black & White", "Grey Scale 1", "Grey Scale 2" and "Colored". The array uses the order first referred to. The driver is now allowed to assign new names that better reflect its interpretation of the settings (e.g. for printers that don't support color but several grey scale qualities). PRTA_ColorCorrect: If the value of this tag is FALSE, the three checkmarks for red, green and blue correction should always be disabled. This means that the driver does either not support color correction or that it uses different settings to control them. PRTA_DensityInfo: The value of this tag is an array of 8 STRPTRs. The first string is currently unused, the following 7 provided additional information for the print densities 1 to 7. Many drivers use the density settings not only to choose the density but also other settings that affect the print qualitiy (e.g. the amount of ink to use for printing). The strings in this array are meant to inform the user of such circumstances. The preferences should of course still report the selected density in DPI (e.g. 300 x 300 DPI) which can be read from PrinterExtendedData. PRTA_Preferences: If the value of this TAG is TRUE the driver supports a set of proprietary printer options. If the tag is true, the printer preferences should enable a button gadget called "Options..." which can be used to edit the additional options. Further explanation on this topic can be found several paragraphs below. Changes regarding printing Printing of text has remained entirely unchanged. Graphics printing now allows the use of RTG bitmaps. Support is provided for all RTG system which support the CyberGFX API cybergraphics.library. If an application previously performed printer ouput using a planar bitmap or converted an RTG bitmap to a color depth of 12 or 8 bits, it can now be adapted to directly print RTG bitmaps. For high (16 bits) and true (24 or 32 bits) color bitmaps a color map need not be specified. The new command PRD_DUMPRPORTTAGS extendeds the old PRD_DUMPRPORT command by one tag list. Currently 3 tags are defined and implemented: DRPA_SourceHook: The value for this tag must be a pointer to a struct hook. This hook will be called in order to read the source data. The hook contains a NULL object and a message that is a pointer to struct DRPSourceMsg. This structure contains the position and size of the array to read as well as a pointer to a field of ULONGs into which the data will be written. This field has the exact size width*height that is contained in the message. Every pixel will be stored as an ULONG of the form 0x00RRGGBB. Typically the height will be 1 for normal printing while for landscape printing the width will be 1. Applications are allowed to use special optimizations for this case but they must always support the general case as well. If the DRPA_SourceHook is used, neither a RastPort nor a color map need to be specified. DRPA_AspectX, DRPA_AspectY: Using these tags you can control the aspect ratio of the graphic. These values superseded those that are normally obtained from the graphics mode (io_Modes) or the GfxBase. If the printing process suppresses scaling the aspect ratio will be ignored. Further information of the printer driver is in turn read from the driver's tag list: PRTA_LeftBorder and PRTA_TopBorder: Most printers have a small margin on the paper to which they can not print. For modern printers this margin will usually measure a few millimeters. If an application does not only show the printable area of the paper but the entire page instead, it can now take this margin into account: The tags specify the paper margins as inches/1000. This value must be converted into the appropriate amount of pixels, depending on the print resolution (LeftBorder*ped_XDPI/1000 or TopBorder*ped_YDPI/1000). Use this values as the offset for the rastport the bitmap of which contains the entire page while printing. This will cause the margin to be skipped while printing and the hardcopy will match the screen display very closely. PRTA_MixBWColor: If the value of this tag is TRUE, the application can mix black/white and color printing at will. When printing segments an application that has e.g. a lot of black text and only a few graphics in the printer output (typical word processor) can significantly speed up the printing process if segments that only contain black text on white paper are printed using the black/white mode of the printer, while segments containg both text and graphics are printed using the color mode. However, some printers use different cartridges or inks, negating the speed gain or resulting in poor quality of the hardcopy. Applications should forego this optimization if the PRTA_MixBWColor has a value of FALSE. One further piece of information is part of ped_PrinterClass and can also be set for old drivers: PPCB_NOSTRIPE: This flag denotes a printer driver that is not capable of segmented (or striped) printing. The consequence is that all print data must be passed to such a driver at once (which is inacceptable, especially for color output and resolutions of 300 DPI and up) or the source hook DRPA_SourceHook must be used "intelligently", i.e. when requesting data through the source hook the data must be created accordingly by the application. Please bear in mind that the source hook will always be called in the context of the driver. This context is a DOS process with a stack of currently 4 KByte. Depending on the concept of your application it may be necessary to use an Exec message within the source hook to return control to the application for creating a print segment. Error handling Error handling can now be left entirely to printer. device. Up to now the printer.device or the printer driver could only return errors if the respective error code was specified in exec/error.h or devices/printer.h. Using V44 of printer.device and new drivers new error messages can be returned as well. The formatting of a localized error message can be performed by printer.device or the driver while the ouput can also be performed by the printer.device or the application. The new command PRD_SETERRORHOOK expects an IOPrtErrReq structure for the IO request: struct IOPrtErrReq { struct Message io_Message; struct Device *io_Device; /* device node pointer */ struct Unit *io_Unit; /* unit (driver private)*/ UWORD io_Command; /* device command */ UBYTE io_Flags; BYTE io_Error; /* error or warning num */ struct Hook *io_Hook; }; The io_Hook field can either contain the value PDHOOK_NONE in order to disable automatic error handling or PDHOOK_STD in order to use an easy-requester for displaying the error message or a pointer to a structure hook. If an I/O operation of the printer.device ends with an error, that hook will be called. The object contains a pointer to the I/O request during which the error occured while the message packet contains a pointer to an PrtErrMsg structure: struct PrtErrMsg { ULONG pe_Version; /* Version of this struct */ ULONG pe_ErrorLevel; /* RETURN_WARN, RETURN_ERROR, RETURN_FAIL */ struct Window *pe_Window; /* window for EasyRequest() */ struct EasyStruct *pe_ES; ULONG *pe_IDCMP; APTR pe_ArgList; }; The pe_Version field containts the value PDHOOK_VERSION (currently 1). pe_ErrorLevel contains RETURN_WARN, RETURN_ERROR or RETURN_FAIL in order to reflect the severeness of the error. pe_Window contains a pointer to a window or NULL, the window should be used in order to use the correct screen for output. pe_ES contains a readily formatted EasyStruct structure including the title and text for the error message. pe_IDCMP may contain a pointer to a longword variable with IDCMP flags that are supposed to close a requester while pe_ArgList contains a parameter field that must be used for formatting the error message and the gadgets. All fields can be used immediately for the EasyRequest() call. The hook can be called in different tasks: either in the task that iniated the I/O request or the process of the printer.device unit. In the second the task is always a DOS process, allowing for file output and other DOS calls. Please note, however, that no stdio is defined for that process. The following source code is a hook that displays the error in an easy-requester. struct Task *ApplicationTask; int easyRequestHook(struct Hook *, struct IORequest *ior, struct PrtErrMsg *msg) { struct Task *me = ApplicationTask; if (me->tc_Node.ln_Type != NT_PROCESS || ((struct Process *) me)->pr_WindowPtr != (struct Window *) -1) { struct Window *window = msg->pe_Window; if (!window && me->tc_Node.ln_Type == NT_PROCESS) window = ((struct Process *) me)->pr_WindowPtr; return EasyRequestArgs(msg->pe_Window, msg->pe_ES, msg->pe_IDCMP, msg->pe_ArgList); } return 0; } struct Hook StdErrorHook = { { NULL, NULL }, (HOOKFUNC) HookEntry, (HOOKFUNC) easyRequestHook, NULL }; This hook uses a global variable called ApplicatoinTask in order to determine whether the error message should be suppressed (pt_WindowPtr == -1 in the case of processes) and which window should be used for EasyRequestArgs in order for the requester to open on the screen of that window. A similar approach uses the PDHOOK_STD hook, but that one always refers to the task that opened the unit. The error hook must be set back to PDHOOK_NONE prior to closing the unit of the printer device. Driver-specfic settings The printer settings of the "Printer" preferences do not suffice for many modern printers and their drivers. Since version 2.1 AmigaOS has had a separate preferences editor for the postscript printer driver that allows more influence on printer output. In order to be able to change such setting from within an application, three new printers commands appropriate possibilities for the drivers are provided. In order to support these possiblities, you must do the following from within your application. 1. Test whether the driver supports proprietary options. In this case the PRTA_Preferences tag will be TRUE. 2. Provide an "Options..." button in your printer settings. Disable this button for old drivers and drivers that do not provided proprietary options. 3. When the user click on the button lock the entire interface and use the PRD_EDITPREFS command via DoIO to open the settings window. The DoIO will return once the user has finished his changes to the settings. The error will be PDERR_CANCEL if the user cancelled the dialog, or 0 otherwise (or another error). The PRD_EDITPREFS command expects a IOPrtPrefsReq structure. This structure contains an additional field called io_TagList and knows the following tags: PPRA_Window: Pointer to a window that specifies the screen that is to be locked against all input while the dialog is open. PPRA_Screen: Pointer to a screen in case PPRA_Window is not specified. PPRA_PubScreen: The name of a public screen in case PPRA_Window is not specified. 4. If the settings made by the user should be saved or if the printer.device is closed and re-opened between changing the settings, the application must read the settings using the PRD_READPREFS command. The PRD_READPREFS command expects an IOStdReq structure. In this context io_Data contains a pointer to a buffer that is large enough to hold the data, io_Lenght contains the length of the buffer, io_Offset must be 0. A length of 8 KB should be sufficient for the most cases - in case the IOERR_BADLENGTH error is returned one should double the buffer size and try again (until success). LONG buflen = 8192; LONG err; for (;;) { if (!(ior->io_Data = AllocVec(buflen,MEMF_PUBLIC))) { err = PDERR_BUFFERMEMORY; break; } ior->io_Length = buflen; ior->io_Offset = 0; if ((err = DoIO(ior)) != IOERR_BADLENGTH) break; FreeVec(ior->io_Data); buflen *= 2; } The number of bytes that were filled in the buffer is returned in io_Actual. This buffer is absolutely private to the driver. It can only be kept in memory, copied bytewise or saved to a file. 5. After opening a printer.device unit the settings that were previously read can be written back. In order to do so, the PRD_WRITEPREFS command is is used to return the buffer with the preferences to the driver in an IOStdReq structure. In this case io_Data will point to the buffer, io_Length denotes the number of filled bytes in the buffer (i.e. it corresponds to the io_Actual of PRD_READPREFS). If an application saves the settings to a file and re-uses them when it is next used it is of course possible that the user has changed the driver for that unit in the meantime. However, the drivers can determine on their own whether or not the settings are meant for them. Therefore applications need not worry about this case.