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