Fl_Device - a Device / Printing patch for FLTK-1.x


License: LGPL with with fltk exceptions, see http://fltk.org/

!!! THE PATCH IS SOURCE COMPATIBLE WITH THE ORIGINAL LIBRARY, BUT NOT BINARY.
This means that if you use this version of fltk you have to recompile also your program.

Quick start

 The printing consists of several steps and can be performed i.e. by :

#include <FL/fl_printer_chooser.H>

/*1*/ Fl_Printer * printer = fl_printer_chooser(...);        // lets the user choose the printer and constructs the device
/*2*/ Fl_Output_Device * previous = printer->set_current();  // sets the printer as the current output device, returns previously active device
/*3*/ printer->page(Fl_Printer::A4);                         // sets the first page with A4 size
/*4*/ printer->place(....);                                  // sets the position and scaling of the drawings on the page;
/*5*/ fl_draw(widget);                                       // draws the widget, usually a group encapsulating other widgets - drawings to be printed
.
.      /* optionally repeat steps 4,5 or 3,4,5 for printing other widgets on the same or other pages,
drawing primitives can be also used */
.
/*6*/ delete printer;                                        // closes printing device
/*7*/ previous->set_current()                                // sets back previously active device, usually screen/computer display


Optionally, you can use sequence

/*1*/ Fl_Printer * printer = fl_printer_chooser(...);        // lets the user choose the printer and constructs the device
/*2*/ printer->page(Fl_Printer::A4);                         // sets the first page with A4 size
/*3*/ printer->place(....);                                  // sets the position of the drawings on the page;
/*4*/ printer->draw(widget);                                 // draws the widget, usually a group encapsulating other widgets - drawings to be printed
// it also swaps the devices
...
/*5*/ delete printer;                                        // closes printing device

without explicit change of the device (printer->draws(..) does this for you. On the other hand approach does not allow you to draw primitives directly - only widgets, groups...).

You should also compile Fl_PS_Printer.cxx and/or Fl_GDI_Printer.cxx from src directory in your project.

Installation:

There are two tarbals at http://phy19.phy.tcd.ie/fltk/Fl_Device/: full version (fltk-1.1.x-Fl_Device-x.tgz) and "patch" version Fl_Device-x.tgz. The advantage of the second one is that it uses the source tree of original fltk. What you need is to make the symbolic link of the original fltk tree to /generic subdirectory in Fl_Device source tree. Fl_Device does not touch that directory, but when you cvs-update the original version, it will be reflected in Fl_Device tree as well (apart from several files). Windows users are out of luck, they need to use full version.

The printing devices - Fl_PS_Printer and Fl_GDI_Printer (for windows printing) are not compiled by default to the library, so if you want to use them you should add Fl_PS_Printer.cxx and/or Fl_GDI_Printer from /src subdirectory to your project, or simply add lines

#include<src/Fl_PS_Printer.cxx>

#ifdef WIN32
#include<src/Fl_GDI_Printer.cxx>
#endif

somewhere to one of your source files, assuming that you have fltk root directory in your include path (use -I switch). In the future the devices will form separate libraries.
tk)

Overview:

This library allows printing of fltk drawings to printers and postscript files. It is performed by dispatching all drawing primitives to alternative printer device.
Printing is performed by constructing particular printing device and redirecting drawings to that device. You should use  Fl_Output_Device::set_current() method or static function  Fl_Output_Device::current(...).   After that you can perform drawing chosen widget (usually an encapsulating group) . The printing usually consists of:


Printing to PostScript files

  1. #include <Fl/Fl_PS_Printer.H>
  2. open  a FILE * to which you want to print
  3. use Fl_PS_Printer(...) constructor upon it (see examples for printing to file)
  4. for paging device (not eps!) use one of overloaded functions Fl_PS_Printer::page(...)
  5. place /scale the widget (usually a group) you want to print using some place() method
  6. draw  the widget using Fl_PS_Printer::draw(Fl_Widget *) method
  7. repeat step 7 respectively 5, 6, 7 for printing of other widgets on the same or  next page(s)
  8. destruct device
  9. close file

Printing to standard (platform dependent) printer

For unices, printing is performed using external printing command to which standard input the postscript stream is piped. You can use either  non-dialog commands (such as lpr) or printer manager GUIs (usually front-ends to cups)  which open a dialog before (kprinter, qtcups, xpp). On windows, printing recently uses  windows standard PrintDlg dialog. It  might be changed for  "FLTK-native" printing dialog in the future.
  1. #include  <FL/ fl_printer_chooser .H>
  2. use fl_file_chooser(...) which returns a pointer to  new printing device.
  3. use steps 4, 5, 6, 7, 8 as for printing to postscript file
  4. destruct the device

Classes:

Class hierarchy

Fl_Output_Device
Fl_Printer
Fl_PS_Printer
Fl_GDI_Printer

Fl_Output_Device

This is the base class which defines all drawing primitives as its virtual methods.  It also represents screen drawing device. The only instance of this class is "fltk", variable "fl" normally points to its address. When printing to a printer, fl points to particular printing device. The standard fltk primitives are inline functions like
inline void fl_draw(...){ fl->draw(...);};
Apart from all primitives the device also defines method

Fl_Output_Device * Fl_Output_Device::set_current() 

which sets particular device as active output (do not change fl directly!!!) The method returns previously active device. If you want to change device / make printing from other thread than mail fltk loop is running, you have to lock  -u sing Fl::lock() - the fltk engine. After setting the current device back to screen one, you should unlock it back. A static function

static Fl_Output_Device * Fl_Output_Device::current( Fl_Output_Device * d) 

is equivalent to  Fl_Output_Device * Fl_Output_Device::current( Fl_Output_Device * d){return d->set_current();}. Function

static Fl_Output_Device * Fl_Output_Device::current() 

just returns active device.

Fl_Printer

Fl_Printer base for all paging devices defines some useful methods for formating and placing output on the page:
int Fl_Printer::page(int format);
int Fl_Printer::page(int w, int h, int media=0);
The first method sets new page for printing with supplied format using enumerations

Fl_Printer::A0
Fl_Printer::A1
Fl_Printer::A2
Fl_Printer::A3
Fl_Printer:: A4
Fl_Printer::A5
Fl_Printer::A6
Fl_Printer::A7
Fl_Printer::A8
Fl_Printer::A9
Fl_Printer::B0
Fl_Printer::B1
Fl_Printer::B2
Fl_Printer::B3
Fl_Printer::B4
Fl_Printer::B5
Fl_Printer::B6
Fl_Printer::B7
Fl_Printer::B8
Fl_Printer::B9
Fl_Printer::B10
Fl_Printer::C5E
Fl_Printer::DLE
Fl_Printer::EXECUTIVE
Fl_Printer::FOLIO
Fl_Printer::LEDGER
Fl_Printer::LEGAL
Fl_Printer:: LETTER
Fl_Printer::TABLOID
Fl_Printer::ENVELOPE

Fl_Printer::PORTRAIT
Fl_Printer::LANDSCAPE

Fl_Printer::MEDIA

You should call that function before each page. The page format can be combined (using OR operator "|" or "+") with  Fl_Printer::LANDSCAPE (default is portrait) to set the orientation. By default, the function only defines paper size for positioning of drawings in the page, it does not set the media itself. A printer manager is usually responsible for media selection. If format specification is combined with Fl_Printer::MEDIA enumeration, the printer tries to set  paging device to fulfill the page size request. In such a case you should set page_policy(int p) to rescribe what to do if particular media is not available (default is 1, see postscript documentation). 

The second format sets  virtual page dimensions according to the values supplied by the user. All units are in points (1/72 inch) but you can use convenient predefined constants  FL_POINT  , FL_INCH ,  FL_MM , FL_CM, FL_PICA  to multiply your particular preferred units. Methods
double Fl_Output_Device::page_width();
double Fl_Output_Device::page_height();
which return dimensions of the page (and depends also on the page orientation)  may be used later for calculation of widget placements on the page.
After page(...) is called, the origin of coordination system is placed in the upper left corner with such scaling that logical unit (pixel on the screen) corresponds to 1/72 inches. In most (all) cases you have to use one of following methods to place/scale your drawing to required position:
void Fl_Printer::place(double x, double y, double tx, double ty, double scale = 1);
void Fl_Printer::place(double x, double y, double w, double h, double tx, double ty, double tw, double th, int align = FL_ALIGN_CENTER);
void Fl_Printer::place(Fl_Widget * w, double tx, double ty, double tw, double th, int align = FL_ALIGN_CENTER );
The first method translates the drawings in a way that position x, y from source will be placed at tx, ty physical coordinates on the page. For instance, if you want place upper left corner of your widget to the upper left corner within margins of 1 inch thick (size of logical point being 0.5 mm), you can write
printer->place(widget->x(), widget->y(), FL_INCH, FL_INCH, 0.5 * FL_MM);
Second and third  functions translate the drawing and scales it to its maximum so that it tries to fit source area [x, y, w, h] inside target rectangle [tx, ty, tw, th]. The scaling is isotropic, the widget is aligned in x respectively y direction according to align parameter using fltk FL_ALIGN_* enumerations. The second form is a shortcut to Fl_Printer::fit(w->x(), w->y(), w->w(); w->h(), tx, ty, tw, th, align). After setting the transformation, you should redirect the drawing by set_current() method or Fl_Device::current(...) static function. From now you can use all drawing primitives (clipping being probably most usefully) and especially new function
fl_draw(Fl_Widget *)
for printing whole single or composite (group) widgets. After printing you *must*  set the screen back to be the active device - again using (set_)current().
An alternative approach - without explicit change of the current device - is to use device member method
     void Fl_Printer::draw(Fl_Widget * w);
This  method  draws the widget to particular device using necessary device swapping  before and after drawing.

Fl_PS_Printer

This class allows printing to postscript devices using FILE * streams.
Fl_PS_Printer(FILE *o, int lang_level, int pages = 0)
This is a constructor of PostScript device. o is the FILE * stream to to which printing will be performed, lang_level sets the postscript language level and is important when printing masked images and images with alpha channel:
  1. For levels 1-2  only mono-color true bitmasks are generated, for multi-color images with transparent color this transparent color is substituted by a background color (see bg_color method, default is FL_GRAY), for alpha channel this color is mixed with RGB.
  2. For level 3 masked images are rendered with proper mask, alpha-images are rendered using 4x4sub-pixel Floyd-Steinberg dithering (with slight propagation of the rounding error to the neighbor pixels)
Parameter pages should correspond to number of pages. If 0 is supplied %%Pages: (attend) is generated.

Fl_PS_Printer(FILE *o, int lang_level, int x, int y, int w, int h)
An encapsulated postscript constructor: x, y, w, h is the bounding box  with x, y bottom-left corner. The origin is placed in the upper left corner of that bounding box, but you can use place(...) methods in the same manner as for paged printing. page_width() and page_height() return dimensions of that box. Do not use page(...) method with encapsulated postscript!
~Fl_PS_Printer()
deletes the device, but it does not close by default the stream. If you assign a close function to the postscript device using
void Fl_PS_Printer::close_function( void(*f)(FILE *)),
it will close the stream by that function when the destructor is called.

Printing to an external command (*nix)

fl_printer_chooser() is rather limited example for printing to external program under POSIX systems. Because you should regard it more as an inspiration, it is more-less fully implemented in the header (you do not need to include it). The command creates a postscript device and returns a pointer to a device to which you can print your drawings. The device will pipe the postscript output to the external program. You can use any program which accepts the postscript from its standard input, ie lpr. Yo can also use GUI managers such as kprinter, qtcups, xpp. Please read their documentation for command line parameters (ie for kprinter you should use --stdin option)

#include <FL/fl_printer_chooser.H> // it also includes (Fl_PS_Printer.H)

Fl_PS_Printer * fl_printer_chooser (int lang_level = 3, int pages = 0, char * * command = 0, const char * default_command = 0,  const char * label = "Choose printing command")

lang_level describes the postscript language level.  Parameter command is a location to the name of external program. If this location is zero, function will ask (when used for the first time) for the name of external program and allocate necessary memory for it. If the location is no-zero, but pointer to the string (*command) is zero, it will also ask the user but newly allocated command will be assigned to that command address. If both command and * command are non-zero, it will use supplied command for execution. The behavior is that if the printing program is not specified, if will ask the user only once. You can use it ie in a sequence

char * my_command = 0;
Fl_PS_Printer * p = fl_printer_chooser(2, 0, &my_command);
printf("This is chosen command: %s\n", my_command); // just checking
The parameter default_command is the default field value  when the user is asked to supply one. Parameter label is the message in the dialog box. When you delete printer device, the stream is closed using pclose().

Note, that using fl_printer_chooser my suspend the execution of your program during printing. This is especially true if you use GUI printer manager which waits for user response (clicking print or cancel buttons) to resume printing. In such a case your program will wait at the point of device destruction, which uses pclose() to close the stream. You can avoid this by starting printing sequence from another thread, but you should use Fl::lock() (see fltk documentation) before any drawing attempt and Fl::unlock()  immediately after printing, but before destruction of the device (just to unlock fltk thread before waiting pclose is executed):

Fl_Printer * p = fl_printer_chooser(...);
p->page(...);
p->place(...);
Fl::lock();
fl_draw(...)
...
Fl::unlock();
delete p;



Fl_GDI_Printer

A class for windows-native printing.
Fl_GDI_Printer(HDC gc, DEVMODE * mode)
Creates the device upon particular device context "gc" and DEVMODE "mode". Usually you do not use this constructor directly but the pointer to new Fl_GDI_Printer is returned by fl_printer_chooser(...).
~Fl_GDI_Printer()
The destructor closes the device, and if mode is not locked anymore, it fries its memory allocation. If you do not want to free this mode, use windows GlobalLock upon that mode - i.e before calling the constructor.

Using standard windows printing

You can construct printing device using function

#include <FL/fl_printer_chooser.H> // it also includes (Fl_GDI_Printer.H)

Fl_GDI_Printer * fl_printer_chooser(Fl_GDI_Printer_Settings * s = 0)

which opens standard windows printing dialog. Yo can control this dialog (i.e. not to open it at all) by changing members of Fl_GDI_Printer_Settings class, which  is a direct public subclass of windows PRINTDLG structure (see windows documentation). Fl_GDI_Printer_Settings has a trivial constructor, so code like

static FL_GDI_Printer_Settings s;
Fl_GDI_Printer * fl_printer_chooser(&s);
...
...
Fl_GDI_Printer * fl_printer_chooser(&s);

will remember last user choice from the dialog.

Example program

There is a device program in test subdirectory. This program tests precision of printing of all primitives.

ENJOY!

Roman