diff --git a/CMakeLists.txt b/CMakeLists.txt index 903cfd774e..bdd1f41007 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -783,6 +783,9 @@ find_package( CURL REQUIRED ) if( UNIX AND NOT APPLE ) find_package( SPNAV REQUIRED ) include_directories( SYSTEM ${SPNAV_INCLUDE_DIR} ) + + find_package( Poppler REQUIRED ) + include_directories( SYSTEM ${POPPLER_INCLUDE_DIRS} ) endif() # diff --git a/cmake/ECMFindModuleHelpers.cmake b/cmake/ECMFindModuleHelpers.cmake new file mode 100644 index 0000000000..92c796e6bd --- /dev/null +++ b/cmake/ECMFindModuleHelpers.cmake @@ -0,0 +1,277 @@ +# SPDX-FileCopyrightText: 2014 Alex Merry +# +# 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() + +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( + RESULT_VAR + KNOWN_COMPONENTS [ [...]] + [SKIP_DEPENDENCY_HANDLING]) + +This macro will populate with a list of components found in +_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 _FIND_REQUIRED) and call +``return()``. + +The order of components in 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 +__component_deps will be checked for dependent components. +If is listed in _FIND_COMPONENTS, then all its (transitive) +dependencies will also be added to . + +:: + + ecm_find_package_handle_library_components( + COMPONENTS [ [...]] + [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 __lib variable should contain the name of this library, +and __header variable should contain the name of a header +file associated with it (whatever relative path is normally passed to +'#include'). __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 ::. + +Additional variables can be used to provide additional information: + +If ``SKIP_PKG_CONFIG``, the __pkg_config variable is set, and +pkg-config is found, the pkg-config module given by +__pkg_config will be searched for and used to help locate the +library and header file. It will also be used to set +__VERSION. + +Note that if version information is found via pkg-config, +__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 will be set to contain the imported +targets for the components listed in __component_deps. +_FOUND will also be set to ``FALSE`` if any of the components in +__component_deps are not found. This requires the components +in __component_deps to be listed before in the +``COMPONENTS`` argument. + +The following variables will be set: + +``_TARGETS`` + the imported targets +``_LIBRARIES`` + the found libraries +``_INCLUDE_DIRS`` + the combined required include directories for the components +``_DEFINITIONS`` + the "other" CFLAGS provided by pkg-config, if any +``_VERSION`` + the value of ``__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 + ``_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() diff --git a/cmake/FindPoppler.cmake b/cmake/FindPoppler.cmake new file mode 100644 index 0000000000..9c9ca1ed6c --- /dev/null +++ b/cmake/FindPoppler.cmake @@ -0,0 +1,132 @@ +# SPDX-FileCopyrightText: 2015 Alex Richardson +# +# 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__FOUND`` will be set to +TRUE if the corresponding Poppler library was found, and FALSE otherwise. If +``Poppler__FOUND`` is TRUE, the imported target +``Poppler::`` 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/" +) diff --git a/common/CMakeLists.txt b/common/CMakeLists.txt index e023bf766e..2eadfb058c 100644 --- a/common/CMakeLists.txt +++ b/common/CMakeLists.txt @@ -818,6 +818,10 @@ target_link_libraries( common ${Fontconfig_LIBRARIES} ) +if( UNIX AND NOT APPLE ) + target_link_libraries( common ${Poppler_LIBRARIES} ) +endif() + if( KICAD_USE_SENTRY ) target_link_libraries( common sentry ) diff --git a/common/advanced_config.cpp b/common/advanced_config.cpp index 649f8cecde..c7f69947ac 100644 --- a/common/advanced_config.cpp +++ b/common/advanced_config.cpp @@ -84,6 +84,7 @@ static const wxChar ExtraZoneDisplayModes[] = wxT( "ExtraZoneDisplayModes" ); static const wxChar MinPlotPenWidth[] = wxT( "MinPlotPenWidth" ); static const wxChar DebugZoneFiller[] = wxT( "DebugZoneFiller" ); static const wxChar DebugPDFWriter[] = wxT( "DebugPDFWriter" ); +static const wxChar UsePdfPrint[] = wxT( "UsePdfPrint" ); static const wxChar SmallDrillMarkSize[] = wxT( "SmallDrillMarkSize" ); static const wxChar HotkeysDumper[] = wxT( "HotkeysDumper" ); static const wxChar DrawBoundingBoxes[] = wxT( "DrawBoundingBoxes" ); @@ -245,6 +246,7 @@ ADVANCED_CFG::ADVANCED_CFG() m_DebugZoneFiller = false; m_DebugPDFWriter = false; + m_UsePdfPrint = false; m_SmallDrillMarkSize = 0.35; m_HotkeysDumper = false; m_DrawBoundingBoxes = false; @@ -456,6 +458,9 @@ void ADVANCED_CFG::loadSettings( wxConfigBase& aCfg ) m_entries.push_back( std::make_unique( true, AC_KEYS::DebugPDFWriter, &m_DebugPDFWriter, m_DebugPDFWriter ) ); + m_entries.push_back( std::make_unique( true, AC_KEYS::UsePdfPrint, + &m_UsePdfPrint, m_UsePdfPrint ) ); + m_entries.push_back( std::make_unique( true, AC_KEYS::SmallDrillMarkSize, &m_SmallDrillMarkSize, m_SmallDrillMarkSize, 0.0, 3.0 ) ); diff --git a/common/dialogs/dialog_generate_database_connection.cpp b/common/dialogs/dialog_generate_database_connection.cpp new file mode 100644 index 0000000000..a73b72bc45 --- /dev/null +++ b/common/dialogs/dialog_generate_database_connection.cpp @@ -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 . + */ + +#include + +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +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 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 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( dsn.ToStdString(), user.ToStdString(), pass.ToStdString(), + timeout, false ); + } + else + { + conn = std::make_unique( 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 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 ); + } +} diff --git a/common/pgm_base.cpp b/common/pgm_base.cpp index 9b523b7878..ec157acd62 100644 --- a/common/pgm_base.cpp +++ b/common/pgm_base.cpp @@ -75,6 +75,9 @@ #include #endif +#ifdef _MSC_VER +#include +#endif /** * Current list of languages supported by KiCad. * @@ -177,6 +180,10 @@ void PGM_BASE::Destroy() APP_MONITOR::SENTRY::Instance()->Cleanup(); 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 +#ifdef _MSC_VER + winrt::init_apartment(winrt::apartment_type::single_threaded); +#endif + m_settings_manager = std::make_unique( aHeadless ); m_background_jobs_monitor = std::make_unique(); m_notifications_manager = std::make_unique(); diff --git a/eeschema/printing/dialog_print.cpp b/eeschema/printing/dialog_print.cpp index fd0507e454..dd93cb45b7 100644 --- a/eeschema/printing/dialog_print.cpp +++ b/eeschema/printing/dialog_print.cpp @@ -31,12 +31,15 @@ #include #include #include +#include #include "dialog_print.h" #include #include +#include +#include #include "sch_printout.h" @@ -113,6 +116,14 @@ DIALOG_PRINT::DIALOG_PRINT( SCH_EDIT_FRAME* aParent ) : m_sdbSizerApply->Hide(); #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(); Layout(); @@ -298,6 +309,51 @@ bool DIALOG_PRINT::TransferDataFromWindow() 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(); wxPrintData& data = m_parent->GetPageSetupData().GetPrintData(); diff --git a/include/advanced_config.h b/include/advanced_config.h index ca7763fe85..5e153b40a3 100644 --- a/include/advanced_config.h +++ b/include/advanced_config.h @@ -322,6 +322,15 @@ public: */ 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" * option is set to "Small mark". diff --git a/include/dialogs/dialog_generate_database_connection.h b/include/dialogs/dialog_generate_database_connection.h new file mode 100644 index 0000000000..5dd346ee22 --- /dev/null +++ b/include/dialogs/dialog_generate_database_connection.h @@ -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 . + */ + +#ifndef DIALOG_GENERATE_DATABASE_CONNECTION_H +#define DIALOG_GENERATE_DATABASE_CONNECTION_H + +#include + +#include +#include + +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 diff --git a/libs/kiplatform/CMakeLists.txt b/libs/kiplatform/CMakeLists.txt index a382d882e4..a2986c37f1 100644 --- a/libs/kiplatform/CMakeLists.txt +++ b/libs/kiplatform/CMakeLists.txt @@ -51,6 +51,7 @@ if( APPLE ) os/apple/policy.mm os/apple/secrets.mm os/apple/sysinfo.cpp + os/apple/printing.mm ) set( PLATFORM_LIBS @@ -58,6 +59,7 @@ if( APPLE ) "-framework AppKit" "-framework CoreData" "-framework Foundation" + "-framework PDFKit" ) elseif( WIN32 ) list( APPEND PLATFORM_SRCS @@ -68,6 +70,7 @@ elseif( WIN32 ) os/windows/policy.cpp os/windows/secrets.cpp os/windows/sysinfo.cpp + os/windows/printing.cpp ) set( PLATFORM_LIBS @@ -75,6 +78,7 @@ elseif( WIN32 ) "winhttp" "wintrust" "imm32" + "windowsapp" ) elseif( UNIX ) list( APPEND PLATFORM_SRCS @@ -85,6 +89,7 @@ elseif( UNIX ) os/unix/policy.cpp os/unix/secrets.cpp os/unix/sysinfo.cpp + os/unix/printing.cpp ) # Detect the secret library and configure it diff --git a/libs/kiplatform/include/printing.h b/libs/kiplatform/include/printing.h new file mode 100644 index 0000000000..d612ba5152 --- /dev/null +++ b/libs/kiplatform/include/printing.h @@ -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 . +*/ + +#ifndef KIPLATFORM_PRINTING_H_ +#define KIPLATFORM_PRINTING_H_ + +#include +#include + +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_ + diff --git a/libs/kiplatform/os/apple/printing.mm b/libs/kiplatform/os/apple/printing.mm new file mode 100644 index 0000000000..71ece19385 --- /dev/null +++ b/libs/kiplatform/os/apple/printing.mm @@ -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 . +*/ + +#include + +#import +#import + +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 \ No newline at end of file diff --git a/libs/kiplatform/os/unix/printing.cpp b/libs/kiplatform/os/unix/printing.cpp new file mode 100644 index 0000000000..a6597f2f5b --- /dev/null +++ b/libs/kiplatform/os/unix/printing.cpp @@ -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 . +*/ +#include + +#include +#include +#include +#include +#include + +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( 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( 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( 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( 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( 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(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( 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 \ No newline at end of file diff --git a/libs/kiplatform/os/windows/printing.cpp b/libs/kiplatform/os/windows/printing.cpp new file mode 100644 index 0000000000..a266b6b736 --- /dev/null +++ b/libs/kiplatform/os/windows/printing.cpp @@ -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 . +*/ + +#include + +#if defined( _MSC_VER ) +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +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 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( std::max( 1.0, std::floor( s.Width * scale + 0.5 ) ) ); + uint32_t h = static_cast( 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(); + + 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*) 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( 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(); + auto pmInterop = factory.as(); + + winrt::Windows::Graphics::Printing::PrintManager printManager{ nullptr }; + + if( FAILED( pmInterop->GetForWindow( m_hwnd, + winrt::guid_of(), + 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 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 m_previewImages; + std::map 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 \ No newline at end of file