diff options
Diffstat (limited to 'app/wlib/gtklib/print.c')
-rw-r--r-- | app/wlib/gtklib/print.c | 858 |
1 files changed, 858 insertions, 0 deletions
diff --git a/app/wlib/gtklib/print.c b/app/wlib/gtklib/print.c new file mode 100644 index 0000000..9f4a4a8 --- /dev/null +++ b/app/wlib/gtklib/print.c @@ -0,0 +1,858 @@ +/** \file print.c + * Printing functions using GTK's print API + */ + +/* XTrkCad - Model Railroad CAD + * Copyright (C) 2015 Martin Fischer + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#include <sys/types.h> +#include <stdio.h> +#include <stdlib.h> +#include <time.h> +#include <pwd.h> +#include <string.h> +#include <ctype.h> +#include <unistd.h> +#ifdef HAVE_MALLOC_H +#include <malloc.h> +#endif +#include <math.h> +#include <locale.h> + +#include <stdint.h> + +#include "gtkint.h" +#include <gtk/gtkprintunixdialog.h> +#include <gtk/gtkprintjob.h> + +#include "wlib.h" +#include "i18n.h" + +extern wDrawColor wDrawColorWhite; +extern wDrawColor wDrawColorBlack; + +/***************************************************************************** + * + * MACROS + * + */ + +#define PRINT_PORTRAIT (0) +#define PRINT_LANDSCAPE (1) + +#define PPI (72.0) +#define P2I( P ) ((P)/PPI) + +#define CENTERMARK_LENGTH (60) /**< size of cross marking center of circles */ +#define DASH_LENGTH (8.0) /**< length of single dash */ + +#define PAGESETTINGS "xtrkcad.page" /**< filename for page settings */ +#define PRINTSETTINGS "xtrkcad.printer" /**< filename for printer settings */ + +/***************************************************************************** + * + * VARIABLES + * + */ + +static GtkPrintSettings *settings; /**< current printer settings */ +static GtkPageSetup *page_setup; /**< current paper settings */ +static GtkPrinter *selPrinter; /**< printer selected by user */ +static GtkPrintJob *curPrintJob; /**< currently active print job */ +extern struct wDraw_t psPrint_d; + +static wBool_t printContinue; /**< control print job, FALSE for cancelling */ + +static wIndex_t pageCount; /**< unused, could be used for progress indicator */ +static wIndex_t totalPageCount; /**< unused, could be used for progress indicator */ + +static double paperWidth; /**< physical paper width */ +static double paperHeight; /**< physical paper height */ +static double tBorder; /**< top margin */ +static double rBorder; /**< right margin */ +static double lBorder; /**< left margin */ +static double bBorder; /**< bottom margin */ + +static long printFormat = PRINT_LANDSCAPE; + +/***************************************************************************** + * + * FUNCTIONS + * + */ + +static void WlibGetPaperSize( void ); + +/** + * Initialize printer und paper selection using the saved settings + * + * \param op IN print operation to initialize. If NULL only the global + * settings are loaded. + */ + +void +WlibApplySettings( GtkPrintOperation *op ) +{ + gchar *filename; + GError *err = NULL; + GtkWidget *dialog; + + filename = g_build_filename( wGetAppWorkDir(), PRINTSETTINGS, NULL ); + + if( !(settings = gtk_print_settings_new_from_file( filename, &err ))) { + if( err->code != G_FILE_ERROR_NOENT ) { + // ignore file not found error as defaults will be used + dialog = gtk_message_dialog_new (GTK_WINDOW (gtkMainW->gtkwin), + GTK_DIALOG_DESTROY_WITH_PARENT, + GTK_MESSAGE_ERROR, GTK_BUTTONS_CLOSE, + err->message); + gtk_dialog_run (GTK_DIALOG (dialog)); + gtk_widget_destroy (dialog); + } else { + // create default print settings + settings = gtk_print_settings_new(); + } + g_error_free (err); + } + g_free( filename ); + + if (settings && op ) + gtk_print_operation_set_print_settings (op, settings); + + err = NULL; + filename = g_build_filename( wGetAppWorkDir(), PAGESETTINGS, NULL ); + if( !(page_setup = gtk_page_setup_new_from_file( filename, &err ))) { + // ignore file not found error as defaults will be used + if( err->code != G_FILE_ERROR_NOENT ) { + dialog = gtk_message_dialog_new (GTK_WINDOW (gtkMainW->gtkwin), + GTK_DIALOG_DESTROY_WITH_PARENT, + GTK_MESSAGE_ERROR, GTK_BUTTONS_CLOSE, + err->message); + gtk_dialog_run (GTK_DIALOG (dialog)); + gtk_widget_destroy (dialog); + } else { + page_setup = gtk_page_setup_new(); + } + g_error_free (err); + } else { + // on success get the paper dimensions + WlibGetPaperSize(); + } + g_free( filename ); + + if( page_setup && op ) + gtk_print_operation_set_default_page_setup (op, page_setup); + +} + +/** + * Save the printer settings. If op is not NULL the settings are retrieved + * from the print operation. Otherwise the state of the globals is saved. + * + * \param op IN printer operation. If NULL the glabal variables are used + */ + +void +WlibSaveSettings( GtkPrintOperation *op ) +{ + GError *err = NULL; + gchar *filename; + GtkWidget *dialog; + + if( op ) { + if (settings != NULL) + g_object_unref (settings); + settings = g_object_ref (gtk_print_operation_get_print_settings (op)); + } + filename = g_build_filename( wGetAppWorkDir(), PRINTSETTINGS, NULL ); + if( !gtk_print_settings_to_file( settings, filename, &err )) { + dialog = gtk_message_dialog_new (GTK_WINDOW (gtkMainW->gtkwin), + GTK_DIALOG_DESTROY_WITH_PARENT, + GTK_MESSAGE_ERROR, GTK_BUTTONS_CLOSE, + err->message); + + g_error_free (err); + gtk_dialog_run (GTK_DIALOG (dialog)); + gtk_widget_destroy (dialog); + } + g_free( filename ); + + if( op ) { + if (page_setup != NULL) + g_object_unref (page_setup); + page_setup = g_object_ref (gtk_print_operation_get_default_page_setup (op)); + } + filename = g_build_filename( wGetAppWorkDir(), PAGESETTINGS, NULL ); + if( !gtk_page_setup_to_file( page_setup, filename, &err )) { + dialog = gtk_message_dialog_new (GTK_WINDOW (gtkMainW->gtkwin), + GTK_DIALOG_DESTROY_WITH_PARENT, + GTK_MESSAGE_ERROR, GTK_BUTTONS_CLOSE, + err->message); + + g_error_free (err); + gtk_dialog_run (GTK_DIALOG (dialog)); + gtk_widget_destroy (dialog); + } + g_free( filename ); + +} + +/** + * Page setup function. Previous settings are loaded and the setup + * dialog is shown. The settings are saved after the dialog ends. + * + * \param callback IN unused + */ + +void wPrintSetup( wPrintSetupCallBack_p callback ) +{ + GtkPageSetup *new_page_setup; + gchar *filename; + GError *err; + GtkWidget *dialog; + + WlibApplySettings( NULL ); + + new_page_setup = gtk_print_run_page_setup_dialog (GTK_WINDOW (gtkMainW->gtkwin), + page_setup, settings); + if (page_setup) + g_object_unref (page_setup); + + page_setup = new_page_setup; + + WlibGetPaperSize(); + WlibSaveSettings( NULL ); +} + +/***************************************************************************** + * + * BASIC PRINTING + * + */ + + +/** + * set the current line type for printing operations + * + * \param lineWidth IN new line width + * \param lineType IN flag for line type (dashed or full) + * \param opts IN unused + * \return + */ + + +static void setLineType( + double lineWidth, + wDrawLineType_e lineType, + wDrawOpts opts ) +{ + cairo_t *cr = psPrint_d.printContext; + double dashLength = DASH_LENGTH; + + if (lineWidth < 0.0) { + lineWidth = P2I(-lineWidth)*2.0; + } + + // make sure that there is a minimum line width used + if ( lineWidth == 0.0 ) + lineWidth = 0.1; + + cairo_set_line_width( cr, lineWidth ); + + if (lineType == wDrawLineDash) + cairo_set_dash( cr, &dashLength, 1, 0.0 ); + else + cairo_set_dash( cr, NULL, 0, 0.0 ); +} + +/** + * set the color for the following print operations + * + * \param color IN the new color + * \return + */ + +static void psSetColor( + wDrawColor color ) +{ + cairo_t *cr = psPrint_d.printContext; + GdkColor* const gcolor = gtkGetColor(color, TRUE); + + cairo_set_source_rgb(cr, gcolor->red / 65535.0, + gcolor->green / 65535.0, + gcolor->blue / 65535.0); +} + +/** + * Print a straight line + * + * \param x0, y0 IN starting point in pixels + * \param x1, y1 IN ending point in pixels + * \param width line width + * \param lineType + * \param color color + * \param opts ? + */ + +void psPrintLine( + wPos_t x0, wPos_t y0, + wPos_t x1, wPos_t y1, + wDrawWidth width, + wDrawLineType_e lineType, + wDrawColor color, + wDrawOpts opts ) +{ + if (color == wDrawColorWhite) + return; + if (opts&wDrawOptTemp) + return; + + psSetColor(color); + setLineType( width, lineType, opts ); + + cairo_move_to( psPrint_d.printContext, + x0, y0 ); + cairo_line_to( psPrint_d.printContext, + x1, y1 ); + cairo_stroke( psPrint_d.printContext ); +} + +/** + * Print an arc around a specified center + * + * \param x0, y0 IN center of arc + * \param r IN radius + * \param angle0, angle1 IN start and end angle + * \param drawCenter draw marking for center + * \param width line width + * \param lineType + * \param color color + * \param opts ? + */ + +void psPrintArc( + wPos_t x0, wPos_t y0, + wPos_t r, + double angle0, + double angle1, + wBool_t drawCenter, + wDrawWidth width, + wDrawLineType_e lineType, + wDrawColor color, + wDrawOpts opts ) +{ + cairo_t *cr = psPrint_d.printContext; + + if (color == wDrawColorWhite) + return; + if (opts&wDrawOptTemp) + return; + + psSetColor(color); + setLineType(width, lineType, opts); + + if (angle1 >= 360.0) + angle1 = 359.999; + angle1 = 90.0-(angle0+angle1); + while (angle1 < 0.0) angle1 += 360.0; + while (angle1 >= 360.0) angle1 -= 360.0; + angle0 = 90.0-angle0; + while (angle0 < 0.0) angle0 += 360.0; + while (angle0 >= 360.0) angle0 -= 360.0; + + // draw the curve + cairo_arc( cr, x0, y0, r, angle1 * M_PI / 180.0, angle0 * M_PI / 180.0 ); + + if( drawCenter ) { + // draw crosshair for center of curve + cairo_move_to( cr, x0 - CENTERMARK_LENGTH / 2, y0 ); + cairo_line_to( cr, x0 + CENTERMARK_LENGTH / 2, y0 ); + cairo_move_to( cr, x0, y0 - CENTERMARK_LENGTH / 2 ); + cairo_line_to( cr, x0, y0 + CENTERMARK_LENGTH / 2 ); + } + cairo_stroke( psPrint_d.printContext ); +} + +/** + * Print a filled rectangle + * + * \param x0, y0 IN top left corner + * \param x1, y1 IN bottom right corner + * \param color IN fill color + * \param opts IN options + * \return + */ + +void psPrintFillRectangle( + wPos_t x0, wPos_t y0, + wPos_t x1, wPos_t y1, + wDrawColor color, + wDrawOpts opts ) +{ + cairo_t *cr = psPrint_d.printContext; + double width = x0 - x1; + double height = y0 - y1; + + if (color == wDrawColorWhite) + return; + if (opts&wDrawOptTemp) + return; + psSetColor(color); + + cairo_rectangle( cr, x0, y0, width, height ); + + cairo_fill( cr ); +} + +/** + * Print a filled polygon + * + * \param p IN a list of x and y coordinates + * \param cnt IN the number of points + * \param color IN fill color + * \param opts IN options + * \return + */ + +void psPrintFillPolygon( + wPos_t p[][2], + int cnt, + wDrawColor color, + wDrawOpts opts ) +{ + int inx; + cairo_t *cr = psPrint_d.printContext; + + if (color == wDrawColorWhite) + return; + if (opts&wDrawOptTemp) + return; + + psSetColor(color); + + cairo_move_to( cr, p[ 0 ][ 0 ], p[ 0 ][ 1 ] ); + for (inx=0; inx<cnt; inx++) + cairo_line_to( cr, p[ inx ][ 0 ], p[ inx ][ 1 ] ); + cairo_fill( cr ); +} + +/** + * Print a filled circle + * + * \param x0, y0 IN coordinates of center (in pixels ) + * \param r IN radius + * \param color IN fill color + * \param opts IN options + * \return + */ + +void psPrintFillCircle( + wPos_t x0, wPos_t y0, + wPos_t r, + wDrawColor color, + wDrawOpts opts ) +{ + if (color == wDrawColorWhite) + return; + if (opts&wDrawOptTemp) + return; + psSetColor(color); + + cairo_arc( psPrint_d.printContext, + x0, y0, r, 0.0, 2 * M_PI ); + + cairo_fill( psPrint_d.printContext ); +} + + +/** + * Print a string at the given position using specified font and text size. + * The orientatoion of the y-axis in XTrackCAD is wrong for cairo. So for + * all other print primitives a flip operation is done. As this would + * also affect the string orientation, printing a string has to be + * treated differently. The starting point is transformed, then the + * string is rotated and scaled as needed. Finally the string position + * translated to the starting point calculated previously. The same + * solution would have to be applied to a bitmap should printing + * bitmaps ever be implemented. + * + * \param x IN x position in pixels + * \param y IN y position in pixels + * \param a IN angle of baseline in degrees. Positive is clockwise, 0 is direction of positive x axis + * \param s IN string to print + * \param fp IN font + * \param fs IN font size + * \param color IN text color + * \param opts IN ??? + * \return + */ + +void psPrintString( + wPos_t x, wPos_t y, + double a, + char * s, + wFont_p fp, + double fs, + wDrawColor color, + wDrawOpts opts ) +{ + char * cp; + double x0 = (double)x, y0 = (double)y; + double text_height; + + cairo_t *cr; + cairo_matrix_t matrix; + + PangoLayout *layout; + PangoFontDescription *desc; + PangoFontMetrics *metrics; + PangoContext *pcontext; + + if (color == wDrawColorWhite) + return; + + cr = psPrint_d.printContext; + + // get the current transformation matrix and transform the starting + // point of the string + cairo_get_matrix( cr, &matrix ); + cairo_matrix_transform_point( &matrix, &x0, &y0 ); + + cairo_save( cr ); + + layout = pango_cairo_create_layout( cr ); + + // set the correct font and size + /** \todo use a getter function instead of double conversion */ + desc = pango_font_description_from_string (gtkFontTranslate( fp )); + + //don't know why the size has to be reduced to 75% :-( + pango_font_description_set_size(desc, fs * PANGO_SCALE *0.75 ); + + // render the string to a Pango layout + pango_layout_set_font_description (layout, desc); + pango_layout_set_text (layout, s, -1); + pango_layout_set_width (layout, -1); + pango_layout_set_alignment (layout, PANGO_ALIGN_LEFT); + + // get the height of the string + pcontext = pango_cairo_create_context( cr ); + metrics = pango_context_get_metrics(pcontext, desc, pango_context_get_language(pcontext)); + text_height = pango_font_metrics_get_ascent(metrics) / PANGO_SCALE; + + // transform the string to the correct position + cairo_identity_matrix( cr ); + + cairo_translate( cr, x0 + text_height * sin ( -a * M_PI / 180.0) , y0 - text_height * cos ( a * M_PI / 180.0) ); + cairo_rotate( cr, -a * M_PI / 180.0 ); + + // set the color + psSetColor( color ); + + // and show the string + pango_cairo_show_layout (cr, layout); + + // free unused objects + g_object_unref( layout ); + g_object_unref( pcontext ); + + cairo_restore( cr ); +} + +/** + * Create clipping retangle. + * + * \param x, y IN starting position + * \param w, h IN width and height of rectangle + * \return + */ + +void wPrintClip( wPos_t x, wPos_t y, wPos_t w, wPos_t h ) +{ + cairo_move_to( psPrint_d.printContext, x, y ); + cairo_rel_line_to( psPrint_d.printContext, w, 0 ); + cairo_rel_line_to( psPrint_d.printContext, 0, h ); + cairo_rel_line_to( psPrint_d.printContext, -w, 0 ); + cairo_close_path( psPrint_d.printContext ); + cairo_clip( psPrint_d.printContext ); +} + +/***************************************************************************** + * + * PAGE FUNCTIONS + * + */ + +/** + * Get the paper dimensions and margins and setup the internal variables + * \return + */ + +static void +WlibGetPaperSize( void ) +{ + double temp; + + bBorder = gtk_page_setup_get_bottom_margin( page_setup, GTK_UNIT_INCH ); + tBorder = gtk_page_setup_get_top_margin( page_setup, GTK_UNIT_INCH ); + lBorder = gtk_page_setup_get_left_margin( page_setup, GTK_UNIT_INCH ); + rBorder = gtk_page_setup_get_right_margin( page_setup, GTK_UNIT_INCH ); + paperHeight = gtk_page_setup_get_paper_height( page_setup, GTK_UNIT_INCH ); + paperWidth = gtk_page_setup_get_paper_width( page_setup, GTK_UNIT_INCH ); + + // XTrackCAD does page orientation itself. Basic assumption is that the + // paper is always oriented in portrait mode. Ignore settings by user + if( paperHeight < paperWidth ) { + temp = paperHeight; + paperHeight = paperWidth; + paperWidth = temp; + } +} + +/** + * Get the paper size. The size returned is the printable area of the + * currently selected paper, ie. the physical size minus the margins. + * \param w OUT printable width of the paper in inches + * \param h OUT printable height of the paper in inches + * \return + */ + +void wPrintGetPageSize( + double * w, + double * h ) +{ + // if necessary load the settings + if( !settings ) + WlibApplySettings( NULL ); + + WlibGetPaperSize(); + + *w = paperWidth -lBorder - rBorder; + *h = paperHeight - tBorder - bBorder; +} + +/** + * Get the paper size. The size returned is the physical size of the + * currently selected paper. + * \param w OUT physical width of the paper in inches + * \param h OUT physical height of the paper in inches + * \return + */ + +void wPrintGetPhysSize( + double * w, + double * h ) +{ + // if necessary load the settings + if( !settings ) + WlibApplySettings( NULL ); + + WlibGetPaperSize(); + + *w = paperWidth; + *h = paperHeight; +} + +/** + * Cancel the current print job. This function is preserved here for + * reference in case the function should be implemented again. + * \param context IN unused + * \return + */ +static void printAbort( void * context ) +{ + printContinue = FALSE; +// wWinShow( printAbortW, FALSE ); +} + +/** + * Initialize new page. + * The cairo_save() / cairo_restore() cycle was added to solve problems + * with a multi page print operation. This might actually be a bug in + * cairo but I didn't examine that any further. + * + * \return print context for the print operation + */ +wDraw_p wPrintPageStart( void ) +{ + pageCount++; + + cairo_save( psPrint_d.printContext ); + + return &psPrint_d; +} + +/** + * End of page. This function returns the contents of printContinue. The + * caller continues printing as long as TRUE is returned. Setting + * printContinue to FALSE in an asynchronous handler therefore cleanly + * terminates a print job at the end of the page. + * + * \param p IN ignored + * \return always printContinue + */ + + +wBool_t wPrintPageEnd( wDraw_p p ) +{ + cairo_show_page( psPrint_d.printContext ); + + cairo_restore( psPrint_d.printContext ); + + return printContinue; +} + +/***************************************************************************** + * + * PRINT START/END + * + */ + + +/** + * Start a new document + * + * \param title IN title of document ( name of layout ) + * \param fTotalPageCount IN number of pages to print (unused) + * \param copiesP OUT ??? + * \return TRUE if successful, FALSE if cancelled by user + */ + +wBool_t wPrintDocStart( const char * title, int fTotalPageCount, int * copiesP ) +{ + GtkWidget *printDialog; + gint res; + cairo_surface_type_t surface_type; + cairo_matrix_t matrix; + + printDialog = gtk_print_unix_dialog_new( title, GTK_WINDOW(gtkMainW->gtkwin)); + + // load the settings + WlibApplySettings( NULL ); + + // and apply them to the printer dialog + gtk_print_unix_dialog_set_settings( (GtkPrintUnixDialog *)printDialog, settings ); + gtk_print_unix_dialog_set_page_setup( (GtkPrintUnixDialog *)printDialog, page_setup ); + + res = gtk_dialog_run( (GtkDialog *)printDialog ); + if( res == GTK_RESPONSE_OK ) { + selPrinter = gtk_print_unix_dialog_get_selected_printer( (GtkPrintUnixDialog *)printDialog ); + + if( settings ) + g_object_unref (settings); + settings = gtk_print_unix_dialog_get_settings( (GtkPrintUnixDialog *)printDialog ); + + if( page_setup ) + g_object_unref( page_setup ); + page_setup = gtk_print_unix_dialog_get_page_setup( (GtkPrintUnixDialog *)printDialog ); + + curPrintJob = gtk_print_job_new( title, + selPrinter, + settings, + page_setup ); + + psPrint_d.curPrintSurface = gtk_print_job_get_surface( curPrintJob, + NULL ); + psPrint_d.printContext = cairo_create( psPrint_d.curPrintSurface ); + + //update the paper dimensions + WlibGetPaperSize(); + + /* for the file based surfaces the resolution is 72 dpi (see documentation) */ + surface_type = cairo_surface_get_type( psPrint_d.curPrintSurface ); + if( surface_type == CAIRO_SURFACE_TYPE_PDF || + surface_type == CAIRO_SURFACE_TYPE_PS || + surface_type == CAIRO_SURFACE_TYPE_SVG ) + psPrint_d.dpi = 72; + else + psPrint_d.dpi = (double)gtk_print_settings_get_resolution( settings ); + + // in XTrackCAD 0,0 is top left, in cairo bottom left. This is + // corrected via the following transformations. + // also the translate makes sure that the drawing is rendered + // within the paper margins + + cairo_scale( psPrint_d.printContext, 1.0, -1.0 ); + cairo_translate( psPrint_d.printContext, lBorder * psPrint_d.dpi, -(paperHeight-bBorder) *psPrint_d.dpi ); + + WlibSaveSettings( NULL ); + } + gtk_widget_destroy (printDialog); + + if (copiesP) + *copiesP = 1; + + printContinue = TRUE; + + if( res != GTK_RESPONSE_OK ) + return FALSE; + else + return TRUE; +} + +/** + * Callback for job finished event. Destroys the cairo context. + * + * \param job IN unused + * \param data IN unused + * \param err IN if != NULL, an error dialog ist displayed + * \return + */ + +void +doPrintJobFinished( GtkPrintJob *job, void *data, GError *err ) +{ + GtkWidget *dialog; + + cairo_destroy( psPrint_d.printContext ); + if( err ) { + dialog = gtk_message_dialog_new (GTK_WINDOW (gtkMainW->gtkwin), + GTK_DIALOG_DESTROY_WITH_PARENT, + GTK_MESSAGE_ERROR, GTK_BUTTONS_CLOSE, + err->message); + } +} + +/** + * Finish the print operation + * \return + */ + +void wPrintDocEnd( void ) +{ + cairo_surface_finish( psPrint_d.curPrintSurface ); + + gtk_print_job_send( curPrintJob, + doPrintJobFinished, + NULL, + NULL ); + +// wWinShow( printAbortW, FALSE ); +} + + +wBool_t wPrintQuit( void ) +{ + return FALSE; +} + + +wBool_t wPrintInit( void ) +{ + return TRUE; +} |