Make printing consistent with plotting.

How you ask?  Plot first -> then read the PDF and print directly to the
platform-specific printers.  Roundabout?  Yes.  Better than wx printing?
Darnbetcha.

Also allows multi-size page printing and proper margins and print
preview

Fixes https://gitlab.com/kicad/code/kicad/-/issues/1831
This commit is contained in:
Seth Hillbrand 2025-08-13 10:06:52 -07:00
parent 911e1630a8
commit 23c1a68da8
15 changed files with 1540 additions and 0 deletions

View File

@ -783,6 +783,9 @@ find_package( CURL REQUIRED )
if( UNIX AND NOT APPLE ) if( UNIX AND NOT APPLE )
find_package( SPNAV REQUIRED ) find_package( SPNAV REQUIRED )
include_directories( SYSTEM ${SPNAV_INCLUDE_DIR} ) include_directories( SYSTEM ${SPNAV_INCLUDE_DIR} )
find_package( Poppler REQUIRED )
include_directories( SYSTEM ${POPPLER_INCLUDE_DIRS} )
endif() endif()
# #

View File

@ -0,0 +1,277 @@
# SPDX-FileCopyrightText: 2014 Alex Merry <alex.merry@kde.org>
#
# SPDX-License-Identifier: BSD-3-Clause
#[=======================================================================[.rst:
ECMFindModuleHelpers
--------------------
Helper macros for find modules: ``ecm_find_package_version_check()``,
``ecm_find_package_parse_components()`` and
``ecm_find_package_handle_library_components()``.
::
ecm_find_package_version_check(<name>)
Prints warnings if the CMake version or the project's required CMake version
is older than that required by extra-cmake-modules.
::
ecm_find_package_parse_components(<name>
RESULT_VAR <variable>
KNOWN_COMPONENTS <component1> [<component2> [...]]
[SKIP_DEPENDENCY_HANDLING])
This macro will populate <variable> with a list of components found in
<name>_FIND_COMPONENTS, after checking that all those components are in the
list of ``KNOWN_COMPONENTS``; if there are any unknown components, it will print
an error or warning (depending on the value of <name>_FIND_REQUIRED) and call
``return()``.
The order of components in <variable> is guaranteed to match the order they
are listed in the ``KNOWN_COMPONENTS`` argument.
If ``SKIP_DEPENDENCY_HANDLING`` is not set, for each component the variable
<name>_<component>_component_deps will be checked for dependent components.
If <component> is listed in <name>_FIND_COMPONENTS, then all its (transitive)
dependencies will also be added to <variable>.
::
ecm_find_package_handle_library_components(<name>
COMPONENTS <component> [<component> [...]]
[SKIP_DEPENDENCY_HANDLING])
[SKIP_PKG_CONFIG])
Creates an imported library target for each component. The operation of this
macro depends on the presence of a number of CMake variables.
The <name>_<component>_lib variable should contain the name of this library,
and <name>_<component>_header variable should contain the name of a header
file associated with it (whatever relative path is normally passed to
'#include'). <name>_<component>_header_subdir variable can be used to specify
which subdirectory of the include path the headers will be found in.
``ecm_find_package_components()`` will then search for the library
and include directory (creating appropriate cache variables) and create an
imported library target named <name>::<component>.
Additional variables can be used to provide additional information:
If ``SKIP_PKG_CONFIG``, the <name>_<component>_pkg_config variable is set, and
pkg-config is found, the pkg-config module given by
<name>_<component>_pkg_config will be searched for and used to help locate the
library and header file. It will also be used to set
<name>_<component>_VERSION.
Note that if version information is found via pkg-config,
<name>_<component>_FIND_VERSION can be set to require a particular version
for each component.
If ``SKIP_DEPENDENCY_HANDLING`` is not set, the ``INTERFACE_LINK_LIBRARIES`` property
of the imported target for <component> will be set to contain the imported
targets for the components listed in <name>_<component>_component_deps.
<component>_FOUND will also be set to ``FALSE`` if any of the components in
<name>_<component>_component_deps are not found. This requires the components
in <name>_<component>_component_deps to be listed before <component> in the
``COMPONENTS`` argument.
The following variables will be set:
``<name>_TARGETS``
the imported targets
``<name>_LIBRARIES``
the found libraries
``<name>_INCLUDE_DIRS``
the combined required include directories for the components
``<name>_DEFINITIONS``
the "other" CFLAGS provided by pkg-config, if any
``<name>_VERSION``
the value of ``<name>_<component>_VERSION`` for the first component that
has this variable set (note that components are searched for in the order
they are passed to the macro), although if it is already set, it will not
be altered
.. note::
These variables are never cleared, so if
``ecm_find_package_handle_library_components()`` is called multiple times with
different components (typically because of multiple ``find_package()`` calls) then
``<name>_TARGETS``, for example, will contain all the targets found in any
call (although no duplicates).
Since pre-1.0.0.
#]=======================================================================]
macro(ecm_find_package_version_check module_name)
if(CMAKE_VERSION VERSION_LESS 3.16.0)
message(FATAL_ERROR "CMake 3.16.0 is required by Find${module_name}.cmake")
endif()
if(CMAKE_MINIMUM_REQUIRED_VERSION VERSION_LESS 3.16.0)
message(AUTHOR_WARNING "Your project should require at least CMake 3.16.0 to use Find${module_name}.cmake")
endif()
endmacro()
macro(ecm_find_package_parse_components module_name)
set(ecm_fppc_options SKIP_DEPENDENCY_HANDLING)
set(ecm_fppc_oneValueArgs RESULT_VAR)
set(ecm_fppc_multiValueArgs KNOWN_COMPONENTS DEFAULT_COMPONENTS)
cmake_parse_arguments(ECM_FPPC "${ecm_fppc_options}" "${ecm_fppc_oneValueArgs}" "${ecm_fppc_multiValueArgs}" ${ARGN})
if(ECM_FPPC_UNPARSED_ARGUMENTS)
message(FATAL_ERROR "Unexpected arguments to ecm_find_package_parse_components: ${ECM_FPPC_UNPARSED_ARGUMENTS}")
endif()
if(NOT ECM_FPPC_RESULT_VAR)
message(FATAL_ERROR "Missing RESULT_VAR argument to ecm_find_package_parse_components")
endif()
if(NOT ECM_FPPC_KNOWN_COMPONENTS)
message(FATAL_ERROR "Missing KNOWN_COMPONENTS argument to ecm_find_package_parse_components")
endif()
if(NOT ECM_FPPC_DEFAULT_COMPONENTS)
set(ECM_FPPC_DEFAULT_COMPONENTS ${ECM_FPPC_KNOWN_COMPONENTS})
endif()
if(${module_name}_FIND_COMPONENTS)
set(ecm_fppc_requestedComps ${${module_name}_FIND_COMPONENTS})
if(NOT ECM_FPPC_SKIP_DEPENDENCY_HANDLING)
# Make sure deps are included
foreach(ecm_fppc_comp ${ecm_fppc_requestedComps})
foreach(ecm_fppc_dep_comp ${${module_name}_${ecm_fppc_comp}_component_deps})
list(FIND ecm_fppc_requestedComps "${ecm_fppc_dep_comp}" ecm_fppc_index)
if("${ecm_fppc_index}" STREQUAL "-1")
if(NOT ${module_name}_FIND_QUIETLY)
message(STATUS "${module_name}: ${ecm_fppc_comp} requires ${${module_name}_${ecm_fppc_comp}_component_deps}")
endif()
list(APPEND ecm_fppc_requestedComps "${ecm_fppc_dep_comp}")
endif()
endforeach()
endforeach()
else()
message(STATUS "Skipping dependency handling for ${module_name}")
endif()
list(REMOVE_DUPLICATES ecm_fppc_requestedComps)
# This makes sure components are listed in the same order as
# KNOWN_COMPONENTS (potentially important for inter-dependencies)
set(${ECM_FPPC_RESULT_VAR})
foreach(ecm_fppc_comp ${ECM_FPPC_KNOWN_COMPONENTS})
list(FIND ecm_fppc_requestedComps "${ecm_fppc_comp}" ecm_fppc_index)
if(NOT "${ecm_fppc_index}" STREQUAL "-1")
list(APPEND ${ECM_FPPC_RESULT_VAR} "${ecm_fppc_comp}")
list(REMOVE_AT ecm_fppc_requestedComps ${ecm_fppc_index})
endif()
endforeach()
# if there are any left, they are unknown components
if(ecm_fppc_requestedComps)
set(ecm_fppc_msgType STATUS)
if(${module_name}_FIND_REQUIRED)
set(ecm_fppc_msgType FATAL_ERROR)
endif()
if(NOT ${module_name}_FIND_QUIETLY)
message(${ecm_fppc_msgType} "${module_name}: requested unknown components ${ecm_fppc_requestedComps}")
endif()
return()
endif()
else()
set(${ECM_FPPC_RESULT_VAR} ${ECM_FPPC_DEFAULT_COMPONENTS})
endif()
endmacro()
macro(ecm_find_package_handle_library_components module_name)
set(ecm_fpwc_options SKIP_PKG_CONFIG SKIP_DEPENDENCY_HANDLING)
set(ecm_fpwc_oneValueArgs)
set(ecm_fpwc_multiValueArgs COMPONENTS)
cmake_parse_arguments(ECM_FPWC "${ecm_fpwc_options}" "${ecm_fpwc_oneValueArgs}" "${ecm_fpwc_multiValueArgs}" ${ARGN})
if(ECM_FPWC_UNPARSED_ARGUMENTS)
message(FATAL_ERROR "Unexpected arguments to ecm_find_package_handle_components: ${ECM_FPWC_UNPARSED_ARGUMENTS}")
endif()
if(NOT ECM_FPWC_COMPONENTS)
message(FATAL_ERROR "Missing COMPONENTS argument to ecm_find_package_handle_components")
endif()
include(FindPackageHandleStandardArgs)
find_package(PkgConfig QUIET)
foreach(ecm_fpwc_comp ${ECM_FPWC_COMPONENTS})
set(ecm_fpwc_dep_vars)
set(ecm_fpwc_dep_targets)
if(NOT SKIP_DEPENDENCY_HANDLING)
foreach(ecm_fpwc_dep ${${module_name}_${ecm_fpwc_comp}_component_deps})
list(APPEND ecm_fpwc_dep_vars "${module_name}_${ecm_fpwc_dep}_FOUND")
list(APPEND ecm_fpwc_dep_targets "${module_name}::${ecm_fpwc_dep}")
endforeach()
endif()
if(NOT ECM_FPWC_SKIP_PKG_CONFIG AND ${module_name}_${ecm_fpwc_comp}_pkg_config)
pkg_check_modules(PKG_${module_name}_${ecm_fpwc_comp} QUIET
${${module_name}_${ecm_fpwc_comp}_pkg_config})
endif()
find_path(${module_name}_${ecm_fpwc_comp}_INCLUDE_DIR
NAMES ${${module_name}_${ecm_fpwc_comp}_header}
HINTS ${PKG_${module_name}_${ecm_fpwc_comp}_INCLUDE_DIRS}
PATH_SUFFIXES ${${module_name}_${ecm_fpwc_comp}_header_subdir}
)
find_library(${module_name}_${ecm_fpwc_comp}_LIBRARY
NAMES ${${module_name}_${ecm_fpwc_comp}_lib}
HINTS ${PKG_${module_name}_${ecm_fpwc_comp}_LIBRARY_DIRS}
)
set(${module_name}_${ecm_fpwc_comp}_VERSION "${PKG_${module_name}_${ecm_fpwc_comp}_VERSION}")
if(NOT ${module_name}_VERSION)
set(${module_name}_VERSION ${${module_name}_${ecm_fpwc_comp}_VERSION})
endif()
set(FPHSA_NAME_MISMATCHED 1)
find_package_handle_standard_args(${module_name}_${ecm_fpwc_comp}
FOUND_VAR
${module_name}_${ecm_fpwc_comp}_FOUND
REQUIRED_VARS
${module_name}_${ecm_fpwc_comp}_LIBRARY
${module_name}_${ecm_fpwc_comp}_INCLUDE_DIR
${ecm_fpwc_dep_vars}
VERSION_VAR
${module_name}_${ecm_fpwc_comp}_VERSION
)
unset(FPHSA_NAME_MISMATCHED)
mark_as_advanced(
${module_name}_${ecm_fpwc_comp}_LIBRARY
${module_name}_${ecm_fpwc_comp}_INCLUDE_DIR
)
if(${module_name}_${ecm_fpwc_comp}_FOUND)
list(APPEND ${module_name}_LIBRARIES
"${${module_name}_${ecm_fpwc_comp}_LIBRARY}")
list(APPEND ${module_name}_INCLUDE_DIRS
"${${module_name}_${ecm_fpwc_comp}_INCLUDE_DIR}")
set(${module_name}_DEFINITIONS
${${module_name}_DEFINITIONS}
${PKG_${module_name}_${ecm_fpwc_comp}_DEFINITIONS})
if(NOT TARGET ${module_name}::${ecm_fpwc_comp})
add_library(${module_name}::${ecm_fpwc_comp} UNKNOWN IMPORTED)
set_target_properties(${module_name}::${ecm_fpwc_comp} PROPERTIES
IMPORTED_LOCATION "${${module_name}_${ecm_fpwc_comp}_LIBRARY}"
INTERFACE_COMPILE_OPTIONS "${PKG_${module_name}_${ecm_fpwc_comp}_DEFINITIONS}"
INTERFACE_INCLUDE_DIRECTORIES "${${module_name}_${ecm_fpwc_comp}_INCLUDE_DIR}"
INTERFACE_LINK_LIBRARIES "${ecm_fpwc_dep_targets}"
)
endif()
list(APPEND ${module_name}_TARGETS
"${module_name}::${ecm_fpwc_comp}")
endif()
endforeach()
if(${module_name}_LIBRARIES)
list(REMOVE_DUPLICATES ${module_name}_LIBRARIES)
endif()
if(${module_name}_INCLUDE_DIRS)
list(REMOVE_DUPLICATES ${module_name}_INCLUDE_DIRS)
endif()
if(${module_name}_DEFINITIONS)
list(REMOVE_DUPLICATES ${module_name}_DEFINITIONS)
endif()
if(${module_name}_TARGETS)
list(REMOVE_DUPLICATES ${module_name}_TARGETS)
endif()
endmacro()

132
cmake/FindPoppler.cmake Normal file
View File

@ -0,0 +1,132 @@
# SPDX-FileCopyrightText: 2015 Alex Richardson <arichardson.kde@gmail.com>
#
# SPDX-License-Identifier: BSD-3-Clause
#[=======================================================================[.rst:
FindPoppler
-----------
Try to find Poppler.
This is a component-based find module, which makes use of the COMPONENTS
and OPTIONAL_COMPONENTS arguments to find_module. The following components
are available::
Core Cpp Qt5 Qt4 Glib
If no components are specified, this module will act as though all components
were passed to OPTIONAL_COMPONENTS.
This module will define the following variables, independently of the
components searched for or found:
``Poppler_FOUND``
TRUE if (the requested version of) Poppler is available
``Poppler_VERSION``
Found Poppler version
``Poppler_TARGETS``
A list of all targets imported by this module (note that there may be more
than the components that were requested)
``Poppler_LIBRARIES``
This can be passed to target_link_libraries() instead of the imported
targets
``Poppler_INCLUDE_DIRS``
This should be passed to target_include_directories() if the targets are
not used for linking
``Poppler_DEFINITIONS``
This should be passed to target_compile_options() if the targets are not
used for linking
For each searched-for components, ``Poppler_<component>_FOUND`` will be set to
TRUE if the corresponding Poppler library was found, and FALSE otherwise. If
``Poppler_<component>_FOUND`` is TRUE, the imported target
``Poppler::<component>`` will be defined. This module will also attempt to
determine ``Poppler_*_VERSION`` variables for each imported target, although
``Poppler_VERSION`` should normally be sufficient.
In general we recommend using the imported targets, as they are easier to use
and provide more control. Bear in mind, however, that if any target is in the
link interface of an exported library, it must be made available by the
package config file.
Since 5.19
#]=======================================================================]
include(${CMAKE_CURRENT_LIST_DIR}/ECMFindModuleHelpers.cmake)
ecm_find_package_version_check(Poppler)
set(Poppler_known_components
Cpp
Qt4
Qt5
Qt6
Glib
)
foreach(_comp ${Poppler_known_components})
string(TOLOWER "${_comp}" _lc_comp)
set(Poppler_${_comp}_component_deps "Core")
set(Poppler_${_comp}_pkg_config "poppler-${_lc_comp}")
set(Poppler_${_comp}_lib "poppler-${_lc_comp}")
set(Poppler_${_comp}_header_subdir "poppler/${_lc_comp}")
endforeach()
set(Poppler_known_components Core ${Poppler_known_components})
set(Poppler_Core_component_deps "")
set(Poppler_Core_pkg_config "poppler")
# poppler-config.h header is only installed with --enable-xpdf-headers
# fall back to using any header from a submodule with a path to make it work in that case too
set(Poppler_Core_header "poppler-config.h" "cpp/poppler-version.h" "qt6/poppler-qt6.h" "qt5/poppler-qt5.h" "qt4/poppler-qt4.h" "glib/poppler.h")
set(Poppler_Core_header_subdir "poppler")
set(Poppler_Core_lib "poppler")
set(Poppler_Cpp_header "poppler-version.h")
set(Poppler_Qt6_header "poppler-qt6.h")
set(Poppler_Qt5_header "poppler-qt5.h")
set(Poppler_Qt4_header "poppler-qt4.h")
set(Poppler_Glib_header "poppler.h")
ecm_find_package_parse_components(Poppler
RESULT_VAR Poppler_components
KNOWN_COMPONENTS ${Poppler_known_components}
)
ecm_find_package_handle_library_components(Poppler
COMPONENTS ${Poppler_components}
)
# If pkg-config didn't provide us with version information,
# try to extract it from poppler-version.h or poppler-config.h
if(NOT Poppler_VERSION)
find_file(Poppler_VERSION_HEADER
NAMES "poppler-config.h" "cpp/poppler-version.h"
HINTS ${Poppler_INCLUDE_DIRS}
PATH_SUFFIXES ${Poppler_Core_header_subdir}
)
mark_as_advanced(Poppler_VERSION_HEADER)
if(Poppler_VERSION_HEADER)
file(READ ${Poppler_VERSION_HEADER} _poppler_version_header_contents)
string(REGEX REPLACE
"^.*[ \t]+POPPLER_VERSION[ \t]+\"([0-9d.]*)\".*$"
"\\1"
Poppler_VERSION
"${_poppler_version_header_contents}"
)
unset(_poppler_version_header_contents)
endif()
endif()
find_package_handle_standard_args(Poppler
FOUND_VAR
Poppler_FOUND
REQUIRED_VARS
Poppler_LIBRARIES
VERSION_VAR
Poppler_VERSION
HANDLE_COMPONENTS
)
include(FeatureSummary)
set_package_properties(Poppler PROPERTIES
DESCRIPTION "A PDF rendering library"
URL "https://poppler.freedesktop.org/"
)

View File

@ -818,6 +818,10 @@ target_link_libraries( common
${Fontconfig_LIBRARIES} ${Fontconfig_LIBRARIES}
) )
if( UNIX AND NOT APPLE )
target_link_libraries( common ${Poppler_LIBRARIES} )
endif()
if( KICAD_USE_SENTRY ) if( KICAD_USE_SENTRY )
target_link_libraries( common target_link_libraries( common
sentry ) sentry )

View File

@ -84,6 +84,7 @@ static const wxChar ExtraZoneDisplayModes[] = wxT( "ExtraZoneDisplayModes" );
static const wxChar MinPlotPenWidth[] = wxT( "MinPlotPenWidth" ); static const wxChar MinPlotPenWidth[] = wxT( "MinPlotPenWidth" );
static const wxChar DebugZoneFiller[] = wxT( "DebugZoneFiller" ); static const wxChar DebugZoneFiller[] = wxT( "DebugZoneFiller" );
static const wxChar DebugPDFWriter[] = wxT( "DebugPDFWriter" ); static const wxChar DebugPDFWriter[] = wxT( "DebugPDFWriter" );
static const wxChar UsePdfPrint[] = wxT( "UsePdfPrint" );
static const wxChar SmallDrillMarkSize[] = wxT( "SmallDrillMarkSize" ); static const wxChar SmallDrillMarkSize[] = wxT( "SmallDrillMarkSize" );
static const wxChar HotkeysDumper[] = wxT( "HotkeysDumper" ); static const wxChar HotkeysDumper[] = wxT( "HotkeysDumper" );
static const wxChar DrawBoundingBoxes[] = wxT( "DrawBoundingBoxes" ); static const wxChar DrawBoundingBoxes[] = wxT( "DrawBoundingBoxes" );
@ -245,6 +246,7 @@ ADVANCED_CFG::ADVANCED_CFG()
m_DebugZoneFiller = false; m_DebugZoneFiller = false;
m_DebugPDFWriter = false; m_DebugPDFWriter = false;
m_UsePdfPrint = false;
m_SmallDrillMarkSize = 0.35; m_SmallDrillMarkSize = 0.35;
m_HotkeysDumper = false; m_HotkeysDumper = false;
m_DrawBoundingBoxes = false; m_DrawBoundingBoxes = false;
@ -456,6 +458,9 @@ void ADVANCED_CFG::loadSettings( wxConfigBase& aCfg )
m_entries.push_back( std::make_unique<PARAM_CFG_BOOL>( true, AC_KEYS::DebugPDFWriter, m_entries.push_back( std::make_unique<PARAM_CFG_BOOL>( true, AC_KEYS::DebugPDFWriter,
&m_DebugPDFWriter, m_DebugPDFWriter ) ); &m_DebugPDFWriter, m_DebugPDFWriter ) );
m_entries.push_back( std::make_unique<PARAM_CFG_BOOL>( true, AC_KEYS::UsePdfPrint,
&m_UsePdfPrint, m_UsePdfPrint ) );
m_entries.push_back( std::make_unique<PARAM_CFG_DOUBLE>( true, AC_KEYS::SmallDrillMarkSize, m_entries.push_back( std::make_unique<PARAM_CFG_DOUBLE>( true, AC_KEYS::SmallDrillMarkSize,
&m_SmallDrillMarkSize, m_SmallDrillMarkSize, &m_SmallDrillMarkSize, m_SmallDrillMarkSize,
0.0, 3.0 ) ); 0.0, 3.0 ) );

View File

@ -0,0 +1,171 @@
/*
* This program source code file is part of KiCad, a free EDA CAD application.
*
* Copyright (C) 2024 KiCad Developers
*
* 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 <dialogs/dialog_generate_database_connection.h>
#include <wx/choice.h>
#include <wx/sizer.h>
#include <wx/spinctrl.h>
#include <wx/stattext.h>
#include <wx/textctrl.h>
#include <wx/button.h>
#include <wx/msgdlg.h>
#include <database/database_connection.h>
#include <vector>
DIALOG_GENERATE_DATABASE_CONNECTION::DIALOG_GENERATE_DATABASE_CONNECTION( wxWindow* aParent ) :
DIALOG_SHIM( aParent, wxID_ANY, _( "Generate Database Connection" ), wxDefaultPosition,
wxDefaultSize, wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER )
{
wxBoxSizer* topSizer = new wxBoxSizer( wxVERTICAL );
m_dsnChoice = new wxChoice( this, wxID_ANY );
std::vector<std::string> dsns;
DATABASE_CONNECTION::ListDataSources( dsns );
for( const std::string& d : dsns )
m_dsnChoice->Append( d );
m_dsnChoice->Append( _( "Custom" ) );
topSizer->Add( new wxStaticText( this, wxID_ANY, _( "Data Source Name" ) ), 0, wxALL, 5 );
topSizer->Add( m_dsnChoice, 0, wxEXPAND | wxLEFT | wxRIGHT | wxBOTTOM, 5 );
wxFlexGridSizer* grid = new wxFlexGridSizer( 2, 2, 5, 5 );
grid->AddGrowableCol( 1, 1 );
grid->Add( new wxStaticText( this, wxID_ANY, _( "Username" ) ), 0, wxALIGN_CENTER_VERTICAL );
m_userCtrl = new wxTextCtrl( this, wxID_ANY );
grid->Add( m_userCtrl, 1, wxEXPAND );
grid->Add( new wxStaticText( this, wxID_ANY, _( "Password" ) ), 0, wxALIGN_CENTER_VERTICAL );
m_passCtrl = new wxTextCtrl( this, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize,
wxTE_PASSWORD );
grid->Add( m_passCtrl, 1, wxEXPAND );
grid->Add( new wxStaticText( this, wxID_ANY, _( "Timeout" ) ), 0, wxALIGN_CENTER_VERTICAL );
m_timeoutCtrl = new wxSpinCtrl( this, wxID_ANY );
m_timeoutCtrl->SetRange( 0, 999 );
m_timeoutCtrl->SetValue( DATABASE_CONNECTION::DEFAULT_TIMEOUT );
grid->Add( m_timeoutCtrl, 0, wxEXPAND );
grid->Add( new wxStaticText( this, wxID_ANY, _( "Connection String" ) ), 0,
wxALIGN_CENTER_VERTICAL );
m_connStrCtrl = new wxTextCtrl( this, wxID_ANY );
grid->Add( m_connStrCtrl, 1, wxEXPAND );
topSizer->Add( grid, 0, wxEXPAND | wxLEFT | wxRIGHT | wxBOTTOM, 5 );
m_testButton = new wxButton( this, wxID_ANY, _( "Test Connection" ) );
topSizer->Add( m_testButton, 0, wxLEFT | wxBOTTOM, 5 );
topSizer->Add( new wxStaticText( this, wxID_ANY, _( "Tables" ) ), 0, wxLEFT | wxRIGHT, 5 );
m_tableChoice = new wxChoice( this, wxID_ANY );
m_tableChoice->Enable( false );
topSizer->Add( m_tableChoice, 0, wxEXPAND | wxLEFT | wxRIGHT | wxBOTTOM, 5 );
SetSizerAndFit( topSizer );
m_dsnChoice->Bind( wxEVT_CHOICE, &DIALOG_GENERATE_DATABASE_CONNECTION::OnDSNChanged, this );
m_testButton->Bind( wxEVT_BUTTON, &DIALOG_GENERATE_DATABASE_CONNECTION::OnTest, this );
UpdateControls();
SetupStandardButtons();
}
DATABASE_SOURCE DIALOG_GENERATE_DATABASE_CONNECTION::GetSource() const
{
DATABASE_SOURCE src;
src.type = DATABASE_SOURCE_TYPE::ODBC;
int sel = m_dsnChoice->GetSelection();
if( sel != wxNOT_FOUND && sel < (int) m_dsnChoice->GetCount() - 1 )
{
src.dsn = m_dsnChoice->GetString( sel ).ToStdString();
src.username = m_userCtrl->GetValue().ToStdString();
src.password = m_passCtrl->GetValue().ToStdString();
src.timeout = m_timeoutCtrl->GetValue();
}
else
{
src.connection_string = m_connStrCtrl->GetValue().ToStdString();
src.timeout = DATABASE_CONNECTION::DEFAULT_TIMEOUT;
}
return src;
}
void DIALOG_GENERATE_DATABASE_CONNECTION::OnDSNChanged( wxCommandEvent& aEvent )
{
UpdateControls();
}
void DIALOG_GENERATE_DATABASE_CONNECTION::UpdateControls()
{
bool custom = m_dsnChoice->GetSelection() == (int) m_dsnChoice->GetCount() - 1;
m_userCtrl->Enable( !custom );
m_passCtrl->Enable( !custom );
m_timeoutCtrl->Enable( !custom );
m_connStrCtrl->Enable( custom );
}
void DIALOG_GENERATE_DATABASE_CONNECTION::OnTest( wxCommandEvent& aEvent )
{
m_tableChoice->Clear();
std::unique_ptr<DATABASE_CONNECTION> conn;
if( m_dsnChoice->GetSelection() != (int) m_dsnChoice->GetCount() - 1 )
{
wxString dsn = m_dsnChoice->GetStringSelection();
wxString user = m_userCtrl->GetValue();
wxString pass = m_passCtrl->GetValue();
int timeout = m_timeoutCtrl->GetValue();
conn = std::make_unique<DATABASE_CONNECTION>( dsn.ToStdString(), user.ToStdString(), pass.ToStdString(),
timeout, false );
}
else
{
conn = std::make_unique<DATABASE_CONNECTION>( m_connStrCtrl->GetValue().ToStdString(),
DATABASE_CONNECTION::DEFAULT_TIMEOUT, false );
}
if( !conn->Connect() )
{
wxMessageBox( _( "Unable to connect to database" ), _( "Database Error" ), wxOK | wxICON_ERROR,
this );
return;
}
std::vector<std::string> tables;
if( conn->GetTables( tables ) )
{
for( const std::string& t : tables )
m_tableChoice->Append( t );
if( !tables.empty() )
m_tableChoice->SetSelection( 0 );
m_tableChoice->Enable( true );
}
}

View File

@ -75,6 +75,9 @@
#include <python_manager.h> #include <python_manager.h>
#endif #endif
#ifdef _MSC_VER
#include <winrt/base.h>
#endif
/** /**
* Current list of languages supported by KiCad. * Current list of languages supported by KiCad.
* *
@ -177,6 +180,10 @@ void PGM_BASE::Destroy()
APP_MONITOR::SENTRY::Instance()->Cleanup(); APP_MONITOR::SENTRY::Instance()->Cleanup();
m_pgm_checker.reset(); m_pgm_checker.reset();
#ifdef _MSC_VER
winrt::uninit_apartment();
#endif
} }
@ -441,6 +448,10 @@ bool PGM_BASE::InitPgm( bool aHeadless, bool aSkipPyInit, bool aIsUnitTest )
} }
#endif #endif
#ifdef _MSC_VER
winrt::init_apartment(winrt::apartment_type::single_threaded);
#endif
m_settings_manager = std::make_unique<SETTINGS_MANAGER>( aHeadless ); m_settings_manager = std::make_unique<SETTINGS_MANAGER>( aHeadless );
m_background_jobs_monitor = std::make_unique<BACKGROUND_JOBS_MONITOR>(); m_background_jobs_monitor = std::make_unique<BACKGROUND_JOBS_MONITOR>();
m_notifications_manager = std::make_unique<NOTIFICATIONS_MANAGER>(); m_notifications_manager = std::make_unique<NOTIFICATIONS_MANAGER>();

View File

@ -31,12 +31,15 @@
#include <settings/settings_manager.h> #include <settings/settings_manager.h>
#include <wx/print.h> #include <wx/print.h>
#include <wx/printdlg.h> #include <wx/printdlg.h>
#include <wx/filename.h>
#include "dialog_print.h" #include "dialog_print.h"
#include <dialogs/panel_printer_list.h> #include <dialogs/panel_printer_list.h>
#include <advanced_config.h> #include <advanced_config.h>
#include <printing.h>
#include <sch_plotter.h>
#include "sch_printout.h" #include "sch_printout.h"
@ -113,6 +116,14 @@ DIALOG_PRINT::DIALOG_PRINT( SCH_EDIT_FRAME* aParent ) :
m_sdbSizerApply->Hide(); m_sdbSizerApply->Hide();
#endif #endif
// New printing subsystem has print preview on all platforms
#if defined( _MSC_VER )
if( ADVANCED_CFG::GetCfg().m_UsePdfPrint )
{
m_sdbSizerApply->Hide();
}
#endif
m_sdbSizerOK->SetFocus(); m_sdbSizerOK->SetFocus();
Layout(); Layout();
@ -298,6 +309,51 @@ bool DIALOG_PRINT::TransferDataFromWindow()
SavePrintOptions(); SavePrintOptions();
#if defined( _MSC_VER )
if( ADVANCED_CFG::GetCfg().m_UsePdfPrint )
{
EESCHEMA_SETTINGS* cfg = m_parent->eeconfig();
SCH_RENDER_SETTINGS renderSettings( *m_parent->GetRenderSettings() );
renderSettings.m_ShowHiddenPins = false;
renderSettings.m_ShowHiddenFields = false;
COLOR_SETTINGS* cs = ::GetColorSettings( cfg->m_Printing.use_theme
? cfg->m_Printing.color_theme
: cfg->m_ColorTheme );
renderSettings.LoadColors( cs );
SCH_PLOT_OPTS plotOpts;
plotOpts.m_plotDrawingSheet = cfg->m_Printing.title_block;
plotOpts.m_blackAndWhite = cfg->m_Printing.monochrome;
plotOpts.m_useBackgroundColor = cfg->m_Printing.background;
plotOpts.m_theme = cfg->m_Printing.use_theme ? cfg->m_Printing.color_theme
: cfg->m_ColorTheme;
wxFileName tmp = wxFileName::CreateTempFileName( wxS( "eeschema_print" ) );
wxRemoveFile( tmp.GetFullPath() );
tmp.SetExt( wxS( "pdf" ) );
plotOpts.m_outputFile = tmp.GetFullPath();
SCH_PLOTTER plotter( m_parent );
Pgm().m_Printing = true;
plotter.Plot( PLOT_FORMAT::PDF, plotOpts, &renderSettings, nullptr );
Pgm().m_Printing = false;
KIPLATFORM::PRINTING::PRINT_RESULT result =
KIPLATFORM::PRINTING::PrintPDF( TO_UTF8( plotter.GetLastOutputFilePath() ) );
if( result != KIPLATFORM::PRINTING::PRINT_RESULT::OK &&
result != KIPLATFORM::PRINTING::PRINT_RESULT::CANCELLED )
{
DisplayError( this, KIPLATFORM::PRINTING::PrintResultToString( result ) );
}
return true;
}
#endif
int sheet_count = m_parent->Schematic().Root().CountSheets(); int sheet_count = m_parent->Schematic().Root().CountSheets();
wxPrintData& data = m_parent->GetPageSetupData().GetPrintData(); wxPrintData& data = m_parent->GetPageSetupData().GetPrintData();

View File

@ -322,6 +322,15 @@ public:
*/ */
bool m_DebugPDFWriter; bool m_DebugPDFWriter;
/**
* Use legacy wxWidgets-based printing.
*
* Setting name: "UsePdfPrint"
* Valid values: 0 or 1
* Default value: 0
*/
bool m_UsePdfPrint;
/** /**
* The diameter of the drill marks on print and plot outputs (in mm) when the "Drill marks" * The diameter of the drill marks on print and plot outputs (in mm) when the "Drill marks"
* option is set to "Small mark". * option is set to "Small mark".

View File

@ -0,0 +1,58 @@
/*
* This program source code file is part of KiCad, a free EDA CAD application.
*
* Copyright (C) 2024 KiCad Developers
*
* 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 DIALOG_GENERATE_DATABASE_CONNECTION_H
#define DIALOG_GENERATE_DATABASE_CONNECTION_H
#include <vector>
#include <dialog_shim.h>
#include <database/database_lib_settings.h>
class wxChoice;
class wxTextCtrl;
class wxSpinCtrl;
class wxButton;
/**
* Dialog for generating database connection settings.
*/
class DIALOG_GENERATE_DATABASE_CONNECTION : public DIALOG_SHIM
{
public:
DIALOG_GENERATE_DATABASE_CONNECTION( wxWindow* aParent );
DATABASE_SOURCE GetSource() const;
private:
void OnDSNChanged( wxCommandEvent& aEvent );
void OnTest( wxCommandEvent& aEvent );
void UpdateControls();
private:
wxChoice* m_dsnChoice;
wxTextCtrl* m_userCtrl;
wxTextCtrl* m_passCtrl;
wxSpinCtrl* m_timeoutCtrl;
wxTextCtrl* m_connStrCtrl;
wxButton* m_testButton;
wxChoice* m_tableChoice;
};
#endif // DIALOG_GENERATE_DATABASE_CONNECTION_H

View File

@ -51,6 +51,7 @@ if( APPLE )
os/apple/policy.mm os/apple/policy.mm
os/apple/secrets.mm os/apple/secrets.mm
os/apple/sysinfo.cpp os/apple/sysinfo.cpp
os/apple/printing.mm
) )
set( PLATFORM_LIBS set( PLATFORM_LIBS
@ -58,6 +59,7 @@ if( APPLE )
"-framework AppKit" "-framework AppKit"
"-framework CoreData" "-framework CoreData"
"-framework Foundation" "-framework Foundation"
"-framework PDFKit"
) )
elseif( WIN32 ) elseif( WIN32 )
list( APPEND PLATFORM_SRCS list( APPEND PLATFORM_SRCS
@ -68,6 +70,7 @@ elseif( WIN32 )
os/windows/policy.cpp os/windows/policy.cpp
os/windows/secrets.cpp os/windows/secrets.cpp
os/windows/sysinfo.cpp os/windows/sysinfo.cpp
os/windows/printing.cpp
) )
set( PLATFORM_LIBS set( PLATFORM_LIBS
@ -75,6 +78,7 @@ elseif( WIN32 )
"winhttp" "winhttp"
"wintrust" "wintrust"
"imm32" "imm32"
"windowsapp"
) )
elseif( UNIX ) elseif( UNIX )
list( APPEND PLATFORM_SRCS list( APPEND PLATFORM_SRCS
@ -85,6 +89,7 @@ elseif( UNIX )
os/unix/policy.cpp os/unix/policy.cpp
os/unix/secrets.cpp os/unix/secrets.cpp
os/unix/sysinfo.cpp os/unix/sysinfo.cpp
os/unix/printing.cpp
) )
# Detect the secret library and configure it # Detect the secret library and configure it

View File

@ -0,0 +1,60 @@
/*
* This program source code file is part of KiCad, a free EDA CAD application.
*
* Copyright The KiCad Developers, see AUTHORS.txt for contributors.
*
* 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 KIPLATFORM_PRINTING_H_
#define KIPLATFORM_PRINTING_H_
#include <string>
#include <wx/translation.h>
namespace KIPLATFORM
{
namespace PRINTING
{
enum class PRINT_RESULT
{
OK = 0,
CANCELLED,
FILE_NOT_FOUND,
FAILED_TO_LOAD,
FAILED_TO_PRINT,
UNSUPPORTED,
UNKNOWN_ERROR
};
inline const wxString PrintResultToString( PRINT_RESULT aResult )
{
switch( aResult )
{
case PRINT_RESULT::OK: return _( "Success" );
case PRINT_RESULT::CANCELLED: return _( "Cancelled" );
case PRINT_RESULT::FILE_NOT_FOUND: return _( "File not found" );
case PRINT_RESULT::FAILED_TO_LOAD: return _( "Failed to load PDF" );
case PRINT_RESULT::FAILED_TO_PRINT:return _( "Failed to print" );
case PRINT_RESULT::UNSUPPORTED: return _( "Unsupported" );
default: return _( "Unknown error" );
}
}
PRINT_RESULT PrintPDF( const std::string& aFile );
}
} // namespace KIPLATFORM
#endif // KIPLATFORM_PRINTING_H_

View File

@ -0,0 +1,96 @@
/*
* This program source code file is part of KiCad, a free EDA CAD application.
*
* Copyright The KiCad Developers, see AUTHORS.txt for contributors.
*
* 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 <printing.h>
#import <AppKit/AppKit.h>
#import <PDFKit/PDFKit.h>
namespace KIPLATFORM
{
namespace PRINTING
{
PRINT_RESULT PrintPDF( const std::string& aFile, bool fit_to_page)
{
@autoreleasepool
{
NSString* path = [NSString stringWithUTF8String:aFile.c_str()];
if( ![[NSFileManager defaultManager] isReadableFileAtPath:path] )
return PRINT_RESULT::FILE_NOT_FOUND;
NSURL* url = [NSURL fileURLWithPath:path];
PDFDocument* document = [[PDFDocument alloc] initWithURL:url];
if( !document || [document pageCount] == 0 )
{
[document release];
return PRINT_RESULT::FAILED_TO_LOAD;
}
NSPrintInfo* printInfo = [NSPrintInfo sharedPrintInfo];
PDFPrintScalingMode scalingMode = fit_to_page ?
kPDFPrintPageScaleDownToFit : kPDFPrintPageScaleNone;
NSPrintOperation* op = [document printOperationForPrintInfo:printInfo
scalingMode:scalingMode
autoRotate:YES];
if( !op )
{
[document release];
return PRINT_RESULT::FAILED_TO_PRINT;
}
[op setShowsPrintPanel:YES];
[op setShowsProgressPanel:YES];
BOOL success = [op runOperation];
PRINT_RESULT result;
if (success)
{
result = PRINT_RESULT::OK;
} else
{
// Check if operation was cancelled (uncertain that this works)
NSPrintInfo* info = [op printInfo];
NSDictionary* settings = [info printSettings];
if ([[settings objectForKey:NSPrintJobDisposition] isEqualToString:NSPrintCancelJob]) {
result = PRINT_RESULT::CANCELLED;
} else {
result = PRINT_RESULT::FAILED_TO_PRINT;
}
}
[document release];
return result;
}
}
PRINT_RESULT PrintPDF(const std::string& aFile)
{
return PrintPDF(aFile, true);
}
} // namespace PRINTING
} // namespace KIPLATFORM

View File

@ -0,0 +1,231 @@
/*
* This program source code file is part of KiCad, a free EDA CAD application.
*
* Copyright The KiCad Developers, see AUTHORS.txt for contributors.
*
* 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 <printing.h>
#include <poppler/glib/poppler.h>
#include <gtk/gtk.h>
#include <unistd.h>
#include <algorithm>
#include <memory>
namespace
{
struct PrintData
{
PopplerDocument* doc;
bool fit_to_page;
PrintData( PopplerDocument* d ) :
doc( d ),
fit_to_page( true )
{
}
};
void draw_page( GtkPrintOperation* operation, GtkPrintContext* context, gint page_nr, gpointer user_data )
{
PrintData* print_data = static_cast<PrintData*>( user_data );
if( !print_data || !print_data->doc )
return;
PopplerPage* page = poppler_document_get_page( print_data->doc, page_nr );
if( !page ) return;
auto cleanup_page = std::unique_ptr<PopplerPage, decltype( &g_object_unref )>( page, &g_object_unref );
cairo_t* cr = gtk_print_context_get_cairo_context( context );
if( !cr ) return;
// Get page dimensions
double page_width, page_height;
poppler_page_get_size( page, &page_width, &page_height );
// Get print context dimensions
double print_width = gtk_print_context_get_width( context );
double print_height = gtk_print_context_get_height( context );
cairo_save( cr );
auto cleanup_cairo = std::unique_ptr<cairo_t, decltype( &cairo_restore )>( cr, &cairo_restore );
if( print_data->fit_to_page )
{
// Calculate scaling to fit page while maintaining aspect ratio
double scale_x = print_width / page_width;
double scale_y = print_height / page_height;
double scale = std::min( scale_x, scale_y );
// Center the page
double scaled_width = page_width * scale;
double scaled_height = page_height * scale;
double offset_x = ( print_width - scaled_width ) / 2.0;
double offset_y = ( print_height - scaled_height ) / 2.0;
// Apply transformations
cairo_translate( cr, offset_x, offset_y );
cairo_scale( cr, scale, scale );
}
// Set white background
cairo_set_source_rgb( cr, 1.0, 1.0, 1.0 );
cairo_paint( cr );
// Render the page
poppler_page_render( page, cr );
}
void begin_print_callback( GtkPrintOperation* operation, GtkPrintContext* context, gpointer user_data )
{
PrintData* print_data = static_cast<PrintData*>( user_data );
if( !print_data || !print_data->doc )
{
gtk_print_operation_cancel( operation );
return;
}
int num_pages = poppler_document_get_n_pages( print_data->doc );
if( num_pages <= 0 )
{
gtk_print_operation_cancel( operation );
return;
}
gtk_print_operation_set_n_pages( operation, num_pages );
}
void request_page_setup_callback( GtkPrintOperation* operation, GtkPrintContext* context, gint page_nr,
GtkPageSetup* setup, gpointer user_data )
{
PrintData* print_data = static_cast<PrintData*>( user_data );
if( !print_data || !print_data->doc )
return;
PopplerPage* page = poppler_document_get_page( print_data->doc, page_nr );
if( !page )
return;
// Get page dimensions to determine orientation
double page_width, page_height;
poppler_page_get_size( page, &page_width, &page_height );
// Set orientation based on page dimensions
GtkPageOrientation orientation =
( page_width > page_height ) ? GTK_PAGE_ORIENTATION_LANDSCAPE : GTK_PAGE_ORIENTATION_PORTRAIT;
gtk_page_setup_set_orientation( setup, orientation );
g_object_unref( page );
}
} // namespace
namespace KIPLATFORM
{
namespace PRINTING
{
PRINT_RESULT PrintPDF( const std::string& aFile, bool fit_to_page )
{
// Check file accessibility
if( access( aFile.c_str(), R_OK ) != 0 )
return PRINT_RESULT::FILE_NOT_FOUND;
// Create file URI
gchar* uri = g_filename_to_uri( aFile.c_str(), NULL, NULL );
if( !uri )
return PRINT_RESULT::FILE_NOT_FOUND;
// Load the PDF document
GError* error = NULL;
PopplerDocument* doc = poppler_document_new_from_file( uri, NULL, &error );
g_free( uri );
if( error )
{
g_error_free( error );
return PRINT_RESULT::FAILED_TO_LOAD;
}
if( !doc )
return PRINT_RESULT::FAILED_TO_LOAD;
auto cleanup_doc = std::unique_ptr<PopplerDocument, decltype(&g_object_unref)>(doc, &g_object_unref);
// Check if document has pages
int num_pages = poppler_document_get_n_pages( doc );
if( num_pages <= 0 )
return PRINT_RESULT::FAILED_TO_LOAD;
// Create print data
PrintData print_data( doc );
print_data.fit_to_page = fit_to_page;
// Create print operation
GtkPrintOperation* op = gtk_print_operation_new();
if( !op )
return PRINT_RESULT::FAILED_TO_PRINT;
auto cleanup_op = std::unique_ptr<GtkPrintOperation, decltype( &g_object_unref )>( op, &g_object_unref );
// Set up print operation properties
gtk_print_operation_set_use_full_page( op, FALSE );
gtk_print_operation_set_unit( op, GTK_UNIT_POINTS );
// Connect callbacks
g_signal_connect( op, "begin-print", G_CALLBACK( begin_print_callback ), &print_data );
g_signal_connect( op, "draw-page", G_CALLBACK( draw_page ), &print_data );
g_signal_connect( op, "request-page-setup", G_CALLBACK( request_page_setup_callback ), &print_data );
// Run the print operation
error = NULL;
GtkPrintOperationResult result =
gtk_print_operation_run( op, GTK_PRINT_OPERATION_ACTION_PRINT_DIALOG, NULL, &error );
// Handle errors and determine result
PRINT_RESULT return_result;
if( error )
{
g_error_free( error );
return_result = PRINT_RESULT::FAILED_TO_PRINT;
}
else
{
switch( result )
{
case GTK_PRINT_OPERATION_RESULT_APPLY: return_result = PRINT_RESULT::OK; break;
case GTK_PRINT_OPERATION_RESULT_CANCEL: return_result = PRINT_RESULT::CANCELLED; break;
case GTK_PRINT_OPERATION_RESULT_ERROR:
default: return_result = PRINT_RESULT::FAILED_TO_PRINT; break;
}
}
return return_result;
}
PRINT_RESULT PrintPDF( const std::string& aFile )
{
return PrintPDF( aFile, true );
}
} // namespace PRINTING
} // namespace KIPLATFORM

View File

@ -0,0 +1,422 @@
/*
* This program source code file is part of KiCad, a free EDA CAD application.
*
* Copyright The KiCad Developers, see AUTHORS.txt for contributors.
*
* 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 <printing.h>
#if defined( _MSC_VER )
#include <windows.h>
#include <algorithm>
#include <cmath>
#include <map>
#include <utility>
#include <roapi.h>
#include <winrt/base.h>
#include <winrt/Windows.Foundation.h>
#include <winrt/Windows.Foundation.Collections.h>
#include <winrt/Windows.Graphics.Printing.h>
#include <winrt/Windows.UI.Xaml.h>
#include <winrt/Windows.UI.Xaml.Controls.h>
#include <winrt/Windows.UI.Xaml.Media.h>
#include <winrt/Windows.UI.Xaml.Printing.h>
#include <winrt/Windows.UI.Xaml.Hosting.h>
#include <winrt/Windows.Storage.h>
#include <winrt/Windows.Storage.Streams.h>
#include <winrt/Windows.Data.Pdf.h>
#include <winrt/Windows.Graphics.Imaging.h>
#include <winrt/base.h>
#include <winrt/Windows.Foundation.h>
#include <winrt/Windows.Foundation.Collections.h>
#include <winrt/Windows.Graphics.Printing.h>
#include <winrt/Windows.UI.Xaml.h>
#include <winrt/Windows.UI.Xaml.Controls.h>
#include <winrt/Windows.UI.Xaml.Media.Imaging.h>
#include <winrt/Windows.UI.Xaml.Printing.h>
#include <winrt/Windows.UI.Xaml.Hosting.h>
#include <winrt/Windows.Storage.h>
#include <winrt/Windows.Storage.Streams.h>
#include <winrt/Windows.Data.Pdf.h>
#include <winrt/Windows.Graphics.Imaging.h>
using namespace winrt;
// Manual declaration of IPrintManagerInterop to avoid missing header
MIDL_INTERFACE("C5435A42-8D43-4E7B-A68A-EF311E392087")
IPrintManagerInterop : public ::IInspectable
{
public:
virtual HRESULT STDMETHODCALLTYPE GetForWindow(
/* [in] */ HWND appWindow,
/* [in] */ REFIID riid,
/* [iid_is][retval][out] */ void **printManager) = 0;
virtual HRESULT STDMETHODCALLTYPE ShowPrintUIForWindowAsync(
/* [in] */ HWND appWindow,
/* [retval][out] */ void **operation) = 0;
};
// Manual declaration of IDesktopWindowXamlSourceNative to avoid missing header
MIDL_INTERFACE("3cbcf1bf-2f76-4e9c-96ab-e84b37972554")
IDesktopWindowXamlSourceNative : public ::IUnknown
{
public:
virtual HRESULT STDMETHODCALLTYPE AttachToWindow(
/* [in] */ HWND parentWnd) = 0;
virtual HRESULT STDMETHODCALLTYPE get_WindowHandle(
/* [retval][out] */ HWND *hWnd) = 0;
};
static inline std::pair<uint32_t, uint32_t> DpToPixels( winrt::Windows::Data::Pdf::PdfPage const& page, double dpi )
{
const auto s = page.Size(); // DIPs (1 DIP = 1/96 inch)
const double scale = dpi / 96.0;
uint32_t w = static_cast<uint32_t>( std::max( 1.0, std::floor( s.Width * scale + 0.5 ) ) );
uint32_t h = static_cast<uint32_t>( std::max( 1.0, std::floor( s.Height * scale + 0.5 ) ) );
return { w, h };
}
// Helper class to manage image with its associated stream
struct ManagedImage
{
winrt::Windows::UI::Xaml::Controls::Image image;
winrt::Windows::Storage::Streams::InMemoryRandomAccessStream stream;
ManagedImage() = default;
ManagedImage(winrt::Windows::UI::Xaml::Controls::Image img, winrt::Windows::Storage::Streams::InMemoryRandomAccessStream str) : image(img), stream(str) {}
ManagedImage(ManagedImage&& other) noexcept
: image(std::move(other.image)), stream(std::move(other.stream)) {}
ManagedImage& operator=(ManagedImage&& other) noexcept {
if (this != &other) {
image = std::move(other.image);
stream = std::move(other.stream);
}
return *this;
}
};
// Render one page to a XAML Image using RenderToStreamAsync
// dpi: e.g., 300 for preview; 600 for print
// Returns a ManagedImage that keeps the stream alive
static ManagedImage RenderPdfPageToImage( winrt::Windows::Data::Pdf::PdfDocument const& pdf, uint32_t pageIndex, double dpi )
{
auto page = pdf.GetPage( pageIndex );
if( !page )
return {};
auto [pxW, pxH] = DpToPixels( page, dpi );
winrt::Windows::Data::Pdf::PdfPageRenderOptions opts;
opts.DestinationWidth( pxW );
opts.DestinationHeight( pxH );
winrt::Windows::Storage::Streams::InMemoryRandomAccessStream stream;
try
{
page.RenderToStreamAsync( stream, opts ).get(); // sync for simplicity
}
catch( ... )
{
return {};
}
// Use a BitmapImage that sources directly from the stream
winrt::Windows::UI::Xaml::Media::Imaging::BitmapImage bmp;
try
{
stream.Seek(0);
bmp.SetSourceAsync( stream ).get();
}
catch( ... )
{
return {};
}
winrt::Windows::UI::Xaml::Controls::Image img;
img.Source( bmp );
img.Stretch( winrt::Windows::UI::Xaml::Media::Stretch::Uniform );
// Return both image and stream to keep stream alive
return ManagedImage{ img, stream };
}
namespace KIPLATFORM {
namespace PRINTING {
class WIN_PDF_PRINTER
{
public:
WIN_PDF_PRINTER( HWND hwndOwner, winrt::Windows::Data::Pdf::PdfDocument const& pdf ) :
m_hwnd( hwndOwner ),
m_pdf( pdf )
{
}
PRINT_RESULT Run()
{
if( !m_pdf )
return PRINT_RESULT::FAILED_TO_LOAD;
// Create hidden XAML Island host
m_xamlSource = winrt::Windows::UI::Xaml::Hosting::DesktopWindowXamlSource();
auto native = m_xamlSource.as<IDesktopWindowXamlSourceNative>();
if( !native )
return PRINT_RESULT::FAILED_TO_PRINT;
RECT rc{ 0, 0, 100, 100 }; // Use larger minimum size
m_host = ::CreateWindowExW( 0, L"STATIC", L"", WS_CHILD | WS_CLIPSIBLINGS | WS_CLIPCHILDREN,
rc.left, rc.top, rc.right - rc.left, rc.bottom - rc.top, m_hwnd, nullptr,
::GetModuleHandleW( nullptr ), nullptr );
auto cleanup_guard = std::unique_ptr<void, std::function<void( void* )>>
( (void*) 1, [this]( void* ){ this->cleanup(); } );
if( !m_host )
return PRINT_RESULT::FAILED_TO_PRINT;
if( FAILED( native->AttachToWindow( m_host ) ) )
return PRINT_RESULT::FAILED_TO_PRINT;
m_root = winrt::Windows::UI::Xaml::Controls::Grid();
m_xamlSource.Content( m_root );
m_printDoc = winrt::Windows::UI::Xaml::Printing::PrintDocument();
m_docSrc = m_printDoc.DocumentSource();
m_pageCount = std::max<uint32_t>( 1, m_pdf.PageCount() );
m_paginateToken = m_printDoc.Paginate(
[this]( winrt::Windows::Foundation::IInspectable const& sender, winrt::Windows::UI::Xaml::Printing::PaginateEventArgs const& e )
{
m_printDoc.SetPreviewPageCount( m_pageCount, winrt::Windows::UI::Xaml::Printing::PreviewPageCountType::Final );
} );
m_getPreviewToken = m_printDoc.GetPreviewPage(
[this]( winrt::Windows::Foundation::IInspectable const& sender, winrt::Windows::UI::Xaml::Printing::GetPreviewPageEventArgs const& e )
{
const uint32_t index = e.PageNumber() - 1; // 1-based from system
auto managedImg = RenderPdfPageToImage( m_pdf, index, /*dpi*/ 300.0 );
if( managedImg.image )
{
// Store the managed image to keep stream alive
m_previewImages[index] = std::move(managedImg);
m_printDoc.SetPreviewPage( e.PageNumber(), m_previewImages[index].image );
}
} );
m_addPagesToken = m_printDoc.AddPages(
[this]( winrt::Windows::Foundation::IInspectable const& sender, winrt::Windows::UI::Xaml::Printing::AddPagesEventArgs const& e )
{
for( uint32_t i = 0; i < m_pageCount; ++i )
{
auto managedImg = RenderPdfPageToImage( m_pdf, i, /*dpi*/ 600.0 );
if( managedImg.image )
{
// Store the managed image to keep stream alive
m_printImages[i] = std::move(managedImg);
m_printDoc.AddPage( m_printImages[i].image );
}
}
m_printDoc.AddPagesComplete();
} );
try
{
auto factory = winrt::get_activation_factory<winrt::Windows::Graphics::Printing::PrintManager>();
auto pmInterop = factory.as<IPrintManagerInterop>();
winrt::Windows::Graphics::Printing::PrintManager printManager{ nullptr };
if( FAILED( pmInterop->GetForWindow( m_hwnd,
winrt::guid_of<winrt::Windows::Graphics::Printing::PrintManager>(),
winrt::put_abi( printManager ) ) ) )
{
return PRINT_RESULT::FAILED_TO_PRINT;
}
// Now we have the WinRT PrintManager directly
m_rtPM = printManager;
m_taskRequestedToken = m_rtPM.PrintTaskRequested(
[this]( winrt::Windows::Foundation::IInspectable const& sender, winrt::Windows::Graphics::Printing::PrintTaskRequestedEventArgs const& e )
{
auto task = e.Request().CreatePrintTask( L"KiCad PDF Print",
[this]( winrt::Windows::Graphics::Printing::PrintTaskSourceRequestedArgs const& sourceRequestedArgs )
{
// Supply document source for preview
sourceRequestedArgs.SetSource( m_docSrc );
} );
} );
winrt::Windows::Foundation::IAsyncOperation<bool> asyncOp{ nullptr };
// Immediately wait for results to keep this in thread
if( FAILED( pmInterop->ShowPrintUIForWindowAsync( m_hwnd, winrt::put_abi(asyncOp) ) ) )
return PRINT_RESULT::FAILED_TO_PRINT;
bool shown = false;
try
{
shown = asyncOp.GetResults();
}
catch( ... )
{
return PRINT_RESULT::FAILED_TO_PRINT;
}
return shown ? PRINT_RESULT::OK : PRINT_RESULT::CANCELLED;
}
catch( ... )
{
return PRINT_RESULT::FAILED_TO_PRINT;
}
}
private:
void cleanup()
{
// Clear image containers first to release streams
m_previewImages.clear();
m_printImages.clear();
if( m_rtPM )
{
m_rtPM.PrintTaskRequested( m_taskRequestedToken );
m_rtPM = nullptr;
}
if( m_printDoc )
{
m_printDoc.AddPages( m_addPagesToken );
m_printDoc.GetPreviewPage( m_getPreviewToken );
m_printDoc.Paginate( m_paginateToken );
}
m_docSrc = nullptr;
m_printDoc = nullptr;
m_root = nullptr;
if( m_host )
{
::DestroyWindow( m_host );
m_host = nullptr;
}
m_xamlSource = nullptr;
}
private:
HWND m_hwnd{};
winrt::Windows::Data::Pdf::PdfDocument m_pdf{ nullptr };
winrt::Windows::UI::Xaml::Hosting::DesktopWindowXamlSource m_xamlSource{ nullptr };
winrt::Windows::UI::Xaml::Controls::Grid m_root{ nullptr };
winrt::Windows::UI::Xaml::Printing::PrintDocument m_printDoc{ nullptr };
winrt::Windows::Graphics::Printing::IPrintDocumentSource m_docSrc{ nullptr };
uint32_t m_pageCount{ 0 };
winrt::Windows::Graphics::Printing::PrintManager m_rtPM{ nullptr };
winrt::event_token m_taskRequestedToken{};
winrt::event_token m_paginateToken{};
winrt::event_token m_getPreviewToken{};
winrt::event_token m_addPagesToken{};
HWND m_host{ nullptr };
// Store managed images to keep streams alive
std::map<uint32_t, ManagedImage> m_previewImages;
std::map<uint32_t, ManagedImage> m_printImages;
};
static std::wstring Utf8ToWide( std::string const& s )
{
if( s.empty() ) return {};
int len = MultiByteToWideChar( CP_UTF8, 0, s.data(), (int) s.size(), nullptr, 0 );
std::wstring out( len, L'\0' );
MultiByteToWideChar( CP_UTF8, 0, s.data(), (int) s.size(), out.data(), len );
return out;
}
PRINT_RESULT PrintPDF(std::string const& aFile )
{
// Validate path
DWORD attrs = GetFileAttributesA( aFile.c_str() );
if( attrs == INVALID_FILE_ATTRIBUTES )
return PRINT_RESULT::FILE_NOT_FOUND;
// Load PDF via Windows.Data.Pdf
winrt::Windows::Data::Pdf::PdfDocument pdf{ nullptr };
try
{
auto path = Utf8ToWide( aFile );
auto file = winrt::Windows::Storage::StorageFile::GetFileFromPathAsync( winrt::hstring( path ) ).get();
pdf = winrt::Windows::Data::Pdf::PdfDocument::LoadFromFileAsync( file ).get();
}
catch( ... )
{
return PRINT_RESULT::FAILED_TO_LOAD;
}
if( !pdf || pdf.PageCount() == 0 ) return PRINT_RESULT::FAILED_TO_LOAD;
HWND hwndOwner = ::GetActiveWindow();
if( !hwndOwner ) hwndOwner = ::GetForegroundWindow();
if( !hwndOwner ) return PRINT_RESULT::FAILED_TO_PRINT;
try
{
WIN_PDF_PRINTER printer( hwndOwner, pdf );
return printer.Run();
}
catch( ... )
{
return PRINT_RESULT::FAILED_TO_PRINT;
}
}
} // namespace PRINTING
} // namespace KIPLATFORM
#else
namespace KIPLATFORM
{
namespace PRINTING
{
PRINT_RESULT PrintPDF( std::string const& )
{
return PRINT_RESULT::UNSUPPORTED;
}
} // namespace PRINTING
} // namespace KIPLATFORM
#endif