From 5a64343fb4fe9f962b02ade49ee95d899c4add51 Mon Sep 17 00:00:00 2001
From: Maciej Suminski <maciej.suminski@cern.ch>
Date: Tue, 18 Sep 2018 11:07:44 +0200
Subject: [PATCH] CAIRO_PRINT_CTX class

CAIRO_PRINT_CTX provides a Cairo context created from wxPrintDC.
It allows one to prepare printouts using the Cairo library and
let wxWidgets handle the rest.
---
 common/CMakeLists.txt            |   1 +
 common/gal/cairo/cairo_print.cpp | 181 +++++++++++++++++++++++++++++++
 include/gal/cairo/cairo_print.h  | 115 ++++++++++++++++++++
 3 files changed, 297 insertions(+)
 create mode 100644 common/gal/cairo/cairo_print.cpp
 create mode 100644 include/gal/cairo/cairo_print.h

diff --git a/common/CMakeLists.txt b/common/CMakeLists.txt
index 37d7532f46..580fc890a2 100644
--- a/common/CMakeLists.txt
+++ b/common/CMakeLists.txt
@@ -67,6 +67,7 @@ set( GAL_SRCS
     # Cairo GAL
     gal/cairo/cairo_gal.cpp
     gal/cairo/cairo_compositor.cpp
+    gal/cairo/cairo_print.cpp
     )
 
 set( LEGACY_GAL_SRCS
diff --git a/common/gal/cairo/cairo_print.cpp b/common/gal/cairo/cairo_print.cpp
new file mode 100644
index 0000000000..4ef673fdb5
--- /dev/null
+++ b/common/gal/cairo/cairo_print.cpp
@@ -0,0 +1,181 @@
+/*
+ * Copyright (C) 2018 CERN
+ * Author: Maciej Suminski <maciej.suminski@cern.ch>
+ * Author: Tomasz Wlostowski <tomasz.wlostowski@cern.ch>
+ *
+ * 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 3 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <gal/cairo/cairo_print.h>
+
+#include <stdexcept>
+#include <wx/dcclient.h>
+#include <wx/dcgraph.h>
+#include <wx/dcmemory.h>
+#include <wx/dcprint.h>
+
+#ifdef __WXMSW__
+#include <windows.h>
+#include <gdiplus.h>
+#include <cairo-win32.h>
+#include <wx/msw/enhmeta.h>
+#endif /* __WXMSW__ */
+
+#ifdef __WXMAC__
+#include <ApplicationServices/ApplicationServices.h>
+#include <cairo-quartz.h>
+#endif /* __WXMAC__ */
+
+using namespace KIGFX;
+
+CAIRO_PRINT_CTX::CAIRO_PRINT_CTX( wxDC* aDC )
+    : m_gcdc( nullptr ), m_ctx( nullptr ), m_surface( nullptr )
+{
+    if( wxPrinterDC* printerDC = dynamic_cast<wxPrinterDC*>( aDC ) )
+        m_gcdc = new wxGCDC( *printerDC );
+    else if( wxMemoryDC* memoryDC = dynamic_cast<wxMemoryDC*>( aDC ) )
+        m_gcdc = new wxGCDC( *memoryDC );
+    else if( wxWindowDC* windowDC = dynamic_cast<wxWindowDC*>( aDC ) )
+        m_gcdc = new wxGCDC( *windowDC );
+#ifdef __WXMSW__
+    else if( wxEnhMetaFileDC* enhMFDC = dynamic_cast<wxEnhMetaFileDC*>( aDC ) )
+        m_gcdc = new wxGCDC( *enhMFDC );
+#endif /* __WXMSW__ */
+    else
+        throw std::runtime_error( "Unhandled wxDC type" );
+
+    wxGraphicsContext* gctx = m_gcdc->GetGraphicsContext();
+
+    if( !gctx )
+        throw std::runtime_error( "Could not get the Graphics Context" );
+
+#ifdef __WXGTK__
+    m_ctx = static_cast<cairo_t*>( gctx->GetNativeContext() );
+    m_surface = cairo_get_target( m_ctx );
+    // Magic value: apparently for linux, all printers are 72 DPI
+    m_dpi = 72.0;
+#endif /* __WXGTK__ */
+
+#ifdef __WXMSW__
+    Gdiplus::Graphics* g = static_cast<Gdiplus::Graphics*>( gctx->GetNativeContext() );
+    m_hdc = g->GetHDC();
+    m_surface = cairo_win32_printing_surface_create( static_cast<HDC>( m_hdc ) );
+    m_ctx = cairo_create( m_surface );
+    m_dpi = GetDeviceCaps( static_cast<HDC>( m_hdc ), LOGPIXELSX );
+#endif /* __WXMSW__ */
+
+#ifdef __WXMAC__
+    wxSize size = m_gcdc->GetSize();
+    CGContextRef cg = (CGContextRef) gctx->GetNativeContext();
+    m_surface = cairo_quartz_surface_create_for_cg_context( cg, size.x, size.y );
+    m_ctx = cairo_create( m_surface );
+    wxASSERT( aDC->GetPPI().x == aDC->GetPPI().y );
+    m_dpi = aDC->GetPPI().x;
+#endif /* __WXMAC__ */
+
+    if( !m_ctx || cairo_status( m_ctx ) != CAIRO_STATUS_SUCCESS )
+        throw std::runtime_error( "Could not create Cairo context" );
+
+    if( !m_surface || cairo_surface_status( m_surface ) != CAIRO_STATUS_SUCCESS )
+        throw std::runtime_error( "Could not create Cairo surface" );
+
+    cairo_reference( m_ctx );
+    cairo_surface_reference( m_surface );
+}
+
+
+CAIRO_PRINT_CTX::~CAIRO_PRINT_CTX()
+{
+#ifdef __WXMSW__
+    cairo_surface_show_page( m_surface );
+    wxGraphicsContext* gctx = m_gcdc->GetGraphicsContext();
+    Gdiplus::Graphics* g = static_cast<Gdiplus::Graphics*>( gctx->GetNativeContext() );
+    g->ReleaseHDC( static_cast<HDC>( m_hdc ) );
+#endif /* __WXMSW__ */
+
+    cairo_surface_destroy( m_surface );
+    cairo_destroy( m_ctx );
+    delete m_gcdc;
+}
+
+
+CAIRO_PRINT_GAL::CAIRO_PRINT_GAL( GAL_DISPLAY_OPTIONS& aDisplayOptions,
+        cairo_t* aContext, cairo_surface_t* aSurface )
+    : CAIRO_GAL_BASE( aDisplayOptions )
+{
+    cairo_reference( aContext );
+    cairo_surface_reference( aSurface );
+    context = currentContext = aContext;
+    surface = aSurface;
+    m_clearColor = COLOR4D( 1.0, 1.0, 1.0, 1.0 );
+    resetContext();
+}
+
+
+void CAIRO_PRINT_GAL::ComputeWorldScreenMatrix()
+{
+    // worldUnitLength = inch per integer
+    worldUnitLength = 1e-9 /* 1 nm */ / 0.0254 /* 1 inch in meters */;
+    worldScale = screenDPI * worldUnitLength * zoomFactor;
+
+    const auto paperSizeIU = VECTOR2D( m_nativePaperSize.y, m_nativePaperSize.x ) /* inches */ * 0.0254 * 1e9 /* 1 inch in nm */;
+    const auto paperSizeIUTransposed = VECTOR2D( paperSizeIU.y, paperSizeIU.x );
+
+    MATRIX3x3D scale, translation, flip, rotate, lookat;
+
+    scale.SetIdentity();
+    translation.SetIdentity();
+    flip.SetIdentity();
+    rotate.SetIdentity();
+    lookat.SetIdentity();
+
+    if( m_hasNativeLandscapeRotation )
+    {
+        translation.SetTranslation( 0.5 / GetZoomFactor() * paperSizeIUTransposed );
+    }
+    else
+    {
+        if( isLandscape() )
+        {
+            translation.SetTranslation( 0.5 / GetZoomFactor() * paperSizeIU );
+            rotate.SetRotation( 90.0 * M_PI / 180.0 );
+        }
+        else
+        {
+            translation.SetTranslation( 0.5 / GetZoomFactor() * paperSizeIUTransposed );
+        }
+    }
+
+    scale.SetScale( VECTOR2D( worldScale, worldScale ) );
+    flip.SetScale( VECTOR2D( globalFlipX ? -1.0 : 1.0, globalFlipY ? -1.0 : 1.0 ) );
+    lookat.SetTranslation( -lookAtPoint );
+
+    worldScreenMatrix = scale * translation * flip * rotate * lookat;
+    screenWorldMatrix = worldScreenMatrix.Inverse();
+}
+
+
+void CAIRO_PRINT_GAL::SetNativePaperSize( const VECTOR2D& aSize, bool aHasNativeLandscapeRotation )
+{
+    m_nativePaperSize = aSize;
+    m_hasNativeLandscapeRotation = aHasNativeLandscapeRotation;
+}
+
+
+void CAIRO_PRINT_GAL::SetSheetSize( const VECTOR2D& aSize )
+{
+    // Convert aSize (inches) to pixels
+    SetScreenSize( VECTOR2I( std::ceil( aSize.x * screenDPI ) * 2,
+                std::ceil( aSize.y * screenDPI ) * 2 ) );
+}
diff --git a/include/gal/cairo/cairo_print.h b/include/gal/cairo/cairo_print.h
new file mode 100644
index 0000000000..90d2b665da
--- /dev/null
+++ b/include/gal/cairo/cairo_print.h
@@ -0,0 +1,115 @@
+/*
+ * Copyright (C) 2018 CERN
+ * Author: Maciej Suminski <maciej.suminski@cern.ch>
+ * Author: Tomasz Wlostowski <tomasz.wlostowski@cern.ch>
+ *
+ * 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 3 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef _CAIRO_PRINT_H_
+#define _CAIRO_PRINT_H_
+
+#include <gal/cairo/cairo_gal.h>
+
+class wxDC;
+class wxGCDC;
+
+namespace KIGFX
+{
+/**
+ * CAIRO_PRINT_CTX provides a Cairo context created from wxPrintDC.
+ * It allows one to prepare printouts using the Cairo library and let wxWidgets handle the rest.
+ */
+class CAIRO_PRINT_CTX
+{
+public:
+    CAIRO_PRINT_CTX( wxDC* aDC );
+    ~CAIRO_PRINT_CTX();
+
+    cairo_t* GetContext() const
+    {
+        return m_ctx;
+    }
+
+    cairo_surface_t* GetSurface() const
+    {
+        return m_surface;
+    }
+
+    double GetNativeDPI() const
+    {
+        return m_dpi;
+    }
+
+    bool HasNativeLandscapeRotation() const
+    {
+#ifdef __WXGTK__
+        return false;
+#else
+        return true;
+#endif /* not __WXGTK__ */
+    }
+
+private:
+    wxGCDC* m_gcdc;
+    cairo_t* m_ctx;
+    cairo_surface_t* m_surface;
+
+#ifdef __WXMSW__
+    ///> DC handle on Windows
+    void* m_hdc;        // the real type is HDC, but do not pull in extra headers
+#endif /* __WXMSW__ */
+
+    double m_dpi;
+};
+
+
+class CAIRO_PRINT_GAL : public CAIRO_GAL_BASE
+{
+public:
+    CAIRO_PRINT_GAL( GAL_DISPLAY_OPTIONS& aDisplayOptions,
+            cairo_t* aContext, cairo_surface_t* aSurface );
+
+    void ComputeWorldScreenMatrix() override;
+
+    /**
+     * @param aSize is the printing sheet size expressed in inches.
+     * @param aRotateIfLandscape true if the platform requires 90 degrees
+     * rotation in order to print in landscape format.
+     */
+    void SetNativePaperSize( const VECTOR2D& aSize, bool aRotateIfLandscape );
+
+    /**
+     * @param aSize is the schematics sheet size expressed in inches.
+     */
+    void SetSheetSize( const VECTOR2D& aSize );
+
+private:
+    ///> Returns true if page orientation is landscape
+    bool isLandscape() const
+    {
+        return m_nativePaperSize.x > m_nativePaperSize.y;
+    }
+
+    ///> Printout size
+    VECTOR2D m_nativePaperSize;
+
+    ///> Flag indicating whether the platform rotates page automatically or
+    ///> GAL needs to handle it in the transformation matrix
+    bool m_hasNativeLandscapeRotation;
+
+};
+} // namespace KIGFX
+
+#endif /* _CAIRO_PRINT_H_ */