diff --git a/CHANGES.txt b/CHANGES.txt index 1348c2aed7..c2ee9e8891 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -56,8 +56,8 @@ Changes in FLTK 1.4.0 Released: Feb ?? 2024 - New Fl_SVG_Image class: gives support of scalable vector graphics images to FLTK using the nanosvg software. - New Fl_ICO_Image class to read Windows .ico icon files. - - New classes Fl_SVG_File_Surface and Fl_EPS_File_Surface to save any FLTK - graphics to SVG or EPS files, respectively. + - New classes Fl_PDF_File_Surface, Fl_SVG_File_Surface and Fl_EPS_File_Surface + to save any FLTK graphics to PDF, SVG or EPS files, respectively. - New member functions Fl_Window::maximize(), Fl_Window::un_maximize() and Fl_Window::maximize_active() to programmatically manage window maximization. - Fl_Button now supports a compact flag that visually groups closely set diff --git a/FL/Fl_Device.H b/FL/Fl_Device.H index 93de0b4f9f..ab216a0e0e 100644 --- a/FL/Fl_Device.H +++ b/FL/Fl_Device.H @@ -52,16 +52,6 @@ class Fl_Image_Surface; For back-compatibility, it is also possible to use the Fl_Surface_Device::set_current() member function to change the current drawing surface, once to the new surface, once to the previous one. - - Class Fl_Surface_Device can also be derived to define new kinds of graphical output - usable with FLTK drawing functions. - An example would be to draw to a PDF file. This would require to create a new class, - say PDF_File_Surface, derived from class Fl_Surface_Device, and another new class, - say PDF_Graphics_Driver, derived from class Fl_Graphics_Driver. - Class PDF_Graphics_Driver should implement all virtual methods of the Fl_Graphics_Driver class - to support all FLTK drawing functions and have them draw into PDF files. Alternatively, - class PDF_Graphics_Driver could implement only some virtual methods, and only part of - the FLTK drawing API would be usable when drawing to PDF files. */ class FL_EXPORT Fl_Surface_Device { /** The graphics driver in use by this surface. */ diff --git a/FL/Fl_PDF_File_Surface.H b/FL/Fl_PDF_File_Surface.H new file mode 100644 index 0000000000..7fd2ed507e --- /dev/null +++ b/FL/Fl_PDF_File_Surface.H @@ -0,0 +1,89 @@ +// +// Declaration of class Fl_PDF_File_Surface for the Fast Light Tool Kit (FLTK). +// +// Copyright 2024 by Bill Spitzak and others. +// +// This library is free software. Distribution and use rights are outlined in +// the file "COPYING" which should have been included with this file. If this +// file is missing or damaged, see the license at: +// +// https://www.fltk.org/COPYING.php +// +// Please see the following page on how to report bugs and issues: +// +// https://www.fltk.org/bugs.php +// + +#ifndef PDF_FILE_SURFACE_H +#define PDF_FILE_SURFACE_H + +#include + +/** + To send graphical output to a PDF file. + Class Fl_PDF_File_Surface is used exactly as the Fl_Printer class except for its 2 member functions begin_job() and begin_document(). +

Platform notes: + - Windows: requires "Microsoft Print to PDF" available in Windows 10 and later. + - Wayland/X11: requires the FLTK library was built with FLTK_USE_PANGO=1. + - macOS: requires macOS 10.9 or later. +

If the running platform doesn't fulfill the requirement above, the program runs but doesn't output any PDF. +*/ +class FL_EXPORT Fl_PDF_File_Surface : public Fl_Paged_Device { +private: + const char **out_filename_; + Fl_Paged_Device *platform_surface_; + static Fl_Paged_Device *new_platform_pdf_surface_(const char ***); +public: + /** \name These attributes are useful for the Wayland/X11 platform only. + \{ + */ + static const char * format_dialog_title; + static const char * format_dialog_page_size; + static const char * format_dialog_orientation; + static const char * format_dialog_default; + /** \} */ + Fl_PDF_File_Surface(); + ~Fl_PDF_File_Surface(); + /** Prepare to draw to a PDF document identified with a file chooser. + A dialog opens to select the location and name of the output PDF document + as well as its page format and orientation. + \param defaultfilename Default name for the PDF document + \param perr NULL or address of a string that receives a message in case of error. + To be deleted[] after use. + \return 0 for success, 1 when the user cancelled the operation, 2 when an error occurred. + */ + int begin_job(const char* defaultfilename, char **perr = NULL); + /** Don't use for this class */ + int begin_job(int, int *, int *, char **) FL_OVERRIDE {return 1;} + /** Prepare to draw to a PDF document identified by its pathname. + \param pathname Path name for the PDF document + \param format The paper format for the PDF document + \param layout The orientation for the PDF document + \param perr NULL or address of a string that receives a message in case of error. + To be deleted[] after use. + \return 0 for success, 2 when an error occurred. + */ + int begin_document(const char* pathname, + enum Fl_Paged_Device::Page_Format format = Fl_Paged_Device::A4, + enum Fl_Paged_Device::Page_Layout layout = Fl_Paged_Device::PORTRAIT, + char **perr = NULL); + int printable_rect(int *w, int *h) FL_OVERRIDE { return platform_surface_->printable_rect(w,h); } + void margins(int *left, int *top, int *right, int *bottom) FL_OVERRIDE { + platform_surface_->margins(left,top,right,bottom); + } + void origin(int x, int y) FL_OVERRIDE {platform_surface_->origin(x, y);} + void origin(int *x, int *y) FL_OVERRIDE {platform_surface_->origin(x, y);} + void scale(float s_x, float s_y = 0) FL_OVERRIDE {platform_surface_->scale(s_x, s_y);} + void rotate(float angle) FL_OVERRIDE {platform_surface_->rotate(angle);} + void translate(int x, int y) FL_OVERRIDE {platform_surface_->translate(x, y);} + void untranslate() FL_OVERRIDE {platform_surface_->untranslate();}; + int begin_page(void) FL_OVERRIDE {return platform_surface_->begin_page();} + int end_page(void) FL_OVERRIDE {return platform_surface_->end_page();} + void end_job(void) FL_OVERRIDE {return platform_surface_->end_job();} + /** Returns the name of the PDF document */ + inline const char *pdf_filename() { return *out_filename_; } + void set_current() FL_OVERRIDE { if (platform_surface_) platform_surface_->set_current(); } + bool is_current() FL_OVERRIDE { return surface() == platform_surface_; } +}; + +#endif // PDF_FILE_SURFACE_H diff --git a/src/Fl_Device.cxx b/src/Fl_Device.cxx index b6c3f4da0e..16a9045bd6 100644 --- a/src/Fl_Device.cxx +++ b/src/Fl_Device.cxx @@ -1,7 +1,7 @@ // // implementation of classes Fl_Surface_Device and Fl_Display_Device for the Fast Light Tool Kit (FLTK). // -// Copyright 2010-2023 by Bill Spitzak and others. +// Copyright 2010-2024 by Bill Spitzak and others. // // This library is free software. Distribution and use rights are outlined in // the file "COPYING" which should have been included with this file. If this @@ -46,6 +46,11 @@ | +- Fl_Posix_Printer_Driver: Fl_Printer uses that under Posix platforms +- Fl_GTK_Printer_Driver: Fl_Printer uses that under Posix+GTK platforms + +- Fl_PDF_File_Surface: draw into a PDF file + +- Fl_PDF_GDI_File_Surface: Windows-specific helper class interfacing FLTK with PDF operations + +- Fl_PDF_Pango_File_Surface: Linux/Unix-specific helper class interfacing FLTK with PDF operations + +- Fl_PDF_Cocoa_File_Surface: macOS-specific helper class interfacing FLTK with PDF operations + +- Fl_Graphics_Driver -> directed to an Fl_Surface_Device object | @@ -154,3 +159,28 @@ Fl_Device_Plugin *Fl_Device_Plugin::opengl_plugin() { } return pi; } + +#if !defined(FL_NO_PRINT_SUPPORT) + +#include + +Fl_PDF_File_Surface::Fl_PDF_File_Surface() { + platform_surface_ = new_platform_pdf_surface_(&out_filename_); + driver(platform_surface_->driver()); +} + + +Fl_PDF_File_Surface::~Fl_PDF_File_Surface() { + delete platform_surface_; +} + +#endif // !defined(FL_NO_PRINT_SUPPORT) + +/** Localizable text of the "PDF document settings" dialog */ +const char * Fl_PDF_File_Surface::format_dialog_title = "PDF document settings"; +/** Localizable text of the "PDF document settings" dialog */ +const char * Fl_PDF_File_Surface::format_dialog_page_size = "Page Size:"; +/** Localizable text of the "PDF document settings" dialog */ +const char * Fl_PDF_File_Surface::format_dialog_default = "Set as default"; +/** Localizable text of the "PDF document settings" dialog */ +const char * Fl_PDF_File_Surface::format_dialog_orientation = "Orientation:"; diff --git a/src/drivers/Cocoa/Fl_Cocoa_Printer_Driver.mm b/src/drivers/Cocoa/Fl_Cocoa_Printer_Driver.mm index 3163f01b30..0df98af7ea 100644 --- a/src/drivers/Cocoa/Fl_Cocoa_Printer_Driver.mm +++ b/src/drivers/Cocoa/Fl_Cocoa_Printer_Driver.mm @@ -1,7 +1,7 @@ // // Mac OS X-specific printing support (objective-c++) for the Fast Light Tool Kit (FLTK). // -// Copyright 2010-2018 by Bill Spitzak and others. +// Copyright 2010-2024 by Bill Spitzak and others. // // This library is free software. Distribution and use rights are outlined in // the file "COPYING" which should have been included with this file. If this @@ -20,6 +20,7 @@ #include "../../Fl_Screen_Driver.H" #include "../Quartz/Fl_Quartz_Graphics_Driver.H" #include "../Darwin/Fl_Darwin_System_Driver.H" +#include #include "Fl_Cocoa_Window_Driver.H" #include @@ -48,7 +49,7 @@ typedef OSStatus (*PMSessionBeginDocumentNoDialog_type)( /** Support for printing on the Apple OS X platform */ class Fl_Cocoa_Printer_Driver : public Fl_Paged_Device { friend class Fl_Printer; -private: +protected: float scale_x; float scale_y; float angle; // rotation angle in radians @@ -391,3 +392,138 @@ - (void)printPanelDidEnd:(NSPrintPanel *)printPanel returnCode:(NSInteger)return { Fl_Paged_Device::origin(x, y); } + + +class Fl_PDF_Cocoa_File_Surface : public Fl_Cocoa_Printer_Driver +{ +public: + char *doc_fname; + Fl_PDF_Cocoa_File_Surface(); + ~Fl_PDF_Cocoa_File_Surface() { if (doc_fname) free(doc_fname); } + int begin_job(const char *defaultname, + char **perr_message = NULL); + int begin_job(int, int*, int *, char **) FL_OVERRIDE {return 1;} // don't use + int begin_document(const char* outname, + enum Fl_Paged_Device::Page_Format format, + enum Fl_Paged_Device::Page_Layout layout, + char **perr_message); +}; + + +Fl_PDF_Cocoa_File_Surface::Fl_PDF_Cocoa_File_Surface() { + driver(new Fl_Quartz_Graphics_Driver()); + doc_fname = NULL; +} + + +int Fl_PDF_Cocoa_File_Surface::begin_job(const char* defaultfilename, + char **perr_message) { + OSStatus status = 0; + if (fl_mac_os_version < 100900) return 1; + Fl_Window *top = Fl::first_window(); + NSWindow *main = (top ? (NSWindow*)fl_xid(top->top_window()) : nil); + if (!main) return 1; + Fl_Cocoa_Window_Driver::q_release_context(); +#if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_9 && defined(__BLOCKS__) + NSPDFInfo *pdf_info = [[NSPDFInfo alloc] init]; // 10.9 + NSPDFPanel *pdf_panel = [NSPDFPanel panel]; // 10.9 + char buf[FL_PATH_MAX]; + strcpy(buf, defaultfilename); + fl_filename_setext(buf, sizeof(buf), NULL); + [pdf_panel setDefaultFileName:[NSString stringWithUTF8String:buf]]; + [pdf_panel setOptions: NSPrintPanelShowsOrientation | NSPrintPanelShowsPaperSize]; + NSInteger retval = -1; + __block NSInteger complete = -1; + [pdf_panel beginSheetWithPDFInfo:pdf_info + modalForWindow:main + completionHandler:^(NSInteger returnCode) { + // this block runs after OK or Cancel was triggered in file dialog + complete = returnCode; + } + ]; + while (complete == -1) Fl::wait(100); // loop until end of file dialog + retval = complete; + [main makeKeyAndOrderFront:nil]; + if (retval != NSModalResponseOK) return 1; + NSURL *url = [pdf_info URL]; + doc_fname = fl_strdup([url fileSystemRepresentation]); + NSPrintInfo *pr_info = [NSPrintInfo sharedPrintInfo]; + [pr_info takeSettingsFromPDFInfo:pdf_info]; + [pdf_info release]; + printSession = (PMPrintSession)[pr_info PMPrintSession]; + printSettings = (PMPrintSettings)[pr_info PMPrintSettings]; + pageFormat = (PMPageFormat)[pr_info PMPageFormat]; + status = PMSessionBeginCGDocumentNoDialog(printSession, printSettings, pageFormat);//from 10.4 +#endif + if (status != noErr) { + if (perr_message) { + NSError *nserr = [NSError errorWithDomain:NSCocoaErrorDomain code:status userInfo:nil]; + NSString *s = [nserr localizedDescription]; + if (s) *perr_message = fl_strdup([s UTF8String]); + } + free(doc_fname); + doc_fname = NULL; + return 2; + } + y_offset = x_offset = 0; + return 0; +} + + +int Fl_PDF_Cocoa_File_Surface::begin_document(const char* outfname, + enum Fl_Paged_Device::Page_Format format, + enum Fl_Paged_Device::Page_Layout layout, + char **perr_message) { + OSStatus status = 0; + fl_open_display(); + if (fl_mac_os_version < 100900) return 1; +#if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_9 + NSPDFInfo *pdf_info = [[NSPDFInfo alloc] init]; // 10.9 + doc_fname = fl_strdup(outfname); + NSURL *url = [NSURL fileURLWithPath:[NSString stringWithUTF8String:doc_fname]]; + [pdf_info setURL:url]; + NSSize psize = {(CGFloat)Fl_Paged_Device::page_formats[format].width, (CGFloat)Fl_Paged_Device::page_formats[format].height}; + [pdf_info setPaperSize:psize]; + [pdf_info setOrientation:(layout == PORTRAIT ? NSPaperOrientationPortrait : NSPaperOrientationLandscape)]; + NSPrintInfo *pr_info = [NSPrintInfo sharedPrintInfo]; + [pr_info takeSettingsFromPDFInfo:pdf_info]; + [pdf_info release]; + printSession = (PMPrintSession)[pr_info PMPrintSession]; + printSettings = (PMPrintSettings)[pr_info PMPrintSettings]; + pageFormat = (PMPageFormat)[pr_info PMPageFormat]; + status = PMSessionBeginCGDocumentNoDialog(printSession, printSettings, pageFormat);//from 10.4 +#endif + if (status != noErr) { + if (perr_message) { + NSError *nserr = [NSError errorWithDomain:NSCocoaErrorDomain code:status userInfo:nil]; + NSString *s = [nserr localizedDescription]; + if (s) *perr_message = fl_strdup([s UTF8String]); + } + free(doc_fname); + doc_fname = NULL; + return 2; + } + y_offset = x_offset = 0; + return 0; +} + + +Fl_Paged_Device *Fl_PDF_File_Surface::new_platform_pdf_surface_(const char ***pfname) { + Fl_PDF_Cocoa_File_Surface *surf = new Fl_PDF_Cocoa_File_Surface(); + *pfname = (const char**)&surf->doc_fname; + return surf; +} + + +int Fl_PDF_File_Surface::begin_job(const char* defaultfilename, + char **perr_message) { + return ((Fl_PDF_Cocoa_File_Surface*)platform_surface_)->begin_job(defaultfilename, perr_message); +} + + +int Fl_PDF_File_Surface::begin_document(const char* defaultfilename, + enum Fl_Paged_Device::Page_Format format, + enum Fl_Paged_Device::Page_Layout layout, + char **perr_message) { + return ((Fl_PDF_Cocoa_File_Surface*)platform_surface_)->begin_document(defaultfilename, format, layout, perr_message); +} diff --git a/src/drivers/PostScript/Fl_PostScript.cxx b/src/drivers/PostScript/Fl_PostScript.cxx index bee34703d3..3b81789d0d 100644 --- a/src/drivers/PostScript/Fl_PostScript.cxx +++ b/src/drivers/PostScript/Fl_PostScript.cxx @@ -1,7 +1,7 @@ // // Classes Fl_PostScript_File_Device and Fl_PostScript_Graphics_Driver for the Fast Light Tool Kit (FLTK). // -// Copyright 2010-2022 by Bill Spitzak and others. +// Copyright 2010-2024 by Bill Spitzak and others. // // This library is free software. Distribution and use rights are outlined in // the file "COPYING" which should have been included with this file. If this @@ -21,11 +21,17 @@ #include #include #include "Fl_PostScript_Graphics_Driver.H" +#include #include #include #include #include "../../Fl_System_Driver.H" +#include +#include +#include +#include #include +#include #include #include #include @@ -33,6 +39,8 @@ #include // for M_PI #include #include +#include +#include # if ! PANGO_VERSION_CHECK(1,10,0) # error "Requires Pango 1.10 or higher" # endif @@ -150,6 +158,7 @@ Fl_PostScript_Graphics_Driver::Fl_PostScript_Graphics_Driver(void) scale_x = scale_y = 1.; #endif ps_filename_ = NULL; + nPages = 0; } /** \brief The destructor. */ @@ -1461,6 +1470,30 @@ void Fl_PostScript_Graphics_Driver::ps_untranslate(void) fprintf(output, "GR GR\n"); } +#if defined(FLTK_USE_X11) || defined(FLTK_USE_WAYLAND) + +Fl_Paged_Device *Fl_PDF_File_Surface::new_platform_pdf_surface_(const char ***pfname) { + *pfname = NULL; + return new Fl_PostScript_File_Device; +} + +int Fl_PDF_File_Surface::begin_job(const char* defaultfilename, + char **perr_message) { + if (perr_message) { + *perr_message = strdup("Class Fl_PDF_File_Surface requires PANGO to be usable."); + } + return 2; +} + +int Fl_PDF_File_Surface::begin_document(const char* defaultfilename, + enum Fl_Paged_Device::Page_Format format, + enum Fl_Paged_Device::Page_Layout layout, + char **perr_message) { + return begin_job(NULL, perr_message); +} + +#endif // defined(FLTK_USE_X11) || defined(FLTK_USE_WAYLAND) + # else // USE_PANGO /* Cairo-based implementation of the PostScript graphics driver */ @@ -1552,6 +1585,232 @@ void Fl_PostScript_Graphics_Driver::transformed_draw(const char* str, int n, dou check_status(); } + +// ======================================================= + + +class Fl_PDF_Pango_File_Surface : public Fl_PostScript_File_Device +{ +public: + char *doc_fname; + Fl_PDF_Pango_File_Surface(); + ~Fl_PDF_Pango_File_Surface() { if (doc_fname) free(doc_fname); } + int begin_job(const char *defaultname, + char **perr_message = NULL); + int begin_job(int, int*, int *, char **) FL_OVERRIDE {return 1;} // don't use + int begin_document(const char* outname, + enum Fl_Paged_Device::Page_Format format, + enum Fl_Paged_Device::Page_Layout layout, + char **perr_message); + int begin_page() FL_OVERRIDE; + void end_job() FL_OVERRIDE; +}; + + +Fl_PDF_Pango_File_Surface::Fl_PDF_Pango_File_Surface() { + doc_fname = NULL; + driver()->output = NULL; +} + + +static Fl_Paged_Device::Page_Format menu_to_size[] = {Fl_Paged_Device::A3, Fl_Paged_Device::A4, + Fl_Paged_Device::A5, Fl_Paged_Device::B4, Fl_Paged_Device::B5, Fl_Paged_Device::EXECUTIVE, + Fl_Paged_Device::LEGAL, Fl_Paged_Device::LETTER, Fl_Paged_Device::TABLOID +}; +static int size_count = sizeof(menu_to_size) / sizeof(menu_to_size[0]); + + +static int update_format_layout(int rank, Fl_Paged_Device::Page_Layout layout, + bool &need_set_default_psize) { + int status = -1; + Fl_Window *modal = new Fl_Window(510, 90, Fl_PDF_File_Surface::format_dialog_title); + modal->begin(); + Fl_Choice *psize = new Fl_Choice(140, 10, 110, 30, Fl_PDF_File_Surface::format_dialog_page_size); + psize->when(FL_WHEN_CHANGED); + for (int i = 0; i < size_count; i++) { + psize->add(Fl_Paged_Device::page_formats[menu_to_size[i]].name); + } + psize->value(rank); + Fl_Check_Button *default_size = new Fl_Check_Button(psize->x(), psize->y() + psize->h(), + psize->w(), psize->h(), Fl_PDF_File_Surface::format_dialog_default); + default_size->value(1); + default_size->user_data(&need_set_default_psize); + FL_INLINE_CALLBACK_2(psize, Fl_Choice*, choice, psize, + Fl_Check_Button*, check_but, default_size, + { + if (check_but->value() && choice->mvalue() && choice->prev_mvalue() && + choice->prev_mvalue() != choice->mvalue()) { + check_but->value(0); + } + }); + FL_INLINE_CALLBACK_2( modal, Fl_Window*, win, modal, + Fl_Check_Button*, check_but, default_size, + { + *((bool*)check_but->user_data()) = check_but->value(); + win->hide(); + } ); + Fl_Choice *orientation = new Fl_Choice(psize->x() + psize->w() + 120, psize->y(), 130, psize->h(), + Fl_PDF_File_Surface::format_dialog_orientation); + orientation->add("PORTRAIT|LANDSCAPE"); + orientation->value(layout == Fl_Paged_Device::PORTRAIT ? 0 : 1); + Fl_Return_Button *ok = new Fl_Return_Button(orientation->x() + orientation->w() - 55, + psize->y() + psize->h() + 10, 55, 30, fl_ok); + FL_INLINE_CALLBACK_4( ok, Fl_Widget*, b, ok, + int*, pstatus, &status, + Fl_Choice*, psize, psize, + Fl_Choice*, orientation, orientation, + { + *pstatus = menu_to_size[psize->value()] + 0x100 * orientation->value(); + b->window()->do_callback(); + } ); + Fl_Button *cancel = new Fl_Button(ok->x() - 90, psize->y() + psize->h() + 10, 70, 30, fl_cancel); + FL_INLINE_CALLBACK_1( cancel, Fl_Widget*, wid, cancel, { wid->window()->do_callback(); } ); + modal->end(); + modal->set_modal(); + modal->show(); + while (modal->shown()) Fl::wait(); + delete modal; + return status; +} + + +int Fl_PDF_Pango_File_Surface::begin_job(const char *defaultname, char **perr_message) { + static Page_Layout layout = PORTRAIT; + + Fl_Preferences print_prefs(Fl_Preferences::CORE_USER, "fltk.org", "printers"); + char *pref_format; + print_prefs.get("PDF/page_size", pref_format, "A4"); + int rank = 1; // corresponds to A4 + for (int i = 0; i < size_count; i++) { + if (strcmp(pref_format, Fl_Paged_Device::page_formats[menu_to_size[i]].name) == 0) { + rank = i; + break; + } + } + bool need_set_default_psize; + int status = update_format_layout(rank, layout, need_set_default_psize); + if (status == -1) return 1; + Page_Format format = (Page_Format)(status & 0xFF); + if (need_set_default_psize) print_prefs.set("PDF/page_size", Fl_Paged_Device::page_formats[format].name); + + Fl_Native_File_Chooser ch(Fl_Native_File_Chooser::BROWSE_SAVE_FILE); + ch.preset_file(defaultname); + ch.filter("*.pdf"); + ch.options(Fl_Native_File_Chooser::SAVEAS_CONFIRM); + int retval = ch.show(); + if (retval) return (retval == -1 ? 2 : 1); + + layout = (Page_Layout)(status & 0x100); + return begin_document(ch.filename(), format, layout, perr_message); +} + + +int Fl_PDF_Pango_File_Surface::begin_document(const char* outfname, + enum Fl_Paged_Device::Page_Format format, + enum Fl_Paged_Device::Page_Layout layout, + char **perr_message) { + int w = page_formats[format].width; + int h = page_formats[format].height; + if (layout == LANDSCAPE) { + int tmp = w; + w = h; + h = tmp; + } + Fl_PostScript_Graphics_Driver *dr = driver(); + dr->output = fopen(outfname, "w"); + cairo_status_t status = CAIRO_STATUS_WRITE_ERROR; + cairo_surface_t* cs = NULL; + if (dr->output) { + cs = cairo_pdf_surface_create_for_stream ( (cairo_write_func_t)write_to_cairo_stream, + dr->output, w, h); + status = cairo_surface_status(cs); + } + if (status != CAIRO_STATUS_SUCCESS) { + if (perr_message) { + const char *mess = cairo_status_to_string(status); + size_t l = strlen(mess) + strlen(outfname) + 100; + *perr_message = new char[l]; + snprintf(*perr_message, l, "Error '%s' while attempting to create %s.", mess, outfname); + } + if (cs) cairo_surface_destroy(cs); + return 2; + } + cairo_pdf_surface_restrict_to_version(cs, CAIRO_PDF_VERSION_1_4); + cairo_t *cr = cairo_create(cs); + cairo_surface_destroy(cs); + dr->set_cairo(cr); + dr->pw_ = w; + dr->ph_ = h; + if (format == Fl_Paged_Device::A4) { + dr->left_margin = 18; + dr->top_margin = 18; + } + else { + dr->left_margin = 12; + dr->top_margin = 12; + } + doc_fname = strdup(outfname); + return 0; +} + + +int Fl_PDF_Pango_File_Surface::begin_page(void) +{ + Fl_PostScript_Graphics_Driver *ps = driver(); + Fl_Surface_Device::push_current(this); + cairo_save(ps->cr()); + cairo_translate(ps->cr(), ps->left_margin, ps->top_margin); + cairo_set_line_width(ps->cr(), 1); + cairo_set_source_rgb(ps->cr(), 1.0, 1.0, 1.0); // white background + cairo_save(ps->cr()); + cairo_save(ps->cr()); + ps->check_status(); + x_offset = 0; + y_offset = 0; + ps->scale_x = ps->scale_y = 1.; + ps->angle = 0; + return 0; +} + + +void Fl_PDF_Pango_File_Surface::end_job() { + Fl_PostScript_Graphics_Driver *ps = driver(); + int error = 0; + cairo_surface_t *s = cairo_get_target(ps->cr()); + cairo_surface_finish(s); + error = cairo_surface_status(s); + int err2 = fclose(ps->output); + ps->output = NULL; + if (!error) error = err2; + cairo_destroy(ps->cr()); + while (ps->clip_){ + Fl_PostScript_Graphics_Driver::Clip * c= ps->clip_; + ps->clip_= ps->clip_->prev; + delete c; + } + if (error) fl_alert ("Error during PostScript data output."); +} + + +Fl_Paged_Device *Fl_PDF_File_Surface::new_platform_pdf_surface_(const char ***pfname) { + Fl_PDF_Pango_File_Surface *surf = new Fl_PDF_Pango_File_Surface(); + *pfname = (const char**)&surf->doc_fname; + return surf; +} + +int Fl_PDF_File_Surface::begin_job(const char* defaultfilename, + char **perr_message) { + return ((Fl_PDF_Pango_File_Surface*)platform_surface_)->begin_job(defaultfilename, perr_message); +} + + +int Fl_PDF_File_Surface::begin_document(const char* defaultfilename, + enum Fl_Paged_Device::Page_Format format, + enum Fl_Paged_Device::Page_Layout layout, + char **perr_message) { + return ((Fl_PDF_Pango_File_Surface*)platform_surface_)->begin_document(defaultfilename, format, layout, perr_message); +} + #endif // USE_PANGO /** diff --git a/src/drivers/PostScript/Fl_PostScript_Graphics_Driver.H b/src/drivers/PostScript/Fl_PostScript_Graphics_Driver.H index 904644f42e..11258e8e4d 100644 --- a/src/drivers/PostScript/Fl_PostScript_Graphics_Driver.H +++ b/src/drivers/PostScript/Fl_PostScript_Graphics_Driver.H @@ -54,6 +54,7 @@ public: ~Fl_PostScript_Graphics_Driver(); void close_command(Fl_PostScript_Close_Command cmd){close_cmd_=cmd;} FILE * file() {return output;} + inline void set_cairo(cairo_t *cr) { cairo_ = cr; } void page(double pw, double ph, int media = 0); void page(int format); int start_postscript (int pagecount, enum Fl_Paged_Device::Page_Format format, enum Fl_Paged_Device::Page_Layout layout); @@ -79,13 +80,11 @@ public: int not_clipped(int x, int y, int w, int h) FL_OVERRIDE; int clip_box(int x, int y, int w, int h, int &X, int &Y, int &W, int &H) FL_OVERRIDE; virtual int has_feature(driver_feature feature_mask) FL_OVERRIDE { return feature_mask & PRINTER; } -#if !FLTK_USE_CAIRO // draw image classes without caching them void draw_rgb_bitmap_(Fl_Image *img,int XP, int YP, int WP, int HP, int cx, int cy); void draw_pixmap(Fl_Pixmap * pxm,int XP, int YP, int WP, int HP, int cx, int cy) FL_OVERRIDE; void draw_bitmap(Fl_Bitmap * bitmap,int XP, int YP, int WP, int HP, int cx, int cy) FL_OVERRIDE; void draw_rgb(Fl_RGB_Image * rgb,int XP, int YP, int WP, int HP, int cx, int cy) FL_OVERRIDE; -#endif // !FLTK_USE_CAIRO }; #else // ! USE_PANGO diff --git a/src/drivers/PostScript/Fl_PostScript_image.cxx b/src/drivers/PostScript/Fl_PostScript_image.cxx index c78627727a..9b5fe16689 100644 --- a/src/drivers/PostScript/Fl_PostScript_image.cxx +++ b/src/drivers/PostScript/Fl_PostScript_image.cxx @@ -70,7 +70,6 @@ void Fl_PostScript_Graphics_Driver::draw_image(const uchar *data, int ix, int iy #if USE_PANGO -#if !FLTK_USE_CAIRO static void destroy_BGRA(void *data) { delete[] (uchar*)data; @@ -182,7 +181,6 @@ void Fl_PostScript_Graphics_Driver::draw_rgb_bitmap_(Fl_Image *img,int XP, int Y } } -#endif // !FLTK_USE_CAIRO #else // ! USE_PANGO diff --git a/src/drivers/WinAPI/Fl_WinAPI_Printer_Driver.cxx b/src/drivers/WinAPI/Fl_WinAPI_Printer_Driver.cxx index 2e59f124cf..21eb509bd3 100644 --- a/src/drivers/WinAPI/Fl_WinAPI_Printer_Driver.cxx +++ b/src/drivers/WinAPI/Fl_WinAPI_Printer_Driver.cxx @@ -1,7 +1,7 @@ // // Printing support for Windows for the Fast Light Tool Kit (FLTK). // -// Copyright 2010-2020 by Bill Spitzak and others. +// Copyright 2010-2024 by Bill Spitzak and others. // // This library is free software. Distribution and use rights are outlined in // the file "COPYING" which should have been included with this file. If this @@ -15,19 +15,24 @@ // #include "../GDI/Fl_GDI_Graphics_Driver.H" +#include #include #include +#include #include #include #include +#include // for fl_win32_xid() +#include // fl_strdup() #include +#include // DocumentProperties(), OpenPrinter(), ClosePrinter() extern HWND fl_window; /** Support for printing on the Windows platform */ class Fl_WinAPI_Printer_Driver : public Fl_Paged_Device { friend class Fl_Printer; -private: +protected: int abortPrint; PRINTDLG pd; HDC hPr; @@ -83,6 +88,171 @@ static void WIN_SetupPrinterDeviceContext(HDC prHDC) } +class Fl_PDF_GDI_File_Surface : public Fl_WinAPI_Printer_Driver +{ +private: + static LPSTR pdf_printer_name_; +public: + char *doc_fname; + Fl_PDF_GDI_File_Surface(); + ~Fl_PDF_GDI_File_Surface() { if (doc_fname) free(doc_fname); } + int begin_job(const char *defaultname, + char **perr_message = NULL); + int begin_job(int, int*, int *, char **) FL_OVERRIDE {return 1;} // don't use + int begin_document(const char* outname, + enum Fl_Paged_Device::Page_Format format, + enum Fl_Paged_Device::Page_Layout layout, + char **perr_message); + void end_job() FL_OVERRIDE; +}; + +LPSTR Fl_PDF_GDI_File_Surface::pdf_printer_name_ = _strdup("Microsoft Print to PDF"); + +Fl_PDF_GDI_File_Surface::Fl_PDF_GDI_File_Surface() { + driver(new Fl_GDI_Graphics_Driver()); + doc_fname = NULL; +} + +Fl_Paged_Device *Fl_PDF_File_Surface::new_platform_pdf_surface_(const char ***pfname) { + Fl_PDF_GDI_File_Surface *surf = new Fl_PDF_GDI_File_Surface(); + *pfname = (const char**)&surf->doc_fname; + return surf; +} + +int Fl_PDF_File_Surface::begin_job(const char* defaultfilename, + char **perr_message) { + return ((Fl_PDF_GDI_File_Surface*)platform_surface_)->begin_job(defaultfilename, perr_message); +} + +int Fl_PDF_File_Surface::begin_document(const char* defaultfilename, + enum Fl_Paged_Device::Page_Format format, + enum Fl_Paged_Device::Page_Layout layout, + char **perr_message) { + return ((Fl_PDF_GDI_File_Surface*)platform_surface_)->begin_document(defaultfilename, format, layout, perr_message); +} + + +int Fl_PDF_GDI_File_Surface::begin_job(const char *defaultfname, char **perr_message) { + int err = 0; + abortPrint = FALSE; + + HANDLE hPr2; + err = OpenPrinterA(pdf_printer_name_, &hPr2, NULL); + if (err == 0) { + if (perr_message) { + int l = 240; + *perr_message = new char[l]; + snprintf(*perr_message, l, + "Class Fl_PDF_File_Surface requires printer '%s' available in Windows 10+.", + pdf_printer_name_); + } + return 1; + } + HWND hwndOwner = fl_win32_xid(Fl::first_window()); + LONG count = DocumentPropertiesA(hwndOwner, hPr2, pdf_printer_name_, NULL, NULL, 0); + if (count <= 0) { ClosePrinter(hPr2); return 1; } + char *buffer = new char[count]; + DEVMODEA *pDevMode = (DEVMODEA*)buffer; + memset(buffer, 0, count); + pDevMode->dmSize = count; + count = DocumentPropertiesA(hwndOwner, hPr2, pdf_printer_name_, pDevMode, NULL, DM_OUT_BUFFER | DM_IN_PROMPT); + ClosePrinter(hPr2); + if (count == IDCANCEL || count < 0) { delete[] buffer; return 1; } + + Fl_Native_File_Chooser fnfc; + fnfc.type(Fl_Native_File_Chooser::BROWSE_SAVE_FILE); + fnfc.filter("PDF\t*.pdf\n"); + if (defaultfname && strlen(defaultfname) > 0) fnfc.preset_file(defaultfname); + fnfc.options(Fl_Native_File_Chooser::SAVEAS_CONFIRM); + if (fnfc.show() == 0) this->hPr = CreateDCA(NULL, pdf_printer_name_, NULL, pDevMode); + delete[] buffer; + if (!this->hPr) return 1; + DOCINFOW di; + wchar_t docName [256]; + wchar_t outName [256]; + fl_utf8towc("FLTK", 4, docName, 256); + fl_utf8towc(fnfc.filename(), strlen(fnfc.filename()), outName, 256); + memset(&di, 0, sizeof(DOCINFOW)); + di.cbSize = sizeof(DOCINFOW); + di.lpszDocName = (LPCWSTR)docName; + di.lpszOutput = (LPCWSTR)outName; + err = StartDocW(this->hPr, &di); + if (err <= 0) { + DWORD dw = GetLastError(); + DeleteDC(this->hPr); + this->hPr = NULL; + if (dw != ERROR_CANCELLED) { + if (perr_message) { + int l = 40; + *perr_message = new char[l]; + snprintf(*perr_message, l, "Error %lu in StartDoc() call", dw); + } + return 2; + } + return 1; + } + x_offset = 0; + y_offset = 0; + WIN_SetupPrinterDeviceContext(this->hPr); + driver()->gc(this->hPr); + doc_fname = fl_strdup(fnfc.filename()); + return 0; +} + + +int Fl_PDF_GDI_File_Surface::begin_document(const char* outfname, + enum Fl_Paged_Device::Page_Format format, + enum Fl_Paged_Device::Page_Layout layout, + char **perr_message) { + int err = 0; + abortPrint = FALSE; + + DEVMODEA inDevMode; + memset(&inDevMode, 0, sizeof(DEVMODEA)); inDevMode.dmSize = sizeof(DEVMODEA); + inDevMode.dmOrientation = (layout == PORTRAIT ? DMORIENT_PORTRAIT : DMORIENT_LANDSCAPE); + inDevMode.dmPaperSize = (format == A4 ? DMPAPER_A4 : DMPAPER_LETTER); + inDevMode.dmFields = DM_ORIENTATION | DM_PAPERSIZE ; + + this->hPr = CreateDCA(NULL, pdf_printer_name_, NULL, &inDevMode); + if (!this->hPr) { + if (perr_message) { + int l = 150; + *perr_message = new char[l]; + snprintf(*perr_message, l, "Class Fl_PDF_File_Surface requires printer '%s'.", + pdf_printer_name_); + } + return 2; + } + DOCINFOW di; + wchar_t docName[256]; + wchar_t outName[256]; + fl_utf8towc("FLTK", 4, docName, 256); + memset(&di, 0, sizeof(DOCINFOW)); + di.cbSize = sizeof(DOCINFOW); + di.lpszDocName = (LPCWSTR)docName; + di.lpszOutput = (LPCWSTR)outName; + fl_utf8towc(outfname, strlen(outfname), outName, 256); + err = StartDocW(hPr, &di); + if (err <= 0) { + DWORD dw = GetLastError(); + DeleteDC(this->hPr); + this->hPr = NULL; + if (perr_message) { + int l = 50; + *perr_message = new char[l]; + snprintf(*perr_message, l, "Error %lu in StartDoc() call", dw); + } + return 2; + } + x_offset = 0; + y_offset = 0; + WIN_SetupPrinterDeviceContext(this->hPr); + driver()->gc(this->hPr); + doc_fname = fl_strdup(outfname); + return 0; +} + + int Fl_WinAPI_Printer_Driver::begin_job (int pagecount, int *frompage, int *topage, char **perr_message) // returns 0 iff OK { @@ -156,6 +326,19 @@ int Fl_WinAPI_Printer_Driver::begin_job (int pagecount, int *frompage, int *topa return err; } +void Fl_PDF_GDI_File_Surface::end_job(void) +{ + if (hPr != NULL) { + if (! abortPrint) { + if (EndDoc (hPr) <= 0) { + fl_message ("Error in EndDoc() call"); + } + int err = DeleteDC (hPr); + } + hPr = NULL; + } +} + void Fl_WinAPI_Printer_Driver::end_job (void) { if (hPr != NULL) { @@ -234,6 +417,7 @@ int Fl_WinAPI_Printer_Driver::begin_page (void) WIN_SetupPrinterDeviceContext (hPr); prerr = StartPage (hPr); if (prerr < 0) { + Fl_Surface_Device::pop_current(); fl_alert ("StartPage error %d", prerr); rsult = 1; } @@ -330,3 +514,5 @@ void Fl_WinAPI_Printer_Driver::origin(int *x, int *y) { Fl_Paged_Device::origin(x, y); } + + diff --git a/test/device.cxx b/test/device.cxx index e039397b61..67e29d2396 100644 --- a/test/device.cxx +++ b/test/device.cxx @@ -30,6 +30,7 @@ #include #include #include +#include #include "pixmaps/porsche.xpm" #include "pixmaps/sorceress.xbm" @@ -523,59 +524,45 @@ void copy(Fl_Widget *, void *data) { Fl_Surface_Device::pop_current(); } - if (strcmp(operation, "Fl_Printer") == 0 || strcmp(operation, "Fl_PostScript_File_Device") == 0) { + if (strcmp(operation, "Fl_Printer") == 0 || strcmp(operation, "Fl_PostScript_File_Device") == 0 + || strcmp(operation, "Fl_PDF_File_Surface") == 0) { Fl_Paged_Device *p; int err; char *err_message = NULL; if (strcmp(operation, "Fl_Printer") == 0) { p = new Fl_Printer(); err = p->begin_job(1, NULL, NULL, &err_message); - } - else { + } else if (strcmp(operation, "Fl_PDF_File_Surface") == 0) { + p = new Fl_PDF_File_Surface(); + err = ((Fl_PDF_File_Surface*)p)->begin_job("FLTK.pdf", &err_message); + } else { p = new Fl_PostScript_File_Device(); err = ((Fl_PostScript_File_Device*)p)->start_job(1); } if (!err) { p->begin_page(); - if (target->as_window()) { - int w, h; - p->printable_rect(&w, &h); - p->origin(w/2, h/2); - p->print_window(target->as_window(), -target->w()/2, -target->h()/2); - } - else p->print_widget(target); + Fl_Window *win = target->as_window(); + int target_w = win ? win->decorated_w() : target->w(); + int target_h = win ? win->decorated_h() : target->h(); + int w, h; + p->printable_rect(&w, &h); + float s = 1, s_aux = 1; + if (target_w > w) + s_aux = float(w) / target_w; + if (target_h > h) + s = float(h) / target_h; + if (s_aux < s) s = s_aux; + p->scale(s); + p->printable_rect(&w, &h); + p->origin(w/2, h/2); + if (win) p->draw_decorated_window(win, - target_w/2, - target_h/2); + else p->draw(target, - target_w/2, - target_h/2); p->end_page(); p->end_job(); } else if (err > 1 && err_message) {fl_alert("%s", err_message); delete[] err_message;} delete p; } - if (strcmp(operation, "Fl_EPS_File_Surface") == 0) { - Fl_Native_File_Chooser fnfc; - fnfc.title("Save a .eps file"); - fnfc.type(Fl_Native_File_Chooser::BROWSE_SAVE_FILE); - fnfc.filter("EPS\t*.eps\n"); - fnfc.options(Fl_Native_File_Chooser::SAVEAS_CONFIRM | Fl_Native_File_Chooser::USE_FILTER_EXT); - if (!fnfc.show() ) { - FILE *eps = fl_fopen(fnfc.filename(), "w"); - if (eps) { - int ww, wh; - if (target->as_window()) { - ww = target->as_window()->decorated_w(); - wh = target->as_window()->decorated_h(); - } else { - ww = target->w(); - wh = target->h(); - } - Fl_EPS_File_Surface p(ww, wh, eps); - if (p.file()) { - if (target->as_window()) p.draw_decorated_window(target->as_window()); - else p.draw(target); - } - } - } - } - if (strcmp(operation, "Fl_SVG_File_Surface") == 0) { Fl_Native_File_Chooser fnfc; fnfc.title("Save a .svg file"); @@ -763,7 +750,7 @@ int main(int argc, char ** argv) { rb = new Fl_Radio_Round_Button(170,4,150,12, "Fl_Copy_Surface"); rb->callback(operation_cb, NULL); rb->labelsize(12); rb = new Fl_Radio_Round_Button(5,17,150,12, "Fl_Printer"); rb->callback(operation_cb, NULL); rb->labelsize(12); rb = new Fl_Radio_Round_Button(170,17,150,12, "Fl_PostScript_File_Device"); rb->callback(operation_cb, NULL); rb->labelsize(12); - rb = new Fl_Radio_Round_Button(5,30,150,12, "Fl_EPS_File_Surface"); rb->callback(operation_cb, NULL); rb->labelsize(12); + rb = new Fl_Radio_Round_Button(5,30,150,12, "Fl_PDF_File_Surface"); rb->callback(operation_cb, NULL); rb->labelsize(12); rb = new Fl_Radio_Round_Button(170,30,150,12, "Fl_SVG_File_Surface"); rb->callback(operation_cb, NULL); rb->labelsize(12); rb = new Fl_Radio_Round_Button(5,43,150,12, "fl_capture_window()"); rb->callback(operation_cb, NULL); rb->labelsize(12); rb = new Fl_Radio_Round_Button(170,43,150,12, "Fl_Image_Surface::mask()"); rb->callback(operation_cb, NULL); rb->labelsize(12);