KiCad PCB EDA Suite
Loading...
Searching...
No Matches
windows/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
20#include <printing.h>
21
22#include <wx/print.h>
23#include <wx/cmndata.h>
24
25#ifndef __MINGW32__
26#include <windows.h>
27#include <algorithm>
28#include <cmath>
29#include <map>
30#include <utility>
31#include <roapi.h>
32
33#include <winrt/base.h>
34#include <winrt/Windows.Foundation.h>
35#include <winrt/Windows.Foundation.Collections.h>
36#include <winrt/Windows.Graphics.Printing.h>
37#include <winrt/Windows.UI.Xaml.h>
38#include <winrt/Windows.UI.Xaml.Controls.h>
39#include <winrt/Windows.UI.Xaml.Media.h>
40#include <winrt/Windows.UI.Xaml.Printing.h>
41#include <winrt/Windows.UI.Xaml.Hosting.h>
42#include <winrt/Windows.Storage.h>
43#include <winrt/Windows.Storage.Streams.h>
44#include <winrt/Windows.Data.Pdf.h>
45#include <winrt/Windows.Graphics.Imaging.h>
46
47#include <winrt/base.h>
48#include <winrt/Windows.Foundation.h>
49#include <winrt/Windows.Foundation.Collections.h>
50#include <winrt/Windows.Graphics.Printing.h>
51#include <winrt/Windows.UI.Xaml.h>
52#include <winrt/Windows.UI.Xaml.Controls.h>
53#include <winrt/Windows.UI.Xaml.Media.Imaging.h>
54#include <winrt/Windows.UI.Xaml.Printing.h>
55#include <winrt/Windows.UI.Xaml.Hosting.h>
56#include <winrt/Windows.Storage.h>
57#include <winrt/Windows.Storage.Streams.h>
58#include <winrt/Windows.Data.Pdf.h>
59#include <winrt/Windows.Graphics.Imaging.h>
60
61#include <wx/log.h>
62
63using namespace winrt;
64
65// Manual declaration of IPrintManagerInterop to avoid missing header
66MIDL_INTERFACE("C5435A42-8D43-4E7B-A68A-EF311E392087")
67IPrintManagerInterop : public ::IInspectable
68{
69public:
70 virtual HRESULT STDMETHODCALLTYPE GetForWindow(
71 /* [in] */ HWND appWindow,
72 /* [in] */ REFIID riid,
73 /* [iid_is][retval][out] */ void **printManager) = 0;
74
75 virtual HRESULT STDMETHODCALLTYPE ShowPrintUIForWindowAsync(
76 /* [in] */ HWND appWindow,
77 /* [retval][out] */ void **operation) = 0;
78};
79
80// Manual declaration of IDesktopWindowXamlSourceNative to avoid missing header
81MIDL_INTERFACE("3cbcf1bf-2f76-4e9c-96ab-e84b37972554")
82IDesktopWindowXamlSourceNative : public ::IUnknown
83{
84public:
85 virtual HRESULT STDMETHODCALLTYPE AttachToWindow(
86 /* [in] */ HWND parentWnd) = 0;
87
88 virtual HRESULT STDMETHODCALLTYPE get_WindowHandle(
89 /* [retval][out] */ HWND *hWnd) = 0;
90};
91
92static inline std::pair<uint32_t, uint32_t> DpToPixels( winrt::Windows::Data::Pdf::PdfPage const& page, double dpi )
93{
94 const auto s = page.Size(); // DIPs (1 DIP = 1/96 inch)
95 const double scale = dpi / 96.0;
96 uint32_t w = static_cast<uint32_t>( std::max( 1.0, std::floor( s.Width * scale + 0.5 ) ) );
97 uint32_t h = static_cast<uint32_t>( std::max( 1.0, std::floor( s.Height * scale + 0.5 ) ) );
98 return { w, h };
99}
100
101// Helper class to manage image with its associated stream
103{
104 winrt::Windows::UI::Xaml::Controls::Image image;
105 winrt::Windows::Storage::Streams::InMemoryRandomAccessStream stream;
106
107 ManagedImage() = default;
108 ManagedImage(winrt::Windows::UI::Xaml::Controls::Image img, winrt::Windows::Storage::Streams::InMemoryRandomAccessStream str) : image(img), stream(str) {}
109
110 ManagedImage(ManagedImage&& other) noexcept
111 : image(std::move(other.image)), stream(std::move(other.stream)) {}
112
114 if (this != &other) {
115 image = std::move(other.image);
116 stream = std::move(other.stream);
117 }
118 return *this;
119 }
120};
121
122// Render one page to a XAML Image using RenderToStreamAsync
123// dpi: e.g., 300 for preview; 600 for print
124// Returns a ManagedImage that keeps the stream alive
125static ManagedImage RenderPdfPageToImage( winrt::Windows::Data::Pdf::PdfDocument const& pdf, uint32_t pageIndex, double dpi )
126{
127 auto page = pdf.GetPage( pageIndex );
128
129 if( !page )
130 {
131 wxLogTrace( PRINTING_TRACE, "Failed to get page %u from PDF document", pageIndex );
132 return {};
133 }
134
135 auto [pxW, pxH] = DpToPixels( page, dpi );
136
137 winrt::Windows::Data::Pdf::PdfPageRenderOptions opts;
138 opts.DestinationWidth( pxW );
139 opts.DestinationHeight( pxH );
140
141 winrt::Windows::Storage::Streams::InMemoryRandomAccessStream stream;
142
143 try
144 {
145 page.RenderToStreamAsync( stream, opts ).get(); // sync for simplicity
146 }
147 catch( std::exception& e )
148 {
149 wxLogTrace( PRINTING_TRACE, "Failed to render page %u to image: %s", pageIndex, e.what() );
150 return {};
151 }
152
153 // Use a BitmapImage that sources directly from the stream
154 winrt::Windows::UI::Xaml::Media::Imaging::BitmapImage bmp;
155
156 try
157 {
158 stream.Seek(0);
159 bmp.SetSourceAsync( stream ).get();
160 }
161 catch( const winrt::hresult_error& e )
162 {
163 wxLogTrace( PRINTING_TRACE, "Failed to set BitmapImage source for page %u: %s", pageIndex, e.message().c_str() );
164 return {};
165 }
166 catch( std::exception& e )
167 {
168 wxLogTrace( PRINTING_TRACE, "Failed to set BitmapImage source for page %u: %s", pageIndex, e.what() );
169 return {};
170 }
171
172 winrt::Windows::UI::Xaml::Controls::Image img;
173 img.Source( bmp );
174 img.Stretch( winrt::Windows::UI::Xaml::Media::Stretch::Uniform );
175
176 // Return both image and stream to keep stream alive
177 return ManagedImage{ img, stream };
178}
179
180
181namespace KIPLATFORM {
182namespace PRINTING {
183
185{
186public:
187 WIN_PDF_PRINTER( HWND hwndOwner, winrt::Windows::Data::Pdf::PdfDocument const& pdf ) :
188 m_hwnd( hwndOwner ),
189 m_pdf( pdf )
190 {
191 }
192
194 {
195 if( !m_pdf )
196 {
197 wxLogTrace( PRINTING_TRACE, "Failed to load PDF document" );
199 }
200
201 // Create hidden XAML Island host
202 m_xamlSource = winrt::Windows::UI::Xaml::Hosting::DesktopWindowXamlSource();
203 auto native = m_xamlSource.as<IDesktopWindowXamlSourceNative>();
204
205 if( !native )
206 {
207 wxLogTrace( PRINTING_TRACE, "Failed to create XAML Island host" );
209 }
210
211 RECT rc{ 0, 0, 100, 100 }; // Use larger minimum size
212 m_host = ::CreateWindowExW( 0, L"STATIC", L"", WS_CHILD | WS_CLIPSIBLINGS | WS_CLIPCHILDREN,
213 rc.left, rc.top, rc.right - rc.left, rc.bottom - rc.top, m_hwnd, nullptr,
214 ::GetModuleHandleW( nullptr ), nullptr );
215
216 auto cleanup_guard = std::unique_ptr<void, std::function<void( void* )>>
217 ( (void*) 1, [this]( void* ){ this->cleanup(); } );
218
219 if( !m_host )
220 {
221 wxLogTrace( PRINTING_TRACE, "Failed to create host window" );
223 }
224
225 if( FAILED( native->AttachToWindow( m_host ) ) )
226 {
227 wxLogTrace( PRINTING_TRACE, "Failed to attach XAML Island to host window" );
229 }
230
231 m_root = winrt::Windows::UI::Xaml::Controls::Grid();
232 m_xamlSource.Content( m_root );
233
234 m_printDoc = winrt::Windows::UI::Xaml::Printing::PrintDocument();
235 m_docSrc = m_printDoc.DocumentSource();
236 m_pageCount = std::max<uint32_t>( 1, m_pdf.PageCount() );
237
238 m_paginateToken = m_printDoc.Paginate(
239 [this]( winrt::Windows::Foundation::IInspectable const& sender, winrt::Windows::UI::Xaml::Printing::PaginateEventArgs const& e )
240 {
241 m_printDoc.SetPreviewPageCount( m_pageCount, winrt::Windows::UI::Xaml::Printing::PreviewPageCountType::Final );
242 } );
243
244 m_getPreviewToken = m_printDoc.GetPreviewPage(
245 [this]( winrt::Windows::Foundation::IInspectable const& sender, winrt::Windows::UI::Xaml::Printing::GetPreviewPageEventArgs const& e )
246 {
247 const uint32_t index = e.PageNumber() - 1; // 1-based from system
248 auto managedImg = RenderPdfPageToImage( m_pdf, index, /*dpi*/ 300.0 );
249 if( managedImg.image )
250 {
251 // Store the managed image to keep stream alive
252 m_previewImages[index] = std::move(managedImg);
253 m_printDoc.SetPreviewPage( e.PageNumber(), m_previewImages[index].image );
254 }
255 } );
256
257 m_addPagesToken = m_printDoc.AddPages(
258 [this]( winrt::Windows::Foundation::IInspectable const& sender, winrt::Windows::UI::Xaml::Printing::AddPagesEventArgs const& e )
259 {
260 for( uint32_t i = 0; i < m_pageCount; ++i )
261 {
262 auto managedImg = RenderPdfPageToImage( m_pdf, i, /*dpi*/ 600.0 );
263 if( managedImg.image )
264 {
265 // Store the managed image to keep stream alive
266 m_printImages[i] = std::move(managedImg);
267 m_printDoc.AddPage( m_printImages[i].image );
268 }
269 }
270 m_printDoc.AddPagesComplete();
271 } );
272
273 try
274 {
275 auto factory = winrt::get_activation_factory<winrt::Windows::Graphics::Printing::PrintManager>();
276 auto pmInterop = factory.as<IPrintManagerInterop>();
277
278 winrt::Windows::Graphics::Printing::PrintManager printManager{ nullptr };
279
280 if( FAILED( pmInterop->GetForWindow( m_hwnd,
281 winrt::guid_of<winrt::Windows::Graphics::Printing::PrintManager>(),
282 winrt::put_abi( printManager ) ) ) )
283 {
284 wxLogTrace( PRINTING_TRACE, "Failed to get PrintManager for window" );
286 }
287
288 // Now we have the WinRT PrintManager directly
290 m_taskRequestedToken = m_rtPM.PrintTaskRequested(
291 [this]( winrt::Windows::Foundation::IInspectable const& sender, winrt::Windows::Graphics::Printing::PrintTaskRequestedEventArgs const& e )
292 {
293 auto task = e.Request().CreatePrintTask( L"KiCad PDF Print",
294 [this]( winrt::Windows::Graphics::Printing::PrintTaskSourceRequestedArgs const& sourceRequestedArgs )
295 {
296 // Supply document source for preview
297 sourceRequestedArgs.SetSource( m_docSrc );
298 } );
299 } );
300
301 winrt::Windows::Foundation::IAsyncOperation<bool> asyncOp{ nullptr };
302
303 // Immediately wait for results to keep this in thread
304 if( FAILED( pmInterop->ShowPrintUIForWindowAsync( m_hwnd, winrt::put_abi(asyncOp) ) ) )
305 {
306 wxLogTrace( PRINTING_TRACE, "Failed to show print UI for window" );
308 }
309
310 bool shown = false;
311
312 try
313 {
314 shown = asyncOp.GetResults();
315 }
316 catch( std::exception& e )
317 {
318 wxLogTrace( PRINTING_TRACE, "GetResults threw an exception for the print window: %s", e.what() );
320 }
321
323 }
324 catch( std::exception& e )
325 {
326 wxLogTrace( PRINTING_TRACE, "Exception caught in print operation: %s", e.what() );
328 }
329 }
330
331private:
332 void cleanup()
333 {
334 // Clear image containers first to release streams
335 m_previewImages.clear();
336 m_printImages.clear();
337
338 if( m_rtPM )
339 {
340 m_rtPM.PrintTaskRequested( m_taskRequestedToken );
341 m_rtPM = nullptr;
342 }
343
344 if( m_printDoc )
345 {
346 m_printDoc.AddPages( m_addPagesToken );
347 m_printDoc.GetPreviewPage( m_getPreviewToken );
348 m_printDoc.Paginate( m_paginateToken );
349 }
350
351 m_docSrc = nullptr;
352 m_printDoc = nullptr;
353 m_root = nullptr;
354
355 if( m_host )
356 {
357 ::DestroyWindow( m_host );
358 m_host = nullptr;
359 }
360
361 m_xamlSource = nullptr;
362 }
363
364private:
365 HWND m_hwnd{};
366 winrt::Windows::Data::Pdf::PdfDocument m_pdf{ nullptr };
367
368 winrt::Windows::UI::Xaml::Hosting::DesktopWindowXamlSource m_xamlSource{ nullptr };
369 winrt::Windows::UI::Xaml::Controls::Grid m_root{ nullptr };
370 winrt::Windows::UI::Xaml::Printing::PrintDocument m_printDoc{ nullptr };
371 winrt::Windows::Graphics::Printing::IPrintDocumentSource m_docSrc{ nullptr };
372
373 uint32_t m_pageCount{ 0 };
374 winrt::Windows::Graphics::Printing::PrintManager m_rtPM{ nullptr };
375 winrt::event_token m_taskRequestedToken{};
376
377 winrt::event_token m_paginateToken{};
378 winrt::event_token m_getPreviewToken{};
379 winrt::event_token m_addPagesToken{};
380
381 HWND m_host{ nullptr };
382
383 // Store managed images to keep streams alive
384 std::map<uint32_t, ManagedImage> m_previewImages;
385 std::map<uint32_t, ManagedImage> m_printImages;
386};
387
388
389static std::wstring Utf8ToWide( std::string const& s )
390{
391 if( s.empty() ) return {};
392
393 int len = MultiByteToWideChar( CP_UTF8, 0, s.data(), (int) s.size(), nullptr, 0 );
394 std::wstring out( len, L'\0' );
395
396 MultiByteToWideChar( CP_UTF8, 0, s.data(), (int) s.size(), out.data(), len );
397 return out;
398}
399
400PRINT_RESULT PrintPDF(std::string const& aFile )
401{
402 // Validate path
403 DWORD attrs = GetFileAttributesA( aFile.c_str() );
404
405 if( attrs == INVALID_FILE_ATTRIBUTES )
407
408 // Load PDF via Windows.Data.Pdf
409 winrt::Windows::Data::Pdf::PdfDocument pdf{ nullptr };
410
411 try
412 {
413 auto path = Utf8ToWide( aFile );
414 auto file = winrt::Windows::Storage::StorageFile::GetFileFromPathAsync( winrt::hstring( path ) ).get();
415 pdf = winrt::Windows::Data::Pdf::PdfDocument::LoadFromFileAsync( file ).get();
416 }
417 catch( ... )
418 {
420 }
421
422 if( !pdf || pdf.PageCount() == 0 ) return PRINT_RESULT::FAILED_TO_LOAD;
423
424 HWND hwndOwner = ::GetActiveWindow();
425 if( !hwndOwner ) hwndOwner = ::GetForegroundWindow();
426 if( !hwndOwner ) return PRINT_RESULT::FAILED_TO_PRINT;
427
428 try
429 {
430 WIN_PDF_PRINTER printer( hwndOwner, pdf );
431 return printer.Run();
432 }
433 catch( ... )
434 {
436 }
437}
438
439} // namespace PRINTING
440} // namespace KIPLATFORM
441
442#else
443
444namespace KIPLATFORM
445{
446namespace PRINTING
447{
448 PRINT_RESULT PrintPDF( std::string const& )
449 {
451 }
452} // namespace PRINTING
453} // namespace KIPLATFORM
454
455#endif
456
457namespace KIPLATFORM
458{
459namespace PRINTING
460{
461 // Windows keeps the print-to-file destination in the wx-level filename, so clearing it
462 // is sufficient here.
463 void ResetPrintToFilePath( wxPrintData& aData )
464 {
465 aData.SetFilename( wxEmptyString );
466 }
467} // namespace PRINTING
468} // namespace KIPLATFORM
int index
winrt::Windows::UI::Xaml::Controls::Grid m_root
winrt::Windows::Graphics::Printing::PrintManager m_rtPM
std::map< uint32_t, ManagedImage > m_previewImages
winrt::Windows::UI::Xaml::Printing::PrintDocument m_printDoc
WIN_PDF_PRINTER(HWND hwndOwner, winrt::Windows::Data::Pdf::PdfDocument const &pdf)
std::map< uint32_t, ManagedImage > m_printImages
winrt::Windows::Data::Pdf::PdfDocument m_pdf
winrt::Windows::Graphics::Printing::IPrintDocumentSource m_docSrc
winrt::Windows::UI::Xaml::Hosting::DesktopWindowXamlSource m_xamlSource
static std::wstring Utf8ToWide(std::string const &s)
void ResetPrintToFilePath(wxPrintData &aData)
Clear any leftover "print to file" destination from aData.
PRINT_RESULT PrintPDF(const std::string &aFile)
#define PRINTING_TRACE
Definition printing.h:28
const int scale
ManagedImage & operator=(ManagedImage &&other) noexcept
winrt::Windows::UI::Xaml::Controls::Image image
ManagedImage()=default
winrt::Windows::Storage::Streams::InMemoryRandomAccessStream stream
ManagedImage(ManagedImage &&other) noexcept
ManagedImage(winrt::Windows::UI::Xaml::Controls::Image img, winrt::Windows::Storage::Streams::InMemoryRandomAccessStream str)
std::string path
IPrintManagerInterop REFIID riid
static ManagedImage RenderPdfPageToImage(winrt::Windows::Data::Pdf::PdfDocument const &pdf, uint32_t pageIndex, double dpi)
virtual HRESULT STDMETHODCALLTYPE get_WindowHandle(HWND *hWnd)=0
virtual HRESULT STDMETHODCALLTYPE ShowPrintUIForWindowAsync(HWND appWindow, void **operation)=0
static std::pair< uint32_t, uint32_t > DpToPixels(winrt::Windows::Data::Pdf::PdfPage const &page, double dpi)
IPrintManagerInterop REFIID void ** printManager