Implement and document new class Fl_PDF_File_Surface

This commit is contained in:
ManoloFLTK 2024-03-31 16:55:49 +02:00
parent b402b6a839
commit 9472ff546c
10 changed files with 733 additions and 59 deletions

View File

@ -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

View File

@ -52,16 +52,6 @@ class Fl_Image_Surface;
</ol>
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. */

89
FL/Fl_PDF_File_Surface.H Normal file
View File

@ -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 <FL/Fl_Paged_Device.H>
/**
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().
<p><b>Platform notes:</b>
- 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.
<p>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

View File

@ -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/Fl_PDF_File_Surface.H>
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:";

View File

@ -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 <FL/Fl_PDF_File_Surface.H>
#include "Fl_Cocoa_Window_Driver.H"
#include <FL/Fl.H>
@ -48,7 +49,7 @@ typedef OSStatus
/** 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 Fl_Cocoa_Printer_Driver::origin(int *x, int *y)
{
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);
}

View File

@ -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 <FL/fl_draw.H>
#include <stdio.h>
#include "Fl_PostScript_Graphics_Driver.H"
#include <FL/Fl_PDF_File_Surface.H>
#include <FL/Fl_PostScript.H>
#include <FL/Fl_Image_Surface.H>
#include <FL/Fl_Native_File_Chooser.H>
#include "../../Fl_System_Driver.H"
#include <FL/Fl_Choice.H>
#include <FL/Fl_Button.H>
#include <FL/Fl_Check_Button.H>
#include <FL/Fl_Return_Button.H>
#include <FL/fl_string_functions.h>
#include <FL/fl_callback_macros.H>
#include <FL/platform.H>
#include <stdarg.h>
#include <time.h>
@ -33,6 +39,8 @@
#include <FL/math.h> // for M_PI
#include <pango/pangocairo.h>
#include <cairo/cairo-ps.h>
#include <cairo/cairo-pdf.h>
#include <FL/Fl_Preferences.H>
# 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
/**

View File

@ -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

View File

@ -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

View File

@ -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 <FL/Fl_PDF_File_Surface.H>
#include <FL/Fl_Paged_Device.H>
#include <FL/Fl_Printer.H>
#include <FL/Fl_Native_File_Chooser.H>
#include <FL/fl_ask.H>
#include <FL/math.h>
#include <FL/fl_draw.H>
#include <FL/platform.H> // for fl_win32_xid()
#include <FL/fl_string_functions.h> // fl_strdup()
#include <commdlg.h>
#include <winspool.h> // 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);
}

View File

@ -30,6 +30,7 @@
#include <FL/Fl_Image_Surface.H>
#include <FL/Fl_Native_File_Chooser.H>
#include <FL/Fl_SVG_File_Surface.H>
#include <FL/Fl_PDF_File_Surface.H>
#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);