Fix the label corruption issue
All checks were successful
Build ptprnt / build (push) Successful in 4m43s
All checks were successful
Build ptprnt / build (push) Successful in 4m43s
This commit is contained in:
8
.vscode/settings.json
vendored
8
.vscode/settings.json
vendored
@@ -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"
|
||||
],
|
||||
}
|
||||
@@ -75,7 +75,7 @@ int PtouchPrint::run() {
|
||||
// Handle --list-all-drivers flag
|
||||
if (mListDriversFlag) {
|
||||
auto driverFactory = std::make_unique<PrinterDriverFactory>();
|
||||
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<PrinterDriverFactory>();
|
||||
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<std::string> 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 {""};
|
||||
}
|
||||
|
||||
@@ -45,17 +45,35 @@ Label::Label(const uint16_t heightPixel)
|
||||
std::vector<uint8_t> 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<uint8_t> 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() {
|
||||
|
||||
@@ -20,10 +20,14 @@
|
||||
#include "FakePrinter.hpp"
|
||||
|
||||
#include <spdlog/spdlog.h>
|
||||
#include <cairo.h>
|
||||
|
||||
#include <cstdint>
|
||||
#include <stdexcept>
|
||||
#include <vector>
|
||||
#include <chrono>
|
||||
#include <iomanip>
|
||||
#include <sstream>
|
||||
|
||||
#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<graphics::ILabel> 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<graphics::ALPHA8>& 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<uint32_t> 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<unsigned char*>(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
|
||||
|
||||
@@ -86,6 +86,20 @@ class FakePrinter : public ::ptprnt::IPrinterDriver {
|
||||
*/
|
||||
graphics::Bitmap<graphics::ALPHA8> 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<graphics::ALPHA8>& 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<graphics::Bitmap<graphics::ALPHA8>> mLastPrint;
|
||||
bool mHasAttachedDevice = false;
|
||||
PrinterStatus mStatus{.tapeWidthMm = 12}; // Default to 12mm tape
|
||||
|
||||
@@ -173,12 +173,19 @@ bool P700Printer::printMonochromeData(const graphics::MonochromeData& data) {
|
||||
|
||||
bool P700Printer::printLabel(std::unique_ptr<graphics::ILabel> 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);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user