/* 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 . */ #include "PtouchPrint.hpp" #include #include #include #include #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(ptprnt::APP_DESC, versionString), std::make_unique()) {} PtouchPrint::PtouchPrint(const char* versionString, std::unique_ptr cliParser, std::unique_ptr 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(); consoleSink->set_level(level); consoleSink->set_pattern("%^%L:%$ %v"); auto fileSink = std::make_shared("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 sinks{consoleSink, fileSink}; auto logger = std::make_shared("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(); auto drivers = driverFactory->listAllDrivers(); fmt::print("Available printer drivers:\n"); for (const auto& driver : drivers) { fmt::print(" - {}\n", driver); } fmt::print("\nUse with: -p or --printer \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 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(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