250 lines
9.1 KiB
C++
250 lines
9.1 KiB
C++
/*
|
|
ptrnt - print labels on linux
|
|
Copyright (C) 2023-2025 Moritz Martinius
|
|
|
|
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 <https://www.gnu.org/licenses/>.
|
|
|
|
*/
|
|
#include "PtouchPrint.hpp"
|
|
|
|
#include <format>
|
|
#include <iostream>
|
|
#include <spdlog/sinks/basic_file_sink.h>
|
|
#include <spdlog/sinks/stdout_color_sinks.h>
|
|
#include <spdlog/spdlog.h>
|
|
|
|
#include "cli/CliParser.hpp"
|
|
#include "cli/interface/ICliParser.hpp"
|
|
#include "constants.hpp"
|
|
#include "core/PrinterDriverFactory.hpp"
|
|
#include "core/PrinterService.hpp"
|
|
#include "core/interface/IPrinterService.hpp"
|
|
#include "graphics/LabelBuilder.hpp"
|
|
|
|
namespace ptprnt {
|
|
|
|
PtouchPrint::PtouchPrint(const char* versionString)
|
|
: PtouchPrint(versionString, std::make_unique<cli::CliParser>(ptprnt::APP_DESC, versionString),
|
|
std::make_unique<core::PrinterService>()) {}
|
|
|
|
PtouchPrint::PtouchPrint(const char* versionString, std::unique_ptr<cli::ICliParser> cliParser,
|
|
std::unique_ptr<core::IPrinterService> printerService)
|
|
: mVersionString(versionString), mCliParser(std::move(cliParser)), mPrinterService(std::move(printerService)) {}
|
|
|
|
PtouchPrint::~PtouchPrint() = default;
|
|
|
|
int PtouchPrint::init(int argc, char** argv) {
|
|
// Parse CLI arguments
|
|
int parseResult = mCliParser->parse(argc, argv);
|
|
if (parseResult != 0) {
|
|
// Pass through: positive = clean exit (help/version), negative = error
|
|
return parseResult;
|
|
}
|
|
|
|
// Setup logging based on CLI flags
|
|
setupLogger();
|
|
|
|
// Initialize printer service
|
|
if (!mPrinterService->initialize()) {
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int PtouchPrint::run() {
|
|
spdlog::info("ptprnt version {}", mVersionString);
|
|
|
|
const auto& options = mCliParser->getOptions();
|
|
|
|
// Handle --list-all-drivers
|
|
if (options.listDrivers) {
|
|
return handleListDrivers() ? 0 : -1;
|
|
}
|
|
|
|
// Handle printing
|
|
return handlePrinting() ? 0 : -1;
|
|
}
|
|
|
|
void PtouchPrint::setupLogger() {
|
|
const auto& options = mCliParser->getOptions();
|
|
|
|
spdlog::level::level_enum level = spdlog::level::warn;
|
|
if (options.trace) {
|
|
level = spdlog::level::trace;
|
|
} else if (options.verbose) {
|
|
level = spdlog::level::debug;
|
|
}
|
|
|
|
auto consoleSink = std::make_shared<spdlog::sinks::stdout_color_sink_mt>();
|
|
consoleSink->set_level(level);
|
|
consoleSink->set_pattern("%^%L:%$ %v");
|
|
|
|
auto fileSink = std::make_shared<spdlog::sinks::basic_file_sink_mt>("ptprnt.log", true);
|
|
fileSink->set_level(spdlog::level::trace);
|
|
fileSink->set_pattern("%Y-%m-%d %H:%m:%S:%e [pid:%P tid:%t] [%^%l%$] %v (%@)");
|
|
|
|
std::vector<spdlog::sink_ptr> sinks{consoleSink, fileSink};
|
|
auto logger = std::make_shared<spdlog::logger>("default_logger", sinks.begin(), sinks.end());
|
|
logger->set_level(spdlog::level::trace);
|
|
spdlog::set_default_logger(logger);
|
|
}
|
|
|
|
bool PtouchPrint::handleListDrivers() {
|
|
auto driverFactory = std::make_unique<PrinterDriverFactory>();
|
|
auto drivers = driverFactory->listAllDrivers();
|
|
|
|
std::cout << "Available printer drivers:\n";
|
|
for (const auto& driver : drivers) {
|
|
std::cout << std::format(" - {}\n", driver);
|
|
}
|
|
std::cout << "\nUse with: -p <driver_name> or --printer <driver_name>\n";
|
|
return true;
|
|
}
|
|
|
|
bool PtouchPrint::handlePrinting() {
|
|
const auto& options = mCliParser->getOptions();
|
|
|
|
// Select printer
|
|
auto printer = mPrinterService->selectPrinter(options.printerSelection);
|
|
if (!printer) {
|
|
spdlog::error("Failed to select printer");
|
|
return false;
|
|
}
|
|
|
|
// Get printer status
|
|
auto status = printer->getPrinterStatus();
|
|
spdlog::info("Detected tape width is {}mm", status.tapeWidthMm);
|
|
|
|
// Check if there are any commands
|
|
if (options.commands.empty()) {
|
|
spdlog::warn("No command specified, nothing to do...");
|
|
return true;
|
|
}
|
|
|
|
// Build label incrementally, appending when --new is encountered
|
|
graphics::LabelBuilder labelBuilder(printer->getPrinterInfo().pixelLines);
|
|
std::unique_ptr<graphics::ILabel> finalLabel = nullptr;
|
|
|
|
// Debug: print command sequence
|
|
spdlog::debug("Processing {} commands:", options.commands.size());
|
|
for (size_t i = 0; i < options.commands.size(); ++i) {
|
|
const auto& [cmdType, value] = options.commands[i];
|
|
spdlog::debug(" Command {}: type={}, value='{}'", i, static_cast<int>(cmdType), value);
|
|
}
|
|
|
|
for (const auto& [cmdType, value] : options.commands) {
|
|
switch (cmdType) {
|
|
case cli::CommandType::NewLabel: {
|
|
// Finish current label and append to final label
|
|
spdlog::debug("Encountered --new, finishing current label segment");
|
|
auto currentLabel = labelBuilder.build();
|
|
if (!finalLabel) {
|
|
// First label becomes the base
|
|
finalLabel = std::move(currentLabel);
|
|
} else {
|
|
// If finalLabel is empty (width=0), replace it instead of appending
|
|
if (finalLabel->getWidth() == 0) {
|
|
spdlog::debug("Final label is empty, replacing instead of appending");
|
|
finalLabel = std::move(currentLabel);
|
|
} else if (currentLabel->getWidth() == 0) {
|
|
// Current label is empty, skip appending
|
|
spdlog::debug("Current label is empty, skipping append");
|
|
} else {
|
|
// Both labels have content, append
|
|
if (!finalLabel->append(*currentLabel)) {
|
|
spdlog::error("Failed to append label");
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
// Reset builder for next label
|
|
labelBuilder = graphics::LabelBuilder(printer->getPrinterInfo().pixelLines);
|
|
break;
|
|
}
|
|
case cli::CommandType::Text:
|
|
labelBuilder.addText(value);
|
|
break;
|
|
case cli::CommandType::Font:
|
|
spdlog::debug("Setting font to {}", value);
|
|
labelBuilder.setFontFamily(value);
|
|
break;
|
|
case cli::CommandType::FontSize:
|
|
spdlog::debug("Setting font size to {}", std::stod(value));
|
|
labelBuilder.setFontSize(std::stod(value));
|
|
break;
|
|
case cli::CommandType::HAlign: {
|
|
spdlog::debug("Setting text horizontal alignment to {}", value);
|
|
auto hPos = HALignPositionMap.find(value);
|
|
if (hPos == HALignPositionMap.end()) {
|
|
spdlog::warn("Invalid horizontal alignment specified!");
|
|
labelBuilder.setHAlign(HAlignPosition::UNKNOWN);
|
|
} else {
|
|
labelBuilder.setHAlign(hPos->second);
|
|
}
|
|
break;
|
|
}
|
|
case cli::CommandType::VAlign: {
|
|
spdlog::debug("Setting text vertical alignment to {}", value);
|
|
auto vPos = VALignPositionMap.find(value);
|
|
if (vPos == VALignPositionMap.end()) {
|
|
spdlog::warn("Invalid vertical alignment specified!");
|
|
labelBuilder.setVAlign(VAlignPosition::UNKNOWN);
|
|
} else {
|
|
labelBuilder.setVAlign(vPos->second);
|
|
}
|
|
break;
|
|
}
|
|
case cli::CommandType::None:
|
|
[[fallthrough]];
|
|
default:
|
|
spdlog::warn("This command is currently not supported.");
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Build and append final label segment
|
|
auto lastLabel = labelBuilder.build();
|
|
if (!finalLabel) {
|
|
// Only one label, no --new was used
|
|
finalLabel = std::move(lastLabel);
|
|
} else {
|
|
// Handle empty labels
|
|
if (finalLabel->getWidth() == 0) {
|
|
// Final label is empty, replace it
|
|
spdlog::debug("Final label is empty, replacing with last segment");
|
|
finalLabel = std::move(lastLabel);
|
|
} else if (lastLabel->getWidth() == 0) {
|
|
// Last segment is empty, skip appending
|
|
spdlog::debug("Last label segment is empty, skipping append");
|
|
} else {
|
|
// Both have content, append
|
|
if (!finalLabel->append(*lastLabel)) {
|
|
spdlog::error("Failed to append final label segment");
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Print the final label
|
|
if (!mPrinterService->printLabel(std::move(finalLabel))) {
|
|
spdlog::error("An error occurred while printing");
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
} // namespace ptprnt
|