From 59b3b34edc21fd4d30729ed3cdbe9a40b55232c5 Mon Sep 17 00:00:00 2001 From: Moritz Martinius Date: Sat, 11 Oct 2025 17:04:55 +0200 Subject: [PATCH] Fix the label corruption issue --- .vscode/settings.json | 10 ++++- src/PtouchPrint.cpp | 17 ++++--- src/graphics/Label.cpp | 38 ++++++++++++---- src/printers/FakePrinter.cpp | 86 +++++++++++++++++++++++++++++++++--- src/printers/FakePrinter.hpp | 14 ++++++ src/printers/P700Printer.cpp | 11 ++++- 6 files changed, 149 insertions(+), 27 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index bf2c87e..351793d 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,5 +1,8 @@ { - "clangd.arguments": ["-background-index", "-compile-commands-dir=builddir/"], + "clangd.arguments": [ + "-background-index", + "-compile-commands-dir=builddir/" + ], "editor.formatOnType": false, "editor.formatOnSave": true, "files.associations": { @@ -84,4 +87,7 @@ "*.ipp": "cpp" }, "clangd.onConfigChanged": "restart", -} + "cSpell.words": [ + "ptrnt" + ], +} \ No newline at end of file diff --git a/src/PtouchPrint.cpp b/src/PtouchPrint.cpp index 9a9d0ca..b79ec29 100644 --- a/src/PtouchPrint.cpp +++ b/src/PtouchPrint.cpp @@ -75,7 +75,7 @@ int PtouchPrint::run() { // Handle --list-all-drivers flag if (mListDriversFlag) { auto driverFactory = std::make_unique(); - auto drivers = driverFactory->listAllDrivers(); + auto drivers = driverFactory->listAllDrivers(); fmt::print("Available printer drivers:\n"); for (const auto& driver : drivers) { @@ -91,7 +91,7 @@ int PtouchPrint::run() { if (mPrinterSelection != "auto") { // Explicit printer selection by name auto driverFactory = std::make_unique(); - printer = driverFactory->createByName(mPrinterSelection); + printer = driverFactory->createByName(mPrinterSelection); if (!printer) { spdlog::error("Failed to create printer driver '{}'", mPrinterSelection); @@ -107,7 +107,7 @@ int PtouchPrint::run() { } else { // Real printer needs USB device const auto printerUsbId = printer->getUsbId(); - auto devices = mUsbDeviceFactory.findDevices(printerUsbId.first, printerUsbId.second); + auto devices = mUsbDeviceFactory.findDevices(printerUsbId.first, printerUsbId.second); if (devices.empty()) { spdlog::error("No USB device found for printer {}. Is it connected and powered on?", mPrinterSelection); @@ -125,7 +125,7 @@ int PtouchPrint::run() { } } else { // Auto-detect printer from USB devices - mDetectedPrinters = getCompatiblePrinters(); + mDetectedPrinters = getCompatiblePrinters(); auto numFoundPrinters = mDetectedPrinters.size(); if (numFoundPrinters == 0) { @@ -137,9 +137,9 @@ int PtouchPrint::run() { return -1; } - printer = mDetectedPrinters[0]; + printer = mDetectedPrinters[0]; const auto printerUsbId = printer->getUsbId(); - auto devices = mUsbDeviceFactory.findDevices(printerUsbId.first, printerUsbId.second); + auto devices = mUsbDeviceFactory.findDevices(printerUsbId.first, printerUsbId.second); if (devices.size() != 1) { spdlog::warn("Found more than one device of the same printer on bus. Currently not supported"); @@ -271,8 +271,7 @@ void PtouchPrint::setupCliParser() { "Select printer driver (default: auto). Use --list-all-drivers to see available options") ->default_val("auto"); - mApp.add_flag("--list-all-drivers", mListDriversFlag, - "List all available printer drivers and exit"); + mApp.add_flag("--list-all-drivers", mListDriversFlag, "List all available printer drivers and exit"); // Text printing options mApp.add_option("-t,--text", @@ -298,7 +297,7 @@ void PtouchPrint::setupCliParser() { ->trigger_on_parse() ->transform([](std::string in) -> std::string { std::unordered_set validValignOptions{"top", "middle", "bottom"}; - std::transform(in.begin(), in.end(), in.begin(), [](unsigned char c) { return std::tolower(c); }); + std::ranges::transform(in, in.begin(), [](unsigned char c) { return std::tolower(c); }); if (validValignOptions.find(in) == validValignOptions.end()) { return {""}; } diff --git a/src/graphics/Label.cpp b/src/graphics/Label.cpp index 72a853e..d978eeb 100644 --- a/src/graphics/Label.cpp +++ b/src/graphics/Label.cpp @@ -45,17 +45,35 @@ Label::Label(const uint16_t heightPixel) std::vector Label::getRaw() { assert(mSurface != nullptr); auto* surface = mSurface.get(); - size_t len = cairo_image_surface_get_height(surface) * cairo_image_surface_get_stride(surface); cairo_surface_flush(surface); assert(cairo_image_surface_get_format(surface) == CAIRO_FORMAT_A8); - spdlog::debug("Cairo Surface data: W: {}; H: {}; S:{}", cairo_image_surface_get_width(surface), - cairo_image_surface_get_height(surface), cairo_image_surface_get_stride(surface)); + + int width = cairo_image_surface_get_width(surface); + int height = cairo_image_surface_get_height(surface); + int stride = cairo_image_surface_get_stride(surface); + + spdlog::debug("Cairo Surface data: W: {}; H: {}; S:{}", width, height, stride); + auto data = cairo_image_surface_get_data(surface); - FILE* write_ptr; - write_ptr = fopen("test.bin", "wb"); // w for write, b for binary - fwrite(data, len, 1, write_ptr); // - return {data, data + len}; + + // If stride equals width, we can return data directly + if (stride == width) { + size_t len = height * stride; + return {data, data + len}; + } + + // Otherwise, we need to copy row by row, removing stride padding + std::vector result; + result.reserve(width * height); + + for (int y = 0; y < height; ++y) { + uint8_t* row_start = data + (y * stride); + result.insert(result.end(), row_start, row_start + width); + } + + spdlog::debug("getRaw: Removed stride padding, returning {} bytes ({}x{})", result.size(), width, height); + return result; } uint8_t Label::getNumLines(std::string_view strv) { @@ -63,11 +81,13 @@ uint8_t Label::getNumLines(std::string_view strv) { } int Label::getWidth() { - return mPrinterHeight; + // Return the actual Cairo surface width (which is the layout width) + return mLayoutWidth; } int Label::getHeight() { - return getLayoutWidth(); + // Return the actual Cairo surface height (which is the printer height) + return mPrinterHeight; } int Label::getLayoutHeight() { diff --git a/src/printers/FakePrinter.cpp b/src/printers/FakePrinter.cpp index 9a96b83..d6f7a91 100644 --- a/src/printers/FakePrinter.cpp +++ b/src/printers/FakePrinter.cpp @@ -20,10 +20,14 @@ #include "FakePrinter.hpp" #include +#include #include #include #include +#include +#include +#include #include "../graphics/Monochrome.hpp" @@ -93,16 +97,30 @@ bool FakePrinter::printMonochromeData(const graphics::MonochromeData& data) { spdlog::info("FakePrinter: Successfully 'printed' label ({}x{} pixels)", mLastPrint->getWidth(), mLastPrint->getHeight()); + // Save to timestamped PNG file + std::string filename = generateTimestampedFilename(); + if (saveBitmapToPng(*mLastPrint, filename)) { + spdlog::info("FakePrinter: Saved output to {}", filename); + } else { + spdlog::error("FakePrinter: Failed to save output to {}", filename); + } + return true; } bool FakePrinter::printLabel(const std::unique_ptr label) { // Convert label directly to MonochromeData + // getRaw() returns data in Cairo surface coordinates matching getWidth() × getHeight() auto pixels = label->getRaw(); - auto mono = graphics::Monochrome(pixels, label->getWidth(), label->getHeight(), graphics::Orientation::PORTRAIT); + + // Create monochrome data in landscape orientation (as stored in Cairo surface) + auto mono = graphics::Monochrome(pixels, label->getWidth(), label->getHeight(), graphics::Orientation::LANDSCAPE); auto monoData = mono.get(); - spdlog::debug("FakePrinter: Label has {}x{} pixel size", label->getWidth(), label->getHeight()); + // Transform to portrait orientation for printing + monoData.transformTo(graphics::Orientation::PORTRAIT); + + spdlog::debug("FakePrinter: Label surface is {}x{}, transformed to portrait", label->getWidth(), label->getHeight()); return printMonochromeData(monoData); } @@ -179,10 +197,68 @@ bool FakePrinter::saveLastPrintToPng(const std::string& filename) const { return false; } - // For now, just log it - actual PNG saving would require Cairo/PNG library integration - spdlog::info("FakePrinter: Successfully 'saved' {}x{} bitmap to {} (simulated)", - mLastPrint->getWidth(), mLastPrint->getHeight(), filename); + return saveBitmapToPng(*mLastPrint, filename); +} + +bool FakePrinter::saveBitmapToPng(const graphics::Bitmap& bitmap, const std::string& filename) const { + // Create Cairo surface from bitmap data + auto pixels = bitmap.getPixelsCpy(); + uint16_t width = bitmap.getWidth(); + uint16_t height = bitmap.getHeight(); + + // Cairo expects ARGB32 format, but we have ALPHA8 + // Convert ALPHA8 (grayscale) to ARGB32 + std::vector argbPixels(width * height); + + for (size_t i = 0; i < pixels.size(); i++) { + uint8_t gray = pixels[i]; + // ARGB32 format: 0xAARRGGBB + // For grayscale: use gray value for R, G, B and 255 for alpha + argbPixels[i] = 0xFF000000 | (gray << 16) | (gray << 8) | gray; + } + + // Create Cairo surface + int stride = cairo_format_stride_for_width(CAIRO_FORMAT_ARGB32, width); + cairo_surface_t* surface = cairo_image_surface_create_for_data( + reinterpret_cast(argbPixels.data()), + CAIRO_FORMAT_ARGB32, + width, + height, + stride + ); + + if (cairo_surface_status(surface) != CAIRO_STATUS_SUCCESS) { + spdlog::error("FakePrinter: Failed to create Cairo surface: {}", + cairo_status_to_string(cairo_surface_status(surface))); + cairo_surface_destroy(surface); + return false; + } + + // Write to PNG file + cairo_status_t status = cairo_surface_write_to_png(surface, filename.c_str()); + + cairo_surface_destroy(surface); + + if (status != CAIRO_STATUS_SUCCESS) { + spdlog::error("FakePrinter: Failed to write PNG file: {}", cairo_status_to_string(status)); + return false; + } + return true; } +std::string FakePrinter::generateTimestampedFilename() const { + // Get current time + auto now = std::chrono::system_clock::now(); + auto time = std::chrono::system_clock::to_time_t(now); + + // Format: fakelabel_YYYYMMDD_HHMMSS.png + std::stringstream ss; + ss << "fakelabel_" + << std::put_time(std::localtime(&time), "%Y%m%d_%H%M%S") + << ".png"; + + return ss.str(); +} + } // namespace ptprnt::printer diff --git a/src/printers/FakePrinter.hpp b/src/printers/FakePrinter.hpp index 2b63b31..09baafa 100644 --- a/src/printers/FakePrinter.hpp +++ b/src/printers/FakePrinter.hpp @@ -86,6 +86,20 @@ class FakePrinter : public ::ptprnt::IPrinterDriver { */ graphics::Bitmap simulatePrinting(const graphics::MonochromeData& data); + /** + * @brief Save bitmap to PNG file using Cairo + * @param bitmap The bitmap to save + * @param filename Output filename + * @return true if successful + */ + bool saveBitmapToPng(const graphics::Bitmap& bitmap, const std::string& filename) const; + + /** + * @brief Generate timestamped filename for fake label output + * @return Filename like "fakelabel_20231011_123456.png" + */ + std::string generateTimestampedFilename() const; + std::unique_ptr> mLastPrint; bool mHasAttachedDevice = false; PrinterStatus mStatus{.tapeWidthMm = 12}; // Default to 12mm tape diff --git a/src/printers/P700Printer.cpp b/src/printers/P700Printer.cpp index 3c900ad..8d5ff1a 100644 --- a/src/printers/P700Printer.cpp +++ b/src/printers/P700Printer.cpp @@ -173,12 +173,19 @@ bool P700Printer::printMonochromeData(const graphics::MonochromeData& data) { bool P700Printer::printLabel(std::unique_ptr label) { // Convert label directly to MonochromeData + // getRaw() returns data in Cairo surface coordinates matching getWidth() × getHeight() auto pixels = label->getRaw(); - auto mono = graphics::Monochrome(pixels, label->getWidth(), label->getHeight(), graphics::Orientation::PORTRAIT); + + // Create monochrome data in landscape orientation (as stored in Cairo surface) + auto mono = graphics::Monochrome(pixels, label->getWidth(), label->getHeight(), graphics::Orientation::LANDSCAPE); auto monoData = mono.get(); + + // Transform to portrait orientation for printing + monoData.transformTo(graphics::Orientation::PORTRAIT); + + spdlog::debug("Label surface is {}x{}, transformed to portrait", label->getWidth(), label->getHeight()); monoData.visualize(); - spdlog::debug("Label has {}x{}px size", label->getWidth(), label->getHeight()); return printMonochromeData(monoData); }