KiCad PCB EDA Suite
Loading...
Searching...
No Matches
unix/printing.cpp
Go to the documentation of this file.
1/*
2* This program source code file is part of KiCad, a free EDA CAD application.
3*
4* Copyright The KiCad Developers, see AUTHORS.txt for contributors.
5*
6* This program is free software: you can redistribute it and/or modify it
7* under the terms of the GNU General Public License as published by the
8* Free Software Foundation, either version 3 of the License, or (at your
9* option) any later version.
10*
11* This program is distributed in the hope that it will be useful, but
12* WITHOUT ANY WARRANTY; without even the implied warranty of
13* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14* General Public License for more details.
15*
16* You should have received a copy of the GNU General Public License
17* along with this program. If not, see <https://www.gnu.org/licenses/>.
18*/
19#include <printing.h>
20
21#include <poppler/glib/poppler.h>
22#include <gtk/gtk.h>
23#include <unistd.h>
24#include <algorithm>
25#include <memory>
26
27#include <wx/filename.h>
28#include <wx/filesys.h>
29#include <wx/print.h>
30#include <wx/cmndata.h>
31#include <wx/regex.h>
32
33#if wxUSE_GTKPRINT
34#include <wx/gtk/print.h>
35#endif
36
37namespace
38{
39struct PrintData
40{
41 PopplerDocument* doc;
42 bool fit_to_page;
43
44 PrintData( PopplerDocument* d ) :
45 doc( d ),
46 fit_to_page( true )
47 {
48 }
49};
50
51void draw_page( GtkPrintOperation* operation, GtkPrintContext* context, gint page_nr, gpointer user_data )
52{
53 PrintData* print_data = static_cast<PrintData*>( user_data );
54
55 if( !print_data || !print_data->doc )
56 return;
57
58 PopplerPage* page = poppler_document_get_page( print_data->doc, page_nr );
59
60 if( !page ) return;
61
62 auto cleanup_page = std::unique_ptr<PopplerPage, decltype( &g_object_unref )>( page, &g_object_unref );
63
64 cairo_t* cr = gtk_print_context_get_cairo_context( context );
65
66 if( !cr ) return;
67
68 // Get page dimensions
69 double page_width, page_height;
70 poppler_page_get_size( page, &page_width, &page_height );
71
72 // Get print context dimensions
73 double print_width = gtk_print_context_get_width( context );
74 double print_height = gtk_print_context_get_height( context );
75
76 cairo_save( cr );
77 auto cleanup_cairo = std::unique_ptr<cairo_t, decltype( &cairo_restore )>( cr, &cairo_restore );
78
79 if( print_data->fit_to_page )
80 {
81 // Calculate scaling to fit page while maintaining aspect ratio
82 double scale_x = print_width / page_width;
83 double scale_y = print_height / page_height;
84 double scale = std::min( scale_x, scale_y );
85
86 // Center the page
87 double scaled_width = page_width * scale;
88 double scaled_height = page_height * scale;
89 double offset_x = ( print_width - scaled_width ) / 2.0;
90 double offset_y = ( print_height - scaled_height ) / 2.0;
91
92 // Apply transformations
93 cairo_translate( cr, offset_x, offset_y );
94 cairo_scale( cr, scale, scale );
95 }
96
97 // Set white background
98 cairo_set_source_rgb( cr, 1.0, 1.0, 1.0 );
99 cairo_paint( cr );
100
101 // Render the page
102 poppler_page_render( page, cr );
103}
104
105void begin_print_callback( GtkPrintOperation* operation, GtkPrintContext* context, gpointer user_data )
106{
107 PrintData* print_data = static_cast<PrintData*>( user_data );
108 if( !print_data || !print_data->doc )
109 {
110 gtk_print_operation_cancel( operation );
111 return;
112 }
113
114 int num_pages = poppler_document_get_n_pages( print_data->doc );
115 if( num_pages <= 0 )
116 {
117 gtk_print_operation_cancel( operation );
118 return;
119 }
120
121 gtk_print_operation_set_n_pages( operation, num_pages );
122}
123
124void request_page_setup_callback( GtkPrintOperation* operation, GtkPrintContext* context, gint page_nr,
125 GtkPageSetup* setup, gpointer user_data )
126{
127 PrintData* print_data = static_cast<PrintData*>( user_data );
128
129 if( !print_data || !print_data->doc )
130 return;
131
132 PopplerPage* page = poppler_document_get_page( print_data->doc, page_nr );
133
134 if( !page )
135 return;
136
137 // Get page dimensions to determine orientation
138 double page_width, page_height;
139 poppler_page_get_size( page, &page_width, &page_height );
140
141 // Set orientation based on page dimensions
142 GtkPageOrientation orientation =
143 ( page_width > page_height ) ? GTK_PAGE_ORIENTATION_LANDSCAPE : GTK_PAGE_ORIENTATION_PORTRAIT;
144 gtk_page_setup_set_orientation( setup, orientation );
145
146 g_object_unref( page );
147}
148} // namespace
149
150namespace KIPLATFORM
151{
152namespace PRINTING
153{
154 PRINT_RESULT PrintPDF( const std::string& aFile, bool fit_to_page )
155 {
156 // Check file accessibility
157 if( access( aFile.c_str(), R_OK ) != 0 )
159
160 // Create file URI
161 gchar* uri = g_filename_to_uri( aFile.c_str(), NULL, NULL );
162 if( !uri )
164
165 // Load the PDF document
166 GError* error = NULL;
167 PopplerDocument* doc = poppler_document_new_from_file( uri, NULL, &error );
168 g_free( uri );
169
170 if( error )
171 {
172 g_error_free( error );
174 }
175
176 if( !doc )
178
179 auto cleanup_doc = std::unique_ptr<PopplerDocument, decltype(&g_object_unref)>(doc, &g_object_unref);
180
181 // Check if document has pages
182 int num_pages = poppler_document_get_n_pages( doc );
183
184 if( num_pages <= 0 )
186
187 // Create print data
188 PrintData print_data( doc );
189 print_data.fit_to_page = fit_to_page;
190
191 // Create print operation
192 GtkPrintOperation* op = gtk_print_operation_new();
193
194 if( !op )
196
197 auto cleanup_op = std::unique_ptr<GtkPrintOperation, decltype( &g_object_unref )>( op, &g_object_unref );
198
199 // Set up print operation properties
200 gtk_print_operation_set_use_full_page( op, FALSE );
201 gtk_print_operation_set_unit( op, GTK_UNIT_POINTS );
202
203 // Connect callbacks
204 g_signal_connect( op, "begin-print", G_CALLBACK( begin_print_callback ), &print_data );
205 g_signal_connect( op, "draw-page", G_CALLBACK( draw_page ), &print_data );
206 g_signal_connect( op, "request-page-setup", G_CALLBACK( request_page_setup_callback ), &print_data );
207
208 // Run the print operation
209 error = NULL;
210 GtkPrintOperationResult result =
211 gtk_print_operation_run( op, GTK_PRINT_OPERATION_ACTION_PRINT_DIALOG, NULL, &error );
212
213 // Handle errors and determine result
214 PRINT_RESULT return_result;
215
216 if( error )
217 {
218 g_error_free( error );
219 return_result = PRINT_RESULT::FAILED_TO_PRINT;
220 }
221 else
222 {
223 switch( result )
224 {
225 case GTK_PRINT_OPERATION_RESULT_APPLY: return_result = PRINT_RESULT::OK; break;
226 case GTK_PRINT_OPERATION_RESULT_CANCEL: return_result = PRINT_RESULT::CANCELLED; break;
227 case GTK_PRINT_OPERATION_RESULT_ERROR:
228 default: return_result = PRINT_RESULT::FAILED_TO_PRINT; break;
229 }
230 }
231
232 return return_result;
233 }
234
235 PRINT_RESULT PrintPDF( const std::string& aFile )
236 {
237 return PrintPDF( aFile, true );
238 }
239
240 // GTK opens its scratch file with the g_mkstemp() templates "gtkprintXXXXXX" and
241 // "gtkprint_XXXXXX", so the name is the only thing separating it from a destination the user
242 // picked. Matching the whole template rather than the prefix keeps a file the user named
243 // gtkprint-something safe
244 static bool isSpoolName( const wxString& aFileName )
245 {
246 static wxRegEx spoolTemplate( wxS( "^gtkprint_?[A-Z0-9]{6}$" ) );
247
248 return spoolTemplate.Matches( aFileName );
249 }
250
251
252 void ResetPrintToFilePath( wxPrintData& aData )
253 {
254 if( isSpoolName( wxFileName( aData.GetFilename() ).GetFullName() ) )
255 aData.SetFilename( wxEmptyString );
256
257#if wxUSE_GTKPRINT
258 wxGtkPrintNativeData* nativeData = dynamic_cast<wxGtkPrintNativeData*>( aData.GetNativeData() );
259
260 if( !nativeData || !nativeData->GetPrintConfig() )
261 return;
262
263 const gchar* uri = gtk_print_settings_get( nativeData->GetPrintConfig(),
264 GTK_PRINT_SETTINGS_OUTPUT_URI );
265
266 if( !uri )
267 return;
268
269 wxFileName destination = wxFileSystem::URLToFileName( wxString::FromUTF8( uri ) );
270
271 if( isSpoolName( destination.GetFullName() ) )
272 gtk_print_settings_unset( nativeData->GetPrintConfig(), GTK_PRINT_SETTINGS_OUTPUT_URI );
273#endif
274 }
275
276} // namespace PRINTING
277} // namespace KIPLATFORM
static bool isSpoolName(const wxString &aFileName)
void ResetPrintToFilePath(wxPrintData &aData)
Discard a "print to file" destination in aData that the platform print backend generated for its own ...
PRINT_RESULT PrintPDF(const std::string &aFile)
const int scale
wxString result
Test unit parsing edge cases and error handling.