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.formatOnType": false,
|
||||||
"editor.formatOnSave": true,
|
"editor.formatOnSave": true,
|
||||||
"files.associations": {
|
"files.associations": {
|
||||||
@@ -84,4 +87,7 @@
|
|||||||
"*.ipp": "cpp"
|
"*.ipp": "cpp"
|
||||||
},
|
},
|
||||||
"clangd.onConfigChanged": "restart",
|
"clangd.onConfigChanged": "restart",
|
||||||
|
"cSpell.words": [
|
||||||
|
"ptrnt"
|
||||||
|
],
|
||||||
}
|
}
|
||||||
@@ -271,8 +271,7 @@ void PtouchPrint::setupCliParser() {
|
|||||||
"Select printer driver (default: auto). Use --list-all-drivers to see available options")
|
"Select printer driver (default: auto). Use --list-all-drivers to see available options")
|
||||||
->default_val("auto");
|
->default_val("auto");
|
||||||
|
|
||||||
mApp.add_flag("--list-all-drivers", mListDriversFlag,
|
mApp.add_flag("--list-all-drivers", mListDriversFlag, "List all available printer drivers and exit");
|
||||||
"List all available printer drivers and exit");
|
|
||||||
|
|
||||||
// Text printing options
|
// Text printing options
|
||||||
mApp.add_option("-t,--text",
|
mApp.add_option("-t,--text",
|
||||||
@@ -298,7 +297,7 @@ void PtouchPrint::setupCliParser() {
|
|||||||
->trigger_on_parse()
|
->trigger_on_parse()
|
||||||
->transform([](std::string in) -> std::string {
|
->transform([](std::string in) -> std::string {
|
||||||
std::unordered_set<std::string> validValignOptions{"top", "middle", "bottom"};
|
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()) {
|
if (validValignOptions.find(in) == validValignOptions.end()) {
|
||||||
return {""};
|
return {""};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -45,17 +45,35 @@ Label::Label(const uint16_t heightPixel)
|
|||||||
std::vector<uint8_t> Label::getRaw() {
|
std::vector<uint8_t> Label::getRaw() {
|
||||||
assert(mSurface != nullptr);
|
assert(mSurface != nullptr);
|
||||||
auto* surface = mSurface.get();
|
auto* surface = mSurface.get();
|
||||||
size_t len = cairo_image_surface_get_height(surface) * cairo_image_surface_get_stride(surface);
|
|
||||||
|
|
||||||
cairo_surface_flush(surface);
|
cairo_surface_flush(surface);
|
||||||
assert(cairo_image_surface_get_format(surface) == CAIRO_FORMAT_A8);
|
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);
|
auto data = cairo_image_surface_get_data(surface);
|
||||||
FILE* write_ptr;
|
|
||||||
write_ptr = fopen("test.bin", "wb"); // w for write, b for binary
|
// If stride equals width, we can return data directly
|
||||||
fwrite(data, len, 1, write_ptr); //
|
if (stride == width) {
|
||||||
|
size_t len = height * stride;
|
||||||
return {data, data + len};
|
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) {
|
uint8_t Label::getNumLines(std::string_view strv) {
|
||||||
@@ -63,11 +81,13 @@ uint8_t Label::getNumLines(std::string_view strv) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
int Label::getWidth() {
|
int Label::getWidth() {
|
||||||
return mPrinterHeight;
|
// Return the actual Cairo surface width (which is the layout width)
|
||||||
|
return mLayoutWidth;
|
||||||
}
|
}
|
||||||
|
|
||||||
int Label::getHeight() {
|
int Label::getHeight() {
|
||||||
return getLayoutWidth();
|
// Return the actual Cairo surface height (which is the printer height)
|
||||||
|
return mPrinterHeight;
|
||||||
}
|
}
|
||||||
|
|
||||||
int Label::getLayoutHeight() {
|
int Label::getLayoutHeight() {
|
||||||
|
|||||||
@@ -20,10 +20,14 @@
|
|||||||
#include "FakePrinter.hpp"
|
#include "FakePrinter.hpp"
|
||||||
|
|
||||||
#include <spdlog/spdlog.h>
|
#include <spdlog/spdlog.h>
|
||||||
|
#include <cairo.h>
|
||||||
|
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
#include <stdexcept>
|
#include <stdexcept>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
#include <chrono>
|
||||||
|
#include <iomanip>
|
||||||
|
#include <sstream>
|
||||||
|
|
||||||
#include "../graphics/Monochrome.hpp"
|
#include "../graphics/Monochrome.hpp"
|
||||||
|
|
||||||
@@ -93,16 +97,30 @@ bool FakePrinter::printMonochromeData(const graphics::MonochromeData& data) {
|
|||||||
spdlog::info("FakePrinter: Successfully 'printed' label ({}x{} pixels)",
|
spdlog::info("FakePrinter: Successfully 'printed' label ({}x{} pixels)",
|
||||||
mLastPrint->getWidth(), mLastPrint->getHeight());
|
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;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool FakePrinter::printLabel(const std::unique_ptr<graphics::ILabel> label) {
|
bool FakePrinter::printLabel(const std::unique_ptr<graphics::ILabel> label) {
|
||||||
// Convert label directly to MonochromeData
|
// Convert label directly to MonochromeData
|
||||||
|
// getRaw() returns data in Cairo surface coordinates matching getWidth() × getHeight()
|
||||||
auto pixels = label->getRaw();
|
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();
|
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);
|
return printMonochromeData(monoData);
|
||||||
}
|
}
|
||||||
@@ -179,10 +197,68 @@ bool FakePrinter::saveLastPrintToPng(const std::string& filename) const {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// For now, just log it - actual PNG saving would require Cairo/PNG library integration
|
return saveBitmapToPng(*mLastPrint, filename);
|
||||||
spdlog::info("FakePrinter: Successfully 'saved' {}x{} bitmap to {} (simulated)",
|
}
|
||||||
mLastPrint->getWidth(), mLastPrint->getHeight(), 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;
|
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
|
} // namespace ptprnt::printer
|
||||||
|
|||||||
@@ -86,6 +86,20 @@ class FakePrinter : public ::ptprnt::IPrinterDriver {
|
|||||||
*/
|
*/
|
||||||
graphics::Bitmap<graphics::ALPHA8> simulatePrinting(const graphics::MonochromeData& data);
|
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;
|
std::unique_ptr<graphics::Bitmap<graphics::ALPHA8>> mLastPrint;
|
||||||
bool mHasAttachedDevice = false;
|
bool mHasAttachedDevice = false;
|
||||||
PrinterStatus mStatus{.tapeWidthMm = 12}; // Default to 12mm tape
|
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) {
|
bool P700Printer::printLabel(std::unique_ptr<graphics::ILabel> label) {
|
||||||
// Convert label directly to MonochromeData
|
// Convert label directly to MonochromeData
|
||||||
|
// getRaw() returns data in Cairo surface coordinates matching getWidth() × getHeight()
|
||||||
auto pixels = label->getRaw();
|
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();
|
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();
|
monoData.visualize();
|
||||||
|
|
||||||
spdlog::debug("Label has {}x{}px size", label->getWidth(), label->getHeight());
|
|
||||||
return printMonochromeData(monoData);
|
return printMonochromeData(monoData);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user