Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions win-linux/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,7 @@ set(COMMON_HEADERS
src/cascapplicationmanagerwrapper_private.h
src/casctabdata.h
src/utils.h
src/docautomation.h
src/chelp.h
src/cfilechecker.h
src/clogger.h
Expand All @@ -182,6 +183,7 @@ set(COMMON_HEADERS
set(COMMON_SOURCES
src/prop/cmainwindowimpl.cpp
src/prop/utils.cpp
src/docautomation.cpp
src/windows/cmainwindow.cpp
src/windows/cwindowbase.cpp
src/windows/ceditorwindow.cpp
Expand Down
4 changes: 3 additions & 1 deletion win-linux/src/chelp.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,9 @@ void CHelp::out()
" --unlock-portals disable forced hiding of the Connect to the cloud button on the start page"
" --updates-appcast-channel=dev set development URL for updates\n"
" --updates-interval=<time> set update check interval in seconds (minimum 30 sec)\n"
" --updates-reset reset all update options\n";
" --updates-reset reset all update options\n"
" <input-file> --convert-to=path/to/output.ext convert a document without starting the UI\n"
" <input-file> --print-to-file=path/to/output.pdf render a document to PDF without starting the UI\n";

#ifdef _WIN32
FILE *pFile = NULL;
Expand Down
119 changes: 119 additions & 0 deletions win-linux/src/docautomation.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
/*
* SPDX-FileCopyrightText: 2026 Euro-Office contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/

#include "docautomation.h"
#include "defines.h"
#include "utils.h"
#include "applicationmanager.h"
#include "x2t.h"
#include "common/File.h"
#include "common/Directory.h"
#include "common/StringBuilder.h"

#include <iostream>

namespace {
std::wstring GetFlagValue(const std::wstring& flag) {
return InputArgs::contains(flag) ? InputArgs::argument_value(flag) : L"";
}

// First non-flag argument is treated as the input file path.
std::wstring GetInputFileArg() {
for (const auto& arg : InputArgs::arguments()) {
if (arg.rfind(L"--", 0) != 0) {
return arg;
}
}
return L"";
}
}

bool NSDocAutomation::ShouldRun() {
return InputArgs::contains(L"--convert-to") || InputArgs::contains(L"--print-to-file");
}

int NSDocAutomation::Run() {
bool bPrintToFile = InputArgs::contains(L"--print-to-file");
std::wstring sOutput = bPrintToFile ? GetFlagValue(L"--print-to-file") : GetFlagValue(L"--convert-to");
std::wstring sInput = GetInputFileArg();

if (sInput.empty() || sOutput.empty()) {
std::wcerr << L"Usage: DesktopEditors <input-file> --convert-to=<output-file>" << std::endl;
std::wcerr << L" DesktopEditors <input-file> --print-to-file=<output-file.pdf>" << std::endl;
return 1;
}

if (!NSFile::CFileBinary::Exists(sInput)) {
std::wcerr << L"Input file not found: " << sInput << std::endl;
return 1;
}

// --print-to-file is an alias for --convert-to that forces PDF output,
// since x2t's PDF export is already what "Print" ultimately produces.
if (bPrintToFile) {
std::wstring sPdfExt = L".pdf";
if (sOutput.length() < sPdfExt.length() ||
0 != sOutput.compare(sOutput.length() - sPdfExt.length(), sPdfExt.length(), sPdfExt)) {
sOutput += sPdfExt;
}
}

// A plain CAscApplicationManager, not the QObject-based GUI singleton
// (AscAppManager::getInstance()) -- that one installs Qt event filters
// in its constructor and crashes without a live QCoreApplication, which
// doesn't exist yet at this point (before any CEF/Qt startup).
CAscApplicationManager oManager;
QString sUserDataPath = Utils::getUserPath() + APP_DATA_PATH;
oManager.m_oSettings.SetUserDataPath(sUserDataPath.toStdWString());
std::wstring sAppPath = NSFile::GetProcessDirectory();
oManager.m_oSettings.file_converter_path = sAppPath + L"/converter";

// Normal startup builds the font cache via CheckFonts() before any document
// is touched; this path skips that startup sequence entirely, so on a
// profile that has never run the GUI (fresh install, container, CI) the
// AllFonts.js referenced below wouldn't exist yet. Block on it here.
oManager.CheckFonts(false);

std::wstring sTempDir = NSDirectory::CreateDirectoryWithUniqueName(NSFile::CFileBinary::GetTempPath());

// Formats are left for x2t to detect from the file extensions, the same
// way its own `x2t <from> <to>` mode does.
NSStringUtils::CStringBuilder oBuilder;
oBuilder.WriteString(L"<?xml version=\"1.0\" encoding=\"utf-8\"?><TaskQueueDataConvert><m_sFileFrom>");
oBuilder.WriteEncodeXmlString(sInput);
oBuilder.WriteString(L"</m_sFileFrom><m_sFileTo>");
oBuilder.WriteEncodeXmlString(sOutput);
oBuilder.WriteString(L"</m_sFileTo><m_sFontDir>");
oBuilder.WriteEncodeXmlString(oManager.m_oSettings.fonts_cache_info_path);
oBuilder.WriteString(L"</m_sFontDir><m_sAllFontsPath>");
oBuilder.WriteEncodeXmlString(oManager.m_oSettings.fonts_cache_info_path + L"/AllFonts.js");
oBuilder.WriteString(L"</m_sAllFontsPath><m_sTempDir>");
oBuilder.WriteEncodeXmlString(sTempDir);
oBuilder.WriteString(L"</m_sTempDir></TaskQueueDataConvert>");

// The ".xml" suffix is load-bearing: x2t only parses its argument as a
// TaskQueueDataConvert file when the name ends in ".xml" (see
// X2tConverter/src/main.cpp). Anything else is taken as the first of a
// bare `x2t <from> <to>` pair, leaving the destination empty and failing
// with "Empty sFileFrom or sFileTo".
std::wstring sTempName = NSFile::CFileBinary::CreateTempFileWithUniqueName(NSFile::CFileBinary::GetTempPath(), L"CLI");
if (NSFile::CFileBinary::Exists(sTempName))
NSFile::CFileBinary::Remove(sTempName);
std::wstring sXmlPath = sTempName + L".xml";
NSFile::CFileBinary::SaveToFile(sXmlPath, oBuilder.GetData(), true);

int nReturnCode = NSX2T::Convert(oManager.m_oSettings.file_converter_path + L"/x2t", sXmlPath, &oManager, true);

NSFile::CFileBinary::Remove(sXmlPath);
NSDirectory::DeleteDirectory(sTempDir);

if (0 == nReturnCode) {
std::wcout << L"OK: " << sOutput << std::endl;
} else {
std::wcerr << L"Conversion failed (x2t exit code " << nReturnCode << L")" << std::endl;
}

return nReturnCode;
}
18 changes: 18 additions & 0 deletions win-linux/src/docautomation.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
/*
* SPDX-FileCopyrightText: 2026 Euro-Office contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/

#ifndef DOCAUTOMATION_H
#define DOCAUTOMATION_H

// Handles the --convert-to and --print-to-file CLI flags by shelling out to
// the packaged x2t converter, without starting CEF/Qt UI. --print-to-file is
// an alias for --convert-to that forces PDF output, since x2t's PDF export is
// already what "Print" ultimately produces.
namespace NSDocAutomation {
bool ShouldRun();
int Run();
}

#endif // DOCAUTOMATION_H
4 changes: 4 additions & 0 deletions win-linux/src/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
#include "utils.h"
#include "chelp.h"
#include "common/File.h"
#include "docautomation.h"
#include <QStyleFactory>


Expand All @@ -65,6 +66,9 @@ int main( int argc, char *argv[] )
return 0;
}
#endif
if ( NSDocAutomation::ShouldRun() ) {
return NSDocAutomation::Run();
}
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
qputenv("QT_ENABLE_HIGHDPI_SCALING", "0");
#else
Expand Down
8 changes: 6 additions & 2 deletions win-linux/src/utils.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -114,8 +114,12 @@ namespace InputArgs {

auto argument_value(const std::wstring& param) -> std::wstring {
for (const auto& item: in_args) {
if ( item.find(param) != std::wstring::npos ) {
return item.substr(param.size() + 1); // substring after '=' or ':' symbol
auto n = item.find(param);
if ( n != std::wstring::npos ) {
auto value_pos = n + param.size() + 1; // position after '=' or ':' symbol
if ( value_pos <= item.length() )
return item.substr(value_pos);
return L"";
}
}

Expand Down